@salesforce/pwa-kit-runtime 3.17.0 → 3.17.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.17.0",
3
+ "version": "3.17.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": {
@@ -48,11 +48,11 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@loadable/component": "^5.15.3",
51
- "@salesforce/pwa-kit-dev": "3.17.0",
51
+ "@salesforce/pwa-kit-dev": "3.17.1",
52
52
  "@serverless/event-mocks": "^1.1.1",
53
53
  "aws-lambda-mock-context": "^3.2.1",
54
54
  "fs-extra": "^11.1.1",
55
- "internal-lib-build": "3.17.0",
55
+ "internal-lib-build": "3.17.1",
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": "3.17.0"
63
+ "@salesforce/pwa-kit-dev": "3.17.1"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "@salesforce/pwa-kit-dev": {
@@ -74,5 +74,5 @@
74
74
  "publishConfig": {
75
75
  "directory": "dist"
76
76
  },
77
- "gitHead": "6d0cfd2308b9f25d8badd722469074889bd3b891"
77
+ "gitHead": "d09ed57f84432913ab3e55e3073802d319c5dc3c"
78
78
  }
@@ -27,6 +27,7 @@ var _loggerInstance = _interopRequireDefault(require("../../utils/logger-instanc
27
27
  var _httpProxyMiddleware = require("http-proxy-middleware");
28
28
  var _hybridProxy = require("../../utils/ssr-server/hybrid-proxy");
29
29
  var _convertExpressRoute = require("../../utils/ssr-server/convert-express-route");
30
+ var _ssrConfig = require("../../utils/ssr-config");
30
31
  var _serverlessAdapter = require("@h4ad/serverless-adapter");
31
32
  var _default = require("@h4ad/serverless-adapter/lib/handlers/default");
32
33
  var _callback = require("@h4ad/serverless-adapter/lib/resolvers/callback");
@@ -445,6 +446,7 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
445
446
  * @private
446
447
  */
447
448
  _setupBasePathMiddleware(app) {
449
+ var _getConfig, _getConfig$app, _getConfig$app$url;
448
450
  // Cache the express route regexes to avoid re-calculating them on every request.
449
451
  let expressRouteRegexes;
450
452
 
@@ -490,11 +492,19 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
490
492
  * If the server receives a request containing the base path, remove it before allowing
491
493
  * the request through to the other express endpoints
492
494
  *
493
- * We scope base path removal to /mobify routes and routes defined by the express app
494
- * (For example /callback or /worker.js)
495
+ * We scope base path removal to /mobify routes, /__pwa-kit routes (when showBasePath
496
+ * is false), and routes defined by the express app (For example /callback or /worker.js).
495
497
  * This is to avoid affecting React Router routes where a site id or locale might be present
496
498
  * that is equal to the base path.
497
499
  *
500
+ * /__pwa-kit routes are a special case. These are internal PWA Kit routes
501
+ * (e.g. /__pwa-kit/refresh) that are registered as React Router routes and are invoked
502
+ * by the Storefront Preview code on Runtime Admin (which always appends a base path if set).
503
+ * When showBasePath is false, React Router has no basename, so we redirect to the clean
504
+ * URL (without base path) so the browser navigates to a path that React Router can match
505
+ * and hydrate correctly on the client.
506
+ * When showBasePath is true, React Router handles the base path via its basename prop.
507
+ *
498
508
  * For example, if you have a base path of /us and a site id of /us we don't want
499
509
  * to remove the /us from www.example.com/us/en-US/category/... as this route is handled by
500
510
  * React Router and the PWA multisite implementation.
@@ -502,6 +512,7 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
502
512
  * @param req {express.req} the incoming request - modified in-place
503
513
  * @private
504
514
  */
515
+ const showBasePath = ((_getConfig = (0, _ssrConfig.getConfig)()) === null || _getConfig === void 0 ? void 0 : (_getConfig$app = _getConfig.app) === null || _getConfig$app === void 0 ? void 0 : (_getConfig$app$url = _getConfig$app.url) === null || _getConfig$app$url === void 0 ? void 0 : _getConfig$app$url.showBasePath) === true;
505
516
  const removeBasePathMiddleware = (req, res, next) => {
506
517
  const basePath = (0, _ssrNamespacePaths.getEnvBasePath)();
507
518
 
@@ -515,8 +526,20 @@ const RemoteServerFactory = exports.RemoteServerFactory = {
515
526
  return next();
516
527
  }
517
528
 
518
- // For other routes, only proceed if path actually starts with base path
519
- if (!req.path.startsWith(basePath)) {
529
+ // /__pwa-kit routes: when showBasePath is false, the browser URL still has the
530
+ // base path but client-side React Router has no basename, so hydration would fail.
531
+ // Redirect to the clean URL so the browser navigates to a path React Router matches.
532
+ if (!showBasePath && req.path.startsWith(`${basePath}/__pwa-kit`)) {
533
+ const cleanPath = removeBasePathFromPath(req.path);
534
+ const {
535
+ search
536
+ } = (0, _ssrServer.parseRequestUrl)(req);
537
+ return res.redirect(302, cleanPath + search);
538
+ }
539
+
540
+ // For other routes, only proceed if path equals basePath or path starts with basePath + '/'
541
+ const pathMatchesBasePath = req.path === basePath || req.path.startsWith(basePath + '/');
542
+ if (!pathMatchesBasePath) {
520
543
  return next();
521
544
  }
522
545
 
@@ -13,6 +13,7 @@ var _ssrCache = require("../../utils/ssr-cache");
13
13
  var _ssrServer = require("../../utils/ssr-server");
14
14
  var ssrServerUtils = _interopRequireWildcard(require("../../utils/ssr-server/utils"));
15
15
  var ssrConfig = _interopRequireWildcard(require("../../utils/ssr-config"));
16
+ var ssrNamespacePaths = _interopRequireWildcard(require("../../utils/ssr-namespace-paths"));
16
17
  var _buildRemoteServer = require("./build-remote-server");
17
18
  var _constants = require("./constants");
18
19
  var _express2 = require("./express");
@@ -1191,10 +1192,16 @@ describe('SLAS private client proxy', () => {
1191
1192
  }));
1192
1193
  });
1193
1194
  describe('Base path tests', () => {
1195
+ beforeEach(() => {
1196
+ // Re-establish the getConfig mock that afterEach's restoreAllMocks clears.
1197
+ // _setupBasePathMiddleware reads getConfig() at setup time.
1198
+ jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({});
1199
+ });
1200
+ afterEach(() => {
1201
+ jest.restoreAllMocks();
1202
+ });
1194
1203
  test('Base path is removed from /mobify request path and still gets through to /mobify endpoint', /*#__PURE__*/_asyncToGenerator(function* () {
1195
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1196
- envBasePath: '/basepath'
1197
- });
1204
+ jest.spyOn(ssrNamespacePaths, 'getEnvBasePath').mockReturnValue('/basepath');
1198
1205
  const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1199
1206
  return (0, _supertest.default)(app).get('/basepath/mobify/ping').then(response => {
1200
1207
  expect(response.status).toBe(200);
@@ -1202,9 +1209,7 @@ describe('Base path tests', () => {
1202
1209
  }), 15000);
1203
1210
  test('should not remove base path from non /mobify non-express routes', /*#__PURE__*/_asyncToGenerator(function* () {
1204
1211
  // Set base path to something that might also be a site id used by react router routes
1205
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1206
- envBasePath: '/us'
1207
- });
1212
+ jest.spyOn(ssrNamespacePaths, 'getEnvBasePath').mockReturnValue('/us');
1208
1213
  const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1209
1214
 
1210
1215
  // Add a middleware to capture the request path after base path processing
@@ -1221,9 +1226,7 @@ describe('Base path tests', () => {
1221
1226
  });
1222
1227
  }), 15000);
1223
1228
  test('should remove base path from routes with path parameters', /*#__PURE__*/_asyncToGenerator(function* () {
1224
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1225
- envBasePath: '/basepath'
1226
- });
1229
+ jest.spyOn(ssrNamespacePaths, 'getEnvBasePath').mockReturnValue('/basepath');
1227
1230
  const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1228
1231
  app.get('/api/users/:id', (req, res) => {
1229
1232
  res.status(200).json({
@@ -1236,9 +1239,7 @@ describe('Base path tests', () => {
1236
1239
  });
1237
1240
  }), 15000);
1238
1241
  test('should remove base path from routes defined with regex', /*#__PURE__*/_asyncToGenerator(function* () {
1239
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1240
- envBasePath: '/basepath'
1241
- });
1242
+ jest.spyOn(ssrNamespacePaths, 'getEnvBasePath').mockReturnValue('/basepath');
1242
1243
  const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1243
1244
  app.get(/\/api\/users\/\d+/, (req, res) => {
1244
1245
  // Extract the user ID from the URL path since regex routes don't create req.params automatically
@@ -1253,25 +1254,8 @@ describe('Base path tests', () => {
1253
1254
  expect(response.body.userId).toBe('123');
1254
1255
  });
1255
1256
  }), 15000);
1256
- test('remove base path can handle multi-part base paths', /*#__PURE__*/_asyncToGenerator(function* () {
1257
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1258
- envBasePath: '/my/base/path'
1259
- });
1260
- const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1261
- app.get('/api/test', (req, res) => {
1262
- res.status(200).json({
1263
- message: 'test'
1264
- });
1265
- });
1266
- return (0, _supertest.default)(app).get('/my/base/path/api/test').then(response => {
1267
- expect(response.status).toBe(200);
1268
- expect(response.body.message).toBe('test');
1269
- });
1270
- }), 15000);
1271
1257
  test('should handle optional characters in route pattern', /*#__PURE__*/_asyncToGenerator(function* () {
1272
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1273
- envBasePath: '/basepath'
1274
- });
1258
+ jest.spyOn(ssrNamespacePaths, 'getEnvBasePath').mockReturnValue('/basepath');
1275
1259
  const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1276
1260
 
1277
1261
  // This route is intentionally made complex to test the following:
@@ -1289,4 +1273,39 @@ describe('Base path tests', () => {
1289
1273
  expect(response.body.message).toBe('test');
1290
1274
  });
1291
1275
  }), 15000);
1276
+ test('should redirect /__pwa-kit routes to clean URL when showBasePath is false', /*#__PURE__*/_asyncToGenerator(function* () {
1277
+ jest.spyOn(ssrNamespacePaths, 'getEnvBasePath').mockReturnValue('/basepath');
1278
+ jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1279
+ app: {
1280
+ url: {
1281
+ showBasePath: false
1282
+ }
1283
+ }
1284
+ });
1285
+ const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1286
+ return (0, _supertest.default)(app).get('/basepath/__pwa-kit/refresh?referrer=/some-page').expect(302).then(response => {
1287
+ // Should redirect to the clean URL without base path
1288
+ expect(response.headers.location).toBe('/__pwa-kit/refresh?referrer=/some-page');
1289
+ });
1290
+ }), 15000);
1291
+ test('should not remove base path from /__pwa-kit routes when showBasePath is true', /*#__PURE__*/_asyncToGenerator(function* () {
1292
+ jest.spyOn(ssrNamespacePaths, 'getEnvBasePath').mockReturnValue('/basepath');
1293
+ jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
1294
+ app: {
1295
+ url: {
1296
+ showBasePath: true
1297
+ }
1298
+ }
1299
+ });
1300
+ const app = _buildRemoteServer.RemoteServerFactory._createApp(opts());
1301
+ let capturedPath = null;
1302
+ app.use((req, res, next) => {
1303
+ capturedPath = req.path;
1304
+ next();
1305
+ });
1306
+ return (0, _supertest.default)(app).get('/basepath/__pwa-kit/refresh').then(() => {
1307
+ // Base path should NOT be stripped since React Router handles it via basename
1308
+ expect(capturedPath).toBe('/basepath/__pwa-kit/refresh');
1309
+ });
1310
+ }), 15000);
1292
1311
  });
@@ -4,9 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.ssrNamespace = exports.slasPrivateProxyPath = exports.proxyBasePath = exports.healthCheckPath = exports.getEnvBasePath = exports.cachingBasePath = exports.bundleBasePath = void 0;
7
- var _ssrConfig = require("./ssr-config");
8
- var _loggerInstance = _interopRequireDefault(require("./logger-instance"));
9
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
7
  /*
11
8
  * Copyright (c) 2025, salesforce.com, inc.
12
9
  * All rights reserved.
@@ -32,35 +29,39 @@ const SLAS_PRIVATE_CLIENT_PROXY_PATH = `${MOBIFY_PATH}/slas/private`;
32
29
 
33
30
  /*
34
31
  * 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 '/'.
32
+ * Returns an empty string if the base path is not set.
33
+ * Throws an error if the base path is not valid.
34
+ *
35
+ * Use this function if you are working with an express route
36
+ * (ie. The route is defined in ssr.js).
37
+ *
38
+ * Use getRouterBasePath (pwa-kit-react-sdk) if you are working
39
+ * with a React Router route
40
+ * (ie. The route is defined in routes.jsx).
36
41
  */
37
42
  const getEnvBasePath = () => {
38
- const config = (0, _ssrConfig.getConfig)();
39
- let basePath = (config === null || config === void 0 ? void 0 : config.envBasePath) || '';
40
- if (typeof basePath !== 'string') {
41
- _loggerInstance.default.warn('Invalid envBasePath configuration. No base path is applied.', {
42
- namespace: 'ssr-namespace-paths.getEnvBasePath'
43
- });
44
- return '';
43
+ let basePath = '';
44
+ if (typeof window !== 'undefined') {
45
+ basePath = window.__MRT_ENV_BASE_PATH__ || '';
46
+ } else {
47
+ basePath = process.env.MRT_ENV_BASE_PATH || '';
45
48
  }
46
49
 
47
- // Normalize the base path
48
- basePath = basePath.trim().replace(/^\/?/, '/') // Ensure leading slash
49
- .replace(/\/+/g, '/') // Normalize multiple slashes
50
- .replace(/\/$/, ''); // Remove trailing slash
51
-
52
- // Return empty string for root path or empty result
53
- if (basePath === '/' || !basePath) {
50
+ // Return empty string if no base path is set
51
+ if (!basePath) {
54
52
  return '';
55
53
  }
56
54
 
57
- // only allow simple, safe characters
58
- // eslint-disable-next-line no-useless-escape
59
- if (!/^\/[a-zA-Z0-9\-_\/]*$/.test(basePath)) {
60
- _loggerInstance.default.warn('Invalid envBasePath configuration. Only letters, numbers, hyphens, underscores, and slashes allowed. No base path is applied.', {
61
- namespace: 'ssr-namespace-paths.getEnvBasePath'
62
- });
63
- return '';
55
+ // MRT will throw an error on bundle upload if the base path does not match
56
+ // the following regex: /^\/[a-zA-Z0-9_.+$~"'@:-]{1,63}$/
57
+ // This validates:
58
+ // - Starts with /
59
+ // - Followed by 1-63 characters (letters, numbers, and special chars: - _ . + $ ~ " ' @ :)
60
+ // - No additional slashes (multi-part paths not allowed, no trailing slashes)
61
+ // - No spaces
62
+ // - Total max length of 64 characters (1 slash + 63 chars)
63
+ if (!/^\/[a-zA-Z0-9_.+$~"'@:-]{1,63}$/.test(basePath)) {
64
+ throw new Error("Invalid envBasePath configuration. Base path must start with '/' followed by 1-63 characters. Only letters, numbers, and the following special characters are allowed: - _ . + $ ~ \" ' @ :");
64
65
  }
65
66
  return basePath;
66
67
  };
@@ -1,55 +1,98 @@
1
1
  "use strict";
2
2
 
3
3
  var _ssrNamespacePaths = require("./ssr-namespace-paths");
4
- var ssrConfig = _interopRequireWildcard(require("./ssr-config"));
5
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
6
- /*
4
+ 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; }
5
+ 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; }
6
+ 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; }
7
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
8
+ 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); } /*
7
9
  * Copyright (c) 2024, Salesforce, Inc.
8
10
  * All rights reserved.
9
11
  * SPDX-License-Identifier: BSD-3-Clause
10
12
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
11
13
  */
12
-
13
- jest.mock('./ssr-config');
14
14
  describe('ssr-namespace-paths tests', () => {
15
- test('getEnvBasePath returns base path from config', () => {
16
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
17
- envBasePath: '/sample'
18
- });
19
- expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('/sample');
15
+ const originalEnv = process.env;
16
+ beforeEach(() => {
17
+ jest.resetModules();
18
+ process.env = _objectSpread({}, originalEnv);
19
+ delete process.env.MRT_ENV_BASE_PATH;
20
+ // Ensure we're in Node environment (no window)
21
+ delete global.window;
20
22
  });
21
- test('getEnvBasePath returns empty string if no base path is set', () => {
22
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({});
23
- expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('');
23
+ afterEach(() => {
24
+ process.env = originalEnv;
25
+ delete global.window;
24
26
  });
25
- test('getEnvBasePath returns empty string if envBasePath is not a string', () => {
26
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
27
- envBasePath: 123
27
+ describe('Node environment (process.env)', () => {
28
+ test('getEnvBasePath returns base path from environment variable', () => {
29
+ process.env.MRT_ENV_BASE_PATH = '/sample';
30
+ expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('/sample');
28
31
  });
29
- expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('');
30
- });
31
- test('getEnvBasePath removes trailing slash', () => {
32
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
33
- envBasePath: '/sample/'
32
+ test('getEnvBasePath returns empty string if no base path is set', () => {
33
+ expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('');
34
34
  });
35
- expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('/sample');
36
- });
37
- test('getEnvBasePath returns empty string if invalid cahracters are detected in envBasePath', () => {
38
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
39
- envBasePath: '/sample.*'
35
+ test('getEnvBasePath throws error for base path with trailing slash', () => {
36
+ process.env.MRT_ENV_BASE_PATH = '/sample/';
37
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
40
38
  });
41
- expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('');
42
- });
43
- test('getEnvBasePath normalizes envBasePath', () => {
44
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
45
- envBasePath: ' //sample/ '
39
+ test('getEnvBasePath throws error for just a slash', () => {
40
+ process.env.MRT_ENV_BASE_PATH = '/';
41
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
42
+ });
43
+ test('getEnvBasePath throws error if invalid characters are detected in envBasePath', () => {
44
+ process.env.MRT_ENV_BASE_PATH = '/sample<script>';
45
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
46
+ });
47
+ test('getEnvBasePath throws error for envBasePath with whitespace', () => {
48
+ process.env.MRT_ENV_BASE_PATH = ' /sample ';
49
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
50
+ });
51
+ test('getEnvBasePath throws error for multi-part base paths with slashes', () => {
52
+ process.env.MRT_ENV_BASE_PATH = '/test/sample';
53
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
54
+ });
55
+ test('getEnvBasePath allows special characters: . + $ ~ " \' @ : -', () => {
56
+ process.env.MRT_ENV_BASE_PATH = '/a.b+c$d~e"f\'g@h:i-j_k';
57
+ expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('/a.b+c$d~e"f\'g@h:i-j_k');
58
+ });
59
+ test('getEnvBasePath throws error if base path exceeds 64 characters', () => {
60
+ // 65 characters total (1 slash + 64 chars)
61
+ process.env.MRT_ENV_BASE_PATH = '/' + 'a'.repeat(64);
62
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
63
+ });
64
+ test('getEnvBasePath allows base path of exactly 64 characters', () => {
65
+ // 64 characters total (1 slash + 63 chars)
66
+ process.env.MRT_ENV_BASE_PATH = '/' + 'a'.repeat(63);
67
+ expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('/' + 'a'.repeat(63));
46
68
  });
47
- expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('/sample');
48
69
  });
49
- test('getEnvBasePath works with multiple part base path', () => {
50
- jest.spyOn(ssrConfig, 'getConfig').mockReturnValue({
51
- envBasePath: '//test/sample/ '
70
+ describe('Browser environment (window)', () => {
71
+ beforeEach(() => {
72
+ global.window = {};
73
+ });
74
+ test('getEnvBasePath returns base path from window global', () => {
75
+ global.window.__MRT_ENV_BASE_PATH__ = '/sample';
76
+ expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('/sample');
77
+ });
78
+ test('getEnvBasePath returns empty string if window global is not set', () => {
79
+ expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('');
80
+ });
81
+ test('getEnvBasePath throws error for base path with trailing slash from window global', () => {
82
+ global.window.__MRT_ENV_BASE_PATH__ = '/sample/';
83
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
84
+ });
85
+ test('getEnvBasePath throws error for window global value with whitespace', () => {
86
+ global.window.__MRT_ENV_BASE_PATH__ = ' /sample ';
87
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
88
+ });
89
+ test('getEnvBasePath throws error if invalid characters in window global', () => {
90
+ global.window.__MRT_ENV_BASE_PATH__ = '/sample<script>';
91
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
92
+ });
93
+ test('getEnvBasePath throws error for multi-part base paths in window global', () => {
94
+ global.window.__MRT_ENV_BASE_PATH__ = '/test/sample';
95
+ expect(() => (0, _ssrNamespacePaths.getEnvBasePath)()).toThrow('Invalid envBasePath configuration');
52
96
  });
53
- expect((0, _ssrNamespacePaths.getEnvBasePath)()).toBe('/test/sample');
54
97
  });
55
98
  });