@salesforce/pwa-kit-runtime 4.0.0-extensibility-preview.3 → 4.0.0-extensibility-preview.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/pwa-kit-runtime",
3
- "version": "4.0.0-extensibility-preview.3",
3
+ "version": "4.0.0-extensibility-preview.4",
4
4
  "description": "The PWAKit Runtime",
5
5
  "homepage": "https://github.com/SalesforceCommerceCloud/pwa-kit/tree/develop/packages/pwa-kit-runtime#readme",
6
6
  "bugs": {
@@ -46,13 +46,13 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@loadable/component": "^5.15.3",
49
- "@salesforce/pwa-kit-dev": "4.0.0-extensibility-preview.3",
50
- "@salesforce/pwa-kit-extension-sdk": "4.0.0-extensibility-preview.3",
49
+ "@salesforce/pwa-kit-dev": "4.0.0-extensibility-preview.4",
50
+ "@salesforce/pwa-kit-extension-sdk": "4.0.0-extensibility-preview.4",
51
51
  "@serverless/event-mocks": "^1.1.1",
52
52
  "@types/express": "^4.17.21",
53
53
  "aws-lambda-mock-context": "^3.2.1",
54
54
  "fs-extra": "^11.1.1",
55
- "internal-lib-build": "4.0.0-extensibility-preview.3",
55
+ "internal-lib-build": "4.0.0-extensibility-preview.4",
56
56
  "nock": "^13.3.0",
57
57
  "nodemon": "^2.0.22",
58
58
  "sinon": "^13.0.2",
@@ -60,7 +60,7 @@
60
60
  "supertest": "^4.0.2"
61
61
  },
62
62
  "peerDependencies": {
63
- "@salesforce/pwa-kit-dev": "4.0.0-extensibility-preview.3"
63
+ "@salesforce/pwa-kit-dev": "4.0.0-extensibility-preview.4"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "@salesforce/pwa-kit-dev": {
@@ -68,11 +68,11 @@
68
68
  }
69
69
  },
70
70
  "engines": {
71
- "node": "^16.11.0 || ^18.0.0 || ^20.0.0",
72
- "npm": "^8.0.0 || ^9.0.0 || ^10.0.0"
71
+ "node": "^16.11.0 || ^18.0.0 || ^20.0.0 || ^22.0.0",
72
+ "npm": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
73
73
  },
74
74
  "publishConfig": {
75
75
  "directory": "dist"
76
76
  },
77
- "gitHead": "904de01e826117febeea952e034478349e16ea4d"
77
+ "gitHead": "5ad3b1f0e598cfe0fbdec5b1212b4dabc0a8733c"
78
78
  }
@@ -1 +1 @@
1
- {"version":3,"file":"build-remote-server.d.ts","sourceRoot":"","sources":["../../../src/ssr/server/build-remote-server.js"],"names":[],"mappings":"AAqEA;;;;GAIG;AACH,gDAKC;;IAWG;;OAEG;IACH,uCAgFC;IAED;;OAEG;IAEH,gDAEC;IAED;;OAEG;IAEH,iDAEC;IAED;;OAEG;IAEH,4CAEC;IAED;;OAEG;IACH,uDAEC;IAED;;OAEG;IAEH,2CAEC;IAED;;OAEG;IACH,6CAEC;IAED;;OAEG;IACH,4DAIC;IAED;;OAEG;IAEH,yCAEC;IAED;;OAEG;IAEH,uCA6CC;IAED;;;OAGG;IACH,uCAcC;IAED;;OAEG;IAEH,+CAEC;IAED;;OAEG;IACH,kDAEC;IAED;;OAEG;IACH,oDAEC;IAED;;OAEG;IACH,+EA2CC;IAED;;OAEG;IACH,sFAyDC;IAED;;OAEG;IAEH,iDAEC;IAED;;;OAGG;IACH,4DAWC;IAED;;OAEG;IACH,6DAwLC;IAED;;OAEG;IAEH,sDAOC;IAED;;OAEG;IACH,yDAOC;IAED;;OAEG;IACH,oEA4EC;IAED;;OAEG;IACH,2CAIC;IAED;;OAEG;IACH,8DAiBC;IAED;;OAEG;IACH,oDA0EC;IAED;;OAEG;IACH,wCAEC;IAED;;OAEG;IACH,gDAGC;IAED;;;;;;;;;;;OAWG;IACH,sDAwBC;IAED;;;;;;;OAOG;IACH,wFAKC;IAED;;OAEG;IACH,4FASC;IAED;;;;;;;;;;OAUG;IACH,6DAWC;IAED;;;;;;;OAOG;IACH;;;;MAgGC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH;;;;;;;;;;;;MAKC;IAED;;OAEG;IAEH,8CAEC;;AA2HE,uDASN"}
1
+ {"version":3,"file":"build-remote-server.d.ts","sourceRoot":"","sources":["../../../src/ssr/server/build-remote-server.js"],"names":[],"mappings":"AAsEA;;;;GAIG;AACH,gDAKC;;IAWG;;OAEG;IACH,uCAiFC;IAED;;OAEG;IAEH,gDAEC;IAED;;OAEG;IAEH,iDAEC;IAED;;OAEG;IAEH,4CAEC;IAED;;OAEG;IACH,uDAEC;IAED;;OAEG;IAEH,2CAEC;IAED;;OAEG;IACH,6CAEC;IAED;;OAEG;IACH,4DAIC;IAED;;OAEG;IAEH,yCAEC;IAED;;OAEG;IAEH,uCA6CC;IAED;;;OAGG;IACH,uCAcC;IAED;;OAEG;IAEH,+CAEC;IAED;;OAEG;IACH,kDAEC;IAED;;OAEG;IACH,oDAEC;IAED;;OAEG;IACH,+EA2CC;IAED;;OAEG;IACH,sFA2DC;IAED;;OAEG;IAEH,iDAEC;IAED;;;OAGG;IACH,4DAWC;IAED;;OAEG;IACH,6DAwLC;IAED;;OAEG;IAEH,sDAOC;IAED;;OAEG;IACH,yDAOC;IAED;;OAEG;IACH,oEA2EC;IAED;;OAEG;IACH,2CAIC;IAED;;OAEG;IACH,8DAiBC;IAED;;OAEG;IACH,oDA0EC;IAED;;OAEG;IACH,wCAEC;IAED;;OAEG;IACH,gDAGC;IAED;;;;;;;;;;;OAWG;IACH,sDA2CC;IAED;;;;;;;OAOG;IACH,wFAKC;IAED;;OAEG;IACH,4FASC;IAED;;;;;;;;;;OAUG;IACH,6DAWC;IAED;;;;;;;OAOG;IACH;;;;MAmGC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH;;;;;;;;;;;;MAKC;IAED;;OAEG;IAEH,8CAEC;;AA2HE,uDASN"}
@@ -103,9 +103,9 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
103
103
  useSLASPrivateClient: false,
104
104
  // A regex for identifying which SLAS endpoints the custom SLAS private
105
105
  // client secret handler will inject an Authorization header.
106
- // Do not modify unless a project wants to customize additional SLAS
107
- // endpoints that we currently do not support (ie. /oauth2/passwordless/token)
108
- applySLASPrivateClientToEndpoints: /\/oauth2\/token/
106
+ // To allow additional SLAS endpoints, users can override this value in
107
+ // their project's ssr.js.
108
+ applySLASPrivateClientToEndpoints: /\/oauth2\/(token|passwordless\/(login|token)|password\/(reset|action))/
109
109
  };
110
110
  options = _extends({}, defaults, options);
111
111
  (0, _ssrServer.setQuiet)(options.quiet || process.env.SSR_QUIET);
@@ -320,6 +320,8 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
320
320
  app.disable('x-powered-by');
321
321
  const mixin = {
322
322
  options,
323
+ // Forcing a GC is no longer necessary, and will be
324
+ // skipped by default (unless FORCE_GC env-var is set).
323
325
  _collectGarbage() {
324
326
  // Do global.gc in a separate 'then' handler so
325
327
  // that all major variables are out of scope and
@@ -611,8 +613,7 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
611
613
  });
612
614
 
613
615
  // We pattern match and add client secrets only to endpoints that
614
- // match the regex specified by options.applySLASPrivateClientToEndpoints.
615
- // By default, this regex matches only calls to SLAS /oauth2/token
616
+ // match the regex specified by options.applySLASPrivateClientToEndpoints
616
617
  // (see option defaults at the top of this file).
617
618
  // Other SLAS endpoints, ie. SLAS authenticate (/oauth2/login) and
618
619
  // SLAS logout (/oauth2/logout), use the Authorization header for a different
@@ -765,8 +766,27 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
765
766
  encoding: 'utf8'
766
767
  });
767
768
 
769
+ // If the service worker is not updated when content security policy headers inside
770
+ // ssr.js are changed, then service worker initiated requests will continue to use
771
+ // the old CSP headers.
772
+ //
773
+ // This is problematic in stacked CDN setups where an old service worker with
774
+ // old CSPs can remain cached if the content of the service worker itself is not changed.
775
+ //
776
+ // To ensure the service worker is refetched when CSPs are changed, we factor in
777
+ // the CSP headers when generating the Etag.
778
+ //
779
+ // See https://gus.lightning.force.com/lightning/r/ADM_Work__c/a07EE000025yeu9YAA/view
780
+ // and https://salesforce-internal.slack.com/archives/C01GLHLBPT5/p1730739370922629
781
+ // for more details.
782
+
783
+ const contentSecurityPolicyHeader = res.getHeaders()[_constants.CONTENT_SECURITY_POLICY] || '';
784
+
768
785
  // Serve the file, with a strong ETag
769
- res.set('etag', (0, _ssrServer.getHashForString)(content));
786
+ // For this to be a valid ETag, the string must be placed between ""
787
+ // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#etag_value for
788
+ // more details
789
+ res.set('etag', `"${(0, _ssrServer.getHashForString)(content + contentSecurityPolicyHeader)}"`);
770
790
  res.set(_constants.CONTENT_TYPE, 'application/javascript');
771
791
  res.send(content);
772
792
  },
@@ -870,12 +890,15 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
870
890
  // the response to the browser.
871
891
  context.callbackWaitsForEmptyEventLoop = false;
872
892
  if (lambdaContainerReused) {
873
- // DESKTOP-434 If this Lambda container is being reused,
874
- // clean up memory now, so that we start with low usage.
875
- // These regular GC calls take about 80-100 mS each, as opposed
876
- // to forced GC calls, which occur randomly and can take several
877
- // hundred mS.
878
- app._collectGarbage();
893
+ const forceGarbageCollection = process.env.FORCE_GC;
894
+ if (forceGarbageCollection && forceGarbageCollection.toLowerCase() === 'true') {
895
+ // DESKTOP-434 If this Lambda container is being reused,
896
+ // clean up memory now, so that we start with low usage.
897
+ // These regular GC calls take about 80-100 mS each, as opposed
898
+ // to forced GC calls, which occur randomly and can take several
899
+ // hundred mS.
900
+ app._collectGarbage();
901
+ }
879
902
  app.sendMetric('LambdaReused');
880
903
  } else {
881
904
  // This is the first use of this container, so set the
@@ -54,6 +54,54 @@ const testFixtures = path.resolve(process.cwd(), 'src/ssr/server/test_fixtures')
54
54
  const httpsAgent = new https.Agent({
55
55
  rejectUnauthorized: false
56
56
  });
57
+ function createServerWithGCSpy() {
58
+ const route = jest.fn((req, res) => {
59
+ res.send('<html/>');
60
+ });
61
+ const options = {
62
+ buildDir: testFixtures,
63
+ mobify: testPackageMobify,
64
+ sslFilePath: path.join(testFixtures, 'localhost.pem'),
65
+ quiet: true,
66
+ port: TEST_PORT,
67
+ fetchAgents: {
68
+ https: httpsAgent
69
+ }
70
+ };
71
+ const {
72
+ handler,
73
+ server,
74
+ app
75
+ } = RemoteServerFactory.createHandler(options, app => {
76
+ app.get('/*', route);
77
+ });
78
+ const collectGarbage = jest.spyOn(app, '_collectGarbage');
79
+ const sendMetric = jest.spyOn(app, 'sendMetric');
80
+ return {
81
+ route,
82
+ handler,
83
+ collectGarbage,
84
+ sendMetric,
85
+ server
86
+ };
87
+ }
88
+ function createApiGatewayEvent() {
89
+ // Set up a fake event and a fake context for the Lambda call
90
+ const event = createEvent('aws:apiGateway', {
91
+ path: '/',
92
+ body: undefined
93
+ });
94
+ if (event.queryStringParameters) {
95
+ delete event.queryStringParameters;
96
+ }
97
+ const context = AWSMockContext({
98
+ functionName: 'SSRTest'
99
+ });
100
+ return {
101
+ event,
102
+ context
103
+ };
104
+ }
57
105
  describe('SSRServer Lambda integration', () => {
58
106
  let savedEnvironment;
59
107
  let server;
@@ -329,42 +377,50 @@ describe('SSRServer Lambda integration', () => {
329
377
  X_HEADERS_TO_REMOVE_ORIGIN.forEach(key => expect(reqHeaders[key]).toBeUndefined());
330
378
  });
331
379
  });
332
- test('Lambda reuse behaviour', () => {
333
- const route = jest.fn((req, res) => {
334
- res.send('<html/>');
335
- });
336
- const options = {
337
- buildDir: testFixtures,
338
- mobify: testPackageMobify,
339
- sslFilePath: path.join(testFixtures, 'localhost.pem'),
340
- quiet: true,
341
- port: TEST_PORT,
342
- fetchAgents: {
343
- https: httpsAgent
344
- }
345
- };
380
+ test('Lambda reuse -- Default Behavior', () => {
346
381
  const {
347
- app,
382
+ route,
348
383
  handler,
349
- server: srv
350
- } = RemoteServerFactory.createHandler(options, app => {
351
- app.get('/*', route);
352
- });
353
- const collectGarbage = jest.spyOn(app, '_collectGarbage');
354
- const sendMetric = jest.spyOn(app, 'sendMetric');
355
- server = srv;
356
-
357
- // Set up a fake event and a fake context for the Lambda call
358
- const event = createEvent('aws:apiGateway', {
359
- path: '/',
360
- body: undefined
361
- });
362
- if (event.queryStringParameters) {
363
- delete event.queryStringParameters;
364
- }
365
- const context = AWSMockContext({
366
- functionName: 'SSRTest'
384
+ collectGarbage,
385
+ sendMetric,
386
+ new_server
387
+ } = createServerWithGCSpy();
388
+ const {
389
+ event,
390
+ context
391
+ } = createApiGatewayEvent();
392
+ server = new_server;
393
+ const call = event => new Promise(resolve => handler(event, context, (err, response) => resolve(response)));
394
+ return Promise.resolve().then(() => call(event)).then(response => {
395
+ // First request - Lambda container created
396
+ expect(response.statusCode).toBe(200);
397
+ expect(collectGarbage.mock.calls).toHaveLength(0);
398
+ expect(route.mock.calls).toHaveLength(1);
399
+ expect(sendMetric).toHaveBeenCalledWith('LambdaCreated');
400
+ expect(sendMetric).not.toHaveBeenCalledWith('LambdaReused');
401
+ }).then(() => call(event)).then(response => {
402
+ // Second call - Lambda container reused
403
+ expect(response.statusCode).toBe(200);
404
+ expect(collectGarbage.mock.calls).toHaveLength(0);
405
+ expect(route.mock.calls).toHaveLength(2);
406
+ expect(sendMetric).toHaveBeenCalledWith('LambdaCreated');
407
+ expect(sendMetric).toHaveBeenCalledWith('LambdaReused');
367
408
  });
409
+ });
410
+ test('Lambda reuse -- with Forced Garbage Collection Enabled', () => {
411
+ process.env.FORCE_GC = 'true';
412
+ const {
413
+ event,
414
+ context
415
+ } = createApiGatewayEvent();
416
+ const {
417
+ route,
418
+ handler,
419
+ collectGarbage,
420
+ sendMetric,
421
+ new_server
422
+ } = createServerWithGCSpy();
423
+ server = new_server;
368
424
  const call = event => new Promise(resolve => handler(event, context, (err, response) => resolve(response)));
369
425
  return Promise.resolve().then(() => call(event)).then(response => {
370
426
  // First request - Lambda container created
@@ -1 +1 @@
1
- {"version":3,"file":"metrics-sender.d.ts","sourceRoot":"","sources":["../../../src/utils/ssr-server/metrics-sender.js"],"names":[],"mappings":"AASA;;;;;;;GAOG;AACH;IAKQ,iDAAe;IAKf,cAAgB;IAGpB;;;OAGG;IACH,0BAEC;IAED;;;;;;OAMG;IACH,eAaC;IAED;;;;;;;;OAQG;IACH,uBA0BC;IAED;;;;OAIG;IACH,+BAyBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,aA4BC;CACJ;;;IAOD;;;;;OAKG;IACH,oCAKC"}
1
+ {"version":3,"file":"metrics-sender.d.ts","sourceRoot":"","sources":["../../../src/utils/ssr-server/metrics-sender.js"],"names":[],"mappings":"AASA;;;;;;;GAOG;AACH;IAKQ,iDAAe;IAKf,cAAgB;IAGpB;;;OAGG;IACH,0BAEC;IAED;;;;;;OAMG;IACH,eAiBC;IAED;;;;;;;;OAQG;IACH,uBA0BC;IAED;;;;OAIG;IACH,+BAyBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,aA4BC;CACJ;;;IAOD;;;;;OAKG;IACH,oCAKC"}
@@ -59,7 +59,11 @@ class MetricsSender {
59
59
  apiVersion: '2010-08-01',
60
60
  // The AWS_REGION variable is defined by the Lambda
61
61
  // environment.
62
- region: process.env.AWS_REGION || 'us-east-1'
62
+ region: process.env.AWS_REGION || 'us-east-1',
63
+ // Setting maxRetries to 0 will prevent the SDK from retrying.
64
+ // This is necessary because under high load, there will be backpressure
65
+ // on the Lambda function, and causing severe performance issues (400-500ms latency)
66
+ maxRetries: 0
63
67
  });
64
68
  }
65
69
  return this._CW;