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