import * as plugins from './plugins.js'; import { VirtualStream } from './classes.virtualstream.js'; import { TypedResponseError } from './classes.typedresponseerror.js'; import { TypedRouter, type ITypedRequestLogEntry } from './classes.typedrouter.js'; import { TypedTarget } from './classes.typedtarget.js'; const webrequestInstance = new plugins.webrequest.WebRequest(); /** * Helper to call global hooks from TypedRequest */ function callGlobalHook( hookName: keyof typeof TypedRouter.globalHooks, entry: ITypedRequestLogEntry ): void { try { TypedRouter.globalHooks[hookName]?.(entry); } catch (err) { console.error(`TypedRequest hook error (${hookName}):`, err); } } export class TypedRequest { /** * in case we post against a url endpoint */ public urlEndPoint?: string; /** * in case we post against a TypedTarget */ typedTarget: TypedTarget; public method: string; /** * When true, hooks will not be called for this request. * Use this for internal/logging requests to prevent infinite loops. */ public skipHooks: boolean = false; /** * @param postEndPointArg * @param methodArg */ constructor(postTarget: string | TypedTarget, methodArg: T['method']) { if (typeof postTarget === 'string') { this.urlEndPoint = postTarget; } else { this.typedTarget = postTarget; } this.method = methodArg; } /** * fires the request */ public async fire(fireArg: T['request'], useCacheArg: boolean = false): Promise { const requestStartTime = Date.now(); let payloadSending: plugins.typedRequestInterfaces.ITypedRequest = { method: this.method, request: fireArg, response: null, correlation: { id: plugins.isounique.uni(), phase: 'request', }, }; // lets preprocess the payload payloadSending = VirtualStream.encodePayloadForNetwork(payloadSending, { sendMethod: (payloadArg: plugins.typedRequestInterfaces.IStreamRequest) => { return this.postTrObject(payloadArg) as Promise; } }); // Hook: outgoing request (skip if this is an internal request) if (!this.skipHooks) { callGlobalHook('onOutgoingRequest', { correlationId: payloadSending.correlation.id, method: this.method, direction: 'outgoing', phase: 'request', timestamp: requestStartTime, payload: fireArg, }); } let payloadReceiving: plugins.typedRequestInterfaces.ITypedRequest; payloadReceiving = await this.postTrObject(payloadSending, useCacheArg); // lets preprocess the response payloadReceiving = VirtualStream.decodePayloadFromNetwork(payloadReceiving, { sendMethod: (payloadArg: plugins.typedRequestInterfaces.IStreamRequest) => { return this.postTrObject(payloadArg) as Promise; } }); // Hook: incoming response (skip if this is an internal request) if (!this.skipHooks) { callGlobalHook('onIncomingResponse', { correlationId: payloadSending.correlation.id, method: this.method, direction: 'incoming', phase: 'response', timestamp: Date.now(), durationMs: Date.now() - requestStartTime, payload: payloadReceiving?.response, error: payloadReceiving?.error?.text, }); } return payloadReceiving.response; } private async postTrObject(payloadSendingArg: plugins.typedRequestInterfaces.ITypedRequest, useCacheArg: boolean = false) { let payloadReceiving: plugins.typedRequestInterfaces.ITypedRequest; if (this.urlEndPoint) { const response = await webrequestInstance.postJson( this.urlEndPoint, payloadSendingArg, useCacheArg ); payloadReceiving = response; } else { payloadReceiving = await this.typedTarget.post(payloadSendingArg); } if (payloadReceiving.error) { console.error( `method: >>${this.method}<< got an ERROR: "${payloadReceiving.error.text}" with data ${JSON.stringify( payloadReceiving.error.data, null, 2 )}` ); if (!payloadReceiving.retry) { throw new TypedResponseError(payloadReceiving.error.text, payloadReceiving.error.data); } return null; } if (payloadReceiving.retry) { console.log( `server requested retry for the following reason: ${payloadReceiving.retry.reason}` ); await plugins.smartdelay.delayFor(payloadReceiving.retry.waitForMs); // tslint:disable-next-line: no-return-await payloadReceiving = await this.postTrObject(payloadSendingArg, useCacheArg); } return payloadReceiving; } }