@sitecore-jss/sitecore-jss-nextjs 22.4.0-canary.2 → 22.4.0-canary.21

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.
@@ -90,7 +90,7 @@ class ServerlessEditingDataService {
90
90
  if (!this.apiRoute.includes('[key]')) {
91
91
  throw new Error(`The specified apiRoute '${this.apiRoute}' is missing '[key]'.`);
92
92
  }
93
- this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new sitecore_jss_1.AxiosDataFetcher({ debugger: sitecore_jss_1.debug.editing });
93
+ this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new sitecore_jss_1.NativeDataFetcher({ debugger: sitecore_jss_1.debug.editing });
94
94
  }
95
95
  /**
96
96
  * Stores Sitecore editor payload data for later retrieval by key
@@ -52,7 +52,7 @@ class ChromesHandler extends render_middleware_1.RenderMiddlewareBase {
52
52
  return `${process.env.VERCEL ? 'https' : 'http'}://${req.headers.host}`;
53
53
  };
54
54
  this.editingDataService = (_a = config === null || config === void 0 ? void 0 : config.editingDataService) !== null && _a !== void 0 ? _a : editing_data_service_1.editingDataService;
55
- this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new sitecore_jss_1.AxiosDataFetcher({ debugger: sitecore_jss_1.debug.editing });
55
+ this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new sitecore_jss_1.NativeDataFetcher({ debugger: sitecore_jss_1.debug.editing });
56
56
  this.resolvePageUrl = (_c = config === null || config === void 0 ? void 0 : config.resolvePageUrl) !== null && _c !== void 0 ? _c : this.defaultResolvePageUrl;
57
57
  this.resolveServerUrl = (_d = config === null || config === void 0 ? void 0 : config.resolveServerUrl) !== null && _d !== void 0 ? _d : this.defaultResolveServerUrl;
58
58
  }
@@ -79,7 +79,7 @@ class ChromesHandler extends render_middleware_1.RenderMiddlewareBase {
79
79
  const cookies = res.getHeader('Set-Cookie');
80
80
  headers.cookie = `${headers.cookie ? headers.cookie + ';' : ''}${cookies.join(';')}`;
81
81
  // Make actual render request for page route, passing on preview cookies as well as any approved query string parameters.
82
- // Note timestamp effectively disables caching the request in Axios (no amount of cache headers seemed to do it)
82
+ // Note timestamp effectively disables caching the request (no amount of cache headers seemed to do it)
83
83
  sitecore_jss_1.debug.editing('fetching page route for %s', editingData.path);
84
84
  const requestUrl = new URL(this.resolvePageUrl({ serverUrl, itemPath: editingData.path }));
85
85
  for (const key in params) {
@@ -125,8 +125,7 @@ class ChromesHandler extends render_middleware_1.RenderMiddlewareBase {
125
125
  catch (err) {
126
126
  const error = err;
127
127
  console.error(error);
128
- if (error.response || error.request) {
129
- // Axios error, which could mean the server or page URL isn't quite right, so provide a more helpful hint
128
+ if (error.response) {
130
129
  console.info(
131
130
  // eslint-disable-next-line quotes
132
131
  "Hint: for non-standard server or Next.js route configurations, you may need to override the 'resolveServerUrl' or 'resolvePageUrl' available on the 'EditingRenderMiddleware' config.");
@@ -28,13 +28,15 @@ class RenderMiddlewareBase {
28
28
  * @returns Object of approved headers
29
29
  */
30
30
  this.getHeadersForPropagation = (headers) => {
31
- const result = {};
32
- constants_1.EDITING_PASS_THROUGH_HEADERS.forEach((header) => {
33
- if (headers[header]) {
34
- result[header] = headers[header];
31
+ // Filter and normalize headers
32
+ const filteredHeaders = constants_1.EDITING_PASS_THROUGH_HEADERS.reduce((acc, header) => {
33
+ const value = headers[header];
34
+ if (value) {
35
+ acc[header] = Array.isArray(value) ? value.join(', ') : value;
35
36
  }
36
- });
37
- return result;
37
+ return acc;
38
+ }, {});
39
+ return filteredHeaders;
38
40
  };
39
41
  }
40
42
  }
package/dist/cjs/index.js CHANGED
@@ -23,11 +23,10 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.FEaaSComponent = exports.EditFrame = exports.DateField = exports.Text = exports.Image = exports.ComponentBuilder = exports.BYOCWrapper = exports.FEaaSWrapper = exports.NextImage = exports.Placeholder = exports.RichText = exports.Link = exports.useComponentProps = exports.ComponentPropsContext = exports.ComponentPropsReactContext = exports.normalizeSiteRewrite = exports.getSiteRewriteData = exports.getSiteRewrite = exports.GraphQLSiteInfoService = exports.SiteResolver = exports.GraphQLRobotsService = exports.GraphQLErrorPagesService = exports.GraphQLSitemapXmlService = exports.MultisiteGraphQLSitemapService = exports.GraphQLSitemapService = exports.DisconnectedSitemapService = exports.ComponentPropsService = exports.CdpHelper = exports.normalizePersonalizedRewrite = exports.getGroomedVariantIds = exports.getPersonalizedRewriteData = exports.getPersonalizedRewrite = exports.personalizeLayout = exports.RestDictionaryService = exports.GraphQLDictionaryService = exports.trackingApi = exports.mediaApi = exports.EditMode = exports.getContentStylesheetLink = exports.getFieldValue = exports.getChildPlaceholder = exports.RestLayoutService = exports.GraphQLLayoutService = exports.LayoutServicePageState = exports.MemoryCacheClient = exports.debug = exports.enableDebug = exports.NativeDataFetcher = exports.AxiosDataFetcher = exports.constants = void 0;
27
- exports.EditingScripts = exports.withEmptyFieldEditingComponent = exports.withFieldMetadata = exports.withDatasourceCheck = exports.withPlaceholder = exports.withEditorChromes = exports.useSitecoreContext = exports.withSitecoreContext = exports.SitecoreContextReactContext = exports.SitecoreContext = exports.VisitorIdentification = exports.DefaultEmptyFieldEditingComponentText = exports.DefaultEmptyFieldEditingComponentImage = exports.File = exports.getComponentLibraryStylesheetLinks = exports.BYOCComponent = exports.fetchFEaaSComponentServerProps = void 0;
26
+ exports.fetchFEaaSComponentServerProps = exports.FEaaSComponent = exports.EditFrame = exports.DateField = exports.Text = exports.Image = exports.ComponentBuilder = exports.BYOCWrapper = exports.FEaaSWrapper = exports.NextImage = exports.Placeholder = exports.RichText = exports.Link = exports.useComponentProps = exports.ComponentPropsContext = exports.ComponentPropsReactContext = exports.normalizeSiteRewrite = exports.getSiteRewriteData = exports.getSiteRewrite = exports.GraphQLSiteInfoService = exports.SiteResolver = exports.GraphQLRobotsService = exports.GraphQLErrorPagesService = exports.GraphQLSitemapXmlService = exports.MultisiteGraphQLSitemapService = exports.GraphQLSitemapService = exports.DisconnectedSitemapService = exports.ComponentPropsService = exports.CdpHelper = exports.normalizePersonalizedRewrite = exports.getGroomedVariantIds = exports.getPersonalizedRewriteData = exports.getPersonalizedRewrite = exports.personalizeLayout = exports.RestDictionaryService = exports.GraphQLDictionaryService = exports.trackingApi = exports.mediaApi = exports.EditMode = exports.getContentStylesheetLink = exports.getFieldValue = exports.getChildPlaceholder = exports.RestLayoutService = exports.GraphQLLayoutService = exports.LayoutServicePageState = exports.MemoryCacheClient = exports.debug = exports.enableDebug = exports.NativeDataFetcher = exports.constants = void 0;
27
+ exports.EditingScripts = exports.withEmptyFieldEditingComponent = exports.withFieldMetadata = exports.withDatasourceCheck = exports.withPlaceholder = exports.withEditorChromes = exports.useSitecoreContext = exports.withSitecoreContext = exports.SitecoreContextReactContext = exports.SitecoreContext = exports.VisitorIdentification = exports.DefaultEmptyFieldEditingComponentText = exports.DefaultEmptyFieldEditingComponentImage = exports.File = exports.getComponentLibraryStylesheetLinks = exports.BYOCComponent = void 0;
28
28
  var sitecore_jss_1 = require("@sitecore-jss/sitecore-jss");
29
29
  Object.defineProperty(exports, "constants", { enumerable: true, get: function () { return sitecore_jss_1.constants; } });
30
- Object.defineProperty(exports, "AxiosDataFetcher", { enumerable: true, get: function () { return sitecore_jss_1.AxiosDataFetcher; } });
31
30
  Object.defineProperty(exports, "NativeDataFetcher", { enumerable: true, get: function () { return sitecore_jss_1.NativeDataFetcher; } });
32
31
  Object.defineProperty(exports, "enableDebug", { enumerable: true, get: function () { return sitecore_jss_1.enableDebug; } });
33
32
  Object.defineProperty(exports, "debug", { enumerable: true, get: function () { return sitecore_jss_1.debug; } });
@@ -45,20 +45,28 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
45
45
  });
46
46
  const createResponse = () => __awaiter(this, void 0, void 0, function* () {
47
47
  var _a;
48
+ const response = res || server_1.NextResponse.next();
48
49
  if (this.config.disabled && this.config.disabled(req, res || server_1.NextResponse.next())) {
49
50
  sitecore_jss_1.debug.redirects('skipped (redirects middleware is disabled)');
50
- return res || server_1.NextResponse.next();
51
+ return response;
51
52
  }
52
53
  if (this.isPreview(req) || this.excludeRoute(pathname)) {
53
54
  sitecore_jss_1.debug.redirects('skipped (%s)', this.isPreview(req) ? 'preview' : 'route excluded');
54
- return res || server_1.NextResponse.next();
55
+ return response;
56
+ }
57
+ // Skip prefetch requests from Next.js, which are not original client requests
58
+ // as they load unnecessary requests that burden the redirects middleware with meaningless traffic
59
+ if (this.isPrefetch(req)) {
60
+ sitecore_jss_1.debug.redirects('skipped (prefetch)');
61
+ response.headers.set('x-middleware-cache', 'no-cache');
62
+ return response;
55
63
  }
56
64
  site = this.getSite(req, res);
57
65
  // Find the redirect from result of RedirectService
58
66
  const existsRedirect = yield this.getExistsRedirect(req, site.name);
59
67
  if (!existsRedirect) {
60
68
  sitecore_jss_1.debug.redirects('skipped (redirect does not exist)');
61
- return res || server_1.NextResponse.next();
69
+ return response;
62
70
  }
63
71
  // Find context site language and replace token
64
72
  if (REGEXP_CONTEXT_SITE_LANG.test(existsRedirect.target) &&
@@ -72,27 +80,26 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
72
80
  url.href = existsRedirect.target;
73
81
  }
74
82
  else {
75
- const source = `${url.pathname.replace(/\/*$/gi, '')}${existsRedirect.matchedQueryString}`;
76
- const urlFirstPart = existsRedirect.target.split('/')[1];
83
+ const isUrl = (0, utils_1.isRegexOrUrl)(existsRedirect.pattern) === 'url';
84
+ const targetParts = existsRedirect.target.split('/');
85
+ const urlFirstPart = targetParts[1];
77
86
  if (this.locales.includes(urlFirstPart)) {
78
87
  req.nextUrl.locale = urlFirstPart;
79
88
  existsRedirect.target = existsRedirect.target.replace(`/${urlFirstPart}`, '');
80
89
  }
81
- const target = source
82
- .replace((0, regex_parser_1.default)(existsRedirect.pattern), existsRedirect.target)
83
- .replace(/^\/\//, '/')
84
- .split('?');
85
- if (url.search && existsRedirect.isQueryStringPreserved) {
86
- const targetQueryString = (_a = target[1]) !== null && _a !== void 0 ? _a : '';
87
- url.search = '?' + new URLSearchParams(`${url.search}&${targetQueryString}`).toString();
88
- }
89
- else if (target[1]) {
90
- url.search = '?' + target[1];
91
- }
92
- else {
93
- url.search = '';
94
- }
95
- const prepareNewURL = new URL(`${target[0]}${url.search}`, url.origin);
90
+ const targetSegments = isUrl
91
+ ? existsRedirect.target.split('?')
92
+ : url.pathname.replace(/\/*$/gi, '') + existsRedirect.matchedQueryString;
93
+ const [targetPath, targetQueryString] = isUrl
94
+ ? targetSegments
95
+ : targetSegments
96
+ .replace((0, regex_parser_1.default)(existsRedirect.pattern), existsRedirect.target)
97
+ .replace(/^\/\//, '/')
98
+ .split('?');
99
+ const mergedQueryString = existsRedirect.isQueryStringPreserved
100
+ ? (0, utils_1.mergeURLSearchParams)(new URLSearchParams((_a = url.search) !== null && _a !== void 0 ? _a : ''), new URLSearchParams(targetQueryString || ''))
101
+ : targetQueryString || '';
102
+ const prepareNewURL = new URL(`${targetPath}${mergedQueryString ? '?' + mergedQueryString : ''}`, url.origin);
96
103
  url.href = prepareNewURL.href;
97
104
  url.pathname = prepareNewURL.pathname;
98
105
  url.search = prepareNewURL.search;
@@ -101,16 +108,16 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
101
108
  /** return Response redirect with http code of redirect type */
102
109
  switch (existsRedirect.redirectType) {
103
110
  case site_1.REDIRECT_TYPE_301: {
104
- return this.createRedirectResponse(url, res, 301, 'Moved Permanently');
111
+ return this.createRedirectResponse(url, response, 301, 'Moved Permanently');
105
112
  }
106
113
  case site_1.REDIRECT_TYPE_302: {
107
- return this.createRedirectResponse(url, res, 302, 'Found');
114
+ return this.createRedirectResponse(url, response, 302, 'Found');
108
115
  }
109
116
  case site_1.REDIRECT_TYPE_SERVER_TRANSFER: {
110
- return this.rewrite(url.href, req, res || server_1.NextResponse.next());
117
+ return this.rewrite(url.href, req, response);
111
118
  }
112
119
  default:
113
- return res || server_1.NextResponse.next();
120
+ return response;
114
121
  }
115
122
  });
116
123
  const response = yield createResponse();
@@ -152,59 +159,43 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
152
159
  */
153
160
  getExistsRedirect(req, siteName) {
154
161
  return __awaiter(this, void 0, void 0, function* () {
155
- const redirects = yield this.redirectsService.fetchRedirects(siteName);
156
162
  const { pathname: targetURL, search: targetQS = '', locale } = this.normalizeUrl(req.nextUrl.clone());
163
+ const normalizedPath = targetURL.replace(/\/*$/gi, '');
164
+ const redirects = yield this.redirectsService.fetchRedirects(siteName);
157
165
  const language = this.getLanguage(req);
158
166
  const modifyRedirects = structuredClone(redirects);
167
+ let matchedQueryString;
159
168
  return modifyRedirects.length
160
169
  ? modifyRedirects.find((redirect) => {
170
+ var _a;
171
+ if ((0, utils_1.isRegexOrUrl)(redirect.pattern) === 'url') {
172
+ const parseUrlPattern = redirect.pattern.endsWith('/')
173
+ ? redirect.pattern.slice(0, -1).split('?')
174
+ : redirect.pattern.split('?');
175
+ return ((parseUrlPattern[0] === normalizedPath ||
176
+ parseUrlPattern[0] === `/${locale}${normalizedPath}`) &&
177
+ (0, utils_1.areURLSearchParamsEqual)(new URLSearchParams((_a = parseUrlPattern[1]) !== null && _a !== void 0 ? _a : ''), new URLSearchParams(targetQS)));
178
+ }
161
179
  // Modify the redirect pattern to ignore the language prefix in the path
162
- redirect.pattern = redirect.pattern.replace(RegExp(`^[^]?/${language}/`, 'gi'), '');
180
+ // And escapes non-special "?" characters in a string or regex.
181
+ redirect.pattern = (0, utils_1.escapeNonSpecialQuestionMarks)(redirect.pattern.replace(new RegExp(`^[^]?/${language}/`, 'gi'), ''));
163
182
  // Prepare the redirect pattern as a regular expression, making it more flexible for matching URLs
164
183
  redirect.pattern = `/^\/${redirect.pattern
165
184
  .replace(/^\/|\/$/g, '') // Removes leading and trailing slashes
166
185
  .replace(/^\^\/|\/\$$/g, '') // Removes unnecessary start (^) and end ($) anchors
167
186
  .replace(/^\^|\$$/g, '') // Further cleans up anchors
168
- .replace(/(?<!\\)\?/g, '\\?') // Escapes question marks in the pattern
169
187
  .replace(/\$\/gi$/g, '')}[\/]?$/i`; // Ensures the pattern allows an optional trailing slash
170
- /**
171
- * This line checks whether the current URL query string (and all its possible permutations)
172
- * matches the redirect pattern.
173
- *
174
- * Query parameters in URLs can come in different orders, but logically they represent the
175
- * same information (e.g., "key1=value1&key2=value2" is the same as "key2=value2&key1=value1").
176
- * To account for this, the method `isPermutedQueryMatch` generates all possible permutations
177
- * of the query parameters and checks if any of those permutations match the regex pattern for the redirect.
178
- *
179
- * NOTE: This fix is specifically implemented for Netlify, where query parameters are sometimes
180
- * automatically sorted, which can cause issues with matching redirects if the order of query
181
- * parameters is important. By checking every possible permutation, we ensure that redirects
182
- * work correctly on Netlify despite this behavior.
183
- *
184
- * It passes several pieces of information to the function:
185
- * 1. `pathname`: The normalized URL path without query parameters (e.g., '/about').
186
- * 2. `queryString`: The current query string from the URL, which will be permuted and matched (e.g., '?key1=value1&key2=value2').
187
- * 3. `pattern`: The regex pattern for the redirect that we are trying to match against the URL (e.g., '/about?key1=value1').
188
- * 4. `locale`: The locale part of the URL (if any), which helps support multilingual URLs.
189
- *
190
- * If one of the permutations of the query string matches the redirect pattern, the function
191
- * returns the matched query string, which is stored in `matchedQueryString`. If no match is found,
192
- * it returns `undefined`. The `matchedQueryString` is later used to indicate whether the query
193
- * string contributed to a successful redirect match.
194
- */
195
- const matchedQueryString = this.isPermutedQueryMatch({
196
- pathname: targetURL,
197
- queryString: targetQS,
198
- pattern: redirect.pattern,
199
- locale,
200
- });
188
+ matchedQueryString = [
189
+ (0, regex_parser_1.default)(redirect.pattern).test(`${normalizedPath}${targetQS}`),
190
+ (0, regex_parser_1.default)(redirect.pattern).test(`/${locale}${normalizedPath}${targetQS}`),
191
+ ].some(Boolean)
192
+ ? targetQS
193
+ : undefined;
201
194
  // Save the matched query string (if found) into the redirect object
202
195
  redirect.matchedQueryString = matchedQueryString || '';
203
- // Return the redirect if the URL path or any query string permutation matches the pattern
204
- return (((0, regex_parser_1.default)(redirect.pattern).test(targetURL) ||
196
+ return (!!((0, regex_parser_1.default)(redirect.pattern).test(targetURL) ||
205
197
  (0, regex_parser_1.default)(redirect.pattern).test(`/${req.nextUrl.locale}${targetURL}`) ||
206
- matchedQueryString) &&
207
- (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true));
198
+ matchedQueryString) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true));
208
199
  })
209
200
  : undefined;
210
201
  });
@@ -269,24 +260,5 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
269
260
  }
270
261
  return redirect;
271
262
  }
272
- /**
273
- * Checks if the current URL query matches the provided pattern, considering all permutations of query parameters.
274
- * It constructs all possible query parameter permutations and tests them against the pattern.
275
- * @param {object} params - The parameters for the URL match.
276
- * @param {string} params.pathname - The current URL pathname.
277
- * @param {string} params.queryString - The current URL query string.
278
- * @param {string} params.pattern - The regex pattern to test the constructed URLs against.
279
- * @param {string} [params.locale] - The locale prefix to include in the URL if present.
280
- * @returns {string | undefined} - return query string if any of the query permutations match the provided pattern, undefined otherwise.
281
- */
282
- isPermutedQueryMatch({ pathname, queryString, pattern, locale, }) {
283
- const paramsArray = Array.from(new URLSearchParams(queryString).entries());
284
- const listOfPermuted = (0, utils_1.getPermutations)(paramsArray).map((permutation) => '?' + permutation.map(([key, value]) => `${key}=${value}`).join('&'));
285
- const normalizedPath = pathname.replace(/\/*$/gi, '');
286
- return listOfPermuted.find((query) => [
287
- (0, regex_parser_1.default)(pattern).test(`${normalizedPath}${query}`),
288
- (0, regex_parser_1.default)(pattern).test(`/${locale}${normalizedPath}${query}`),
289
- ].some(Boolean));
290
- }
291
263
  }
292
264
  exports.RedirectsMiddleware = RedirectsMiddleware;
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { QUERY_PARAM_EDITING_SECRET } from '@sitecore-jss/sitecore-jss/editing';
11
- import { AxiosDataFetcher, debug } from '@sitecore-jss/sitecore-jss';
11
+ import { NativeDataFetcher, debug } from '@sitecore-jss/sitecore-jss';
12
12
  import { editingDataDiskCache } from './editing-data-cache';
13
13
  import { getJssEditingSecret } from '../utils/utils';
14
14
  /**
@@ -85,7 +85,7 @@ export class ServerlessEditingDataService {
85
85
  if (!this.apiRoute.includes('[key]')) {
86
86
  throw new Error(`The specified apiRoute '${this.apiRoute}' is missing '[key]'.`);
87
87
  }
88
- this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new AxiosDataFetcher({ debugger: debug.editing });
88
+ this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new NativeDataFetcher({ debugger: debug.editing });
89
89
  }
90
90
  /**
91
91
  * Stores Sitecore editor payload data for later retrieval by key
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { STATIC_PROPS_ID, SERVER_PROPS_ID } from 'next/constants';
11
- import { AxiosDataFetcher, debug } from '@sitecore-jss/sitecore-jss';
11
+ import { NativeDataFetcher, debug } from '@sitecore-jss/sitecore-jss';
12
12
  import { EditMode } from '@sitecore-jss/sitecore-jss/layout';
13
13
  import { QUERY_PARAM_EDITING_SECRET, EDITING_ALLOWED_ORIGINS, } from '@sitecore-jss/sitecore-jss/editing';
14
14
  import { editingDataService } from './editing-data-service';
@@ -49,7 +49,7 @@ export class ChromesHandler extends RenderMiddlewareBase {
49
49
  return `${process.env.VERCEL ? 'https' : 'http'}://${req.headers.host}`;
50
50
  };
51
51
  this.editingDataService = (_a = config === null || config === void 0 ? void 0 : config.editingDataService) !== null && _a !== void 0 ? _a : editingDataService;
52
- this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new AxiosDataFetcher({ debugger: debug.editing });
52
+ this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new NativeDataFetcher({ debugger: debug.editing });
53
53
  this.resolvePageUrl = (_c = config === null || config === void 0 ? void 0 : config.resolvePageUrl) !== null && _c !== void 0 ? _c : this.defaultResolvePageUrl;
54
54
  this.resolveServerUrl = (_d = config === null || config === void 0 ? void 0 : config.resolveServerUrl) !== null && _d !== void 0 ? _d : this.defaultResolveServerUrl;
55
55
  }
@@ -76,7 +76,7 @@ export class ChromesHandler extends RenderMiddlewareBase {
76
76
  const cookies = res.getHeader('Set-Cookie');
77
77
  headers.cookie = `${headers.cookie ? headers.cookie + ';' : ''}${cookies.join(';')}`;
78
78
  // Make actual render request for page route, passing on preview cookies as well as any approved query string parameters.
79
- // Note timestamp effectively disables caching the request in Axios (no amount of cache headers seemed to do it)
79
+ // Note timestamp effectively disables caching the request (no amount of cache headers seemed to do it)
80
80
  debug.editing('fetching page route for %s', editingData.path);
81
81
  const requestUrl = new URL(this.resolvePageUrl({ serverUrl, itemPath: editingData.path }));
82
82
  for (const key in params) {
@@ -122,8 +122,7 @@ export class ChromesHandler extends RenderMiddlewareBase {
122
122
  catch (err) {
123
123
  const error = err;
124
124
  console.error(error);
125
- if (error.response || error.request) {
126
- // Axios error, which could mean the server or page URL isn't quite right, so provide a more helpful hint
125
+ if (error.response) {
127
126
  console.info(
128
127
  // eslint-disable-next-line quotes
129
128
  "Hint: for non-standard server or Next.js route configurations, you may need to override the 'resolveServerUrl' or 'resolvePageUrl' available on the 'EditingRenderMiddleware' config.");
@@ -25,13 +25,15 @@ export class RenderMiddlewareBase {
25
25
  * @returns Object of approved headers
26
26
  */
27
27
  this.getHeadersForPropagation = (headers) => {
28
- const result = {};
29
- EDITING_PASS_THROUGH_HEADERS.forEach((header) => {
30
- if (headers[header]) {
31
- result[header] = headers[header];
28
+ // Filter and normalize headers
29
+ const filteredHeaders = EDITING_PASS_THROUGH_HEADERS.reduce((acc, header) => {
30
+ const value = headers[header];
31
+ if (value) {
32
+ acc[header] = Array.isArray(value) ? value.join(', ') : value;
32
33
  }
33
- });
34
- return result;
34
+ return acc;
35
+ }, {});
36
+ return filteredHeaders;
35
37
  };
36
38
  }
37
39
  }
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { constants, AxiosDataFetcher, NativeDataFetcher, enableDebug, debug, MemoryCacheClient, } from '@sitecore-jss/sitecore-jss';
1
+ export { constants, NativeDataFetcher, enableDebug, debug, MemoryCacheClient, } from '@sitecore-jss/sitecore-jss';
2
2
  export { LayoutServicePageState, GraphQLLayoutService, RestLayoutService, getChildPlaceholder, getFieldValue, getContentStylesheetLink, EditMode, } from '@sitecore-jss/sitecore-jss/layout';
3
3
  export { mediaApi } from '@sitecore-jss/sitecore-jss/media';
4
4
  export { trackingApi, } from '@sitecore-jss/sitecore-jss/tracking';
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { debug } from '@sitecore-jss/sitecore-jss';
11
11
  import { GraphQLRedirectsService, REDIRECT_TYPE_301, REDIRECT_TYPE_302, REDIRECT_TYPE_SERVER_TRANSFER, } from '@sitecore-jss/sitecore-jss/site';
12
- import { getPermutations } from '@sitecore-jss/sitecore-jss/utils';
12
+ import { areURLSearchParamsEqual, escapeNonSpecialQuestionMarks, isRegexOrUrl, mergeURLSearchParams, } from '@sitecore-jss/sitecore-jss/utils';
13
13
  import { NextResponse } from 'next/server';
14
14
  import regexParser from 'regex-parser';
15
15
  import { MiddlewareBase } from './middleware';
@@ -39,20 +39,28 @@ export class RedirectsMiddleware extends MiddlewareBase {
39
39
  });
40
40
  const createResponse = () => __awaiter(this, void 0, void 0, function* () {
41
41
  var _a;
42
+ const response = res || NextResponse.next();
42
43
  if (this.config.disabled && this.config.disabled(req, res || NextResponse.next())) {
43
44
  debug.redirects('skipped (redirects middleware is disabled)');
44
- return res || NextResponse.next();
45
+ return response;
45
46
  }
46
47
  if (this.isPreview(req) || this.excludeRoute(pathname)) {
47
48
  debug.redirects('skipped (%s)', this.isPreview(req) ? 'preview' : 'route excluded');
48
- return res || NextResponse.next();
49
+ return response;
50
+ }
51
+ // Skip prefetch requests from Next.js, which are not original client requests
52
+ // as they load unnecessary requests that burden the redirects middleware with meaningless traffic
53
+ if (this.isPrefetch(req)) {
54
+ debug.redirects('skipped (prefetch)');
55
+ response.headers.set('x-middleware-cache', 'no-cache');
56
+ return response;
49
57
  }
50
58
  site = this.getSite(req, res);
51
59
  // Find the redirect from result of RedirectService
52
60
  const existsRedirect = yield this.getExistsRedirect(req, site.name);
53
61
  if (!existsRedirect) {
54
62
  debug.redirects('skipped (redirect does not exist)');
55
- return res || NextResponse.next();
63
+ return response;
56
64
  }
57
65
  // Find context site language and replace token
58
66
  if (REGEXP_CONTEXT_SITE_LANG.test(existsRedirect.target) &&
@@ -66,27 +74,26 @@ export class RedirectsMiddleware extends MiddlewareBase {
66
74
  url.href = existsRedirect.target;
67
75
  }
68
76
  else {
69
- const source = `${url.pathname.replace(/\/*$/gi, '')}${existsRedirect.matchedQueryString}`;
70
- const urlFirstPart = existsRedirect.target.split('/')[1];
77
+ const isUrl = isRegexOrUrl(existsRedirect.pattern) === 'url';
78
+ const targetParts = existsRedirect.target.split('/');
79
+ const urlFirstPart = targetParts[1];
71
80
  if (this.locales.includes(urlFirstPart)) {
72
81
  req.nextUrl.locale = urlFirstPart;
73
82
  existsRedirect.target = existsRedirect.target.replace(`/${urlFirstPart}`, '');
74
83
  }
75
- const target = source
76
- .replace(regexParser(existsRedirect.pattern), existsRedirect.target)
77
- .replace(/^\/\//, '/')
78
- .split('?');
79
- if (url.search && existsRedirect.isQueryStringPreserved) {
80
- const targetQueryString = (_a = target[1]) !== null && _a !== void 0 ? _a : '';
81
- url.search = '?' + new URLSearchParams(`${url.search}&${targetQueryString}`).toString();
82
- }
83
- else if (target[1]) {
84
- url.search = '?' + target[1];
85
- }
86
- else {
87
- url.search = '';
88
- }
89
- const prepareNewURL = new URL(`${target[0]}${url.search}`, url.origin);
84
+ const targetSegments = isUrl
85
+ ? existsRedirect.target.split('?')
86
+ : url.pathname.replace(/\/*$/gi, '') + existsRedirect.matchedQueryString;
87
+ const [targetPath, targetQueryString] = isUrl
88
+ ? targetSegments
89
+ : targetSegments
90
+ .replace(regexParser(existsRedirect.pattern), existsRedirect.target)
91
+ .replace(/^\/\//, '/')
92
+ .split('?');
93
+ const mergedQueryString = existsRedirect.isQueryStringPreserved
94
+ ? mergeURLSearchParams(new URLSearchParams((_a = url.search) !== null && _a !== void 0 ? _a : ''), new URLSearchParams(targetQueryString || ''))
95
+ : targetQueryString || '';
96
+ const prepareNewURL = new URL(`${targetPath}${mergedQueryString ? '?' + mergedQueryString : ''}`, url.origin);
90
97
  url.href = prepareNewURL.href;
91
98
  url.pathname = prepareNewURL.pathname;
92
99
  url.search = prepareNewURL.search;
@@ -95,16 +102,16 @@ export class RedirectsMiddleware extends MiddlewareBase {
95
102
  /** return Response redirect with http code of redirect type */
96
103
  switch (existsRedirect.redirectType) {
97
104
  case REDIRECT_TYPE_301: {
98
- return this.createRedirectResponse(url, res, 301, 'Moved Permanently');
105
+ return this.createRedirectResponse(url, response, 301, 'Moved Permanently');
99
106
  }
100
107
  case REDIRECT_TYPE_302: {
101
- return this.createRedirectResponse(url, res, 302, 'Found');
108
+ return this.createRedirectResponse(url, response, 302, 'Found');
102
109
  }
103
110
  case REDIRECT_TYPE_SERVER_TRANSFER: {
104
- return this.rewrite(url.href, req, res || NextResponse.next());
111
+ return this.rewrite(url.href, req, response);
105
112
  }
106
113
  default:
107
- return res || NextResponse.next();
114
+ return response;
108
115
  }
109
116
  });
110
117
  const response = yield createResponse();
@@ -146,59 +153,43 @@ export class RedirectsMiddleware extends MiddlewareBase {
146
153
  */
147
154
  getExistsRedirect(req, siteName) {
148
155
  return __awaiter(this, void 0, void 0, function* () {
149
- const redirects = yield this.redirectsService.fetchRedirects(siteName);
150
156
  const { pathname: targetURL, search: targetQS = '', locale } = this.normalizeUrl(req.nextUrl.clone());
157
+ const normalizedPath = targetURL.replace(/\/*$/gi, '');
158
+ const redirects = yield this.redirectsService.fetchRedirects(siteName);
151
159
  const language = this.getLanguage(req);
152
160
  const modifyRedirects = structuredClone(redirects);
161
+ let matchedQueryString;
153
162
  return modifyRedirects.length
154
163
  ? modifyRedirects.find((redirect) => {
164
+ var _a;
165
+ if (isRegexOrUrl(redirect.pattern) === 'url') {
166
+ const parseUrlPattern = redirect.pattern.endsWith('/')
167
+ ? redirect.pattern.slice(0, -1).split('?')
168
+ : redirect.pattern.split('?');
169
+ return ((parseUrlPattern[0] === normalizedPath ||
170
+ parseUrlPattern[0] === `/${locale}${normalizedPath}`) &&
171
+ areURLSearchParamsEqual(new URLSearchParams((_a = parseUrlPattern[1]) !== null && _a !== void 0 ? _a : ''), new URLSearchParams(targetQS)));
172
+ }
155
173
  // Modify the redirect pattern to ignore the language prefix in the path
156
- redirect.pattern = redirect.pattern.replace(RegExp(`^[^]?/${language}/`, 'gi'), '');
174
+ // And escapes non-special "?" characters in a string or regex.
175
+ redirect.pattern = escapeNonSpecialQuestionMarks(redirect.pattern.replace(new RegExp(`^[^]?/${language}/`, 'gi'), ''));
157
176
  // Prepare the redirect pattern as a regular expression, making it more flexible for matching URLs
158
177
  redirect.pattern = `/^\/${redirect.pattern
159
178
  .replace(/^\/|\/$/g, '') // Removes leading and trailing slashes
160
179
  .replace(/^\^\/|\/\$$/g, '') // Removes unnecessary start (^) and end ($) anchors
161
180
  .replace(/^\^|\$$/g, '') // Further cleans up anchors
162
- .replace(/(?<!\\)\?/g, '\\?') // Escapes question marks in the pattern
163
181
  .replace(/\$\/gi$/g, '')}[\/]?$/i`; // Ensures the pattern allows an optional trailing slash
164
- /**
165
- * This line checks whether the current URL query string (and all its possible permutations)
166
- * matches the redirect pattern.
167
- *
168
- * Query parameters in URLs can come in different orders, but logically they represent the
169
- * same information (e.g., "key1=value1&key2=value2" is the same as "key2=value2&key1=value1").
170
- * To account for this, the method `isPermutedQueryMatch` generates all possible permutations
171
- * of the query parameters and checks if any of those permutations match the regex pattern for the redirect.
172
- *
173
- * NOTE: This fix is specifically implemented for Netlify, where query parameters are sometimes
174
- * automatically sorted, which can cause issues with matching redirects if the order of query
175
- * parameters is important. By checking every possible permutation, we ensure that redirects
176
- * work correctly on Netlify despite this behavior.
177
- *
178
- * It passes several pieces of information to the function:
179
- * 1. `pathname`: The normalized URL path without query parameters (e.g., '/about').
180
- * 2. `queryString`: The current query string from the URL, which will be permuted and matched (e.g., '?key1=value1&key2=value2').
181
- * 3. `pattern`: The regex pattern for the redirect that we are trying to match against the URL (e.g., '/about?key1=value1').
182
- * 4. `locale`: The locale part of the URL (if any), which helps support multilingual URLs.
183
- *
184
- * If one of the permutations of the query string matches the redirect pattern, the function
185
- * returns the matched query string, which is stored in `matchedQueryString`. If no match is found,
186
- * it returns `undefined`. The `matchedQueryString` is later used to indicate whether the query
187
- * string contributed to a successful redirect match.
188
- */
189
- const matchedQueryString = this.isPermutedQueryMatch({
190
- pathname: targetURL,
191
- queryString: targetQS,
192
- pattern: redirect.pattern,
193
- locale,
194
- });
182
+ matchedQueryString = [
183
+ regexParser(redirect.pattern).test(`${normalizedPath}${targetQS}`),
184
+ regexParser(redirect.pattern).test(`/${locale}${normalizedPath}${targetQS}`),
185
+ ].some(Boolean)
186
+ ? targetQS
187
+ : undefined;
195
188
  // Save the matched query string (if found) into the redirect object
196
189
  redirect.matchedQueryString = matchedQueryString || '';
197
- // Return the redirect if the URL path or any query string permutation matches the pattern
198
- return ((regexParser(redirect.pattern).test(targetURL) ||
190
+ return (!!(regexParser(redirect.pattern).test(targetURL) ||
199
191
  regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${targetURL}`) ||
200
- matchedQueryString) &&
201
- (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true));
192
+ matchedQueryString) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true));
202
193
  })
203
194
  : undefined;
204
195
  });
@@ -263,23 +254,4 @@ export class RedirectsMiddleware extends MiddlewareBase {
263
254
  }
264
255
  return redirect;
265
256
  }
266
- /**
267
- * Checks if the current URL query matches the provided pattern, considering all permutations of query parameters.
268
- * It constructs all possible query parameter permutations and tests them against the pattern.
269
- * @param {object} params - The parameters for the URL match.
270
- * @param {string} params.pathname - The current URL pathname.
271
- * @param {string} params.queryString - The current URL query string.
272
- * @param {string} params.pattern - The regex pattern to test the constructed URLs against.
273
- * @param {string} [params.locale] - The locale prefix to include in the URL if present.
274
- * @returns {string | undefined} - return query string if any of the query permutations match the provided pattern, undefined otherwise.
275
- */
276
- isPermutedQueryMatch({ pathname, queryString, pattern, locale, }) {
277
- const paramsArray = Array.from(new URLSearchParams(queryString).entries());
278
- const listOfPermuted = getPermutations(paramsArray).map((permutation) => '?' + permutation.map(([key, value]) => `${key}=${value}`).join('&'));
279
- const normalizedPath = pathname.replace(/\/*$/gi, '');
280
- return listOfPermuted.find((query) => [
281
- regexParser(pattern).test(`${normalizedPath}${query}`),
282
- regexParser(pattern).test(`/${locale}${normalizedPath}${query}`),
283
- ].some(Boolean));
284
- }
285
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-jss/sitecore-jss-nextjs",
3
- "version": "22.4.0-canary.2",
3
+ "version": "22.4.0-canary.21",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "sideEffects": false,
@@ -73,9 +73,9 @@
73
73
  "react-dom": "^18.2.0"
74
74
  },
75
75
  "dependencies": {
76
- "@sitecore-jss/sitecore-jss": "^22.4.0-canary.2",
77
- "@sitecore-jss/sitecore-jss-dev-tools": "^22.4.0-canary.2",
78
- "@sitecore-jss/sitecore-jss-react": "^22.4.0-canary.2",
76
+ "@sitecore-jss/sitecore-jss": "^22.4.0-canary.21",
77
+ "@sitecore-jss/sitecore-jss-dev-tools": "^22.4.0-canary.21",
78
+ "@sitecore-jss/sitecore-jss-react": "^22.4.0-canary.21",
79
79
  "@vercel/kv": "^0.2.1",
80
80
  "prop-types": "^15.8.1",
81
81
  "regex-parser": "^2.2.11",
@@ -83,7 +83,7 @@
83
83
  },
84
84
  "description": "",
85
85
  "types": "types/index.d.ts",
86
- "gitHead": "df94d89c4aaf6dabcb64b46f622888e8912494b5",
86
+ "gitHead": "f0cf339eadfbdd0f56e58b1c4c83c0b1ccd7d8a6",
87
87
  "files": [
88
88
  "dist",
89
89
  "types",
@@ -1,4 +1,4 @@
1
- import { AxiosDataFetcher } from '@sitecore-jss/sitecore-jss';
1
+ import { NativeDataFetcher } from '@sitecore-jss/sitecore-jss';
2
2
  import { EditingData } from './editing-data';
3
3
  import { EditingDataCache } from './editing-data-cache';
4
4
  import { PreviewData } from 'next';
@@ -87,11 +87,11 @@ export interface ServerlessEditingDataServiceConfig {
87
87
  */
88
88
  apiRoute?: string;
89
89
  /**
90
- * The `AxiosDataFetcher` instance to use for API requests.
91
- * @default new AxiosDataFetcher()
92
- * @see AxiosDataFetcher
90
+ * The `NativeDataFetcher` instance to use for API requests.
91
+ * @default new NativeDataFetcher()
92
+ * @see NativeDataFetcher
93
93
  */
94
- dataFetcher?: AxiosDataFetcher;
94
+ dataFetcher?: NativeDataFetcher;
95
95
  }
96
96
  /**
97
97
  * Service responsible for maintaining Sitecore editor data between requests
@@ -1,5 +1,5 @@
1
1
  import { NextApiRequest, NextApiResponse } from 'next';
2
- import { AxiosDataFetcher } from '@sitecore-jss/sitecore-jss';
2
+ import { NativeDataFetcher } from '@sitecore-jss/sitecore-jss';
3
3
  import { EditMode, LayoutServicePageState } from '@sitecore-jss/sitecore-jss/layout';
4
4
  import { RenderMetadataQueryParams, LayoutKind } from '@sitecore-jss/sitecore-jss/editing';
5
5
  import { EditingDataService } from './editing-data-service';
@@ -11,11 +11,11 @@ export type EditingRenderMiddlewareConfig = {
11
11
  /**
12
12
  * -- Edit Mode Chromes --
13
13
  *
14
- * The `AxiosDataFetcher` instance to use for API requests.
15
- * @default new AxiosDataFetcher()
16
- * @see AxiosDataFetcher
14
+ * The `NativeDataFetcher` instance to use for API requests.
15
+ * @default new NativeDataFetcher()
16
+ * @see NativeDataFetcher
17
17
  */
18
- dataFetcher?: AxiosDataFetcher;
18
+ dataFetcher?: NativeDataFetcher;
19
19
  /**
20
20
  * -- Edit Mode Chromes --
21
21
  *
@@ -20,6 +20,6 @@ export declare abstract class RenderMiddlewareBase {
20
20
  * @returns Object of approved headers
21
21
  */
22
22
  protected getHeadersForPropagation: (headers: IncomingHttpHeaders) => {
23
- [key: string]: string | string[];
23
+ [key: string]: string;
24
24
  };
25
25
  }
package/types/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { constants, HttpDataFetcher, HttpResponse, AxiosResponse, AxiosDataFetcher, AxiosDataFetcherConfig, NativeDataFetcher, NativeDataFetcherConfig, HTMLLink, enableDebug, debug, CacheClient, CacheOptions, MemoryCacheClient, } from '@sitecore-jss/sitecore-jss';
1
+ export { constants, HttpDataFetcher, NativeDataFetcher, NativeDataFetcherConfig, NativeDataFetcherResponse, NativeDataFetcherError, HTMLLink, enableDebug, debug, CacheClient, CacheOptions, MemoryCacheClient, } from '@sitecore-jss/sitecore-jss';
2
2
  export { LayoutService, LayoutServiceData, LayoutServicePageState, LayoutServiceContext, LayoutServiceContextData, GraphQLLayoutService, GraphQLLayoutServiceConfig, RestLayoutService, RestLayoutServiceConfig, PlaceholderData, PlaceholdersData, RouteData, Field, Item, HtmlElementRendering, getChildPlaceholder, getFieldValue, ComponentRendering, ComponentFields, ComponentParams, getContentStylesheetLink, EditMode, } from '@sitecore-jss/sitecore-jss/layout';
3
3
  export { mediaApi } from '@sitecore-jss/sitecore-jss/media';
4
4
  export { trackingApi, TrackingRequestOptions, CampaignInstance, GoalInstance, OutcomeInstance, EventInstance, PageViewInstance, } from '@sitecore-jss/sitecore-jss/tracking';
@@ -54,15 +54,4 @@ export declare class RedirectsMiddleware extends MiddlewareBase {
54
54
  * @returns {NextResponse<unknown>} The redirect response.
55
55
  */
56
56
  private createRedirectResponse;
57
- /**
58
- * Checks if the current URL query matches the provided pattern, considering all permutations of query parameters.
59
- * It constructs all possible query parameter permutations and tests them against the pattern.
60
- * @param {object} params - The parameters for the URL match.
61
- * @param {string} params.pathname - The current URL pathname.
62
- * @param {string} params.queryString - The current URL query string.
63
- * @param {string} params.pattern - The regex pattern to test the constructed URLs against.
64
- * @param {string} [params.locale] - The locale prefix to include in the URL if present.
65
- * @returns {string | undefined} - return query string if any of the query permutations match the provided pattern, undefined otherwise.
66
- */
67
- private isPermutedQueryMatch;
68
57
  }