@opra/angular
Version:
Opra internationalization package
246 lines (229 loc) • 7.79 kB
text/typescript
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;
};
}