diff --git a/packages/core/src/api/common/request-context.service.ts b/packages/core/src/api/common/request-context.service.ts index 2d3281188b..e8a292a66e 100644 --- a/packages/core/src/api/common/request-context.service.ts +++ b/packages/core/src/api/common/request-context.service.ts @@ -39,6 +39,7 @@ export class RequestContextService { const authorizedAsOwnerOnly = !isAuthorized && hasOwnerPermission; const translationFn = (req as any).t; return new RequestContext({ + req, apiType, channel, languageCode, diff --git a/packages/core/src/api/common/request-context.ts b/packages/core/src/api/common/request-context.ts index 15df581ac9..00a345f8c0 100644 --- a/packages/core/src/api/common/request-context.ts +++ b/packages/core/src/api/common/request-context.ts @@ -1,5 +1,7 @@ import { LanguageCode } from '@vendure/common/lib/generated-types'; import { ID, JsonCompatible } from '@vendure/common/lib/shared-types'; +import { isObject } from '@vendure/common/lib/shared-utils'; +import { Request } from 'express'; import { TFunction } from 'i18next'; import { CachedSession } from '../../config/session-cache/session-cache-strategy'; @@ -8,6 +10,7 @@ import { Channel } from '../../entity/channel/channel.entity'; import { ApiType } from './get-api-type'; export type SerializedRequestContext = { + _req?: any; _session: JsonCompatible>; _apiType: ApiType; _channel: JsonCompatible; @@ -45,11 +48,13 @@ export class RequestContext { private readonly _authorizedAsOwnerOnly: boolean; private readonly _translationFn: TFunction; private readonly _apiType: ApiType; + private readonly _req?: Request; /** * @internal */ constructor(options: { + req?: Request; apiType: ApiType; channel: Channel; session?: CachedSession; @@ -58,7 +63,8 @@ export class RequestContext { authorizedAsOwnerOnly: boolean; translationFn?: TFunction; }) { - const { apiType, channel, session, languageCode, translationFn } = options; + const { req, apiType, channel, session, languageCode, translationFn } = options; + this._req = req; this._apiType = apiType; this._channel = channel; this._session = session; @@ -90,6 +96,7 @@ export class RequestContext { */ static deserialize(ctxObject: SerializedRequestContext): RequestContext { return new RequestContext({ + req: ctxObject._req as any, apiType: ctxObject._apiType, channel: new Channel(ctxObject._channel), session: { @@ -109,7 +116,11 @@ export class RequestContext { * process, e.g. to pass it to the Worker process via the {@link WorkerService}. */ serialize(): SerializedRequestContext { - return JSON.parse(JSON.stringify(this)); + const serializableThis: any = Object.assign({}, this); + if (this._req) { + serializableThis._req = this.shallowCloneRequestObject(this._req); + } + return JSON.parse(JSON.stringify(serializableThis)); } /** @@ -122,10 +133,26 @@ export class RequestContext { return Object.assign(Object.create(Object.getPrototypeOf(this)), this); } + /** + * @description + * The raw Express request object. + */ + get req(): Request | undefined { + return this._req; + } + + /** + * @description + * Signals which API this request was received by, e.g. `admin` or `shop`. + */ get apiType(): ApiType { return this._apiType; } + /** + * @description + * The active {@link Channel} of this request. + */ get channel(): Channel { return this._channel; } @@ -174,4 +201,33 @@ export class RequestContext { return `Translation format error: ${e.message}). Original key: ${key}`; } } + + /** + * The Express "Request" object is huge and contains many circular + * references. We will preserve just a subset of the whole, by preserving + * only the serializable properties up to 2 levels deep. + * @private + */ + private shallowCloneRequestObject(req: Request) { + function copySimpleFieldsToDepth(target: any, maxDepth: number, depth: number = 0) { + const result: any = {}; + // tslint:disable-next-line:forin + for (const key in target) { + if (key === 'host' && depth === 0) { + // avoid Express "deprecated: req.host" warning + continue; + } + const val = (target as any)[key]; + if (!isObject(val) && typeof val !== 'function') { + result[key] = val; + } else if (depth < maxDepth) { + depth++; + result[key] = copySimpleFieldsToDepth(val, maxDepth, depth); + depth--; + } + } + return result; + } + return copySimpleFieldsToDepth(req, 1); + } }