Skip to content

Commit

Permalink
feat: built-in tus protocol support
Browse files Browse the repository at this point in the history
  • Loading branch information
kukhariev committed Sep 11, 2019
1 parent 6aa3ed6 commit ed8ec48
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 13 deletions.
1 change: 1 addition & 0 deletions src/uploadx/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './uploader';
export * from './uploaderx';
export * from './uploadx.module';
export * from './uploadx.service';
export * from './tus';
58 changes: 58 additions & 0 deletions src/uploadx/src/tus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { UploadxOptions } from './interfaces';
import { Uploader } from './uploader';
import { b64, resolveUrl } from './utils';

export class Tus extends Uploader {
constructor(readonly file: File, options: UploadxOptions) {
super(file, options);
this.headers['Tus-Resumable'] = '1.0.0';
}

async getFileUrl(): Promise<string> {
const encodedMetaData = b64.serialize(this.metadata);
const headers = {
'Upload-Length': `${this.size}`,
'Upload-Metadata': `${encodedMetaData}`
};

await this.request({
method: 'POST',
url: this.endpoint,
headers
});
const location = this.getValueFromResponse('location');
if (!location) {
throw new Error('Invalid or missing Location header');
}
this.offset = this.responseStatus === 201 ? 0 : undefined;
return resolveUrl(location, this.endpoint);
}

async sendFileContent(): Promise<number | undefined> {
const { body } = this.getChunk();
const headers = {
'Content-Type': 'application/offset+octet-stream',
'Upload-Offset': `${this.offset}`
};
await this.request({
method: 'PATCH',
body,
headers
});
return this.getOffsetFromResponse();
}

async getOffset(): Promise<number | undefined> {
await this.request({ method: 'HEAD' });
return this.getOffsetFromResponse();
}

protected getOffsetFromResponse(): number | undefined {
const offsetStr = this.getValueFromResponse('Upload-Offset');
return offsetStr ? parseInt(offsetStr, 10) : undefined;
}

protected setAuth(token: string) {
this.headers.Authorization = `Bearer ${token}`;
}
}
16 changes: 15 additions & 1 deletion src/uploadx/src/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { resolveUrl } from './utils';
import { b64, resolveUrl } from './utils';
const _URL = window.URL;
const base = 'http://www.example.com/upload';
const rel = '/upload?upload_id=12345';
Expand Down Expand Up @@ -35,3 +35,17 @@ describe('resolveUrl:polyfill', () => {
window.URL = _URL;
});
});

fdescribe('b64', () => {
const data = {
name: 'спутник.mp4',
lastModified: '1437390138231'
};
const encoded = 'name 0YHQv9GD0YLQvdC40LoubXA0,lastModified MTQzNzM5MDEzODIzMQ==';
it('serialize', async () => {
expect(b64.serialize(data)).toBe(encoded);
});
it('parse', async () => {
expect(b64.parse(encoded)).toEqual(data);
});
});
41 changes: 29 additions & 12 deletions src/uploadx/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,33 @@ export function createHash(str: string): number {
hash ^= str.charCodeAt(i);
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
return hash;
}
export function b64Encode(str: string) {
return btoa(unescape(encodeURIComponent(str)));
}
export function b64Decode(str: string) {
return decodeURIComponent(escape(window.atob(str)));
}
export function objectToB64String(obj: Record<string, any>) {
return Object.entries(obj)
.map(([key, value]) => `${key} ${b64Encode(String(value))}`)
.toString();
return hash >>> 0;
}
export const b64 = {
encode: (str: string) =>
btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_match, p1) =>
String.fromCharCode(Number.parseInt(p1, 16))
)
),
decode: (str: string) =>
decodeURIComponent(
atob(str)
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
),
serialize: (obj: Record<string, any>) => {
return Object.entries(obj)
.map(([key, value]) => `${key} ${b64.encode(String(value))}`)
.toString();
},
parse: (encoded: string) => {
const kvPairs = encoded.split(',').map(kv => kv.split(' '));
const decoded = Object.create(null);
for (const [key, value] of kvPairs) {
decoded[key] = b64.decode(value);
}
return decoded;
}
};

0 comments on commit ed8ec48

Please sign in to comment.