UNPKG

@finos/legend-shared

Version:
352 lines (331 loc) 9.42 kB
/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import type { PlainObject } from '../CommonUtils.js'; import { guaranteeNonNullable } from '../error/AssertionUtils.js'; import { type Parameters, type RequestHeaders, type RequestProcessConfig, type ResponseProcessConfig, HttpMethod, makeUrl, createRequestHeaders, NetworkClient, } from './NetworkUtils.js'; import type { TraceData, TracerService } from './TracerService.js'; export interface ServerClientConfig { baseUrl?: string; queryBaseUrl?: string | undefined; networkClientOptions?: PlainObject; enableCompression?: boolean; /** * Allowing debugging payload so we can see the payload being sent to the server * This is needed due to a limitation with certain browsers (e.g. Chrome) where * when then payload gets huge, the developer tools can no longer display them * which makes it hard to debug. */ enableDebuggingPayload?: boolean; payloadDebugger?: | ((payload: unknown, identifier: string) => void) | undefined; baseHeaders?: RequestHeaders | undefined; /** * This supports a basic re-authenticate mechanism using <iframe>. * The provided URL will be loaded in the <iframe> in the background * and supposedly re-authentication would happen without user's action * i.e. could be that auth cookie is being refreshed. * * NOTE: this is very specific to the deployment context. */ autoReAuthenticateUrl?: string; } /** * This is a template for server clients, it is a wrapper around `NetworkClient` with more features * such as request payload compression, etc. */ export abstract class AbstractServerClient { private networkClient: NetworkClient; private _tracerService?: TracerService; enableCompression: boolean; enableDebuggingPayload: boolean; payloadDebugger?: | ((payload: unknown, identifier: string) => void) | undefined; baseUrl?: string | undefined; baseHeaders?: RequestHeaders | undefined; autoReAuthenticateUrl?: string | undefined; constructor(config: ServerClientConfig) { this.networkClient = new NetworkClient({ baseUrl: config.baseUrl, options: config.networkClientOptions, }); this.baseUrl = config.baseUrl; this.enableCompression = Boolean(config.enableCompression); this.enableDebuggingPayload = Boolean(config.enableDebuggingPayload); this.payloadDebugger = config.payloadDebugger; this.baseHeaders = config.baseHeaders; this.autoReAuthenticateUrl = config.autoReAuthenticateUrl; } setBaseUrl(val: string | undefined): void { this.baseUrl = val; this.networkClient.baseUrl = val; } setCompression(val: boolean): void { this.enableCompression = val; } setDebugPayload(val: boolean): void { this.enableDebuggingPayload = val; } debugPayload<T>(payload: T, identifier: string): T { if (this.enableDebuggingPayload && this.payloadDebugger) { this.payloadDebugger( payload, // convert the identifier to kebab case ot be more machine friendly identifier.replace(/[\s_]+/g, '-').toLowerCase(), ); } return payload; } setTracerService(val: TracerService): void { this._tracerService = val; } private get tracerService(): TracerService { return guaranteeNonNullable( this._tracerService, `Tracer service has not been set`, ); } async get<T>( url: string, options: RequestInit = {}, headers?: RequestHeaders, parameters?: Parameters, requestProcessConfig?: RequestProcessConfig, responseProcessConfig?: ResponseProcessConfig, ): Promise<T> { // NOTE: do not use Content-Type for GET to avoid unnecessary pre-flight when cross-origin return this.request( HttpMethod.GET, url, undefined, options, headers, parameters, requestProcessConfig, responseProcessConfig, ); } async put<T>( url: string, data: unknown = {}, options: RequestInit = {}, headers?: RequestHeaders, parameters?: Parameters, requestProcessConfig?: RequestProcessConfig, responseProcessConfig?: ResponseProcessConfig, ): Promise<T> { return this.request( HttpMethod.PUT, url, data, options, headers, parameters, requestProcessConfig, responseProcessConfig, ); } async post<T>( url: string, data: unknown = {}, options: RequestInit = {}, headers?: RequestHeaders, parameters?: Parameters, requestProcessConfig?: RequestProcessConfig, responseProcessConfig?: ResponseProcessConfig, ): Promise<T> { return this.request( HttpMethod.POST, url, data, options, headers, parameters, requestProcessConfig, responseProcessConfig, ); } async delete<T>( url: string, data: unknown = {}, options: RequestInit = {}, headers?: RequestHeaders, parameters?: Parameters, requestProcessConfig?: RequestProcessConfig, responseProcessConfig?: ResponseProcessConfig, ): Promise<T> { return this.request( HttpMethod.DELETE, url, data, options, headers, parameters, requestProcessConfig, responseProcessConfig, ); } async getWithTracing<T>( traceData: TraceData, url: string, options: RequestInit = {}, headers?: RequestHeaders, parameters?: Parameters, requestProcessConfig?: RequestProcessConfig, responseProcessConfig?: ResponseProcessConfig, ): Promise<T> { // NOTE: do not use Content-Type for GET to avoid unnecessary pre-flight when cross-origin return this.request( HttpMethod.GET, url, undefined, options, headers, parameters, requestProcessConfig, responseProcessConfig, traceData, ); } async putWithTracing<T>( traceData: TraceData, url: string, data: unknown = {}, options: RequestInit = {}, headers?: RequestHeaders, parameters?: Parameters, requestProcessConfig?: RequestProcessConfig, responseProcessConfig?: ResponseProcessConfig, ): Promise<T> { return this.request( HttpMethod.PUT, url, data, options, headers, parameters, requestProcessConfig, responseProcessConfig, traceData, ); } async postWithTracing<T>( traceData: TraceData, url: string, data: unknown = {}, options: RequestInit = {}, headers?: RequestHeaders, parameters?: Parameters, requestProcessConfig?: RequestProcessConfig, responseProcessConfig?: ResponseProcessConfig, ): Promise<T> { return this.request( HttpMethod.POST, url, data, options, headers, parameters, requestProcessConfig, responseProcessConfig, traceData, ); } async deleteWithTracing<T>( traceData: TraceData, url: string, data: unknown = {}, options: RequestInit = {}, headers?: RequestHeaders, parameters?: Parameters, requestProcessConfig?: RequestProcessConfig, responseProcessConfig?: ResponseProcessConfig, ): Promise<T> { return this.request( HttpMethod.DELETE, url, data, options, headers, parameters, requestProcessConfig, responseProcessConfig, traceData, ); } async request<T>( method: HttpMethod, url: string, data: unknown, options: RequestInit, headers?: RequestHeaders | undefined, parameters?: Parameters | undefined, requestProcessConfig?: RequestProcessConfig | undefined, responseProcessConfig?: ResponseProcessConfig | undefined, traceData?: TraceData | undefined, ): Promise<T> { const requestUrl = makeUrl( this.networkClient.baseUrl, url, parameters ?? {}, ); headers = createRequestHeaders(method, headers); // tracing const trace = this.tracerService.createTrace( traceData, method.toString(), requestUrl, headers, ); return this.networkClient .request<T>( method, url, data, options, this.baseHeaders ? { ...this.baseHeaders, ...headers } : headers, parameters, { ...(requestProcessConfig ?? {}), enableCompression: requestProcessConfig?.enableCompression && this.enableCompression, }, { ...(responseProcessConfig ?? {}), preprocess: (response: Response) => trace.bootstrap(response), autoReAuthenticateUrl: this.autoReAuthenticateUrl, }, ) .then((result) => { trace.reportSuccess(); return Promise.resolve(result); }) .catch((error) => { trace.reportError(error); return Promise.reject(error); }); } }