@salesforce/pwa-kit-runtime 3.12.0-preview.0 → 3.12.0-preview.1

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": "3.12.0-preview.0",
3
+ "version": "3.12.0-preview.1",
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,11 +46,11 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@loadable/component": "^5.15.3",
49
- "@salesforce/pwa-kit-dev": "3.12.0-preview.0",
49
+ "@salesforce/pwa-kit-dev": "3.12.0-preview.1",
50
50
  "@serverless/event-mocks": "^1.1.1",
51
51
  "aws-lambda-mock-context": "^3.2.1",
52
52
  "fs-extra": "^11.1.1",
53
- "internal-lib-build": "3.12.0-preview.0",
53
+ "internal-lib-build": "3.12.0-preview.1",
54
54
  "nock": "^13.3.0",
55
55
  "nodemon": "^2.0.22",
56
56
  "sinon": "^13.0.2",
@@ -58,7 +58,7 @@
58
58
  "supertest": "^4.0.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "@salesforce/pwa-kit-dev": "3.12.0-preview.0"
61
+ "@salesforce/pwa-kit-dev": "3.12.0-preview.1"
62
62
  },
63
63
  "peerDependenciesMeta": {
64
64
  "@salesforce/pwa-kit-dev": {
@@ -72,5 +72,5 @@
72
72
  "publishConfig": {
73
73
  "directory": "dist"
74
74
  },
75
- "gitHead": "f79699d763ecdf8e0733e2d6c08b71487af8b15c"
75
+ "gitHead": "0f66d26584f98d3322bffcb70e7feed08c81cc7a"
76
76
  }
@@ -403,78 +403,84 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
403
403
  * @private
404
404
  */
405
405
  _setupRemoveBasePathFromPathMiddleware(app) {
406
- const removeBasePathFromPath = path => {
407
- if (!(0, _ssrNamespacePaths.getEnvBasePath)()) return path;
408
- const regex = new RegExp(`^${(0, _ssrNamespacePaths.getEnvBasePath)()}(/|$)`);
409
- return path.replace(regex, '/');
410
- };
411
- const _convertExpressRouteToRegex = routePattern => {
412
- if (!routePattern) return null;
406
+ // Use envBasePath as the feature flag for this middleware
407
+ // If envBasePath is `/`, '', or undefined when the server starts, we don't need to
408
+ // initialize this middleware.
409
+ if ((0, _ssrNamespacePaths.getEnvBasePath)()) {
410
+ const removeBasePathFromPath = path => {
411
+ const regex = new RegExp(`^${(0, _ssrNamespacePaths.getEnvBasePath)()}(/|$)`);
412
+ return path.replace(regex, '/');
413
+ };
414
+ const _convertExpressRouteToRegex = routePattern => {
415
+ if (!routePattern) return null;
416
+ if (routePattern instanceof RegExp) return routePattern;
417
+ if (typeof routePattern !== 'string') return null;
413
418
 
414
- // Replace route parameters like :id with regex capture groups
415
- let regexPattern = routePattern.replace(/:[^/]+/g, '[^/]+').replace(/\/\*/g, '/.*').replace(/\*/g, '.*');
419
+ // Replace route parameters like :id with regex capture groups
420
+ let regexPattern = routePattern.replace(/:[^/]+/g, '[^/]+').replace(/\/\*/g, '/.*').replace(/\*/g, '.*');
416
421
 
417
- // Escape other regex special characters except those we just handled
418
- regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
422
+ // Escape other regex special characters except those we just handled
423
+ regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
419
424
 
420
- // Unescape the patterns we want to keep
421
- regexPattern = regexPattern.replace(/\\\[\\\^\/\\\]\\\+/g, '[^/]+').replace(/\\\/\\\.\\\*/g, '/.*').replace(/\\\.\\\*/g, '.*').replace(/\\\(/g, '(').replace(/\\\)/g, ')').replace(/\\\?/g, '?');
422
- return new RegExp(`^${regexPattern}$`);
423
- };
425
+ // Unescape the patterns we want to keep
426
+ regexPattern = regexPattern.replace(/\\\[\\\^\/\\\]\\\+/g, '[^/]+').replace(/\\\/\\\.\\\*/g, '/.*').replace(/\\\.\\\*/g, '.*').replace(/\\\(/g, '(').replace(/\\\)/g, ')').replace(/\\\?/g, '?');
427
+ return new RegExp(`^${regexPattern}$`);
428
+ };
424
429
 
425
- /**
426
- * Very early request processing.
427
- *
428
- * If the server receives a request containing the base path, remove it before allowing it through
429
- *
430
- * @param req {express.req} the incoming request - modified in-place
431
- * @private
432
- */
433
- const removeBasePathFromPathMiddleware = (req, res, next) => {
434
- // Scope base path removal to /mobify routes and routes defined by the express app (ie. worker.js)
435
- // This is to avoid affecting other paths where a base path might be present if it happens to
436
- // be equal to a site id.
437
- // For example, if you have a base path of /us and a site id of /us we don't want
438
- // to remove the /us from www.example.com/us/en-US/category/...
439
-
440
- const basePath = (0, _ssrNamespacePaths.getEnvBasePath)();
441
- let shouldRemoveBasePath = false;
442
- if (basePath) {
443
- if (req.path.startsWith(`${basePath}/mobify`)) {
444
- shouldRemoveBasePath = true;
445
- }
430
+ /**
431
+ * Very early request processing.
432
+ *
433
+ * If the server receives a request containing the base path, remove it before allowing it through
434
+ *
435
+ * @param req {express.req} the incoming request - modified in-place
436
+ * @private
437
+ */
438
+ const removeBasePathFromPathMiddleware = (req, res, next) => {
439
+ // Scope base path removal to /mobify routes and routes defined by the express app (ie. worker.js)
440
+ // This is to avoid affecting other paths where a base path might be present if it happens to
441
+ // be equal to a site id.
442
+ // For example, if you have a base path of /us and a site id of /us we don't want
443
+ // to remove the /us from www.example.com/us/en-US/category/...
444
+
445
+ const basePath = (0, _ssrNamespacePaths.getEnvBasePath)();
446
+ let shouldRemoveBasePath = false;
447
+ if (basePath) {
448
+ if (req.path.startsWith(`${basePath}/mobify`)) {
449
+ shouldRemoveBasePath = true;
450
+ }
446
451
 
447
- // Check if path matches any existing express route with base path prepended
448
- if (!shouldRemoveBasePath) {
449
- // Routes are dynamically checked since we want to ensure that any express route
450
- // defined after the app is created, such as routes defined in ssr.js are included.
451
- const expressRoutes = app._router.stack
452
- // specifically omit the generic wildcard from the express routes we want to
453
- // remove the base path from since it is mapped to the app render
454
- .filter(layer => layer.route && layer.route.path && layer.route.path !== '*').map(layer => layer.route.path);
455
- for (const route of expressRoutes) {
456
- if (route) {
457
- const routeRegex = _convertExpressRouteToRegex(route);
458
- if (routeRegex) {
459
- const pathWithoutBase = req.path.replace(new RegExp(`^${basePath}`), '');
460
- if (routeRegex.test(pathWithoutBase)) {
461
- shouldRemoveBasePath = true;
462
- break;
452
+ // Check if path matches any existing express route with base path prepended
453
+ if (!shouldRemoveBasePath) {
454
+ // Routes are dynamically checked since we want to ensure that any express route
455
+ // defined after the app is created, such as routes defined in ssr.js, are included.
456
+ const expressRoutes = app._router.stack
457
+ // specifically omit the generic wildcard from the express routes we want to
458
+ // remove the base path from since it is mapped to the app render
459
+ .filter(layer => layer.route && layer.route.path && layer.route.path !== '*').map(layer => layer.route.path);
460
+ for (const route of expressRoutes) {
461
+ if (route) {
462
+ const routeRegex = _convertExpressRouteToRegex(route);
463
+ if (routeRegex) {
464
+ const pathWithoutBase = req.path.replace(new RegExp(`^${basePath}`), '');
465
+ if (routeRegex.test(pathWithoutBase)) {
466
+ shouldRemoveBasePath = true;
467
+ break;
468
+ }
463
469
  }
464
470
  }
465
471
  }
466
472
  }
467
473
  }
468
- }
469
- if (shouldRemoveBasePath) {
470
- const updatedPath = removeBasePathFromPath(req.path);
471
- const parsed = _url.default.parse(req.url);
472
- parsed.pathname = updatedPath;
473
- req.url = _url.default.format(parsed);
474
- }
475
- next();
476
- };
477
- app.use(removeBasePathFromPathMiddleware);
474
+ if (shouldRemoveBasePath) {
475
+ const updatedPath = removeBasePathFromPath(req.path);
476
+ const parsed = _url.default.parse(req.url);
477
+ parsed.pathname = updatedPath;
478
+ req.url = _url.default.format(parsed);
479
+ }
480
+ next();
481
+ };
482
+ app.use(removeBasePathFromPathMiddleware);
483
+ }
478
484
  },
479
485
  /**
480
486
  * @private
@@ -674,6 +680,15 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
674
680
  if (!options.useSLASPrivateClient) {
675
681
  return;
676
682
  }
683
+
684
+ // This is the full path to the SLAS trusted-system endpoint
685
+ // We want to throw an error if the regex defined options.applySLASPrivateClientToEndpoints
686
+ // matches this path as an early warning to developers that they should update their regex
687
+ // in ssr.js to exclude this path.
688
+ const trustedSystemPath = '/shopper/auth/v1/oauth2/trusted-system/token';
689
+ if (trustedSystemPath.match(options.applySLASPrivateClientToEndpoints)) {
690
+ throw new Error('It is not allowed to include /oauth2/trusted-system endpoints in `applySLASPrivateClientToEndpoints`');
691
+ }
677
692
  (0, _ssrServer.localDevLog)(`Proxying ${_ssrNamespacePaths.slasPrivateProxyPath} to ${options.slasTarget}`);
678
693
  const clientId = (_options$mobify2 = options.mobify) === null || _options$mobify2 === void 0 ? void 0 : (_options$mobify2$app = _options$mobify2.app) === null || _options$mobify2$app === void 0 ? void 0 : (_options$mobify2$app$ = _options$mobify2$app.commerceAPI) === null || _options$mobify2$app$ === void 0 ? void 0 : (_options$mobify2$app$2 = _options$mobify2$app$.parameters) === null || _options$mobify2$app$2 === void 0 ? void 0 : _options$mobify2$app$2.clientId;
679
694
  const clientSecret = process.env.PWA_KIT_SLAS_CLIENT_SECRET;
@@ -695,7 +710,7 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
695
710
  return path.replace(regex, '');
696
711
  },
697
712
  onProxyReq: (proxyRequest, incomingRequest, res) => {
698
- var _incomingRequest$path, _incomingRequest$path2, _incomingRequest$path3;
713
+ var _incomingRequest$path, _incomingRequest$path2, _incomingRequest$path3, _incomingRequest$path4;
699
714
  (0, _configureProxy.applyProxyRequestHeaders)({
700
715
  proxyRequest,
701
716
  incomingRequest,
@@ -704,15 +719,9 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
704
719
  targetProtocol: 'https'
705
720
  });
706
721
 
707
- // We pattern match and add client secrets only to endpoints that
708
- // match the regex specified by options.applySLASPrivateClientToEndpoints
709
- // (see option defaults at the top of this file).
710
- // Other SLAS endpoints, ie. SLAS authenticate (/oauth2/login) and
711
- // SLAS logout (/oauth2/logout), use the Authorization header for a different
712
- // purpose so we don't want to overwrite the header for those calls.
713
- if ((_incomingRequest$path = incomingRequest.path) !== null && _incomingRequest$path !== void 0 && _incomingRequest$path.match(options.applySLASPrivateClientToEndpoints)) {
714
- proxyRequest.setHeader('Authorization', `Basic ${encodedSlasCredentials}`);
715
- } else if (!((_incomingRequest$path2 = incomingRequest.path) !== null && _incomingRequest$path2 !== void 0 && _incomingRequest$path2.match(options.slasApiPath))) {
722
+ // We don't want the proxy to handle any non-SLAS requests
723
+ // or any trusted system requests
724
+ if (!((_incomingRequest$path = incomingRequest.path) !== null && _incomingRequest$path !== void 0 && _incomingRequest$path.match(options.slasApiPath)) || (_incomingRequest$path2 = incomingRequest.path) !== null && _incomingRequest$path2 !== void 0 && _incomingRequest$path2.match(/\/oauth2\/trusted-system/)) {
716
725
  const message = `Request to ${incomingRequest.path} is not allowed through the SLAS Private Client Proxy`;
717
726
  _loggerInstance.default.error(message);
718
727
  return res.status(403).json({
@@ -720,8 +729,17 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
720
729
  });
721
730
  }
722
731
 
723
- // /oauth2/trusted-agent/token endpoint requires a different auth header
724
- if ((_incomingRequest$path3 = incomingRequest.path) !== null && _incomingRequest$path3 !== void 0 && _incomingRequest$path3.match(/\/oauth2\/trusted-agent\/token/)) {
732
+ // We pattern match and add client secrets only to endpoints that
733
+ // match the regex specified by options.applySLASPrivateClientToEndpoints.
734
+ //
735
+ // Other SLAS endpoints, ie. SLAS authenticate (/oauth2/login) and
736
+ // SLAS logout (/oauth2/logout), use the Authorization header for a different
737
+ // purpose so we don't want to overwrite the header for those calls.
738
+ if ((_incomingRequest$path3 = incomingRequest.path) !== null && _incomingRequest$path3 !== void 0 && _incomingRequest$path3.match(options.applySLASPrivateClientToEndpoints)) {
739
+ proxyRequest.setHeader('Authorization', `Basic ${encodedSlasCredentials}`);
740
+ } else if ((_incomingRequest$path4 = incomingRequest.path) !== null && _incomingRequest$path4 !== void 0 && _incomingRequest$path4.match(/\/oauth2\/trusted-agent\/token/)) {
741
+ // /oauth2/trusted-agent/token endpoint auth header comes from Account Manager
742
+ // so the SLAS private client is sent via this special header
725
743
  proxyRequest.setHeader('_sfdc_client_auth', encodedSlasCredentials);
726
744
  }
727
745
  }
@@ -14,7 +14,7 @@ const APPLICATION_OCTET_STREAM = exports.APPLICATION_OCTET_STREAM = 'application
14
14
  const BUILD = exports.BUILD = 'build';
15
15
  const STATIC_ASSETS = exports.STATIC_ASSETS = 'static_assets';
16
16
 
17
- /** * @deprecated Use ssr-namespace-paths.proxyBasePath instead */
17
+ /** * @deprecated Use ssr-namespace-paths proxyBasePath instead */
18
18
  const PROXY_PATH_PREFIX = exports.PROXY_PATH_PREFIX = '/mobify/proxy';
19
19
 
20
20
  // All these values MUST be lower case
@@ -918,9 +918,24 @@ describe('DevServer middleware', () => {
918
918
  describe('SLAS private client proxy', () => {
919
919
  const savedEnvironment = _extends({}, process.env);
920
920
  let proxyApp;
921
+ let proxyServer;
921
922
  const proxyPort = 12345;
922
923
  const proxyPath = '/shopper/auth/responseHeaders';
923
924
  const slasTarget = `http://localhost:${proxyPort}${proxyPath}`;
925
+ const appConfig = {
926
+ mobify: {
927
+ app: {
928
+ commerceAPI: {
929
+ parameters: {
930
+ clientId: 'clientId',
931
+ shortCode: 'shortCode'
932
+ }
933
+ }
934
+ }
935
+ },
936
+ useSLASPrivateClient: true,
937
+ slasTarget: slasTarget
938
+ };
924
939
  beforeAll(() => {
925
940
  jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({});
926
941
  // by setting slasTarget, rather than forwarding the request to SLAS,
@@ -929,14 +944,38 @@ describe('SLAS private client proxy', () => {
929
944
  proxyApp.use(proxyPath, (req, res) => {
930
945
  res.send(req.headers);
931
946
  });
932
- proxyApp.listen(proxyPort);
947
+ proxyServer = proxyApp.listen(proxyPort);
933
948
  });
934
949
  afterEach(() => {
935
950
  process.env = savedEnvironment;
936
951
  });
937
- afterAll(() => {
938
- proxyApp.close();
939
- });
952
+
953
+ // There is a lot of cleanup done here to ensure the proxy server is closed
954
+ // after these tests.
955
+ afterAll(/*#__PURE__*/_asyncToGenerator(function* () {
956
+ if (proxyServer) {
957
+ // Close the server and wait for it to fully close
958
+ yield new Promise(resolve => {
959
+ proxyServer.close(() => {
960
+ resolve();
961
+ });
962
+ });
963
+
964
+ // Additional cleanup to ensure all connections are closed
965
+ proxyServer.unref();
966
+
967
+ // Force close any remaining connections
968
+ if (proxyServer._handle) {
969
+ proxyServer._handle.close();
970
+ }
971
+
972
+ // Clear any remaining event listeners
973
+ proxyServer.removeAllListeners();
974
+ }
975
+
976
+ // Clear any remaining timers or intervals
977
+ jest.clearAllTimers();
978
+ }));
940
979
  test('should not create proxy by default', () => {
941
980
  const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
942
981
  return (0, _supertest.default)(app).get('/mobify/slas/private').expect(404);
@@ -950,20 +989,7 @@ describe('SLAS private client proxy', () => {
950
989
  });
951
990
  test('does not insert client secret if request not for /oauth2/token', /*#__PURE__*/_asyncToGenerator(function* () {
952
991
  process.env.PWA_KIT_SLAS_CLIENT_SECRET = 'a secret';
953
- const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
954
- mobify: {
955
- app: {
956
- commerceAPI: {
957
- parameters: {
958
- clientId: 'clientId',
959
- shortCode: 'shortCode'
960
- }
961
- }
962
- }
963
- },
964
- useSLASPrivateClient: true,
965
- slasTarget: slasTarget
966
- }));
992
+ const app = _buildRemoteServer.RemoteServerFactory._createApp(opts(appConfig));
967
993
  return yield (0, _supertest.default)(app).get('/mobify/slas/private/shopper/auth/v1/somePath').then(response => {
968
994
  expect(response.body.authorization).toBeUndefined();
969
995
  expect(response.body.host).toBe('shortCode.api.commercecloud.salesforce.com');
@@ -973,20 +999,7 @@ describe('SLAS private client proxy', () => {
973
999
  test('inserts client secret if request is for /oauth2/token', /*#__PURE__*/_asyncToGenerator(function* () {
974
1000
  process.env.PWA_KIT_SLAS_CLIENT_SECRET = 'a secret';
975
1001
  const encodedCredentials = Buffer.from('clientId:a secret').toString('base64');
976
- const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
977
- mobify: {
978
- app: {
979
- commerceAPI: {
980
- parameters: {
981
- clientId: 'clientId',
982
- shortCode: 'shortCode'
983
- }
984
- }
985
- }
986
- },
987
- useSLASPrivateClient: true,
988
- slasTarget: slasTarget
989
- }));
1002
+ const app = _buildRemoteServer.RemoteServerFactory._createApp(opts(appConfig));
990
1003
  return yield (0, _supertest.default)(app).get('/mobify/slas/private/shopper/auth/v1/oauth2/token').then(response => {
991
1004
  expect(response.body.authorization).toBe(`Basic ${encodedCredentials}`);
992
1005
  expect(response.body.host).toBe('shortCode.api.commercecloud.salesforce.com');
@@ -995,21 +1008,7 @@ describe('SLAS private client proxy', () => {
995
1008
  }), 15000);
996
1009
  test('does not add _sfdc_client_auth header if request not for /oauth2/trusted-agent/token', /*#__PURE__*/_asyncToGenerator(function* () {
997
1010
  process.env.PWA_KIT_SLAS_CLIENT_SECRET = 'a secret';
998
- const encodedCredentials = Buffer.from('clientId:a secret').toString('base64');
999
- const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
1000
- mobify: {
1001
- app: {
1002
- commerceAPI: {
1003
- parameters: {
1004
- clientId: 'clientId',
1005
- shortCode: 'shortCode'
1006
- }
1007
- }
1008
- }
1009
- },
1010
- useSLASPrivateClient: true,
1011
- slasTarget: slasTarget
1012
- }));
1011
+ const app = _buildRemoteServer.RemoteServerFactory._createApp(opts(appConfig));
1013
1012
  return yield (0, _supertest.default)(app).get('/mobify/slas/private/shopper/auth/v1/oauth2/other-path').then(response => {
1014
1013
  expect(response.body._sfdc_client_auth).toBeUndefined();
1015
1014
  });
@@ -1040,21 +1039,33 @@ describe('SLAS private client proxy', () => {
1040
1039
  }), 15000);
1041
1040
  test('returns 403 if request is not for /shopper/auth endpoints', /*#__PURE__*/_asyncToGenerator(function* () {
1042
1041
  process.env.PWA_KIT_SLAS_CLIENT_SECRET = 'a secret';
1043
- const app = _buildRemoteServer.RemoteServerFactory._createApp(opts({
1044
- mobify: {
1045
- app: {
1046
- commerceAPI: {
1047
- parameters: {
1048
- clientId: 'clientId',
1049
- shortCode: 'shortCode'
1042
+ const app = _buildRemoteServer.RemoteServerFactory._createApp(opts(appConfig));
1043
+ return yield (0, _supertest.default)(app).get('/mobify/slas/private/shopper/auth-admin/v1/other-path').expect(403);
1044
+ }), 15000);
1045
+ test('returns 403 if request is for /oauth2/trusted-system/* endpoint', /*#__PURE__*/_asyncToGenerator(function* () {
1046
+ process.env.PWA_KIT_SLAS_CLIENT_SECRET = 'a secret';
1047
+ const app = _buildRemoteServer.RemoteServerFactory._createApp(opts(appConfig));
1048
+ return yield (0, _supertest.default)(app).get('/mobify/slas/private/shopper/auth/v1/oauth2/trusted-system/token').expect(403);
1049
+ }), 15000);
1050
+ test('throws an error if /oauth2/trusted-system/* is included in applySLASPrivateClientToEndpoints', /*#__PURE__*/_asyncToGenerator(function* () {
1051
+ process.env.PWA_KIT_SLAS_CLIENT_SECRET = 'a secret';
1052
+ expect(() => {
1053
+ _buildRemoteServer.RemoteServerFactory._createApp(opts({
1054
+ mobify: {
1055
+ app: {
1056
+ commerceAPI: {
1057
+ parameters: {
1058
+ clientId: 'clientId',
1059
+ shortCode: 'shortCode'
1060
+ }
1050
1061
  }
1051
1062
  }
1052
- }
1053
- },
1054
- useSLASPrivateClient: true,
1055
- slasTarget: slasTarget
1056
- }));
1057
- return yield (0, _supertest.default)(app).get('/mobify/slas/private/shopper/auth-admin/v1/other-path').expect(403);
1063
+ },
1064
+ useSLASPrivateClient: true,
1065
+ slasTarget: slasTarget,
1066
+ applySLASPrivateClientToEndpoints: /\/oauth2\/trusted-system/
1067
+ }));
1068
+ }).toThrow('It is not allowed to include /oauth2/trusted-system endpoints in `applySLASPrivateClientToEndpoints`');
1058
1069
  }), 15000);
1059
1070
  });
1060
1071
  describe('Base path tests', () => {
@@ -1099,6 +1110,24 @@ describe('Base path tests', () => {
1099
1110
  expect(response.body.userId).toBe('123');
1100
1111
  });
1101
1112
  }), 15000);
1113
+ test('should remove base path from routes defined with regex', /*#__PURE__*/_asyncToGenerator(function* () {
1114
+ jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1115
+ envBasePath: '/basepath'
1116
+ });
1117
+ const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1118
+ app.get(/\/api\/users\/\d+/, (req, res) => {
1119
+ // Extract the user ID from the URL path since regex routes don't create req.params automatically
1120
+ const match = req.path.match(/\/api\/users\/(\d+)/);
1121
+ const userId = match ? match[1] : 'unknown';
1122
+ res.status(200).json({
1123
+ userId: userId
1124
+ });
1125
+ });
1126
+ return (0, _supertest.default)(app).get('/basepath/api/users/123').then(response => {
1127
+ expect(response.status).toBe(200);
1128
+ expect(response.body.userId).toBe('123');
1129
+ });
1130
+ }), 15000);
1102
1131
  test('remove base path can handle complex base paths', /*#__PURE__*/_asyncToGenerator(function* () {
1103
1132
  jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1104
1133
  envBasePath: '/my/base/path'
@@ -32,6 +32,7 @@ const SLAS_PRIVATE_CLIENT_PROXY_PATH = `${MOBIFY_PATH}/slas/private`;
32
32
 
33
33
  /*
34
34
  * Returns the base path. This is prepended to a /mobify path.
35
+ * Returns an empty string if the base path is not set or is '/'.
35
36
  */
36
37
  const getEnvBasePath = () => {
37
38
  const config = (0, _ssrConfig.getConfig)();