Skip to content

Commit

Permalink
feat: add progress timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
uzenith360 committed Dec 16, 2023
1 parent 3907470 commit bf6eb55
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface EnvironmentConfig {
getUploadLinkURL: string;
ignoreProgressReports?: boolean;
progressTimeoutDuration?: number;
// ignoreProgressReports?: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class FileUploadTimeoutError extends Error {
constructor() {
super('File Upload Timeout has occurred');
}
}
95 changes: 79 additions & 16 deletions projects/ngx-file-upload/src/lib/file-upload/file-upload.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { HttpClient, HttpErrorResponse, HttpResponse, HttpUploadProgressEvent } from '@angular/common/http';
import { HttpClient, HttpErrorResponse, HttpEventType, HttpResponse, HttpUploadProgressEvent } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import type { UploadURLResult } from '@uzenith360/aws-s3-generate-upload-url';
import { HandledHttpResponse, HttpError, httpRetry } from '@uzenith360/http-utils';
import { Observable, catchError, throwError, concatMap, map, of } from 'rxjs';
import { Observable, catchError, throwError, concatMap, map, of, TimeoutError, switchMap, timeout } from 'rxjs';
import { EnvironmentConfig } from '../environment-config.interface';
import EnvironmentConfigService from '../environment-config.service';
import { FileUploadTimeoutError } from './file-upload-timeout.error';

@Injectable({
providedIn: 'root',
Expand Down Expand Up @@ -78,6 +79,12 @@ export class FileUploadService {
private uploadFileToURL(uploadFileURL: string, file: File): Observable<HttpResponse<void> | HttpUploadProgressEvent> {
const pathname: string = (new URL(uploadFileURL)).pathname;

// Define the progress timeout duration in milliseconds.
const progressTimeoutDuration: number = this.config.progressTimeoutDuration ?? 3 * 60 * 1000; // 3 minutes.

// tracks last time a progress event was received
let lastProgressTimestamp: number = Date.now();

return this.http.put(
uploadFileURL,
file,
Expand All @@ -90,26 +97,82 @@ export class FileUploadService {
'ngsw-bypass': "true",
// This Content-Disposition is to force the browser to download file
// rather than preview it when the download button is clicked
'Content-Disposition': `attachment; filename=${file.name ?? pathname.substring(pathname.lastIndexOf('/') + 1)}`,
'Content-Disposition': `attachment; filename=${file.name ?? pathname.substring(pathname.lastIndexOf('/') + 1)}`,
},
observe: 'events',
reportProgress: !this.config.ignoreProgressReports,
reportProgress: /*!this.config.ignoreProgressReports*/true,
}
).pipe(
httpRetry(),
switchMap((event: HttpResponse<void> | HttpUploadProgressEvent) => {
if (event.type === HttpEventType.UploadProgress) {
lastProgressTimestamp = Date.now();
}

return new Observable<HttpResponse<void> | HttpUploadProgressEvent>(observer => {
observer.next(event);

return { unsubscribe() { } };
}).pipe(
// timeoutWith(
// progressTimeoutDuration,
// new Observable<HttpResponse<void> | HttpUploadProgressEvent>(observer => {
// const currentTime = Date.now();
// if (currentTime - lastProgressTimestamp > progressTimeoutDuration) {
// observer.error(new CustomTimeoutError());
// } else {
// observer.complete(); // Do not timeout since we have received progress events.
// }
// }
// )
// ),
// timeout(
// {
// each: progressTimeoutDuration,
// with: (info: TimeoutInfo<HttpResponse<void> | HttpUploadProgressEvent, unknown>) =>
// new Observable<HttpResponse<void> | HttpUploadProgressEvent>(observer => {
// const currentTime = Date.now();
// if (currentTime - lastProgressTimestamp > progressTimeoutDuration) {
// observer.error(new CustomTimeoutError());
// } else {
// observer.complete(); // Do not timeout since we have received progress events.
// }
// }
// )
// }
// )
timeout({
each: progressTimeoutDuration,
with: (/*info: TimeoutInfo<HttpResponse<void> | HttpUploadProgressEvent, unknown>*/) => {
const currentTime: number = Date.now();

if (currentTime - lastProgressTimestamp > progressTimeoutDuration) {
return throwError(() => new FileUploadTimeoutError());
} else {
return new Observable<HttpResponse<void> | HttpUploadProgressEvent>(observer => observer.complete()); // Do not timeout since we have received progress events.
}
}
}),
);
}),

catchError((err: HttpErrorResponse, caught: Observable<HttpResponse<void> | HttpUploadProgressEvent>) => {
switch (err.status) {
case 500:
return throwError(() => new HttpError('Problem uploading file, please try again', err.status));
case 0:
default:
return throwError(
() => new HttpError(
(err.error?.message?.join && err.error?.message?.join(', ')) ?? err.error?.message ?? err?.message ?? 'Problem uploading file, please check network and try again',
err.status,
),
);
};
if (err instanceof TimeoutError) {
return throwError(() => new FileUploadTimeoutError());
} else {
switch (err.status) {
case 500:
return throwError(() => new HttpError('Problem uploading file, please try again', err.status));
case 0:
default:
return throwError(
() => new HttpError(
(err.error?.message?.join && err.error?.message?.join(', ')) ?? err.error?.message ?? err?.message ?? 'Problem uploading file, please check network and try again',
err.status,
),
);
};
}
}),
);
}
Expand Down
1 change: 1 addition & 0 deletions projects/ngx-file-upload/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

export * from './lib/file-upload/file-upload-response.interface';
export * from './lib/file-upload/file-upload-timeout.error';
export * from './lib/file-upload/file-upload.service';
export * from './lib/environment-config.interface';
export * from './lib/ngx-file-upload.module';

0 comments on commit bf6eb55

Please sign in to comment.