Skip to content

Commit e20dd20

Browse files
committed
Add retries
1 parent 7a86f69 commit e20dd20

File tree

5 files changed

+120
-26
lines changed

5 files changed

+120
-26
lines changed

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/src/index.d.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
interface OptionsWithUploadToken extends Options {
2+
uploadToken: string;
3+
}
4+
interface OptionsWithAccessToken extends Options {
5+
accessToken: string;
6+
videoId: string;
7+
}
18
interface Options {
29
file: File;
3-
uploadToken: string;
410
chunkSize?: number;
5-
uploadEndpoint?: string;
11+
apiHost?: string;
12+
retries?: number;
613
}
714
interface UploadProgressEvent {
815
loaded: number;
@@ -12,17 +19,18 @@ export declare class VideoUploader {
1219
private file;
1320
private chunkSize;
1421
private uploadEndpoint;
15-
private uploadToken;
1622
private currentChunk;
1723
private chunksCount;
1824
private fileSize;
1925
private fileName;
2026
private videoId?;
27+
private retries;
2128
private onProgressCallbacks;
22-
constructor(options: Options);
29+
private headers;
30+
constructor(options: OptionsWithAccessToken | OptionsWithUploadToken);
2331
onProgress(cb: () => UploadProgressEvent): void;
2432
upload(): Promise<any>;
25-
private validateOptions;
33+
private sleep;
2634
private createFormData;
2735
private uploadCurrentChunk;
2836
}

package-lock.json

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"tslint": "^6.1.3",
3535
"typescript": "^4.0.2",
3636
"webpack": "^4.44.1",
37-
"webpack-cli": "^3.3.12"
37+
"webpack-cli": "^3.3.12",
38+
"xhr-mock": "^2.5.1"
3839
},
3940
"dependencies": {
4041
"core-js": "^3.8.3"

src/index.ts

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
interface OptionsWithUploadToken extends Options {
2+
uploadToken: string;
3+
}
4+
interface OptionsWithAccessToken extends Options {
5+
accessToken: string;
6+
videoId: string;
7+
}
18
interface Options {
29
file: File;
3-
uploadToken: string;
410
chunkSize?: number;
5-
uploadEndpoint?: string;
11+
apiHost?: string;
12+
retries?: number;
613
}
714

815
interface UploadProgressEvent {
@@ -11,25 +18,46 @@ interface UploadProgressEvent {
1118
}
1219

1320
const DEFAULT_CHUNK_SIZE = 1024 * 1024; // 1mb
14-
const DEFAULT_UPLOAD_ENDPOINT = "https://ws.api.video/upload";
21+
const DEFAULT_RETRIES = 5;
22+
const DEFAULT_API_HOST = "ws.api.video";
1523

1624
export class VideoUploader {
1725
private file: File;
1826
private chunkSize: number;
1927
private uploadEndpoint: string;
20-
private uploadToken: string;
2128
private currentChunk: number = 0;
2229
private chunksCount: number;
2330
private fileSize: number;
2431
private fileName: string;
2532
private videoId?: string;
33+
private retries: number;
2634
private onProgressCallbacks: ((e: UploadProgressEvent) => void)[] = [];
35+
private headers: { [name: string]: string } = {};
36+
37+
constructor(options: OptionsWithAccessToken | OptionsWithUploadToken) {
38+
const apiHost = options.apiHost || DEFAULT_API_HOST;
39+
40+
if (!options.file) {
41+
throw new Error("'file' is missing");
42+
}
43+
44+
if (options.hasOwnProperty("uploadToken")) {
45+
const optionsWithUploadToken = options as OptionsWithUploadToken;
46+
this.uploadEndpoint = `https://${apiHost}/upload?token=${optionsWithUploadToken.uploadToken}`;
47+
48+
} else if (options.hasOwnProperty("accessToken")) {
49+
const optionsWithAccessToken = options as OptionsWithAccessToken;
50+
if (!optionsWithAccessToken.videoId) {
51+
throw new Error("'videoId' is missing");
52+
}
53+
this.uploadEndpoint = `https://${apiHost}/videos/${optionsWithAccessToken.videoId}/source`;
54+
this.headers.Authorization = `Bearer ${optionsWithAccessToken.accessToken}`;
55+
} else {
56+
throw new Error(`You must provide either an accessToken or an uploadToken`);
57+
}
2758

28-
constructor(options: Options) {
29-
this.validateOptions(options);
3059
this.chunkSize = options.chunkSize || DEFAULT_CHUNK_SIZE;
31-
this.uploadEndpoint = options.uploadEndpoint || DEFAULT_UPLOAD_ENDPOINT;
32-
this.uploadToken = options.uploadToken;
60+
this.retries = options.retries || DEFAULT_RETRIES;
3361
this.file = options.file;
3462
this.fileSize = this.file.size;
3563
this.fileName = this.file.name;
@@ -44,20 +72,30 @@ export class VideoUploader {
4472
public upload(): Promise<any> {
4573
return new Promise(async (resolve, reject) => {
4674
let response;
75+
let retriesCount = 0;
4776
while (this.currentChunk < this.chunksCount) {
48-
response = await this.uploadCurrentChunk();
49-
this.videoId = response.videoId;
50-
this.currentChunk++;
77+
try {
78+
response = await this.uploadCurrentChunk();
79+
this.videoId = response.videoId;
80+
this.currentChunk++;
81+
82+
} catch (e) {
83+
if(retriesCount >= this.retries) {
84+
reject(e);
85+
break;
86+
}
87+
await this.sleep(200 + retriesCount * 500);
88+
retriesCount++;
89+
}
5190
}
5291
resolve(response);
5392
});
5493
}
5594

56-
private validateOptions(options: Options) {
57-
const required = ['file', 'uploadToken']
58-
required.forEach(r => {
59-
if (!(options as any)[r]) throw new Error(`"${r}" is missing`);
60-
});
95+
private sleep(duration: number): Promise<void> {
96+
return new Promise((resolve, reject) => {
97+
setTimeout(() => resolve(), duration);
98+
})
6199
}
62100

63101
private createFormData(startByte: number, endByte: number): FormData {
@@ -78,10 +116,22 @@ export class VideoUploader {
78116

79117
const contentRange = `bytes ${firstByte}-${lastByte - 1}/${this.fileSize}`;
80118

81-
const xhr = new XMLHttpRequest();
82-
xhr.open("POST", `${this.uploadEndpoint}?token=${this.uploadToken}`, true);
119+
const xhr = new window.XMLHttpRequest();
120+
xhr.open("POST", `${this.uploadEndpoint}`, true);
83121
xhr.setRequestHeader("Content-Range", contentRange);
84-
122+
for (const headerName of Object.keys(this.headers)) {
123+
xhr.setRequestHeader(headerName, this.headers[headerName]);
124+
}
125+
xhr.onreadystatechange = (e) => {
126+
if (xhr.readyState === 4) { // DONE
127+
if (xhr.status >= 400) {
128+
reject({
129+
status: xhr.status,
130+
message: xhr.response
131+
});
132+
}
133+
}
134+
};
85135
xhr.onload = (e) => resolve(JSON.parse(xhr.response));
86136
xhr.onprogress = (e) => this.onProgressCallbacks.forEach(cb => cb({
87137
loaded: e.loaded + firstByte,

0 commit comments

Comments
 (0)