UNPKG

@opra/angular

Version:

Opra internationalization package

246 lines (229 loc) 7.79 kB
import { HttpContext, HttpHeaders, HttpParams } from '@angular/common/http'; import * as Angular from '@angular/common/http'; import typeIs from '@browsery/type-is'; import { HttpBackend, type HttpDownloadProgressEvent, type HttpEvent, HttpEventType, HttpResponse, type HttpResponseEvent, type HttpResponseHeaderEvent, type HttpSentEvent, type HttpUploadProgressEvent, } from '@opra/client'; import { isBlob } from '@opra/common'; import { Observable } from 'rxjs'; import { isReadableStreamLike } from 'rxjs/internal/util/isReadableStreamLike'; import type { StrictOmit } from 'ts-gems'; /** * * @class AngularBackend */ export class AngularBackend extends HttpBackend { defaults: AngularBackend.RequestDefaults; constructor( readonly httpClient: Angular.HttpClient, serviceUrl: string, options?: AngularBackend.Options, ) { super(serviceUrl, options); this.defaults = { ...options?.defaults, headers: options?.defaults?.headers instanceof Headers ? options?.defaults?.headers : new Headers(options?.defaults?.headers), params: options?.defaults?.params instanceof URLSearchParams ? options?.defaults?.params : new URLSearchParams(options?.defaults?.params), }; } handle(init: AngularBackend.RequestInit): Observable<HttpEvent> { const requestInit = this.prepareRequest(init); const request = new Angular.HttpRequest( requestInit.method, requestInit.url.toString(), { ...requestInit, headers: new HttpHeaders(requestInit.headers), }, ); const _this = this; return new Observable<HttpEvent>(subscriber => { // Send request this.send(request).subscribe({ next(event) { if (event.type === Angular.HttpEventType.Sent) { // Emit 'Sent' event subscriber.next({ type: HttpEventType.Sent, request, } satisfies HttpSentEvent); return; } if (event.type === Angular.HttpEventType.ResponseHeader) { // Emit 'ResponseHeader' event const headersResponse = _this.createResponse({ url: request.url, headers: requestInit.headers, status: event.status, statusText: event.statusText, hasBody: event.headers.has('Content-Type') || event.headers.has('Content-Length'), }) as HttpResponse<never>; subscriber.next({ request, type: HttpEventType.ResponseHeader, response: headersResponse, } satisfies HttpResponseHeaderEvent); return; } if (event.type === Angular.HttpEventType.DownloadProgress) { // Emit 'DownloadProgress' event subscriber.next({ request, type: HttpEventType.DownloadProgress, loaded: event.loaded, total: event.total, } satisfies HttpDownloadProgressEvent); } if (event.type === Angular.HttpEventType.UploadProgress) { // Emit 'UploadProgress' event subscriber.next({ request, type: HttpEventType.UploadProgress, loaded: event.loaded, total: event.total, } satisfies HttpUploadProgressEvent); } if (event.type === Angular.HttpEventType.Response) { const headers = new Headers(); event.headers .keys() .forEach(k => headers.set(k, event.headers.get(k) || '')); const response = _this.createResponse({ url: request.url, headers, status: event.status, statusText: event.statusText, hasBody: !!event.body, body: event.body, }); // Emit 'Response' event subscriber.next({ type: HttpEventType.Response, request, response, } satisfies HttpResponseEvent); } }, error(error) { subscriber.error(error); }, complete() { subscriber.complete(); }, }); }); } protected send(request: Angular.HttpRequest<any>) { return this.httpClient.request(request); } protected prepareRequest( init: AngularBackend.RequestInit, ): AngularBackend.RequestInit { const headers = init.headers || new Headers(); const requestInit: AngularBackend.RequestInit = { ...init, headers, }; this.defaults.headers.forEach((val, key) => { if (!headers.has(key)) headers.set(key, val); }); const url = new URL(requestInit.url, this.serviceUrl); if (this.defaults.params.size) { this.defaults.params.forEach((val, key) => { if (!url.searchParams.has(key)) url.searchParams.set(key, val); }); requestInit.url = url.toString(); } if (requestInit.body) { let body: any; let contentType: string; if ( typeof requestInit.body === 'string' || typeof requestInit.body === 'number' || typeof requestInit.body === 'boolean' ) { contentType = 'text/plain; charset=UTF-8"'; body = String(requestInit.body); headers.delete('Content-Size'); } else if (isReadableStreamLike(requestInit.body)) { contentType = 'application/octet-stream'; body = requestInit.body; } else if (Buffer.isBuffer(requestInit.body)) { contentType = 'application/octet-stream'; body = requestInit.body; headers.set('Content-Size', String(requestInit.body.length)); } else if (isBlob(requestInit.body)) { contentType = requestInit.body.type || 'application/octet-stream'; body = requestInit.body; headers.set('Content-Size', String(requestInit.body.size)); } else { contentType = 'application/json'; body = JSON.stringify(requestInit.body); headers.delete('Content-Size'); } if (!headers.has('Content-Type') && contentType) headers.set('Content-Type', contentType); requestInit.body = body; } return requestInit; } protected createResponse(init: HttpResponse.Initiator): HttpResponse { return new HttpResponse(init); } protected async parseBody(fetchResponse: Response): Promise<any> { let body: any; const contentType = fetchResponse.headers.get('Content-Type') || ''; if (typeIs.is(contentType, ['json', 'application/*+json'])) { body = await fetchResponse.json(); if (typeof body === 'string') body = JSON.parse(body); } else if (typeIs.is(contentType, ['text'])) body = await fetchResponse.text(); else if (typeIs.is(contentType, ['multipart'])) body = await fetchResponse.formData(); else { const buf = await fetchResponse.arrayBuffer(); if (buf.byteLength) body = buf; } return body; } } /** * @namespace AngularBackend */ export namespace AngularBackend { export interface Options extends HttpBackend.Options { defaults?: RequestDefaults; } export interface RequestInit extends HttpBackend.RequestInit { context?: HttpContext; reportProgress?: boolean; params?: HttpParams; responseType?: 'arraybuffer' | 'blob' | 'json' | 'text'; withCredentials?: boolean; } export interface RequestOptions extends Pick< RequestInit, 'context' | 'reportProgress' | 'withCredentials' > {} export type RequestDefaults = StrictOmit<RequestOptions, 'context'> & { headers: Headers; params: URLSearchParams; }; }