Skip to content

Commit

Permalink
feat(core): Include express request object in RequestContext
Browse files Browse the repository at this point in the history
Closes #581
  • Loading branch information
michaelbromley committed Dec 11, 2020
1 parent 75b5b7a commit c4352b2
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/core/src/api/common/request-context.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class RequestContextService {
const authorizedAsOwnerOnly = !isAuthorized && hasOwnerPermission;
const translationFn = (req as any).t;
return new RequestContext({
req,
apiType,
channel,
languageCode,
Expand Down
60 changes: 58 additions & 2 deletions packages/core/src/api/common/request-context.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,6 +10,7 @@ import { Channel } from '../../entity/channel/channel.entity';
import { ApiType } from './get-api-type';

export type SerializedRequestContext = {
_req?: any;
_session: JsonCompatible<Required<CachedSession>>;
_apiType: ApiType;
_channel: JsonCompatible<Channel>;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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: {
Expand All @@ -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));
}

/**
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
}
}

0 comments on commit c4352b2

Please sign in to comment.