@salesforce/pwa-kit-runtime 3.8.0-preview.0-basepath → 3.8.0-preview.2-basepath

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 (52) hide show
  1. package/package.json +6 -5
  2. package/ssr/server/build-remote-server.js +1159 -0
  3. package/ssr/server/build-remote-server.test.js +41 -0
  4. package/ssr/server/constants.js +37 -0
  5. package/ssr/server/express.js +462 -0
  6. package/ssr/server/express.lambda.test.js +390 -0
  7. package/ssr/server/express.test.js +963 -0
  8. package/ssr/server/test_fixtures/favicon.ico +0 -0
  9. package/ssr/server/test_fixtures/loadable-stats.json +1 -0
  10. package/ssr/server/test_fixtures/localhost.pem +45 -0
  11. package/ssr/server/test_fixtures/main.js +7 -0
  12. package/ssr/server/test_fixtures/mobify.png +0 -0
  13. package/ssr/server/test_fixtures/server-renderer.js +12 -0
  14. package/ssr/server/test_fixtures/worker.js +7 -0
  15. package/ssr/server/test_fixtures/worker.js.map +1 -0
  16. package/utils/logger-factory.js +154 -0
  17. package/utils/logger-factory.test.js +71 -0
  18. package/utils/logger-instance.js +19 -0
  19. package/utils/middleware/index.js +16 -0
  20. package/utils/middleware/security.js +111 -0
  21. package/utils/middleware/security.test.js +110 -0
  22. package/utils/morgan-stream.js +28 -0
  23. package/utils/ssr-cache.js +177 -0
  24. package/utils/ssr-cache.test.js +64 -0
  25. package/utils/ssr-config.client.js +23 -0
  26. package/utils/ssr-config.client.test.js +25 -0
  27. package/utils/ssr-config.js +20 -0
  28. package/utils/ssr-config.server.js +98 -0
  29. package/utils/ssr-config.server.test.js +50 -0
  30. package/utils/ssr-namespace-paths.js +60 -0
  31. package/utils/ssr-namespace-paths.test.js +30 -0
  32. package/utils/ssr-paths.js +51 -0
  33. package/utils/ssr-paths.test.js +49 -0
  34. package/utils/ssr-proxying.js +859 -0
  35. package/utils/ssr-proxying.test.js +593 -0
  36. package/utils/ssr-request-processing.js +164 -0
  37. package/utils/ssr-request-processing.test.js +95 -0
  38. package/utils/ssr-server/cached-response.js +116 -0
  39. package/utils/ssr-server/configure-proxy.js +304 -0
  40. package/utils/ssr-server/metrics-sender.js +204 -0
  41. package/utils/ssr-server/outgoing-request-hook.js +139 -0
  42. package/utils/ssr-server/parse-end-parameters.js +38 -0
  43. package/utils/ssr-server/process-express-response.js +56 -0
  44. package/utils/ssr-server/process-lambda-response.js +43 -0
  45. package/utils/ssr-server/update-global-agent-options.js +41 -0
  46. package/utils/ssr-server/update-global-agent-options.test.js +28 -0
  47. package/utils/ssr-server/utils.js +124 -0
  48. package/utils/ssr-server/utils.test.js +69 -0
  49. package/utils/ssr-server/wrap-response-write.js +40 -0
  50. package/utils/ssr-server.js +115 -0
  51. package/utils/ssr-server.test.js +869 -0
  52. package/utils/ssr-shared.js +183 -0
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ var _buildRemoteServer = require("./build-remote-server");
4
+ /*
5
+ * Copyright (c) 2021, salesforce.com, inc.
6
+ * All rights reserved.
7
+ * SPDX-License-Identifier: BSD-3-Clause
8
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
9
+ */
10
+
11
+ jest.mock('../../utils/ssr-config', () => {
12
+ return {
13
+ getConfig: () => {}
14
+ };
15
+ });
16
+ describe('the once function', () => {
17
+ test('should prevent a function being called more than once', () => {
18
+ const fn = jest.fn(() => ({
19
+ test: 'test'
20
+ }));
21
+ const wrapped = (0, _buildRemoteServer.once)(fn);
22
+ expect(fn.mock.calls).toHaveLength(0);
23
+ const v1 = wrapped();
24
+ expect(fn.mock.calls).toHaveLength(1);
25
+ const v2 = wrapped();
26
+ expect(fn.mock.calls).toHaveLength(1);
27
+ expect(v1).toBe(v2); // The exact same instance
28
+ });
29
+ });
30
+ describe('remote server factory test coverage', () => {
31
+ test('getSlasEndpoint returns undefined if useSLASPrivateClient is false', () => {
32
+ const endpoint = _buildRemoteServer.RemoteServerFactory._getSlasEndpoint({});
33
+ expect(endpoint).toBeUndefined();
34
+ });
35
+ test('getSlasEndpoint returns endpoint if useSLASPrivateClient is true', () => {
36
+ const endpoint = _buildRemoteServer.RemoteServerFactory._getSlasEndpoint({
37
+ useSLASPrivateClient: true
38
+ });
39
+ expect(endpoint).toBeDefined();
40
+ });
41
+ });
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.X_ORIGINAL_CONTENT_TYPE = exports.X_MOBIFY_QUERYSTRING = exports.X_MOBIFY_FROM_CACHE = exports.STRICT_TRANSPORT_SECURITY = exports.STATIC_ASSETS = exports.SLAS_CUSTOM_PROXY_PATH = exports.SET_COOKIE = exports.PROXY_PATH_PREFIX = exports.NO_CACHE = exports.CONTENT_TYPE = exports.CONTENT_SECURITY_POLICY = exports.CONTENT_ENCODING = exports.CACHE_CONTROL = exports.BUILD = exports.APPLICATION_OCTET_STREAM = void 0;
7
+ /*
8
+ * Copyright (c) 2021, salesforce.com, inc.
9
+ * All rights reserved.
10
+ * SPDX-License-Identifier: BSD-3-Clause
11
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
12
+ */
13
+ const APPLICATION_OCTET_STREAM = exports.APPLICATION_OCTET_STREAM = 'application/octet-stream';
14
+ const BUILD = exports.BUILD = 'build';
15
+ const STATIC_ASSETS = exports.STATIC_ASSETS = 'static_assets';
16
+
17
+ /**
18
+ * @deprecated Use ssr-paths getProxyPath() instead
19
+ * */
20
+ const PROXY_PATH_PREFIX = exports.PROXY_PATH_PREFIX = '/mobify/proxy';
21
+
22
+ // All these values MUST be lower case
23
+ const CONTENT_TYPE = exports.CONTENT_TYPE = 'content-type';
24
+ const CONTENT_ENCODING = exports.CONTENT_ENCODING = 'content-encoding';
25
+ const X_ORIGINAL_CONTENT_TYPE = exports.X_ORIGINAL_CONTENT_TYPE = 'x-original-content-type';
26
+ const X_MOBIFY_QUERYSTRING = exports.X_MOBIFY_QUERYSTRING = 'x-mobify-querystring';
27
+ const X_MOBIFY_FROM_CACHE = exports.X_MOBIFY_FROM_CACHE = 'x-mobify-from-cache';
28
+ const SET_COOKIE = exports.SET_COOKIE = 'set-cookie';
29
+ const CACHE_CONTROL = exports.CACHE_CONTROL = 'cache-control';
30
+ const NO_CACHE = exports.NO_CACHE = 'max-age=0, nocache, nostore, must-revalidate';
31
+ const CONTENT_SECURITY_POLICY = exports.CONTENT_SECURITY_POLICY = 'content-security-policy';
32
+ const STRICT_TRANSPORT_SECURITY = exports.STRICT_TRANSPORT_SECURITY = 'strict-transport-security';
33
+
34
+ /**
35
+ * @deprecated Use ssr-paths getSlasPrivateProxyPath() instead
36
+ * */
37
+ const SLAS_CUSTOM_PROXY_PATH = exports.SLAS_CUSTOM_PROXY_PATH = '/mobify/slas/private';
@@ -0,0 +1,462 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.sendCachedResponse = exports.respondFromBundle = exports.getRuntime = exports.getResponseFromCache = exports.generateCacheKey = exports.cacheResponseWhenDone = exports.RESOLVED_PROMISE = void 0;
7
+ var _url = _interopRequireDefault(require("url"));
8
+ var _ssrServer = require("../../utils/ssr-server");
9
+ var _constants = require("./constants");
10
+ var _ssrProxying = require("../../utils/ssr-proxying");
11
+ var _buildRemoteServer = require("./build-remote-server");
12
+ var _loggerInstance = _interopRequireDefault(require("../../utils/logger-instance"));
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
15
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
16
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
17
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
18
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /*
19
+ * Copyright (c) 2022, Salesforce, Inc.
20
+ * All rights reserved.
21
+ * SPDX-License-Identifier: BSD-3-Clause
22
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
23
+ */ /**
24
+ * @module progressive-web-sdk/ssr/server/express
25
+ */
26
+ const RESOLVED_PROMISE = exports.RESOLVED_PROMISE = Promise.resolve();
27
+
28
+ /**
29
+ * Use properties of the request, such as URL and querystring, to generate
30
+ * a cache key string suitable for passing to sendResponseFromCache or
31
+ * storeResponseInCache.
32
+ *
33
+ * This method is provided as a convenience: you do not have to use it,
34
+ * but it should cover the most common use cases. It is the default
35
+ * cache key generator used by sendResponseFromCache and
36
+ * storeResponseInCache. If you override the cache key function
37
+ * in the options for storeResponseInCache, you may call this function
38
+ * and then further modify the returned key.
39
+ *
40
+ * The cache key is based on the request's path (case-independent) and
41
+ * querystring (case-dependent). The order of parameters in the querystring
42
+ * is important: if the order changes, the cache key changes. This is done
43
+ * because the order of query parameters is important for some systems.
44
+ *
45
+ * To allow for simple extension of the default algorithm, the optional
46
+ * 'options.extras' parameter may be used to pass an array of strings that
47
+ * will also be used (in the order they are passed) to build the key.
48
+ * Undefined values are allowed in the extras array.
49
+ *
50
+ * By default, the key generation does NOT consider the Accept,
51
+ * Accept-Charset or Accept-Language request header values. If it's
52
+ * appropriate to include these, the caller should add their values
53
+ * (or values based on them) to the options.extras array.
54
+ *
55
+ * By default, method will generate different cache keys for requests with
56
+ * different request classes (effectively, the value of the request-class
57
+ * string is included in 'extras'). To suppress this, pass true for
58
+ * options.ignoreRequestClass
59
+ *
60
+ * @param req {IncomingMessage} the request to generate the key for.
61
+ * @param [options] {Object} values that affect the cache key generation.
62
+ * @param [options.extras] {Array<String|undefined>} extra string values
63
+ * to be included in the key.
64
+ * @param [options.ignoreRequestClass] {Boolean} set this to true to suppress
65
+ * automatic variation of the key by request class.
66
+ * @returns {String} the generated key.
67
+ *
68
+ * @private
69
+ */
70
+ const generateCacheKey = (req, options = {}) => {
71
+ let {
72
+ pathname,
73
+ query
74
+ } = _url.default.parse(req.url);
75
+
76
+ // remove the trailing slash
77
+ if (pathname.charAt(pathname.length - 1) === '/') {
78
+ pathname = pathname.substring(0, pathname.length - 1);
79
+ }
80
+ const elements = [];
81
+ if (query) {
82
+ const filteredQueryStrings = query.split('&').filter(querystring => !/^mobify_devicetype=/.test(querystring));
83
+ elements.push(...filteredQueryStrings);
84
+ }
85
+ if (!options.ignoreRequestClass) {
86
+ const requestClass = req.get(_ssrProxying.X_MOBIFY_REQUEST_CLASS);
87
+ if (requestClass) {
88
+ elements.push(`class=${requestClass}`);
89
+ }
90
+ }
91
+ if (options.extras) {
92
+ options.extras.forEach((extra, index) => elements.push(`ex${index}=${extra}`));
93
+ }
94
+ return pathname.toLowerCase() + '/' + (0, _ssrServer.getHashForString)(elements.join('-'));
95
+ };
96
+
97
+ /**
98
+ * Internal handler that is called on completion of a response
99
+ * that is to be cached.
100
+ *
101
+ * @param req {http.IncomingMessage} the request
102
+ * @param res {http.ServerResponse} the response
103
+ * @returns {Promise} resolved when caching is done (also
104
+ * stored in locals.responseCaching.promise
105
+ * @private
106
+ */
107
+ exports.generateCacheKey = generateCacheKey;
108
+ const storeResponseInCache = (req, res) => {
109
+ const locals = res.locals;
110
+ const caching = locals.responseCaching;
111
+ const metadata = {
112
+ status: res.statusCode,
113
+ headers: res.getHeaders()
114
+ };
115
+
116
+ // ADN-118 When the response is created, we intercept the data
117
+ // as it's written, and store it so that we can cache it here.
118
+ // However, ExpressJS will apply compression *after* we store
119
+ // the data, but will add a content-encoding header *before*
120
+ // this method is called. If we store the headers unchanged,
121
+ // we'll cache a response with an uncompressed body, but
122
+ // a content-encoding header. We therefore remove the content-
123
+ // encoding at this point, so that the response is stored
124
+ // in a consistent way.
125
+ // The exception is if the contentEncodingSet flag is set on
126
+ // the response. If it's truthy, then project code set a
127
+ // content-encoding before the Express compression code was
128
+ // called; in that case, we must leave the content-encoding
129
+ // unchanged.
130
+ if (!locals.contentEncodingSet) {
131
+ delete metadata.headers[_constants.CONTENT_ENCODING];
132
+ }
133
+ const cacheControl = (0, _ssrServer.parseCacheControl)(res.get('cache-control'));
134
+ const expiration = parseInt(caching.expiration || cacheControl['s-maxage'] || cacheControl['max-age'] || 7 * 24 * 3600);
135
+
136
+ // Return a Promise that will be resolved when caching is complete.
137
+ let dataToCache;
138
+ /* istanbul ignore else */
139
+ if (caching.chunks.length) {
140
+ // Concat the body into a single buffer.\
141
+ // caching.chunks will be an Array of Buffer
142
+ // values, and may be empty.
143
+ dataToCache = Buffer.concat(caching.chunks);
144
+ }
145
+ return req.app.applicationCache.put({
146
+ key: caching.cacheKey,
147
+ namespace: caching.cacheNamespace,
148
+ data: dataToCache,
149
+ metadata,
150
+ expiration: expiration * 1000 // in mS
151
+ })
152
+ // If an error occurs,we don't want to prevent the
153
+ // response being sent, so we just log.
154
+ .catch(err => {
155
+ _loggerInstance.default.warn(`Unexpected error in cache put: ${err}`, {
156
+ namespace: 'express.storeResponseInCache'
157
+ });
158
+ });
159
+ };
160
+
161
+ /**
162
+ * Configure a response so that it will be cached when it has been sent.
163
+ * Caching ExpressJS responses requires intercepting of all the header
164
+ * and body setting calls on it, which may occur at any point in the
165
+ * response lifecycle, so this call must be made before the response
166
+ * is generated.
167
+ *
168
+ * If no key is provided, it's generated by generateCacheKey.
169
+ * Project code may call generateCacheKey with extra options to affect
170
+ * the key, or may use custom key generation logic. If code has
171
+ * previously called getResponseFromCache, the key and namespace are
172
+ * available as properties on the CachedResponse instance returned
173
+ * from that method.
174
+ *
175
+ * When caching response, the cache expiration time is set by
176
+ * the expiration parameter. The cache expiration time may be
177
+ * different to the response expiration time as set by the cache-control
178
+ * header. See the documentation for the 'expiration' parameter for
179
+ * details.
180
+ *
181
+ * @param req {express.request}
182
+ * @param res {express.response}
183
+ * @param [expiration] {Number} the number of seconds
184
+ * that a cached response should persist in the cache. If this is
185
+ * not provided, then the expiration time is taken from the
186
+ * Cache-Control header; the s-maxage value is used if available,
187
+ * then the max-age value. If no value can be found in the
188
+ * Cache-Control header, the default expiration time is
189
+ * one week.
190
+ * @param [key] {String} the key to use - if this is not supplied,
191
+ * generateCacheKey will be called to derive the key.
192
+ * @param [namespace] {String|undefined} the cache namespace to use.
193
+ * @param [shouldCacheResponse] {Function} an optional callback that is passed a
194
+ * Response after it has been sent but immediately before it is stored in
195
+ * the cache, and can control whether or not caching actually takes place.
196
+ * The function takes the request and response as parameters and should
197
+ * return true if the response should be cached, false if not.
198
+ * @private
199
+ */
200
+ const cacheResponseWhenDone = ({
201
+ req,
202
+ res,
203
+ expiration,
204
+ key,
205
+ namespace,
206
+ shouldCacheResponse
207
+ }) => {
208
+ const locals = res.locals;
209
+ const caching = locals.responseCaching;
210
+
211
+ // If we have a key passed in, use that.
212
+ // If we have a key already generated by getResponseFromCache, use that.
213
+ // Otherwise generate the key from the request
214
+ /* istanbul ignore next */
215
+ caching.cacheKey = key || caching.cacheKey || generateCacheKey(req);
216
+
217
+ // Save values that will be needed when we store the response
218
+ caching.cacheNamespace = namespace;
219
+ caching.expiration = expiration;
220
+
221
+ // Set a flag that we use to detect a double call to end().
222
+ // Because we delay the actual call to end() until after
223
+ // caching is complete, we also delay when the response's 'finished'
224
+ // flag becomes true, and when the 'finished' event is emitted.
225
+ // This means that code may call end() more than once. We need
226
+ // to ignore any second call.
227
+ caching.endCalled = false;
228
+
229
+ /*
230
+ Headers can be retrieved at any point, so there's no need to
231
+ intercept them. They're still present after the response ends
232
+ (contrary to some StackOverflow responses).
233
+ The response body can be sent in multiple chunks, at any time,
234
+ so we need a way to store references to those chunks so that
235
+ we can access the whole body to store it in the cache.
236
+ We patch the write() method on the response (which is a subclass of
237
+ node's ServerResponse, implementing the stream.Writeable interface)
238
+ and record the chunks as they are sent.
239
+ */
240
+ (0, _ssrServer.wrapResponseWrite)(res);
241
+
242
+ /*
243
+ Patch the end() method of the response to call _storeResponseInCache.
244
+ We use this patching method instead of firing on the 'finished' event
245
+ because we want to guarantee that caching is complete before we
246
+ send the event. If we use the event, then caching may happen after
247
+ the event is complete, but in a Lambda environment processing is
248
+ halted once the event is sent, so caching might not occur.
249
+ */
250
+ const originalEnd = res.end;
251
+ res.end = (...params) => {
252
+ // Check the cached flag - in some cases, end() may be
253
+ // called twice and we want to ignore the second call.
254
+ if (caching.endCalled) {
255
+ return;
256
+ }
257
+ caching.endCalled = true;
258
+
259
+ // Handle any data writing that must be done before end()
260
+ // is called.
261
+ const {
262
+ data,
263
+ encoding,
264
+ callback
265
+ } = (0, _ssrServer.parseEndParameters)(params);
266
+ if (data) {
267
+ // We ignore the return value from write(), because we
268
+ // don't care whether the data is queued in user memory
269
+ // or is accepted by the OS, as long as we call write()
270
+ // before we call end()
271
+ res.write(data, encoding);
272
+ }
273
+
274
+ // The response has been sent. If there is a shouldCacheResponse
275
+ // callback, we call it to decide whether to cache or not.
276
+ if (shouldCacheResponse) {
277
+ if (!shouldCacheResponse(req, res)) {
278
+ (0, _ssrServer.localDevLog)(`Req ${locals.requestId}: not caching response for ${req.url}`);
279
+ return originalEnd.call(res, callback);
280
+ }
281
+ }
282
+
283
+ // We know that all the data has been written, so we
284
+ // can now store the response in the cache and call
285
+ // end() on it.
286
+ req.app.applicationCache._cacheDeletePromise.then(() => {
287
+ (0, _ssrServer.localDevLog)(`Req ${locals.requestId}: caching response for ${req.url}`);
288
+ return storeResponseInCache(req, res);
289
+ }).finally(() => {
290
+ originalEnd.call(res, callback);
291
+ });
292
+ };
293
+ };
294
+
295
+ /**
296
+ * Given a CachedResponse that represents a response from the
297
+ * cache, send it. Once this method has been called, the response
298
+ * is sent and can no longer be modified. If this method is
299
+ * called from the requestHook, the caller should return, and
300
+ * should not call next()
301
+ *
302
+ * @param cached {CachedResponse} the cached response to send
303
+ * @private
304
+ */
305
+ exports.cacheResponseWhenDone = cacheResponseWhenDone;
306
+ const sendCachedResponse = cached => {
307
+ if (!(cached && cached.found)) {
308
+ throw new Error(`Cannot send a non-cached CachedResponse`);
309
+ }
310
+ cached._send();
311
+ cached._res.end();
312
+ };
313
+
314
+ /**
315
+ * Look up a cached response for the given request in the persistent cache
316
+ * and return a CachedResponse that represents what was found.
317
+ *
318
+ * This method would generally be called in the requestHook. The caller
319
+ * should check the result of resolving the Promise returned by this
320
+ * method. The returned object's 'found' property is true if a response
321
+ * was found, 'false' if no response was found.
322
+ *
323
+ * The CachedResponse instance returned has details of any cached response
324
+ * found, and project code can then choose whether to send it or not. For
325
+ * example, the headers may be checked. To send that cached response, call
326
+ * sendCachedResponse with it.
327
+ *
328
+ * If there is no cached response found, or the project code does not
329
+ * choose to send it, then the code can also choose whether the
330
+ * response generated by the server should be cached. If so, it
331
+ * should call cacheResponseWhenDone.
332
+ *
333
+ * If no key is provided, it's generated by generateCacheKey.
334
+ * Project code may call generateCacheKey with extra options to affect
335
+ * the key, or may use custom key generation logic.
336
+ *
337
+ * By default, all cache entries occupy the same namespace, so responses
338
+ * cached for a given URL/querystring/headers by one version of the UPWA
339
+ * may be retrieved and used by other, later versions. If this is not
340
+ * the required behaviour, the options parameter may be used to pass a
341
+ * 'namespace' value. The same cache key may be used in different
342
+ * namespaces to cache different responses. For example, passing the
343
+ * bundle id as the namespace will result in each publish bundle starting
344
+ * with a cache that is effectively per-bundle. The namespace value
345
+ * may be any string, or an array of strings.
346
+ *
347
+ * @param req {express.request}
348
+ * @param res {express.response}
349
+ * @param [key] {String} the key to use - if this is not supplied,
350
+ * generateCacheKey will be called to derive the key.
351
+ * @param [namespace] {String|undefined} the cache namespace to use.
352
+ * @returns {Promise<CachedResponse>} resolves to a CachedResponse
353
+ * that represents the result of the cache lookup.
354
+ * @private
355
+ */
356
+ exports.sendCachedResponse = sendCachedResponse;
357
+ const getResponseFromCache = ({
358
+ req,
359
+ res,
360
+ namespace,
361
+ key
362
+ }) => {
363
+ /* istanbul ignore next */
364
+ const locals = res.locals;
365
+ const workingKey = key || generateCacheKey(req);
366
+
367
+ // Save the key as the default for caching
368
+ locals.responseCaching.cacheKey = workingKey;
369
+
370
+ // Return a Promise that handles the asynchronous cache lookup
371
+ return req.app.applicationCache.get({
372
+ key: workingKey,
373
+ namespace
374
+ }).then(entry => {
375
+ (0, _ssrServer.localDevLog)(`Req ${locals.requestId}: ${entry.found ? 'Found' : 'Did not find'} cached response for ${req.url}`);
376
+ if (!entry.found) {
377
+ res.setHeader(_constants.X_MOBIFY_FROM_CACHE, 'false');
378
+ }
379
+ return new _ssrServer.CachedResponse({
380
+ entry,
381
+ req,
382
+ res
383
+ });
384
+ });
385
+ };
386
+
387
+ /**
388
+ * Provided for use by requestHook overrides.
389
+ *
390
+ * Call this to return a res that is a redirect to a bundle asset.
391
+ * Be careful with res caching - 301 responses can be cached. You
392
+ * can call res.set to set the 'Cache-Control' header before
393
+ * calling this function.
394
+ *
395
+ * This function returns a Promise that resolves when the res
396
+ * has been sent. The caller does not need to wait on this Promise.
397
+ *
398
+ * @param {Object} options
399
+ * @param {Request} options.req - the ExpressJS request object
400
+ * @param {Response} options.res - the ExpressJS res object
401
+ * @param {String} [options.path] - the path to the bundle asset (relative
402
+ * to the bundle root/build directory). If this is falsy, then
403
+ * request.path is used (i.e. '/robots.txt' would be the path for
404
+ * 'robots.txt' at the top level of the build directory).
405
+ * @param {Number} [options.redirect] a 301 or 302 status code, which
406
+ * will be used to respond with a redirect to the bundle asset.
407
+ * @private
408
+ */
409
+ exports.getResponseFromCache = getResponseFromCache;
410
+ const respondFromBundle = ({
411
+ req,
412
+ res,
413
+ path,
414
+ redirect = 301
415
+ }) => {
416
+ // The path *may* start with a slash
417
+ const workingPath = path || req.path;
418
+
419
+ // Validate redirect
420
+ const workingRedirect = Number.parseInt(redirect);
421
+ /* istanbul ignore next */
422
+ if (workingRedirect < 301 || workingRedirect > 307) {
423
+ throw new Error('The redirect parameter must be a number between 301 and 307 inclusive');
424
+ }
425
+
426
+ // assetPath will not start with a slash
427
+ /* istanbul ignore next */
428
+ const assetPath = workingPath.startsWith('/') ? workingPath.slice(1) : workingPath;
429
+
430
+ // This is the relative or absolute location of the asset via the
431
+ // /mobify/bundle path
432
+ const location = `${(0, _ssrServer.getBundleBaseUrl)()}${assetPath}`;
433
+ (0, _ssrServer.localDevLog)(`Req ${res.locals.requestId}: redirecting ${assetPath} to ${location} (${workingRedirect})`);
434
+ res.redirect(workingRedirect, location);
435
+ };
436
+
437
+ /**
438
+ * Get the appropriate runtime object for the current environment (remote or development)
439
+ * @returns Shallow of the runtime object with bound methods
440
+ */
441
+ exports.respondFromBundle = respondFromBundle;
442
+ const getRuntime = () => {
443
+ const runtime = (0, _ssrServer.isRemote)() ? _buildRemoteServer.RemoteServerFactory :
444
+ // The dev server is for development only, and should not be deployed to production.
445
+ // To avoid deploying the dev server (and all of its dependencies) to production, it exists
446
+ // as an optional peer dependency to this package. The unusual `require` statement is needed
447
+ // to bypass webpack and ensure that the dev server does not get bundled.
448
+ eval('require').main.require('@salesforce/pwa-kit-dev/ssr/server/build-dev-server').DevServerFactory;
449
+
450
+ // The runtime is a JavaScript object.
451
+ // Sometimes the runtime APIs are invoked directly as express middlewares.
452
+ // In order to make sure the "this" keyword always have the correct context,
453
+ // we bind every single method to have the context of the object itself
454
+ const boundRuntime = _objectSpread({}, runtime);
455
+ for (const property of Object.keys(boundRuntime)) {
456
+ if (typeof boundRuntime[property] === 'function') {
457
+ boundRuntime[property] = boundRuntime[property].bind(boundRuntime);
458
+ }
459
+ }
460
+ return boundRuntime;
461
+ };
462
+ exports.getRuntime = getRuntime;