Skip to content

Commit

Permalink
feat: prerequest option (#246)
Browse files Browse the repository at this point in the history
* feat: prerequest option

* fix: types

* chore: app update

* refactor: allow void prerequest function

* test: add prerequest test

* docs: add prerequest

* fix: extend prerequest type
  • Loading branch information
kukhariev committed Jul 16, 2020
1 parent 435cb7a commit 0b47315
Show file tree
Hide file tree
Showing 20 changed files with 232 additions and 89 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export class AppHomeComponent {
- `metadata`: Custom uploads metadata
- `prerequest`: Function called before every request
- `uploaderClass`: Upload API implementation. Built-in: `Uploaderx`(default), `Tus`. More [examples](uploader-examples).
- `token`: Authorization token as a `string` or function returning a `string` or `Promise<string>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class DirectiveWayComponent implements OnDestroy {

onUpload(events$: Observable<UploadState>): void {
this.state$ = events$;
events$.pipe(takeUntil(this.unsubscribe$)).subscribe((evt: UploadState) => {
events$.pipe(takeUntil(this.unsubscribe$)).subscribe(evt => {
const target = this.uploads.find(f => f.uploadId === evt.uploadId);
if (target) {
target.progress = evt.progress;
Expand Down
41 changes: 39 additions & 2 deletions integrations/ng10/src/app/on-push/on-push.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { Uploader, UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx';
import {
b64,
RequestParams,
Uploader,
UploaderX,
UploadState,
UploadxOptions,
UploadxService
} from 'ngx-uploadx';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { AuthService } from '../auth.service';
Expand All @@ -14,6 +22,7 @@ export class OnPushComponent implements OnDestroy {
uploads$: Observable<Uploader[]>;
options: UploadxOptions = {
endpoint: `${environment.api}/files?uploadType=uploadx`,
prerequest: injectDigestHeader,
token: this.tokenGetter.bind(this)
};

Expand All @@ -38,7 +47,35 @@ export class OnPushComponent implements OnDestroy {
this.uploadService.control({ action: 'upload' });
}

async tokenGetter(httpStatus: number): Promise<string> {
async tokenGetter(httpStatus?: number): Promise<string> {
return httpStatus === 401 ? await this.auth.renewToken().toPromise() : this.auth.accessToken;
}
}

const hasher = {
isSupported: window.crypto && !!window.crypto.subtle,
async sha(data: ArrayBuffer): Promise<string> {
return this.hex(await crypto.subtle.digest('SHA-1', data));
},
getDigest(body: Blob): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => resolve(await this.sha(reader.result as ArrayBuffer));
reader.onerror = reject;
reader.readAsArrayBuffer(body);
});
},
hex(buffer: ArrayBuffer): string {
return [...new Uint8Array(buffer)].map(b => b.toString(16).padStart(2, '0')).join('');
}
};

async function injectDigestHeader(this: UploaderX, req: RequestParams): Promise<RequestParams> {
if (hasher.isSupported && req.method === 'PUT' && req.body instanceof Blob) {
const headers = req.headers || {};
const sha = await hasher.getDigest(req.body);
headers.Digest = 'sha=' + b64.encode(sha);
console.log(sha, headers.Digest);
}
return req;
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ServiceCodeWayComponent implements OnDestroy, OnInit {

onUpload(events$: Observable<UploadState>): void {
this.state$ = events$;
events$.pipe(takeUntil(this.unsubscribe$)).subscribe((ufile: UploadState) => {
events$.pipe(takeUntil(this.unsubscribe$)).subscribe(ufile => {
const target = this.uploads.find(f => f.uploadId === ufile.uploadId);
target ? Object.assign(target, ufile) : this.uploads.push(new Ufile(ufile));
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { UploaderX } from 'ngx-uploadx';

export class UploaderExt extends UploaderX {
// Disable delete files on cancel
onCancel(): void {}

// override auth scheme
setAuth(encoded: string): void {
this.headers.Authorization = `Basic ${encoded}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class DirectiveWayComponent implements OnDestroy {

onUpload(events$: Observable<UploadState>): void {
this.state$ = events$;
events$.pipe(takeUntil(this.unsubscribe$)).subscribe((evt: UploadState) => {
events$.pipe(takeUntil(this.unsubscribe$)).subscribe(evt => {
const target = this.uploads.find(f => f.uploadId === evt.uploadId);
if (target) {
target.progress = evt.progress;
Expand Down
41 changes: 39 additions & 2 deletions integrations/ng8/src/app/on-push/on-push.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { Uploader, UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx';
import {
b64,
RequestParams,
Uploader,
UploaderX,
UploadState,
UploadxOptions,
UploadxService
} from 'ngx-uploadx';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { AuthService } from '../auth.service';
Expand All @@ -14,6 +22,7 @@ export class OnPushComponent implements OnDestroy {
uploads$: Observable<Uploader[]>;
options: UploadxOptions = {
endpoint: `${environment.api}/files?uploadType=uploadx`,
prerequest: injectDigestHeader,
token: this.tokenGetter.bind(this)
};

Expand All @@ -38,7 +47,35 @@ export class OnPushComponent implements OnDestroy {
this.uploadService.control({ action: 'upload' });
}

async tokenGetter(httpStatus: number): Promise<string> {
async tokenGetter(httpStatus?: number): Promise<string> {
return httpStatus === 401 ? await this.auth.renewToken().toPromise() : this.auth.accessToken;
}
}

const hasher = {
isSupported: window.crypto && !!window.crypto.subtle,
async sha(data: ArrayBuffer): Promise<string> {
return this.hex(await crypto.subtle.digest('SHA-1', data));
},
getDigest(body: Blob): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => resolve(await this.sha(reader.result as ArrayBuffer));
reader.onerror = reject;
reader.readAsArrayBuffer(body);
});
},
hex(buffer: ArrayBuffer): string {
return [...new Uint8Array(buffer)].map(b => b.toString(16).padStart(2, '0')).join('');
}
};

async function injectDigestHeader(this: UploaderX, req: RequestParams): Promise<RequestParams> {
if (hasher.isSupported && req.method === 'PUT' && req.body instanceof Blob) {
const headers = req.headers || {};
const sha = await hasher.getDigest(req.body);
headers.Digest = 'sha=' + b64.encode(sha);
console.log(sha, headers.Digest);
}
return req;
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ServiceCodeWayComponent implements OnDestroy, OnInit {

onUpload(events$: Observable<UploadState>): void {
this.state$ = events$;
events$.pipe(takeUntil(this.unsubscribe$)).subscribe((ufile: UploadState) => {
events$.pipe(takeUntil(this.unsubscribe$)).subscribe(ufile => {
const target = this.uploads.find(f => f.uploadId === ufile.uploadId);
target ? Object.assign(target, ufile) : this.uploads.push(new Ufile(ufile));
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { UploaderX } from 'ngx-uploadx';

export class UploaderExt extends UploaderX {
// Disable delete files on cancel
onCancel(): void {}

// override auth scheme
setAuth(encoded: string): void {
this.headers.Authorization = `Basic ${encoded}`;
Expand Down
2 changes: 1 addition & 1 deletion src/app/directive-way/directive-way.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class DirectiveWayComponent implements OnDestroy {

onUpload(events$: Observable<UploadState>): void {
this.state$ = events$;
events$.pipe(takeUntil(this.unsubscribe$)).subscribe((evt: UploadState) => {
events$.pipe(takeUntil(this.unsubscribe$)).subscribe(evt => {
const target = this.uploads.find(f => f.uploadId === evt.uploadId);
if (target) {
target.progress = evt.progress;
Expand Down
3 changes: 1 addition & 2 deletions src/app/multi-service/multi-service.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MultiServiceComponent } from './multi-service.component';

describe('MultiServiceComponent', () => {
Expand Down
41 changes: 39 additions & 2 deletions src/app/on-push/on-push.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { Uploader, UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx';
import {
b64,
RequestParams,
Uploader,
UploaderX,
UploadState,
UploadxOptions,
UploadxService
} from 'ngx-uploadx';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { AuthService } from '../auth.service';
Expand All @@ -14,6 +22,7 @@ export class OnPushComponent implements OnDestroy {
uploads$: Observable<Uploader[]>;
options: UploadxOptions = {
endpoint: `${environment.api}/files?uploadType=uploadx`,
prerequest: injectDigestHeader,
token: this.tokenGetter.bind(this)
};

Expand All @@ -38,7 +47,35 @@ export class OnPushComponent implements OnDestroy {
this.uploadService.control({ action: 'upload' });
}

async tokenGetter(httpStatus: number): Promise<string> {
async tokenGetter(httpStatus?: number): Promise<string> {
return httpStatus === 401 ? await this.auth.renewToken().toPromise() : this.auth.accessToken;
}
}

const hasher = {
isSupported: window.crypto && !!window.crypto.subtle,
async sha(data: ArrayBuffer): Promise<string> {
return this.hex(await crypto.subtle.digest('SHA-1', data));
},
getDigest(body: Blob): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => resolve(await this.sha(reader.result as ArrayBuffer));
reader.onerror = reject;
reader.readAsArrayBuffer(body);
});
},
hex(buffer: ArrayBuffer): string {
return [...new Uint8Array(buffer)].map(b => b.toString(16).padStart(2, '0')).join('');
}
};

async function injectDigestHeader(this: UploaderX, req: RequestParams): Promise<RequestParams> {
if (hasher.isSupported && req.method === 'PUT' && req.body instanceof Blob) {
const headers = req.headers || {};
const sha = await hasher.getDigest(req.body);
headers.Digest = 'sha=' + b64.encode(sha);
console.log(sha, headers.Digest);
}
return req;
}
2 changes: 1 addition & 1 deletion src/app/service-code-way/service-code-way.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ServiceCodeWayComponent implements OnDestroy, OnInit {

onUpload(events$: Observable<UploadState>): void {
this.state$ = events$;
events$.pipe(takeUntil(this.unsubscribe$)).subscribe((ufile: UploadState) => {
events$.pipe(takeUntil(this.unsubscribe$)).subscribe(ufile => {
const target = this.uploads.find(f => f.uploadId === ufile.uploadId);
target ? Object.assign(target, ufile) : this.uploads.push(new Ufile(ufile));
});
Expand Down
3 changes: 0 additions & 3 deletions src/app/service-code-way/uploader-ext.class.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { UploaderX } from 'ngx-uploadx';

export class UploaderExt extends UploaderX {
// Disable delete files on cancel
onCancel(): void {}

// override auth scheme
setAuth(encoded: string): void {
this.headers.Authorization = `Basic ${encoded}`;
Expand Down
12 changes: 12 additions & 0 deletions src/uploadx/lib/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export interface UploaderOptions extends UploadItem {
chunkSize?: number;
withCredentials?: boolean;
readonly stateChange?: (evt: UploadEvent) => void;
/**
* Function called before every request
*/
prerequest?: (req: RequestParams) => Promise<RequestParams> | RequestParams | void;
}

/**
Expand Down Expand Up @@ -116,3 +120,11 @@ export interface UploadxOptions extends UploaderOptions {
*/
multiple?: boolean;
}

export interface RequestParams {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
body?: BodyInit | null;
url?: string;
headers?: Record<string, string>;
progress?: boolean;
}
4 changes: 2 additions & 2 deletions src/uploadx/lib/store.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
class Store {
constructor(public prefix = 'UPLOADX-V3.0-') {}
prefix = 'UPLOADX-V3.0-';

set(key: string, value: string): void {
localStorage.setItem(this.prefix + key, value);
}

get(key: string): string | null | false {
get(key: string): string | null {
return localStorage.getItem(this.prefix + key);
}

Expand Down
25 changes: 25 additions & 0 deletions src/uploadx/lib/uploader.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// noinspection ES6PreferShortImport
import { ErrorHandler } from './error-handler';
import { Uploader } from './uploader';

Expand Down Expand Up @@ -176,3 +177,27 @@ describe('start()', () => {
expect(uploader.status).toEqual('complete');
});
});
describe('prerequest', () => {
let uploader: MockUploader;
const injected = { headers: { Auth: 'token' } };
it('sync', async () => {
uploader = new MockUploader(file, { prerequest: req => ({ ...req, ...injected }) });
const _request = spyOn<any>(uploader, '_request').and.callThrough();
await uploader.request({ method: 'POST' });
expect(_request).toHaveBeenCalledWith({ method: 'POST', headers: { Auth: 'token' } });
});
it('async', async () => {
uploader = new MockUploader(file, {
prerequest: req => Promise.resolve({ ...req, ...injected })
});
const _request = spyOn<any>(uploader, '_request').and.callThrough();
await uploader.request({ method: 'POST' });
expect(_request).toHaveBeenCalledWith({ method: 'POST', headers: { Auth: 'token' } });
});
it('void', async () => {
uploader = new MockUploader(file, { prerequest: (() => {}) as any });
const _request = spyOn<any>(uploader, '_request').and.callThrough();
await uploader.request({ method: 'POST' });
expect(_request).toHaveBeenCalledWith({ method: 'POST' });
});
});

0 comments on commit 0b47315

Please sign in to comment.