@salesforce/mrt-utilities 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +49 -45
  2. package/dist/cjs/index.d.ts +4 -0
  3. package/dist/cjs/index.js +10 -0
  4. package/dist/cjs/index.js.map +1 -0
  5. package/dist/cjs/metrics/index.d.ts +1 -0
  6. package/dist/cjs/metrics/index.js +7 -0
  7. package/dist/cjs/metrics/index.js.map +1 -0
  8. package/dist/cjs/metrics/metrics-sender.d.ts +136 -0
  9. package/dist/cjs/metrics/metrics-sender.js +240 -0
  10. package/dist/cjs/metrics/metrics-sender.js.map +1 -0
  11. package/dist/cjs/middleware/data-store.d.ts +56 -0
  12. package/dist/cjs/middleware/data-store.js +124 -0
  13. package/dist/cjs/middleware/data-store.js.map +1 -0
  14. package/dist/cjs/middleware/index.d.ts +3 -0
  15. package/dist/cjs/middleware/index.js +8 -0
  16. package/dist/cjs/middleware/index.js.map +1 -0
  17. package/dist/cjs/middleware/middleware.d.ts +126 -0
  18. package/dist/cjs/middleware/middleware.js +411 -0
  19. package/dist/cjs/middleware/middleware.js.map +1 -0
  20. package/dist/cjs/package.json +1 -0
  21. package/dist/cjs/streaming/create-lambda-adapter.d.ts +68 -0
  22. package/dist/cjs/streaming/create-lambda-adapter.js +797 -0
  23. package/dist/cjs/streaming/create-lambda-adapter.js.map +1 -0
  24. package/dist/cjs/streaming/index.d.ts +1 -0
  25. package/dist/cjs/streaming/index.js +7 -0
  26. package/dist/cjs/streaming/index.js.map +1 -0
  27. package/dist/cjs/utils/configure-proxying.d.ts +160 -0
  28. package/dist/cjs/utils/configure-proxying.js +204 -0
  29. package/dist/cjs/utils/configure-proxying.js.map +1 -0
  30. package/dist/cjs/utils/ssr-proxying.d.ts +300 -0
  31. package/dist/cjs/utils/ssr-proxying.js +713 -0
  32. package/dist/cjs/utils/ssr-proxying.js.map +1 -0
  33. package/dist/cjs/utils/utils.d.ts +27 -0
  34. package/dist/cjs/utils/utils.js +44 -0
  35. package/dist/cjs/utils/utils.js.map +1 -0
  36. package/dist/esm/index.d.ts +4 -0
  37. package/dist/esm/index.js +10 -0
  38. package/dist/esm/index.js.map +1 -0
  39. package/dist/esm/metrics/index.d.ts +1 -0
  40. package/dist/esm/metrics/index.js +7 -0
  41. package/dist/esm/metrics/index.js.map +1 -0
  42. package/dist/esm/metrics/metrics-sender.d.ts +136 -0
  43. package/dist/esm/metrics/metrics-sender.js +240 -0
  44. package/dist/esm/metrics/metrics-sender.js.map +1 -0
  45. package/dist/esm/middleware/data-store.d.ts +56 -0
  46. package/dist/esm/middleware/data-store.js +124 -0
  47. package/dist/esm/middleware/data-store.js.map +1 -0
  48. package/dist/esm/middleware/index.d.ts +3 -0
  49. package/dist/esm/middleware/index.js +9 -0
  50. package/dist/esm/middleware/index.js.map +1 -0
  51. package/dist/esm/middleware/middleware.d.ts +126 -0
  52. package/dist/esm/middleware/middleware.js +411 -0
  53. package/dist/esm/middleware/middleware.js.map +1 -0
  54. package/dist/esm/streaming/create-lambda-adapter.d.ts +68 -0
  55. package/dist/esm/streaming/create-lambda-adapter.js +797 -0
  56. package/dist/esm/streaming/create-lambda-adapter.js.map +1 -0
  57. package/dist/esm/streaming/index.d.ts +1 -0
  58. package/dist/esm/streaming/index.js +7 -0
  59. package/dist/esm/streaming/index.js.map +1 -0
  60. package/dist/esm/utils/configure-proxying.d.ts +160 -0
  61. package/dist/esm/utils/configure-proxying.js +204 -0
  62. package/dist/esm/utils/configure-proxying.js.map +1 -0
  63. package/dist/esm/utils/ssr-proxying.d.ts +300 -0
  64. package/dist/esm/utils/ssr-proxying.js +713 -0
  65. package/dist/esm/utils/ssr-proxying.js.map +1 -0
  66. package/dist/esm/utils/utils.d.ts +27 -0
  67. package/dist/esm/utils/utils.js +44 -0
  68. package/dist/esm/utils/utils.js.map +1 -0
  69. package/package.json +129 -7
@@ -0,0 +1,124 @@
1
+ /*
2
+ * Copyright (c) 2025, Salesforce, Inc.
3
+ * SPDX-License-Identifier: Apache-2
4
+ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
7
+ import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
8
+ import { logMRTError } from '../utils/utils.js';
9
+ export class DataStoreNotFoundError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'DataStoreNotFoundError';
13
+ Object.setPrototypeOf(this, DataStoreNotFoundError.prototype);
14
+ }
15
+ }
16
+ export class DataStoreServiceError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = 'DataStoreServiceError';
20
+ Object.setPrototypeOf(this, DataStoreServiceError.prototype);
21
+ }
22
+ }
23
+ export class DataStoreUnavailableError extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = 'DataStoreUnavailableError';
27
+ Object.setPrototypeOf(this, DataStoreUnavailableError.prototype);
28
+ }
29
+ }
30
+ /**
31
+ * A class for reading entries from the data store.
32
+ *
33
+ * This class uses a singleton pattern.
34
+ * Use DataStore.getDataStore() to get the singleton instance.
35
+ */
36
+ export class DataStore {
37
+ _tableName = '';
38
+ _ddb = null;
39
+ static _instance = null;
40
+ /** @internal Test hook: inject a document client for unit tests */
41
+ static _testDocumentClient = null;
42
+ /** @internal Test hook: inject logMRTError for unit tests */
43
+ static _testLogMRTError = null;
44
+ constructor() {
45
+ // Private constructor for singleton; use DataStore.getDataStore() instead.
46
+ }
47
+ /**
48
+ * Get or create a DynamoDB document client (for abstraction of attribute values).
49
+ *
50
+ * @private
51
+ * @returns The DynamoDB document client
52
+ * @throws {DataStoreUnavailableError} The data store is unavailable
53
+ */
54
+ getClient() {
55
+ if (!this.isDataStoreAvailable()) {
56
+ throw new DataStoreUnavailableError('The data store is unavailable.');
57
+ }
58
+ if (DataStore._testDocumentClient) {
59
+ this._tableName = `DataAccessLayer-${process.env.AWS_REGION}`;
60
+ return DataStore._testDocumentClient;
61
+ }
62
+ if (!this._ddb) {
63
+ this._tableName = `DataAccessLayer-${process.env.AWS_REGION}`;
64
+ this._ddb = DynamoDBDocumentClient.from(new DynamoDBClient({
65
+ region: process.env.AWS_REGION,
66
+ }));
67
+ }
68
+ return this._ddb;
69
+ }
70
+ /**
71
+ * Get or create the singleton DataStore instance.
72
+ *
73
+ * @returns The singleton DataStore instance
74
+ */
75
+ static getDataStore() {
76
+ if (!DataStore._instance) {
77
+ DataStore._instance = new DataStore();
78
+ }
79
+ return DataStore._instance;
80
+ }
81
+ /**
82
+ * Whether the data store can be used in the current environment.
83
+ *
84
+ * @returns true if the data store is available, false otherwise
85
+ */
86
+ isDataStoreAvailable() {
87
+ return Boolean(process.env.AWS_REGION && process.env.MOBIFY_PROPERTY_ID && process.env.DEPLOY_TARGET);
88
+ }
89
+ /**
90
+ * Fetch an entry from the data store.
91
+ *
92
+ * @param key The data store entry's key
93
+ * @returns An object containing the entry's key and value
94
+ * @throws {DataStoreUnavailableError} The data store is unavailable
95
+ * @throws {DataStoreNotFoundError} An entry with the given key cannot be found
96
+ * @throws {DataStoreServiceError} An internal error occurred
97
+ */
98
+ async getEntry(key) {
99
+ if (!this.isDataStoreAvailable()) {
100
+ throw new DataStoreUnavailableError('The data store is unavailable.');
101
+ }
102
+ const ddb = this.getClient();
103
+ let response;
104
+ try {
105
+ response = await ddb.send(new GetCommand({
106
+ TableName: this._tableName,
107
+ Key: {
108
+ projectEnvironment: `${process.env.MOBIFY_PROPERTY_ID} ${process.env.DEPLOY_TARGET}`,
109
+ key,
110
+ },
111
+ }));
112
+ }
113
+ catch (error) {
114
+ const logFn = DataStore._testLogMRTError ?? logMRTError;
115
+ logFn('data_store', error, { key, tableName: this._tableName });
116
+ throw new DataStoreServiceError('Data store request failed.');
117
+ }
118
+ if (!response.Item?.value) {
119
+ throw new DataStoreNotFoundError(`Data store entry '${key}' not found.`);
120
+ }
121
+ return { key, value: response.Item.value };
122
+ }
123
+ }
124
+ //# sourceMappingURL=data-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-store.js","sourceRoot":"","sources":["../../../src/middleware/data-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from './data-store.js';
2
+ export * from './middleware.js';
3
+ export { type ProxyConfig } from '../utils/configure-proxying.js';
@@ -0,0 +1,8 @@
1
+ /*
2
+ * Copyright (c) 2025, Salesforce, Inc.
3
+ * SPDX-License-Identifier: Apache-2
4
+ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ export * from './data-store.js';
7
+ export * from './middleware.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,126 @@
1
+ import { type ProxyResult, type ProxyConfig, type CreateProxyMiddlewareFn } from '../utils/configure-proxying.js';
2
+ import { type RequestHandler, type Response } from 'express';
3
+ export declare const X_MOBIFY_REQUEST_CLASS = "x-mobify-request-class";
4
+ export declare const X_MOBIFY_QUERYSTRING = "x-mobify-querystring";
5
+ export declare const X_MOBIFY_REQUEST_PROCESSOR_LOCAL = "x-mobify-rp-local";
6
+ /**
7
+ * Creates a middleware function that processes incoming requests using a custom request processor.
8
+ *
9
+ * This middleware handles:
10
+ * - Skipping processing for proxy and bundle paths
11
+ * - Loading and executing custom request processors
12
+ * - Processing custom query strings from headers
13
+ * - Removing API Gateway headers
14
+ * - Enforcing HTTP method restrictions for root path
15
+ * - Updating request paths and query strings when paths change
16
+ *
17
+ * @param requestProcessorPath - Path to the request processor module file
18
+ * @param proxyConfigs - Array of proxy configurations
19
+ * @returns Express middleware function
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const middleware = createMRTRequestProcessorMiddleware(
24
+ * '/path/to/processor.js',
25
+ * [{ host: 'https://api.example.com', path: 'api' }]
26
+ * );
27
+ * app.use(middleware);
28
+ * ```
29
+ */
30
+ export declare const createMRTRequestProcessorMiddleware: (requestProcessorPath: string | undefined, proxyConfigs: ProxyConfig[] | undefined) => RequestHandler;
31
+ /**
32
+ * Creates proxy middleware functions for the specified proxy configurations.
33
+ *
34
+ * This function creates Express middleware functions that handle proxying requests
35
+ * to external services. It can optionally create both regular proxy and caching
36
+ * proxy middlewares for each configuration. The app hostname is automatically
37
+ * retrieved from environment variables (EXTERNAL_DOMAIN_NAME or defaults to 'localhost:2401').
38
+ *
39
+ * @param proxyConfigs - Array of proxy configurations
40
+ * @param appProtocol - The protocol to use for the app (defaults to 'http')
41
+ * @param includeCaching - Whether to include caching proxy middlewares (defaults to false)
42
+ * @returns Array of proxy middleware results with their paths
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const proxyMiddlewares = createMRTProxyMiddlewares(
47
+ * [{ host: 'https://api.example.com', path: 'api' }],
48
+ * 'https',
49
+ * true // Include caching middlewares
50
+ * );
51
+ *
52
+ * proxyMiddlewares.forEach(({ fn, path }) => {
53
+ * app.use(path, fn);
54
+ * });
55
+ * ```
56
+ */
57
+ export declare const createMRTProxyMiddlewares: (proxyConfigs: ProxyConfig[], appProtocol?: string, includeCaching?: boolean, createProxyFn?: CreateProxyMiddlewareFn) => ProxyResult[];
58
+ /**
59
+ * Sets appropriate HTTP headers for local asset files.
60
+ *
61
+ * This function sets content-type, caching, and other headers for static assets
62
+ * served from the local filesystem. It uses the file's modification time for
63
+ * ETag and Last-Modified headers, and sets no-cache directives for local assets.
64
+ *
65
+ * @param res - Express response object
66
+ * @param assetPath - Path to the asset file
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * app.use('/static', express.static('public', {
71
+ * setHeaders: setLocalAssetHeaders
72
+ * }));
73
+ * ```
74
+ */
75
+ export declare const setLocalAssetHeaders: (res: Response, assetPath: string) => void;
76
+ /**
77
+ * Creates an Express static middleware configured for MRT asset serving.
78
+ *
79
+ * This function creates a static file serving middleware with MRT-specific
80
+ * configurations including custom header setting and security options.
81
+ *
82
+ * @param staticAssetDir - Directory path containing static assets
83
+ * @returns Express static middleware function
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const staticMiddleware = createMRTStaticAssetServingMiddleware('/path/to/assets');
88
+ * app.use('/static', staticMiddleware);
89
+ * ```
90
+ */
91
+ export declare const createMRTStaticAssetServingMiddleware: (staticAssetDir: string) => RequestHandler;
92
+ /**
93
+ * Creates a common middleware function that sets the host header based on environment variables.
94
+ *
95
+ * The host header is set to EXTERNAL_DOMAIN_NAME if available, otherwise defaults to 'localhost:2401'.
96
+ * Use this middleware in all environments (local and deployed), at the top of your middleware stack.
97
+ *
98
+ * @returns Express middleware function
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const middleware = createMRTCommonMiddleware();
103
+ * app.use(middleware);
104
+ * ```
105
+ */
106
+ export declare const createMRTCommonMiddleware: () => RequestHandler;
107
+ /**
108
+ * Creates a cleanup middleware function that removes internal headers and cleans up request state.
109
+ *
110
+ * This middleware performs cleanup operations on requests:
111
+ * - Removes internal MRT headers (X_MOBIFY_REQUEST_PROCESSOR_LOCAL, X_MOBIFY_QUERYSTRING)
112
+ * - Removes API Gateway headers that shouldn't be forwarded
113
+ * - Optionally updates the path and querystring if the request wasn't processed by the request processor
114
+ *
115
+ * Use this middleware in all environments (local and deployed), at the end of the middleware chain,
116
+ * to ensure all internal headers are removed before the request is handled by the application.
117
+ *
118
+ * @returns Express middleware function
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const cleanupMiddleware = createMRTCleanUpMiddleware();
123
+ * app.use(cleanupMiddleware);
124
+ * ```
125
+ */
126
+ export declare const createMRTCleanUpMiddleware: () => RequestHandler;
@@ -0,0 +1,411 @@
1
+ /*
2
+ * Copyright (c) 2025, Salesforce, Inc.
3
+ * SPDX-License-Identifier: Apache-2
4
+ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ /**
7
+ * @fileoverview MRT (Managed Runtime) Middleware for Express.js applications.
8
+ *
9
+ * This module provides middleware functions for handling requests in a managed runtime environment,
10
+ * including request processing, proxy configuration, static asset serving, and local development
11
+ * utilities. It's designed to work with Salesforce Commerce Cloud's managed runtime platform.
12
+ *
13
+ * @author Salesforce Commerce Cloud
14
+ * @version 0.0.1
15
+ */
16
+ import { Headers } from '../utils/ssr-proxying.js';
17
+ import { configureProxying, } from '../utils/configure-proxying.js';
18
+ import express from 'express';
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ import mimeTypes from 'mime-types';
22
+ import qs from 'qs';
23
+ const MOBIFY_PATH = '/mobify';
24
+ const PROXY_PATH_BASE = `${MOBIFY_PATH}/proxy`;
25
+ const CACHING_PATH_BASE = `${MOBIFY_PATH}/caching`;
26
+ const BUNDLE_PATH_BASE = `${MOBIFY_PATH}/bundle`;
27
+ const proxyBasePath = PROXY_PATH_BASE;
28
+ const bundleBasePath = BUNDLE_PATH_BASE;
29
+ const X_HEADERS_TO_REMOVE_ORIGIN = [
30
+ 'x-api-key',
31
+ 'x-apigateway-event',
32
+ 'x-apigateway-context',
33
+ 'x-mobify-access-key',
34
+ 'x-sfdc-access-control',
35
+ ];
36
+ export const X_MOBIFY_REQUEST_CLASS = 'x-mobify-request-class';
37
+ export const X_MOBIFY_QUERYSTRING = 'x-mobify-querystring';
38
+ export const X_MOBIFY_REQUEST_PROCESSOR_LOCAL = 'x-mobify-rp-local';
39
+ const CONTENT_TYPE = 'content-type';
40
+ const NO_CACHE = 'max-age=0, nocache, nostore, must-revalidate';
41
+ /**
42
+ * Checks if a URL is for a bundle or proxy path that should be skipped by request processing.
43
+ *
44
+ * @param url - The URL to check
45
+ * @returns True if the URL starts with a proxy or bundle base path
46
+ * @private
47
+ */
48
+ const _isBundleOrProxyPath = (url) => {
49
+ return url.startsWith(proxyBasePath) || url.startsWith(bundleBasePath);
50
+ };
51
+ /**
52
+ * Dynamically imports a request processor module if it exists.
53
+ *
54
+ * @param requestProcessorPath - The file path to the request processor module
55
+ * @returns The default export of the module, or null if the file doesn't exist
56
+ * @private
57
+ */
58
+ const _getRequestProcessor = async (requestProcessorPath) => {
59
+ if (requestProcessorPath && fs.existsSync(requestProcessorPath)) {
60
+ const module = await import(requestProcessorPath);
61
+ return module;
62
+ }
63
+ return null;
64
+ };
65
+ /**
66
+ * Retrieves request processor parameters from environment variables with defaults.
67
+ *
68
+ * This function reads environment variables to determine the application hostname,
69
+ * deployment target, and environment. It provides sensible defaults for local development.
70
+ *
71
+ * @returns Object containing appHostname, deployTarget, and environment
72
+ * @private
73
+ */
74
+ const getRequestProcessorParameters = () => {
75
+ return {
76
+ appHostname: process.env.EXTERNAL_DOMAIN_NAME || 'localhost:2401',
77
+ deployTarget: process.env.DEPLOY_TARGET || 'local-target',
78
+ environment: process.env.ENVIRONMENT || 'development',
79
+ };
80
+ };
81
+ /**
82
+ * Updates the request's path and querystring, and parses the query parameters.
83
+ *
84
+ * This function updates the Express request object's originalUrl and query properties.
85
+ * It handles both cases where a querystring is present and where it's not. For Express 5
86
+ * compatibility, it uses Object.defineProperty to update the query object since direct
87
+ * modification is no longer allowed.
88
+ *
89
+ * @param req - Express request object to update
90
+ * @param updatedPath - The new path to set
91
+ * @param updatedQuerystring - The new querystring (optional, if undefined the querystring is removed)
92
+ * @private
93
+ */
94
+ const updatePathAndQueryString = (req, updatedPath, updatedQuerystring) => {
95
+ let newQuery = {};
96
+ if (updatedQuerystring) {
97
+ newQuery = qs.parse(updatedQuerystring);
98
+ req.originalUrl = `${updatedPath}?${updatedQuerystring}`;
99
+ }
100
+ else {
101
+ req.originalUrl = updatedPath;
102
+ }
103
+ // Express 5 no longer allows direct modification of the query property
104
+ Object.defineProperty(req, 'query', {
105
+ value: { ...newQuery },
106
+ writable: true,
107
+ enumerable: true,
108
+ configurable: true,
109
+ });
110
+ };
111
+ /**
112
+ * Removes internal MRT headers and API Gateway headers from the request.
113
+ *
114
+ * This function cleans up headers that should not be forwarded to downstream services.
115
+ * It removes API Gateway-specific headers and internal MRT headers. When called from
116
+ * the cleanup middleware, it also removes the X_MOBIFY_REQUEST_PROCESSOR_LOCAL header
117
+ * to indicate that cleanup has been performed.
118
+ *
119
+ * @param req - Express request object to clean up
120
+ * @param cleanupLocalRequestProcessorHeader - If true, removes X_MOBIFY_REQUEST_PROCESSOR_LOCAL header
121
+ * @private
122
+ */
123
+ const cleanUpHeaders = (req, cleanupLocalRequestProcessorHeader = false) => {
124
+ // If the cleanup is happening in the local request processor
125
+ // we don't want to remove the X_MOBIFY_REQUEST_PROCESSOR_LOCAL header
126
+ // because we need to not overwrite it in the cleanup middleware
127
+ if (cleanupLocalRequestProcessorHeader) {
128
+ delete req.headers[X_MOBIFY_REQUEST_PROCESSOR_LOCAL];
129
+ }
130
+ X_HEADERS_TO_REMOVE_ORIGIN.forEach((key) => {
131
+ delete req.headers[key];
132
+ });
133
+ };
134
+ /**
135
+ * Retrieves and processes the querystring from the x-mobify-querystring header.
136
+ *
137
+ * This function checks for the x-mobify-querystring header and uses it as the
138
+ * definitive querystring if present and non-empty. This header is used in production
139
+ * environments to override the URL querystring, but is also handled in local development
140
+ * to allow for testing. After processing, the header is removed from the request.
141
+ *
142
+ * If the header is present but empty, or if it's not present at all, the original
143
+ * querystring is returned unchanged.
144
+ *
145
+ * @param req - Express request object containing the headers
146
+ * @param originalQuerystring - The original querystring from the URL (may be undefined)
147
+ * @returns The querystring to use (from header if present and non-empty, otherwise original)
148
+ * @private
149
+ */
150
+ const getMobifyQueryString = (req, originalQuerystring) => {
151
+ // If there's an x-querystring header, use that as the definitive
152
+ // querystring. This header is used in production, not in local dev,
153
+ // but we always handle it here to allow for testing.
154
+ let updatedQuerystring = originalQuerystring;
155
+ const xQueryString = req.headers[X_MOBIFY_QUERYSTRING];
156
+ if (xQueryString && xQueryString !== '') {
157
+ updatedQuerystring = xQueryString;
158
+ }
159
+ delete req.headers[X_MOBIFY_QUERYSTRING];
160
+ return updatedQuerystring;
161
+ };
162
+ /**
163
+ * Creates a middleware function that processes incoming requests using a custom request processor.
164
+ *
165
+ * This middleware handles:
166
+ * - Skipping processing for proxy and bundle paths
167
+ * - Loading and executing custom request processors
168
+ * - Processing custom query strings from headers
169
+ * - Removing API Gateway headers
170
+ * - Enforcing HTTP method restrictions for root path
171
+ * - Updating request paths and query strings when paths change
172
+ *
173
+ * @param requestProcessorPath - Path to the request processor module file
174
+ * @param proxyConfigs - Array of proxy configurations
175
+ * @returns Express middleware function
176
+ *
177
+ * @example
178
+ * ```typescript
179
+ * const middleware = createMRTRequestProcessorMiddleware(
180
+ * '/path/to/processor.js',
181
+ * [{ host: 'https://api.example.com', path: 'api' }]
182
+ * );
183
+ * app.use(middleware);
184
+ * ```
185
+ */
186
+ export const createMRTRequestProcessorMiddleware = (requestProcessorPath, proxyConfigs) => {
187
+ const processIncomingRequest = async (req, res) => {
188
+ // If the request is for a proxy or bundle path, do nothing
189
+ if (_isBundleOrProxyPath(req.originalUrl)) {
190
+ return;
191
+ }
192
+ const requestProcessor = await _getRequestProcessor(requestProcessorPath);
193
+ const originalQuerystring = req.originalUrl.split('?')[1];
194
+ // If there's no querystring the value will be undefined
195
+ // but TypeScript will complain if we don't explicitly set it to undefined.
196
+ let updatedQuerystring = originalQuerystring || undefined;
197
+ let updatedPath = req.originalUrl.split('?')[0];
198
+ updatedQuerystring = getMobifyQueryString(req, updatedQuerystring);
199
+ if (requestProcessor) {
200
+ // Allow the processor to handle this request. Because this code
201
+ // runs only in the local development server, we intentionally do
202
+ // not swallow errors - we want them to happen and show up on the
203
+ // console because that's how developers can test the processor.
204
+ const headers = new Headers(req.headers, 'http');
205
+ const { appHostname, deployTarget, environment } = getRequestProcessorParameters();
206
+ const processed = requestProcessor.processRequest({
207
+ headers,
208
+ path: req.path,
209
+ querystring: updatedQuerystring,
210
+ getRequestClass: () => headers.getHeader(X_MOBIFY_REQUEST_CLASS),
211
+ setRequestClass: (value) => headers.setHeader(X_MOBIFY_REQUEST_CLASS, value),
212
+ // This matches the set of parameters passed in the
213
+ // Lambda@Edge context.
214
+ parameters: {
215
+ deployTarget,
216
+ appHostname,
217
+ proxyConfigs: proxyConfigs || [],
218
+ environment,
219
+ },
220
+ });
221
+ // Aid debugging by checking the return value
222
+ console.assert(processed && 'path' in processed && 'querystring' in processed, 'Expected processRequest to return an object with ' +
223
+ '"path" and "querystring" properties, ' +
224
+ `but got ${JSON.stringify(processed, null, 2)}`);
225
+ // Update the request.
226
+ updatedQuerystring = processed.querystring;
227
+ updatedPath = processed.path;
228
+ if (headers.modified) {
229
+ req.headers = headers.toObject();
230
+ }
231
+ }
232
+ // Update the request.
233
+ if (updatedQuerystring !== originalQuerystring) {
234
+ updatePathAndQueryString(req, updatedPath, updatedQuerystring);
235
+ }
236
+ // Get the request class and store it for general use. We
237
+ // must do this AFTER the request-processor, because that's
238
+ // what may set the request class.
239
+ res.locals.requestClass = req.headers[X_MOBIFY_REQUEST_CLASS];
240
+ req.headers[X_MOBIFY_REQUEST_PROCESSOR_LOCAL] = 'true'; // Mark the request as processed by the request processor
241
+ };
242
+ const ssrRequestProcessorMiddleware = async (req, res, next) => {
243
+ // If the path is /, we enforce that the only methods
244
+ // allowed are GET, HEAD or OPTIONS. This is a restriction
245
+ // imposed by API Gateway: we enforce it here so that the
246
+ // local dev server has the same behaviour.
247
+ if (req.path === '/' && !['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
248
+ res.sendStatus(405);
249
+ return;
250
+ }
251
+ // Apply custom query parameter parsing.
252
+ await processIncomingRequest(req, res);
253
+ // Strip out API Gateway headers from the incoming request. We
254
+ // do that now so that the rest of the code don't have to deal
255
+ // with these headers, which can be large and may be accidentally
256
+ // forwarded to other servers.
257
+ cleanUpHeaders(req, false);
258
+ // Hand off to the next middleware
259
+ next();
260
+ };
261
+ return ssrRequestProcessorMiddleware;
262
+ };
263
+ /**
264
+ * Creates proxy middleware functions for the specified proxy configurations.
265
+ *
266
+ * This function creates Express middleware functions that handle proxying requests
267
+ * to external services. It can optionally create both regular proxy and caching
268
+ * proxy middlewares for each configuration. The app hostname is automatically
269
+ * retrieved from environment variables (EXTERNAL_DOMAIN_NAME or defaults to 'localhost:2401').
270
+ *
271
+ * @param proxyConfigs - Array of proxy configurations
272
+ * @param appProtocol - The protocol to use for the app (defaults to 'http')
273
+ * @param includeCaching - Whether to include caching proxy middlewares (defaults to false)
274
+ * @returns Array of proxy middleware results with their paths
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * const proxyMiddlewares = createMRTProxyMiddlewares(
279
+ * [{ host: 'https://api.example.com', path: 'api' }],
280
+ * 'https',
281
+ * true // Include caching middlewares
282
+ * );
283
+ *
284
+ * proxyMiddlewares.forEach(({ fn, path }) => {
285
+ * app.use(path, fn);
286
+ * });
287
+ * ```
288
+ */
289
+ export const createMRTProxyMiddlewares = (proxyConfigs, appProtocol = 'http', includeCaching = false, createProxyFn) => {
290
+ if (!proxyConfigs) {
291
+ return [];
292
+ }
293
+ const { appHostname } = getRequestProcessorParameters();
294
+ const proxies = configureProxying(proxyConfigs, appHostname, appProtocol, createProxyFn);
295
+ const middlewares = [];
296
+ proxies.forEach((proxy) => {
297
+ const proxyPath = `${PROXY_PATH_BASE}/${proxy.path}`;
298
+ const cachingProxyPath = `${CACHING_PATH_BASE}/${proxy.path}`;
299
+ middlewares.push({ fn: proxy.fn, path: proxyPath });
300
+ if (includeCaching) {
301
+ middlewares.push({ fn: proxy.fn, path: cachingProxyPath });
302
+ }
303
+ });
304
+ return middlewares;
305
+ };
306
+ /**
307
+ * Sets appropriate HTTP headers for local asset files.
308
+ *
309
+ * This function sets content-type, caching, and other headers for static assets
310
+ * served from the local filesystem. It uses the file's modification time for
311
+ * ETag and Last-Modified headers, and sets no-cache directives for local assets.
312
+ *
313
+ * @param res - Express response object
314
+ * @param assetPath - Path to the asset file
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * app.use('/static', express.static('public', {
319
+ * setHeaders: setLocalAssetHeaders
320
+ * }));
321
+ * ```
322
+ */
323
+ export const setLocalAssetHeaders = (res, assetPath) => {
324
+ const base = path.basename(assetPath);
325
+ const contentType = mimeTypes.lookup(base) || 'application/octet-stream';
326
+ res.set(CONTENT_TYPE, contentType);
327
+ // Stat the file and return the last-modified Date
328
+ // in RFC1123 format. Also use that value as the ETag
329
+ // and Last-Modified
330
+ const mtime = fs.statSync(assetPath).mtime;
331
+ const mtimeRFC1123 = mtime.toUTCString();
332
+ res.set('date', mtimeRFC1123);
333
+ res.set('last-modified', mtimeRFC1123);
334
+ res.set('etag', mtime.getTime().toString());
335
+ // We don't cache local bundle assets
336
+ res.set('cache-control', NO_CACHE);
337
+ };
338
+ /**
339
+ * Creates an Express static middleware configured for MRT asset serving.
340
+ *
341
+ * This function creates a static file serving middleware with MRT-specific
342
+ * configurations including custom header setting and security options.
343
+ *
344
+ * @param staticAssetDir - Directory path containing static assets
345
+ * @returns Express static middleware function
346
+ *
347
+ * @example
348
+ * ```typescript
349
+ * const staticMiddleware = createMRTStaticAssetServingMiddleware('/path/to/assets');
350
+ * app.use('/static', staticMiddleware);
351
+ * ```
352
+ */
353
+ export const createMRTStaticAssetServingMiddleware = (staticAssetDir) => {
354
+ return express.static(staticAssetDir, {
355
+ dotfiles: 'deny',
356
+ setHeaders: setLocalAssetHeaders,
357
+ fallthrough: false,
358
+ });
359
+ };
360
+ /**
361
+ * Creates a common middleware function that sets the host header based on environment variables.
362
+ *
363
+ * The host header is set to EXTERNAL_DOMAIN_NAME if available, otherwise defaults to 'localhost:2401'.
364
+ * Use this middleware in all environments (local and deployed), at the top of your middleware stack.
365
+ *
366
+ * @returns Express middleware function
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * const middleware = createMRTCommonMiddleware();
371
+ * app.use(middleware);
372
+ * ```
373
+ */
374
+ export const createMRTCommonMiddleware = () => {
375
+ return (req, res, next) => {
376
+ req.headers.host = process.env.EXTERNAL_DOMAIN_NAME || 'localhost:2401';
377
+ next();
378
+ };
379
+ };
380
+ /**
381
+ * Creates a cleanup middleware function that removes internal headers and cleans up request state.
382
+ *
383
+ * This middleware performs cleanup operations on requests:
384
+ * - Removes internal MRT headers (X_MOBIFY_REQUEST_PROCESSOR_LOCAL, X_MOBIFY_QUERYSTRING)
385
+ * - Removes API Gateway headers that shouldn't be forwarded
386
+ * - Optionally updates the path and querystring if the request wasn't processed by the request processor
387
+ *
388
+ * Use this middleware in all environments (local and deployed), at the end of the middleware chain,
389
+ * to ensure all internal headers are removed before the request is handled by the application.
390
+ *
391
+ * @returns Express middleware function
392
+ *
393
+ * @example
394
+ * ```typescript
395
+ * const cleanupMiddleware = createMRTCleanUpMiddleware();
396
+ * app.use(cleanupMiddleware);
397
+ * ```
398
+ */
399
+ export const createMRTCleanUpMiddleware = () => {
400
+ return (req, res, next) => {
401
+ if (!req.headers[X_MOBIFY_REQUEST_PROCESSOR_LOCAL]) {
402
+ const originalQuerystring = req.originalUrl.split('?')[1] || undefined;
403
+ const updatedQuerystring = getMobifyQueryString(req, originalQuerystring);
404
+ const updatedPath = req.originalUrl.split('?')[0];
405
+ updatePathAndQueryString(req, updatedPath, updatedQuerystring);
406
+ }
407
+ cleanUpHeaders(req, true);
408
+ next();
409
+ };
410
+ };
411
+ //# sourceMappingURL=middleware.js.map