import { HttpClient, HttpEventType, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { FileStatus, FileUploadEvent } from '@core/typings/file.typing'; import { environment } from '@environment'; import { IHttpRestService } from '@yourcause/common/http'; import { filter, map, mergeMap, Observable, of } from 'rxjs'; import { VirusScanService } from './virus-scan.service'; @Injectable({ providedIn: 'root' }) export class HttpRestService implements IHttpRestService { constructor ( private virusScanService: VirusScanService, private http: HttpClient ) { } get (url: string, headers?: any): Promise { return this.makeRequest(url, 'get', null, headers); } post (url: string, data: B, headers?: any): Promise { return this.makeRequest(url, 'post', data, headers); } /** * Returns an observable of `FileEvent`s indicating upload progress * * @param url URL to ultimately upload the file * @param fileToUpload File being uploaded * @param paramName Param on the post body for the file * @param fileName Name of the file (if uploading a Blob) */ postFileWithProgress ( url: string, fileToUpload: File, paramName = 'file', fileName?: string ): Observable> { const { request, formData, file } = this.setupFormData(fileToUpload, fileName, paramName, url); if (environment.virusScanEnabled) { return this.virusScanService.scanFileForVirusesNew(file).pipe( mergeMap, Observable>((scanValue) => { if (scanValue.status === FileStatus.COMPLETE_VIRUS_SCAN) { formData.append('ClamAVToken', scanValue.result); return this.performPostFileWithProgress(request); } return of(scanValue); })) .pipe(filter(value => value !== null)); } return this.performPostFileWithProgress(request); } private performPostFileWithProgress (request: HttpRequest): Observable> { return this.http.request(request) .pipe(map(event => { let returnValue: FileUploadEvent | null = null; switch (event.type) { case HttpEventType.UploadProgress: returnValue = { progress: event.loaded / event.total, status: FileStatus.UPLOAD_API }; break; case HttpEventType.Response: const response: T = JSON.parse((>event).body); returnValue = { status: FileStatus.COMPLETE, result: response }; break; } return returnValue; })).pipe(filter(value => value !== null)); } async postFile ( url: string, fileToUpload: File|Blob|string, paramName = 'file', fileName?: string ) { const { request, formData, file } = this.setupFormData(fileToUpload, fileName, paramName, url); if (environment.virusScanEnabled) { const virusResponse = await this.virusScanService.scanFileForViruses(file); formData.append('ClamAVToken', virusResponse.scanToken); } return this.http.request(request).pipe( filter(event => event instanceof HttpResponse), map(event => (>event).body)) .toPromise(); } private setupFormData ( file: string | File | Blob, fileName: string, paramName: string, url: string, responseType: 'text'|'arraybuffer'|'blob'|'json' = 'json' ) { const formData = new FormData(); if (typeof file === 'string') { file = new File([new Blob([file])], fileName || 'upload'); } if (!(file instanceof File) && fileName) { formData.append(paramName, file, fileName); } else { formData.append(paramName, file); } const request = new HttpRequest('POST', url, formData, { reportProgress: true, headers: new HttpHeaders({ 'ngsw-bypass': 'true' }), responseType }); return { formData, request, file }; } put (url: string, data: any, headers?: any): Promise { return this.makeRequest(url, 'put', data, headers); } delete (url: string, headers?: any): Promise { return this.makeRequest(url, 'delete', null, headers); } putS3PublicRead (url: string, file: any, contentType: string): Promise { return this.putS3(url, file, contentType, true); } protected makeRequest ( url: string, method: 'get'|'post'|'put'|'delete', data: B, inputHeaders?: any ): Promise { let observable: Observable; switch (method) { case 'get': observable = this.http.get(url, { headers: inputHeaders }); break; case 'post': observable = this.http.post(url, data, { headers: inputHeaders }); break; case 'put': observable = this.http.put(url, data, { headers: inputHeaders }); break; case 'delete': observable = this.http.delete(url, { headers: inputHeaders }); break; } return observable .toPromise(); } private putS3 ( url: string, file: File, contentType: string, isPublicRead: boolean ): Promise { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('PUT', url); xhr.setRequestHeader('Content-Type', contentType); if (isPublicRead) { xhr.setRequestHeader('x-amz-acl', 'public-read'); } xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve(xhr.response); } else { reject(xhr.response); } } }; xhr.send(file); }); } }