@sitecore-jss/sitecore-jss-nextjs 21.1.0-canary.7 → 21.1.0-canary.71
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/dist/cjs/components/EditingComponentPlaceholder.js +12 -0
- package/dist/cjs/components/Link.js +2 -2
- package/dist/cjs/editing/editing-data-cache.js +15 -10
- package/dist/cjs/editing/editing-data-middleware.js +2 -2
- package/dist/cjs/editing/editing-data-service.js +2 -2
- package/dist/cjs/editing/editing-render-middleware.js +9 -1
- package/dist/cjs/index.js +16 -6
- package/dist/cjs/middleware/index.js +5 -1
- package/dist/cjs/middleware/multisite-middleware.js +102 -0
- package/dist/cjs/middleware/personalize-middleware.js +20 -4
- package/dist/cjs/middleware/redirects-middleware.js +75 -42
- package/dist/cjs/monitoring/healthcheck-middleware.js +30 -0
- package/dist/cjs/monitoring/index.js +5 -0
- package/dist/cjs/services/graphql-sitemap-service.js +42 -19
- package/dist/esm/components/EditingComponentPlaceholder.js +5 -0
- package/dist/esm/components/Link.js +2 -2
- package/dist/esm/editing/editing-data-cache.js +15 -10
- package/dist/esm/editing/editing-data-middleware.js +2 -2
- package/dist/esm/editing/editing-data-service.js +2 -2
- package/dist/esm/editing/editing-render-middleware.js +9 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/middleware/index.js +2 -0
- package/dist/esm/middleware/multisite-middleware.js +98 -0
- package/dist/esm/middleware/personalize-middleware.js +20 -4
- package/dist/esm/middleware/redirects-middleware.js +75 -42
- package/dist/esm/monitoring/healthcheck-middleware.js +26 -0
- package/dist/esm/monitoring/index.js +1 -0
- package/dist/esm/services/graphql-sitemap-service.js +41 -18
- package/monitoring.d.ts +1 -0
- package/monitoring.js +1 -0
- package/package.json +16 -15
- package/types/components/EditingComponentPlaceholder.d.ts +4 -0
- package/types/editing/editing-data-cache.d.ts +4 -4
- package/types/index.d.ts +3 -3
- package/types/middleware/index.d.ts +2 -0
- package/types/middleware/multisite-middleware.d.ts +42 -0
- package/types/middleware/personalize-middleware.d.ts +11 -0
- package/types/middleware/redirects-middleware.d.ts +32 -3
- package/types/monitoring/healthcheck-middleware.d.ts +12 -0
- package/types/monitoring/index.d.ts +1 -0
- package/types/services/graphql-sitemap-service.d.ts +8 -3
|
@@ -9,12 +9,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.GraphQLSitemapService = exports.getSiteEmptyError = exports.languageError = void 0;
|
|
12
|
+
exports.GraphQLSitemapService = exports.getSiteEmptyError = exports.sitesError = exports.languageError = void 0;
|
|
13
13
|
const graphql_1 = require("@sitecore-jss/sitecore-jss/graphql");
|
|
14
14
|
const sitecore_jss_1 = require("@sitecore-jss/sitecore-jss");
|
|
15
15
|
const personalize_1 = require("@sitecore-jss/sitecore-jss/personalize");
|
|
16
|
+
const site_1 = require("@sitecore-jss/sitecore-jss/site");
|
|
16
17
|
/** @private */
|
|
17
18
|
exports.languageError = 'The list of languages cannot be empty';
|
|
19
|
+
exports.sitesError = 'The list of sites cannot be empty';
|
|
18
20
|
/**
|
|
19
21
|
* @param {string} siteName to inject into error text
|
|
20
22
|
* @private
|
|
@@ -53,7 +55,7 @@ query ${usesPersonalize ? 'PersonalizeSitemapQuery' : 'DefaultSitemapQuery'}(
|
|
|
53
55
|
hasNext
|
|
54
56
|
}
|
|
55
57
|
results {
|
|
56
|
-
path: routePath
|
|
58
|
+
path: routePath
|
|
57
59
|
${usesPersonalize
|
|
58
60
|
? `
|
|
59
61
|
route {
|
|
@@ -66,7 +68,7 @@ query ${usesPersonalize ? 'PersonalizeSitemapQuery' : 'DefaultSitemapQuery'}(
|
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
|
-
}
|
|
71
|
+
}
|
|
70
72
|
`;
|
|
71
73
|
/**
|
|
72
74
|
* Service that fetches the list of site pages using Sitecore's GraphQL API.
|
|
@@ -132,43 +134,64 @@ class GraphQLSitemapService {
|
|
|
132
134
|
*/
|
|
133
135
|
fetchSitemap(languages, formatStaticPath) {
|
|
134
136
|
return __awaiter(this, void 0, void 0, function* () {
|
|
137
|
+
const paths = new Array();
|
|
135
138
|
if (!languages.length) {
|
|
136
139
|
throw new RangeError(exports.languageError);
|
|
137
140
|
}
|
|
138
|
-
//
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
// Get all sites
|
|
142
|
+
const sites = this.options.sites;
|
|
143
|
+
if (!sites || !sites.length) {
|
|
144
|
+
throw new RangeError(exports.sitesError);
|
|
145
|
+
}
|
|
146
|
+
// Fetch paths for each site
|
|
147
|
+
for (let i = 0; i < sites.length; i++) {
|
|
148
|
+
const siteName = sites[i];
|
|
149
|
+
const multiSiteName = sites.length > 1 ? siteName : undefined;
|
|
150
|
+
// Fetch paths using all locales
|
|
151
|
+
yield Promise.all(languages.map((language) => __awaiter(this, void 0, void 0, function* () {
|
|
152
|
+
if (language === '') {
|
|
153
|
+
throw new RangeError(languageEmptyError);
|
|
154
|
+
}
|
|
155
|
+
sitecore_jss_1.debug.sitemap('fetching sitemap data for %s %s', language, siteName);
|
|
156
|
+
const results = yield this.fetchLanguageSitePaths(language, siteName);
|
|
157
|
+
const transformedPaths = yield this.transformLanguageSitePaths(results, formatStaticPath, language, multiSiteName);
|
|
158
|
+
paths.push(...transformedPaths);
|
|
159
|
+
})));
|
|
160
|
+
}
|
|
147
161
|
return [].concat(...paths);
|
|
148
162
|
});
|
|
149
163
|
}
|
|
150
|
-
transformLanguageSitePaths(sitePaths, formatStaticPath, language) {
|
|
164
|
+
transformLanguageSitePaths(sitePaths, formatStaticPath, language, multiSiteName) {
|
|
151
165
|
return __awaiter(this, void 0, void 0, function* () {
|
|
152
166
|
const formatPath = (path) => formatStaticPath(path.replace(/^\/|\/$/g, '').split('/'), language);
|
|
153
167
|
const aggregatedPaths = [];
|
|
154
168
|
sitePaths.forEach((item) => {
|
|
155
|
-
var _a, _b, _c, _d;
|
|
169
|
+
var _a, _b, _c, _d, _e, _f;
|
|
156
170
|
if (!item)
|
|
157
171
|
return;
|
|
158
|
-
|
|
172
|
+
if (!multiSiteName) {
|
|
173
|
+
aggregatedPaths.push(formatPath(item.path));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
aggregatedPaths.push(formatPath(site_1.getSiteRewrite(item.path, { siteName: multiSiteName })));
|
|
177
|
+
}
|
|
159
178
|
// check for type safety's sake - personalize may be empty depending on query type
|
|
160
179
|
if ((_b = (_a = item.route) === null || _a === void 0 ? void 0 : _a.personalization) === null || _b === void 0 ? void 0 : _b.variantIds.length) {
|
|
161
|
-
|
|
180
|
+
multiSiteName
|
|
181
|
+
? aggregatedPaths.push(...(_d = (_c = item.route) === null || _c === void 0 ? void 0 : _c.personalization) === null || _d === void 0 ? void 0 : _d.variantIds.map((varId) => formatPath(personalize_1.getPersonalizedRewrite(site_1.getSiteRewrite(item.path, { siteName: multiSiteName }), {
|
|
182
|
+
variantId: varId,
|
|
183
|
+
}))))
|
|
184
|
+
: aggregatedPaths.push(...(_f = (_e = item.route) === null || _e === void 0 ? void 0 : _e.personalization) === null || _f === void 0 ? void 0 : _f.variantIds.map((varId) => formatPath(personalize_1.getPersonalizedRewrite(item.path, { variantId: varId }))));
|
|
162
185
|
}
|
|
163
186
|
});
|
|
164
187
|
return aggregatedPaths;
|
|
165
188
|
});
|
|
166
189
|
}
|
|
167
|
-
fetchLanguageSitePaths(language) {
|
|
190
|
+
fetchLanguageSitePaths(language, siteName) {
|
|
168
191
|
var _a, _b, _c, _d;
|
|
169
192
|
return __awaiter(this, void 0, void 0, function* () {
|
|
170
193
|
const args = {
|
|
171
|
-
siteName:
|
|
194
|
+
siteName: siteName,
|
|
172
195
|
language: language,
|
|
173
196
|
pageSize: this.options.pageSize,
|
|
174
197
|
includedPaths: this.options.includedPaths,
|
|
@@ -180,7 +203,7 @@ class GraphQLSitemapService {
|
|
|
180
203
|
while (hasNext) {
|
|
181
204
|
const fetchResponse = yield this.graphQLClient.request(this.query, Object.assign(Object.assign({}, args), { after }));
|
|
182
205
|
if (!((_a = fetchResponse === null || fetchResponse === void 0 ? void 0 : fetchResponse.site) === null || _a === void 0 ? void 0 : _a.siteInfo)) {
|
|
183
|
-
throw new RangeError(getSiteEmptyError(
|
|
206
|
+
throw new RangeError(getSiteEmptyError(siteName));
|
|
184
207
|
}
|
|
185
208
|
else {
|
|
186
209
|
results = results.concat((_b = fetchResponse.site.siteInfo.routes) === null || _b === void 0 ? void 0 : _b.results);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { EDITING_COMPONENT_ID, EDITING_COMPONENT_PLACEHOLDER, } from '@sitecore-jss/sitecore-jss/layout';
|
|
3
|
+
import { Placeholder } from './Placeholder';
|
|
4
|
+
export const EditingComponentPlaceholder = ({ rendering, }) => (React.createElement("div", { id: EDITING_COMPONENT_ID },
|
|
5
|
+
React.createElement(Placeholder, { name: EDITING_COMPONENT_PLACEHOLDER, rendering: rendering })));
|
|
@@ -18,13 +18,13 @@ export const Link = forwardRef((props, ref) => {
|
|
|
18
18
|
const value = (field.href
|
|
19
19
|
? field
|
|
20
20
|
: field.value);
|
|
21
|
-
const { href, querystring } = value;
|
|
21
|
+
const { href, querystring, anchor } = value;
|
|
22
22
|
const isEditing = editable && field.editable;
|
|
23
23
|
if (href && !isEditing) {
|
|
24
24
|
const text = showLinkTextWithChildrenPresent || !children ? value.text || value.href : null;
|
|
25
25
|
// determine if a link is a route or not.
|
|
26
26
|
if (internalLinkMatcher.test(href)) {
|
|
27
|
-
return (React.createElement(NextLink, { href: { pathname: href, query: querystring }, key: "link", locale: false },
|
|
27
|
+
return (React.createElement(NextLink, { href: { pathname: href, query: querystring, hash: anchor }, key: "link", locale: false },
|
|
28
28
|
React.createElement("a", Object.assign({ title: value.title, target: value.target, className: value.class }, htmlLinkProps, { ref: ref }),
|
|
29
29
|
text,
|
|
30
30
|
children)));
|
|
@@ -16,19 +16,24 @@ export class EditingDataDiskCache {
|
|
|
16
16
|
}
|
|
17
17
|
set(key, editingData) {
|
|
18
18
|
const filePath = this.cache.set(key, JSON.stringify(editingData));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
if (!filePath || filePath.length === 0) {
|
|
21
|
+
reject(new Error(`Editing data cache not set for key ${key} at ${this.cache.root}`));
|
|
22
|
+
}
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
22
25
|
}
|
|
23
26
|
get(key) {
|
|
24
27
|
const entry = this.cache.get(key);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
if (!entry.isCached) {
|
|
30
|
+
console.warn(`Editing data cache miss for key ${key} at ${this.cache.root}`);
|
|
31
|
+
resolve(undefined);
|
|
32
|
+
}
|
|
33
|
+
// Remove to preserve disk-space (as a macrotask so as not to block current execution)
|
|
34
|
+
setTimeout(() => this.cache.remove(key));
|
|
35
|
+
resolve(JSON.parse(entry.value));
|
|
36
|
+
});
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
/** EditingDataDiskCache singleton */
|
|
@@ -33,7 +33,7 @@ export class EditingDataMiddleware {
|
|
|
33
33
|
switch (method) {
|
|
34
34
|
case 'GET': {
|
|
35
35
|
// Get cache value
|
|
36
|
-
const data = this.editingDataCache.get(key);
|
|
36
|
+
const data = yield this.editingDataCache.get(key);
|
|
37
37
|
res.status(200).json(data);
|
|
38
38
|
break;
|
|
39
39
|
}
|
|
@@ -43,7 +43,7 @@ export class EditingDataMiddleware {
|
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
45
45
|
// Set cache value
|
|
46
|
-
this.editingDataCache.set(key, body);
|
|
46
|
+
yield this.editingDataCache.set(key, body);
|
|
47
47
|
res.status(200).end();
|
|
48
48
|
}
|
|
49
49
|
break;
|
|
@@ -52,7 +52,7 @@ export class BasicEditingDataService {
|
|
|
52
52
|
key,
|
|
53
53
|
};
|
|
54
54
|
debug.editing('storing editing data for %o: %o', previewData, data);
|
|
55
|
-
this.editingDataCache.set(key, data);
|
|
55
|
+
yield this.editingDataCache.set(key, data);
|
|
56
56
|
return { key };
|
|
57
57
|
});
|
|
58
58
|
}
|
|
@@ -65,7 +65,7 @@ export class BasicEditingDataService {
|
|
|
65
65
|
return __awaiter(this, void 0, void 0, function* () {
|
|
66
66
|
const editingPreviewData = previewData;
|
|
67
67
|
debug.editing('retrieving editing data for %o', previewData);
|
|
68
|
-
return this.editingDataCache.get(editingPreviewData.key);
|
|
68
|
+
return yield this.editingDataCache.get(editingPreviewData.key);
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -9,6 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { STATIC_PROPS_ID, SERVER_PROPS_ID } from 'next/constants';
|
|
11
11
|
import { AxiosDataFetcher, debug } from '@sitecore-jss/sitecore-jss';
|
|
12
|
+
import { EDITING_COMPONENT_ID, RenderingType } from '@sitecore-jss/sitecore-jss/layout';
|
|
13
|
+
import { parse } from 'node-html-parser';
|
|
12
14
|
import { editingDataService, QUERY_PARAM_EDITING_SECRET, } from './editing-data-service';
|
|
13
15
|
import { getJssEditingSecret } from '../utils';
|
|
14
16
|
/**
|
|
@@ -22,7 +24,7 @@ export class EditingRenderMiddleware {
|
|
|
22
24
|
constructor(config) {
|
|
23
25
|
var _a, _b, _c, _d;
|
|
24
26
|
this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
var _e;
|
|
27
|
+
var _e, _f;
|
|
26
28
|
const { method, query, body, headers } = req;
|
|
27
29
|
debug.editing('editing render middleware start: %o', {
|
|
28
30
|
method,
|
|
@@ -91,6 +93,12 @@ export class EditingRenderMiddleware {
|
|
|
91
93
|
// certain route configurations (e.g. multiple catch-all routes).
|
|
92
94
|
// The following line will trick it into thinking we're SSR, thus avoiding any router.replace.
|
|
93
95
|
html = html.replace(STATIC_PROPS_ID, SERVER_PROPS_ID);
|
|
96
|
+
if (editingData.layoutData.sitecore.context.renderingType === RenderingType.Component) {
|
|
97
|
+
// Handle component rendering. Extract component markup only
|
|
98
|
+
html = (_f = parse(html).getElementById(EDITING_COMPONENT_ID)) === null || _f === void 0 ? void 0 : _f.innerHTML;
|
|
99
|
+
if (!html)
|
|
100
|
+
throw new Error(`Failed to render component for ${requestUrl}`);
|
|
101
|
+
}
|
|
94
102
|
const body = { html };
|
|
95
103
|
// Return expected JSON result
|
|
96
104
|
debug.editing('editing render middleware end: %o', { status: 200, body });
|
package/dist/esm/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
export { constants, AxiosDataFetcher, NativeDataFetcher, enableDebug, } from '@sitecore-jss/sitecore-jss';
|
|
2
2
|
export { isEditorActive, resetEditorChromes, resolveUrl } from '@sitecore-jss/sitecore-jss/utils';
|
|
3
|
-
export { LayoutServicePageState, GraphQLLayoutService, RestLayoutService, getChildPlaceholder, getFieldValue, } from '@sitecore-jss/sitecore-jss/layout';
|
|
3
|
+
export { LayoutServicePageState, GraphQLLayoutService, RestLayoutService, getChildPlaceholder, getFieldValue, RenderingType, EDITING_COMPONENT_PLACEHOLDER, EDITING_COMPONENT_ID, } from '@sitecore-jss/sitecore-jss/layout';
|
|
4
4
|
export { mediaApi } from '@sitecore-jss/sitecore-jss/media';
|
|
5
5
|
export { trackingApi, } from '@sitecore-jss/sitecore-jss/tracking';
|
|
6
6
|
export { GraphQLDictionaryService, RestDictionaryService, } from '@sitecore-jss/sitecore-jss/i18n';
|
|
7
7
|
export { personalizeLayout, getPersonalizedRewrite, getPersonalizedRewriteData, normalizePersonalizedRewrite, CdpHelper, } from '@sitecore-jss/sitecore-jss/personalize';
|
|
8
|
-
export { GraphQLRobotsService, } from '@sitecore-jss/sitecore-jss/site';
|
|
9
8
|
export { GraphQLRequestClient } from '@sitecore-jss/sitecore-jss';
|
|
10
9
|
export { ComponentPropsService } from './services/component-props-service';
|
|
11
10
|
export { DisconnectedSitemapService } from './services/disconnected-sitemap-service';
|
|
12
11
|
export { GraphQLSitemapService, } from './services/graphql-sitemap-service';
|
|
13
|
-
export { GraphQLSitemapXmlService, GraphQLErrorPagesService, } from '@sitecore-jss/sitecore-jss/site';
|
|
12
|
+
export { GraphQLSitemapXmlService, GraphQLErrorPagesService, GraphQLRobotsService, SiteResolver, GraphQLSiteInfoService, getSiteRewrite, getSiteRewriteData, normalizeSiteRewrite, } from '@sitecore-jss/sitecore-jss/site';
|
|
14
13
|
export { ComponentPropsReactContext, ComponentPropsContext, useComponentProps, } from './components/ComponentPropsContext';
|
|
15
14
|
export { handleEditorFastRefresh, getPublicUrl } from './utils';
|
|
16
15
|
export { Link } from './components/Link';
|
|
17
16
|
export { RichText } from './components/RichText';
|
|
18
17
|
export { Placeholder } from './components/Placeholder';
|
|
18
|
+
export { EditingComponentPlaceholder } from './components/EditingComponentPlaceholder';
|
|
19
19
|
export { NextImage } from './components/NextImage';
|
|
20
20
|
export { Image, Text, DateField, File, VisitorIdentification, SitecoreContext, SitecoreContextReactContext, withSitecoreContext, useSitecoreContext, withEditorChromes, withPlaceholder, withDatasourceCheck, } from '@sitecore-jss/sitecore-jss-react';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { NextResponse } from 'next/server';
|
|
11
|
+
import { getSiteRewrite } from '@sitecore-jss/sitecore-jss/site';
|
|
12
|
+
import { debug } from '@sitecore-jss/sitecore-jss';
|
|
13
|
+
/**
|
|
14
|
+
* Middleware / handler for multisite support
|
|
15
|
+
*/
|
|
16
|
+
export class MultisiteMiddleware {
|
|
17
|
+
/**
|
|
18
|
+
* @param {MultisiteMiddlewareConfig} [config] Multisite middleware config
|
|
19
|
+
*/
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
var _a;
|
|
24
|
+
const pathname = req.nextUrl.pathname;
|
|
25
|
+
const hostHeader = (_a = req.headers.get('host')) === null || _a === void 0 ? void 0 : _a.split(':')[0];
|
|
26
|
+
const hostname = hostHeader || this.defaultHostname;
|
|
27
|
+
debug.multisite('multisite middleware start: %o', {
|
|
28
|
+
pathname,
|
|
29
|
+
hostname,
|
|
30
|
+
});
|
|
31
|
+
if (!hostHeader) {
|
|
32
|
+
debug.multisite(`host header is missing, default ${hostname} is used`);
|
|
33
|
+
}
|
|
34
|
+
// Response will be provided if other middleware is run before us
|
|
35
|
+
let response = res || NextResponse.next();
|
|
36
|
+
if (this.excludeRoute(pathname) ||
|
|
37
|
+
(this.config.excludeRoute && this.config.excludeRoute(pathname))) {
|
|
38
|
+
debug.multisite('skipped (route excluded)');
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
// Site name can be forced by query string parameter or cookie
|
|
42
|
+
const siteName = req.nextUrl.searchParams.get('sc_site') ||
|
|
43
|
+
req.cookies.get('sc_site') ||
|
|
44
|
+
this.config.getSite(hostname).name;
|
|
45
|
+
// Rewrite to site specific path
|
|
46
|
+
const rewritePath = getSiteRewrite(pathname, {
|
|
47
|
+
siteName,
|
|
48
|
+
});
|
|
49
|
+
// Note an absolute URL is required: https://nextjs.org/docs/messages/middleware-relative-urls
|
|
50
|
+
const rewriteUrl = req.nextUrl.clone();
|
|
51
|
+
rewriteUrl.pathname = rewritePath;
|
|
52
|
+
response = NextResponse.rewrite(rewriteUrl);
|
|
53
|
+
// Share site name with the following executed middlewares
|
|
54
|
+
response.cookies.set('sc_site', siteName);
|
|
55
|
+
// Share rewrite path with following executed middlewares
|
|
56
|
+
response.headers.set('x-sc-rewrite', rewritePath);
|
|
57
|
+
debug.multisite('multisite middleware end: %o', {
|
|
58
|
+
rewritePath,
|
|
59
|
+
siteName,
|
|
60
|
+
headers: this.extractDebugHeaders(response.headers),
|
|
61
|
+
cookies: response.cookies,
|
|
62
|
+
});
|
|
63
|
+
return response;
|
|
64
|
+
});
|
|
65
|
+
this.defaultHostname = config.defaultHostname || 'localhost';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Gets the Next.js middleware handler with error handling
|
|
69
|
+
* @returns middleware handler
|
|
70
|
+
*/
|
|
71
|
+
getHandler() {
|
|
72
|
+
return (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
try {
|
|
74
|
+
return yield this.handler(req, res);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.log('Multisite middleware failed:');
|
|
78
|
+
console.log(error);
|
|
79
|
+
return res || NextResponse.next();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
excludeRoute(pathname) {
|
|
84
|
+
if (pathname.includes('.') || // Ignore files
|
|
85
|
+
pathname.startsWith('/api/') || // Ignore Next.js API calls
|
|
86
|
+
pathname.startsWith('/sitecore/') || // Ignore Sitecore API calls
|
|
87
|
+
pathname.startsWith('/_next') // Ignore next service calls
|
|
88
|
+
) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
extractDebugHeaders(incomingHeaders) {
|
|
94
|
+
const headers = {};
|
|
95
|
+
incomingHeaders.forEach((value, key) => (headers[key] = value));
|
|
96
|
+
return headers;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -20,13 +20,20 @@ export class PersonalizeMiddleware {
|
|
|
20
20
|
constructor(config) {
|
|
21
21
|
this.config = config;
|
|
22
22
|
this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
var _a;
|
|
24
|
+
const hostHeader = (_a = req.headers.get('host')) === null || _a === void 0 ? void 0 : _a.split(':')[0];
|
|
25
|
+
const hostname = hostHeader || this.defaultHostname;
|
|
23
26
|
const pathname = req.nextUrl.pathname;
|
|
24
27
|
const language = req.nextUrl.locale || req.nextUrl.defaultLocale || 'en';
|
|
28
|
+
const siteName = (res === null || res === void 0 ? void 0 : res.cookies.get('sc_site')) || this.config.getSite(hostname).name;
|
|
25
29
|
let browserId = this.getBrowserId(req);
|
|
26
30
|
debug.personalize('personalize middleware start: %o', {
|
|
27
31
|
pathname,
|
|
28
32
|
language,
|
|
29
33
|
});
|
|
34
|
+
if (!hostHeader) {
|
|
35
|
+
debug.personalize(`host header is missing, default ${hostname} is used`);
|
|
36
|
+
}
|
|
30
37
|
// Response will be provided if other middleware is run before us (e.g. redirects)
|
|
31
38
|
let response = res || NextResponse.next();
|
|
32
39
|
if (this.config.disabled && this.config.disabled(req, response)) {
|
|
@@ -35,12 +42,13 @@ export class PersonalizeMiddleware {
|
|
|
35
42
|
}
|
|
36
43
|
if (response.redirected || // Don't attempt to personalize a redirect
|
|
37
44
|
this.isPreview(req) || // No need to personalize for preview (layout data is already prepared for preview)
|
|
38
|
-
|
|
45
|
+
this.excludeRoute(pathname) ||
|
|
46
|
+
(this.config.excludeRoute && this.config.excludeRoute(pathname))) {
|
|
39
47
|
debug.personalize('skipped (%s)', response.redirected ? 'redirected' : this.isPreview(req) ? 'preview' : 'route excluded');
|
|
40
48
|
return response;
|
|
41
49
|
}
|
|
42
50
|
// Get personalization info from Experience Edge
|
|
43
|
-
const personalizeInfo = yield this.personalizeService.getPersonalizeInfo(pathname, language);
|
|
51
|
+
const personalizeInfo = yield this.personalizeService.getPersonalizeInfo(pathname, language, siteName);
|
|
44
52
|
if (!personalizeInfo) {
|
|
45
53
|
// Likely an invalid route / language
|
|
46
54
|
debug.personalize('skipped (personalize info not found)');
|
|
@@ -70,8 +78,10 @@ export class PersonalizeMiddleware {
|
|
|
70
78
|
debug.personalize('skipped (invalid variant)');
|
|
71
79
|
return response;
|
|
72
80
|
}
|
|
81
|
+
// Path can be rewritten by previously executed middleware
|
|
82
|
+
const basePath = (res === null || res === void 0 ? void 0 : res.headers.get('x-sc-rewrite')) || pathname;
|
|
73
83
|
// Rewrite to persononalized path
|
|
74
|
-
const rewritePath = getPersonalizedRewrite(
|
|
84
|
+
const rewritePath = getPersonalizedRewrite(basePath, { variantId });
|
|
75
85
|
// Note an absolute URL is required: https://nextjs.org/docs/messages/middleware-relative-urls
|
|
76
86
|
const rewriteUrl = req.nextUrl.clone();
|
|
77
87
|
rewriteUrl.pathname = rewritePath;
|
|
@@ -79,8 +89,12 @@ export class PersonalizeMiddleware {
|
|
|
79
89
|
// Disable preflight caching to force revalidation on client-side navigation (personalization may be influenced)
|
|
80
90
|
// See https://github.com/vercel/next.js/issues/32727
|
|
81
91
|
response.headers.set('x-middleware-cache', 'no-cache');
|
|
92
|
+
// Share rewrite path with following executed middlewares
|
|
93
|
+
response.headers.set('x-sc-rewrite', rewritePath);
|
|
82
94
|
// Set browserId cookie on the response
|
|
83
95
|
this.setBrowserId(response, browserId);
|
|
96
|
+
// Share site name with the following executed middlewares
|
|
97
|
+
response.cookies.set('sc_site', siteName);
|
|
84
98
|
debug.personalize('personalize middleware end: %o', {
|
|
85
99
|
rewritePath,
|
|
86
100
|
browserId,
|
|
@@ -92,13 +106,15 @@ export class PersonalizeMiddleware {
|
|
|
92
106
|
// (underlying default 'cross-fetch' is not currently compatible: https://github.com/lquixada/cross-fetch/issues/78)
|
|
93
107
|
this.personalizeService = new GraphQLPersonalizeService(Object.assign(Object.assign({}, config.edgeConfig), { fetch: fetch }));
|
|
94
108
|
// NOTE: same here, we provide NativeDataFetcher for compatibility on Next.js Edge Runtime
|
|
95
|
-
this.cdpService = new CdpService(Object.assign(Object.assign({}, config.cdpConfig), { dataFetcherResolver: ({ timeout }) => {
|
|
109
|
+
this.cdpService = new CdpService(Object.assign(Object.assign({}, config.cdpConfig), { dataFetcherResolver: ({ timeout, headers, }) => {
|
|
96
110
|
const fetcher = new NativeDataFetcher({
|
|
97
111
|
debugger: debug.personalize,
|
|
98
112
|
timeout,
|
|
113
|
+
headers,
|
|
99
114
|
});
|
|
100
115
|
return (url, data) => fetcher.fetch(url, data);
|
|
101
116
|
} }));
|
|
117
|
+
this.defaultHostname = config.defaultHostname || 'localhost';
|
|
102
118
|
}
|
|
103
119
|
/**
|
|
104
120
|
* Gets the Next.js middleware handler with error handling
|
|
@@ -19,46 +19,61 @@ export class RedirectsMiddleware {
|
|
|
19
19
|
* @param {RedirectsMiddlewareConfig} [config] redirects middleware config
|
|
20
20
|
*/
|
|
21
21
|
constructor(config) {
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
const hostname = this.getHostname(req);
|
|
25
|
+
const siteName = (res === null || res === void 0 ? void 0 : res.cookies.get('sc_site')) || this.config.getSite(hostname).name;
|
|
26
|
+
const createResponse = () => __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
if ((this.config.disabled && this.config.disabled(req, NextResponse.next())) ||
|
|
28
|
+
this.excludeRoute(req.nextUrl.pathname) ||
|
|
29
|
+
(this.config.excludeRoute && this.config.excludeRoute(req.nextUrl.pathname))) {
|
|
30
|
+
return res || NextResponse.next();
|
|
31
|
+
}
|
|
32
|
+
// Find the redirect from result of RedirectService
|
|
33
|
+
const existsRedirect = yield this.getExistsRedirect(req, siteName);
|
|
34
|
+
if (!existsRedirect) {
|
|
35
|
+
return res || NextResponse.next();
|
|
36
|
+
}
|
|
37
|
+
const url = req.nextUrl.clone();
|
|
38
|
+
const absoluteUrlRegex = new RegExp('^(?:[a-z]+:)?//', 'i');
|
|
39
|
+
if (absoluteUrlRegex.test(existsRedirect.target)) {
|
|
40
|
+
url.href = existsRedirect.target;
|
|
41
|
+
url.locale = req.nextUrl.locale;
|
|
40
42
|
}
|
|
41
43
|
else {
|
|
42
|
-
url.
|
|
44
|
+
url.search = existsRedirect.isQueryStringPreserved ? url.search : '';
|
|
45
|
+
const urlFirstPart = existsRedirect.target.split('/')[1];
|
|
46
|
+
if (this.locales.includes(urlFirstPart)) {
|
|
47
|
+
url.locale = urlFirstPart;
|
|
48
|
+
url.pathname = existsRedirect.target.replace(`/${urlFirstPart}`, '');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
url.pathname = existsRedirect.target;
|
|
52
|
+
}
|
|
43
53
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
54
|
+
const redirectUrl = decodeURIComponent(url.href);
|
|
55
|
+
/** return Response redirect with http code of redirect type **/
|
|
56
|
+
switch (existsRedirect.redirectType) {
|
|
57
|
+
case REDIRECT_TYPE_301:
|
|
58
|
+
return NextResponse.redirect(redirectUrl, 301);
|
|
59
|
+
case REDIRECT_TYPE_302:
|
|
60
|
+
return NextResponse.redirect(redirectUrl, 302);
|
|
61
|
+
case REDIRECT_TYPE_SERVER_TRANSFER:
|
|
62
|
+
return NextResponse.rewrite(redirectUrl);
|
|
63
|
+
default:
|
|
64
|
+
return NextResponse.next();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const response = yield createResponse();
|
|
68
|
+
// Share site name with the following executed middlewares
|
|
69
|
+
response.cookies.set('sc_site', siteName);
|
|
70
|
+
return response;
|
|
57
71
|
});
|
|
58
72
|
// NOTE: we provide native fetch for compatibility on Next.js Edge Runtime
|
|
59
73
|
// (underlying default 'cross-fetch' is not currently compatible: https://github.com/lquixada/cross-fetch/issues/78)
|
|
60
74
|
this.redirectsService = new GraphQLRedirectsService(Object.assign(Object.assign({}, config), { fetch: fetch }));
|
|
61
75
|
this.locales = config.locales;
|
|
76
|
+
this.defaultHostname = config.defaultHostname || 'localhost';
|
|
62
77
|
}
|
|
63
78
|
/**
|
|
64
79
|
* Gets the Next.js API route handler
|
|
@@ -67,22 +82,40 @@ export class RedirectsMiddleware {
|
|
|
67
82
|
getHandler() {
|
|
68
83
|
return this.handler;
|
|
69
84
|
}
|
|
85
|
+
excludeRoute(pathname) {
|
|
86
|
+
if (pathname.includes('.') || // Ignore files
|
|
87
|
+
pathname.startsWith('/api/') || // Ignore Next.js API calls
|
|
88
|
+
pathname.startsWith('/sitecore/') || // Ignore Sitecore API calls
|
|
89
|
+
pathname.startsWith('/_next') // Ignore next service calls
|
|
90
|
+
) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
getHostname(req) {
|
|
96
|
+
var _a;
|
|
97
|
+
const hostHeader = (_a = req.headers.get('host')) === null || _a === void 0 ? void 0 : _a.split(':')[0];
|
|
98
|
+
return hostHeader || this.defaultHostname;
|
|
99
|
+
}
|
|
70
100
|
/**
|
|
71
101
|
* Method returns RedirectInfo when matches
|
|
72
|
-
* @param {NextRequest} req
|
|
102
|
+
* @param {NextRequest} req request
|
|
103
|
+
* @param {string} siteName site name
|
|
73
104
|
* @returns Promise<RedirectInfo | undefined>
|
|
74
105
|
* @private
|
|
75
106
|
*/
|
|
76
|
-
getExistsRedirect(req) {
|
|
107
|
+
getExistsRedirect(req, siteName) {
|
|
77
108
|
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
-
const redirects = yield this.redirectsService.fetchRedirects();
|
|
79
|
-
return redirects.
|
|
80
|
-
|
|
81
|
-
regexParser(redirect.pattern.toLowerCase()).test(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
const redirects = yield this.redirectsService.fetchRedirects(siteName);
|
|
110
|
+
return redirects.length
|
|
111
|
+
? redirects.find((redirect) => {
|
|
112
|
+
return ((regexParser(redirect.pattern.toLowerCase()).test(req.nextUrl.pathname.toLowerCase()) ||
|
|
113
|
+
regexParser(redirect.pattern.toLowerCase()).test(`/${req.nextUrl.locale}${req.nextUrl.pathname}`.toLowerCase())) &&
|
|
114
|
+
(redirect.locale
|
|
115
|
+
? redirect.locale.toLowerCase() === req.nextUrl.locale.toLowerCase()
|
|
116
|
+
: true));
|
|
117
|
+
})
|
|
118
|
+
: undefined;
|
|
86
119
|
});
|
|
87
120
|
}
|
|
88
121
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Middleware / handler for use in healthcheck Next.js API route (e.g. '/api/healthz').
|
|
12
|
+
*/
|
|
13
|
+
export class HealthcheckMiddleware {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.handler = (_req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
res.status(200).send('Healthy');
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Gets the Next.js API route handler
|
|
21
|
+
* @returns route handler
|
|
22
|
+
*/
|
|
23
|
+
getHandler() {
|
|
24
|
+
return this.handler;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HealthcheckMiddleware } from './healthcheck-middleware';
|