@sitecore-jss/sitecore-jss-proxy 22.3.0 → 22.3.1-canary.2
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/README.md +1 -1
- package/dist/cjs/index.js +19 -447
- package/dist/cjs/middleware/editing/config.js +27 -0
- package/dist/cjs/middleware/editing/index.js +89 -0
- package/dist/cjs/middleware/editing/render.js +103 -0
- package/dist/cjs/middleware/headless-ssr-proxy/index.js +450 -0
- package/dist/cjs/middleware/healthcheck/index.js +16 -0
- package/dist/cjs/middleware/index.js +32 -0
- package/dist/cjs/personalize/PersonalizeHelper.js +243 -0
- package/dist/cjs/personalize/index.js +5 -0
- package/dist/cjs/personalize/test-data/personalizeData.js +86 -0
- package/dist/cjs/types/personalize.js +2 -0
- package/dist/esm/index.js +4 -441
- package/dist/esm/middleware/editing/config.js +23 -0
- package/dist/esm/middleware/editing/index.js +84 -0
- package/dist/esm/middleware/editing/render.js +98 -0
- package/dist/esm/middleware/headless-ssr-proxy/index.js +441 -0
- package/dist/esm/middleware/healthcheck/index.js +12 -0
- package/dist/esm/middleware/index.js +4 -0
- package/dist/esm/personalize/PersonalizeHelper.js +236 -0
- package/dist/esm/personalize/index.js +1 -0
- package/dist/esm/personalize/test-data/personalizeData.js +82 -0
- package/dist/esm/types/personalize.js +1 -0
- package/package.json +22 -9
- package/types/index.d.ts +4 -21
- package/types/middleware/editing/config.d.ts +27 -0
- package/types/middleware/editing/index.d.ts +32 -0
- package/types/middleware/editing/render.d.ts +42 -0
- package/types/{ProxyConfig.d.ts → middleware/headless-ssr-proxy/ProxyConfig.d.ts} +2 -5
- package/types/middleware/headless-ssr-proxy/index.d.ts +20 -0
- package/types/middleware/healthcheck/index.d.ts +6 -0
- package/types/middleware/index.d.ts +3 -0
- package/types/personalize/PersonalizeHelper.d.ts +43 -0
- package/types/personalize/index.d.ts +2 -0
- package/types/personalize/test-data/personalizeData.d.ts +59 -0
- package/types/types/AppRenderer.d.ts +35 -0
- package/types/types/index.d.ts +3 -0
- package/types/types/personalize.d.ts +85 -0
- package/types/AppRenderer.d.ts +0 -10
- package/types/RenderResponse.d.ts +0 -15
- /package/dist/cjs/{ProxyConfig.js → middleware/headless-ssr-proxy/ProxyConfig.js} +0 -0
- /package/dist/cjs/{AppRenderer.js → types/AppRenderer.js} +0 -0
- /package/dist/cjs/{RouteUrlParser.js → types/RouteUrlParser.js} +0 -0
- /package/dist/cjs/{RenderResponse.js → types/index.js} +0 -0
- /package/dist/esm/{ProxyConfig.js → middleware/headless-ssr-proxy/ProxyConfig.js} +0 -0
- /package/dist/esm/{AppRenderer.js → types/AppRenderer.js} +0 -0
- /package/dist/esm/{RouteUrlParser.js → types/RouteUrlParser.js} +0 -0
- /package/dist/esm/{RenderResponse.js → types/index.js} +0 -0
- /package/types/{RouteUrlParser.d.ts → types/RouteUrlParser.d.ts} +0 -0
|
@@ -0,0 +1,236 @@
|
|
|
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 { CloudSDK } from '@sitecore-cloudsdk/core/server';
|
|
11
|
+
import { personalize } from '@sitecore-cloudsdk/personalize/server';
|
|
12
|
+
import { debug } from '@sitecore-jss/sitecore-jss';
|
|
13
|
+
import { CdpHelper, DEFAULT_VARIANT, GraphQLPersonalizeService, getGroomedVariantIds, personalizeLayout, } from '@sitecore-jss/sitecore-jss/personalize';
|
|
14
|
+
import querystring from 'querystring';
|
|
15
|
+
export class PersonalizeHelper {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
/**
|
|
19
|
+
* Performs personalize on layout data before a page is rendered
|
|
20
|
+
* @param {IncomingMessage} req Incoming request nodejs object
|
|
21
|
+
* @param {OutgoingMessage} res Outgoing response nodejs object
|
|
22
|
+
* @param {LayoutServiceData} layoutData layoutData for the page
|
|
23
|
+
* @returns layout data with personalization applied
|
|
24
|
+
*/
|
|
25
|
+
this.personalizeLayoutData = (req, res, layoutData) => __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
if (!((_a = layoutData.sitecore) === null || _a === void 0 ? void 0 : _a.context)) {
|
|
28
|
+
debug.personalize('skipped (sitecore context is empty)');
|
|
29
|
+
return layoutData;
|
|
30
|
+
}
|
|
31
|
+
if (!((_b = layoutData.sitecore) === null || _b === void 0 ? void 0 : _b.route)) {
|
|
32
|
+
debug.personalize('skipped (layout is empty)');
|
|
33
|
+
return layoutData;
|
|
34
|
+
}
|
|
35
|
+
// current method can run for page requests and for layout service requests.
|
|
36
|
+
// the latter will not have the correct path - so we use path from layoutData instead
|
|
37
|
+
const pathname = layoutData.sitecore.context.itemPath;
|
|
38
|
+
const language = this.getLanguage(layoutData);
|
|
39
|
+
const hostname = this.getHostHeader(req) || this.defaultHostname;
|
|
40
|
+
const startTimestamp = Date.now();
|
|
41
|
+
if (!pathname) {
|
|
42
|
+
debug.personalize('skipped (pathname missing from layoutData)');
|
|
43
|
+
return layoutData;
|
|
44
|
+
}
|
|
45
|
+
debug.personalize('personalize layout start: %o', {
|
|
46
|
+
pathname,
|
|
47
|
+
language,
|
|
48
|
+
hostname,
|
|
49
|
+
headers: this.extractDebugHeaders(req.headers),
|
|
50
|
+
});
|
|
51
|
+
if (this.excludeRoute(pathname)) {
|
|
52
|
+
debug.personalize('skipped (route excluded)');
|
|
53
|
+
return layoutData;
|
|
54
|
+
}
|
|
55
|
+
if (this.config.disabled && this.config.disabled(req, res)) {
|
|
56
|
+
debug.personalize('skipped (personalize is disabled)');
|
|
57
|
+
return layoutData;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
yield this.initPersonalizeServer(req, res, hostname);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
debug.personalize('skipped (CloudSDK initialization failed), error %o', e);
|
|
64
|
+
return layoutData;
|
|
65
|
+
}
|
|
66
|
+
const variantIds = yield this.getVariantIds(req, language, pathname);
|
|
67
|
+
if (!variantIds) {
|
|
68
|
+
return layoutData;
|
|
69
|
+
}
|
|
70
|
+
const personalizeData = getGroomedVariantIds(variantIds);
|
|
71
|
+
// layout will be personalized here
|
|
72
|
+
personalizeLayout(layoutData, personalizeData.variantId, personalizeData.componentVariantIds);
|
|
73
|
+
debug.personalize('personalize layout end in %dms: %o', Date.now() - startTimestamp, {
|
|
74
|
+
headers: this.extractDebugHeaders(req.headers),
|
|
75
|
+
variantIds: variantIds,
|
|
76
|
+
});
|
|
77
|
+
return layoutData;
|
|
78
|
+
});
|
|
79
|
+
this.getVariantIds = (req, language, pathname) => __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
const timeout = this.config.cdpConfig.timeout;
|
|
81
|
+
// Get personalization info from Experience Edge
|
|
82
|
+
const personalizeInfo = yield this.personalizeService.getPersonalizeInfo(pathname, language, this.config.sitecoreSiteName);
|
|
83
|
+
if (!personalizeInfo) {
|
|
84
|
+
// Likely an invalid route / language
|
|
85
|
+
debug.personalize('skipped (personalize info not found)');
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
if (personalizeInfo.variantIds.length === 0) {
|
|
89
|
+
debug.personalize('skipped (no personalization configured)');
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const params = this.getExperienceParams(req);
|
|
93
|
+
const executions = this.getPersonalizeExecutions(personalizeInfo, language);
|
|
94
|
+
const identifiedVariantIds = [];
|
|
95
|
+
try {
|
|
96
|
+
yield Promise.all(executions.map((execution) => this.personalize({
|
|
97
|
+
friendlyId: execution.friendlyId,
|
|
98
|
+
variantIds: execution.variantIds,
|
|
99
|
+
params,
|
|
100
|
+
language,
|
|
101
|
+
timeout,
|
|
102
|
+
}, req).then((personalization) => {
|
|
103
|
+
const variantId = personalization.variantId;
|
|
104
|
+
if (variantId) {
|
|
105
|
+
if (!execution.variantIds.includes(variantId)) {
|
|
106
|
+
debug.personalize('invalid variant %s', variantId);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
identifiedVariantIds.push(variantId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
})));
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
debug.personalize('skipped, error %o', e);
|
|
116
|
+
}
|
|
117
|
+
if (identifiedVariantIds.length === 0) {
|
|
118
|
+
debug.personalize('skipped (no variant(s) identified)');
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
return identifiedVariantIds;
|
|
122
|
+
});
|
|
123
|
+
this.personalizeService = new GraphQLPersonalizeService(Object.assign({}, config.edgeConfig));
|
|
124
|
+
this.defaultHostname = config.defaultHostname || 'localhost';
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Init CloudSDK personalization on server side
|
|
128
|
+
* @param {IncomingMessage} request incoming nodejs request object
|
|
129
|
+
* @param {OutgoingMessage} response outgoing nodejs response object
|
|
130
|
+
* @param {string} hostname host for cookies. Usually a host header, or a fallback config
|
|
131
|
+
*/
|
|
132
|
+
initPersonalizeServer(request, response, hostname) {
|
|
133
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
134
|
+
yield CloudSDK(request, response, {
|
|
135
|
+
sitecoreEdgeUrl: this.config.cdpConfig.sitecoreEdgeUrl,
|
|
136
|
+
sitecoreEdgeContextId: this.config.cdpConfig.sitecoreEdgeContextId,
|
|
137
|
+
siteName: this.config.sitecoreSiteName,
|
|
138
|
+
cookieDomain: hostname,
|
|
139
|
+
enableServerCookie: true,
|
|
140
|
+
})
|
|
141
|
+
.addPersonalize({ enablePersonalizeCookie: true })
|
|
142
|
+
.initialize();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
getLanguage(layoutData) {
|
|
146
|
+
var _a, _b;
|
|
147
|
+
return ((_b = (_a = layoutData.sitecore) === null || _a === void 0 ? void 0 : _a.context) === null || _b === void 0 ? void 0 : _b.language) || this.config.defaultLanguage || 'en';
|
|
148
|
+
}
|
|
149
|
+
getHostHeader(req) {
|
|
150
|
+
var _a;
|
|
151
|
+
return ((_a = req.headers.host) === null || _a === void 0 ? void 0 : _a.split(':')[0]) || '';
|
|
152
|
+
}
|
|
153
|
+
excludeRoute(pathname) {
|
|
154
|
+
var _a, _b;
|
|
155
|
+
return ((_a = this.config) === null || _a === void 0 ? void 0 : _a.excludeRoute) && ((_b = this.config) === null || _b === void 0 ? void 0 : _b.excludeRoute(pathname));
|
|
156
|
+
}
|
|
157
|
+
extractDebugHeaders(incomingHeaders) {
|
|
158
|
+
const headers = {};
|
|
159
|
+
Object.keys(incomingHeaders).forEach((key) => incomingHeaders[key] && (headers[key] = incomingHeaders[key]));
|
|
160
|
+
return headers;
|
|
161
|
+
}
|
|
162
|
+
personalize(_a, request_1) {
|
|
163
|
+
return __awaiter(this, arguments, void 0, function* ({ params, friendlyId, language, timeout, variantIds, }, request) {
|
|
164
|
+
var _b;
|
|
165
|
+
debug.personalize('executing experience for %s %o', friendlyId, params);
|
|
166
|
+
return (yield personalize(request, {
|
|
167
|
+
channel: this.config.cdpConfig.channel || 'WEB',
|
|
168
|
+
currency: (_b = this.config.cdpConfig.currency) !== null && _b !== void 0 ? _b : 'USD',
|
|
169
|
+
friendlyId,
|
|
170
|
+
params,
|
|
171
|
+
language,
|
|
172
|
+
pageVariantIds: variantIds,
|
|
173
|
+
}, { timeout }));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
getExperienceParams(req) {
|
|
177
|
+
var _a;
|
|
178
|
+
// nodejs req.url does not have a hostname, we parse query string the old fashioned way
|
|
179
|
+
const rawQs = ((_a = req.url) === null || _a === void 0 ? void 0 : _a.split('?')[1]) || '';
|
|
180
|
+
const queryString = querystring.parse(rawQs);
|
|
181
|
+
// also need to account for types (string | string[]) returned by parse()
|
|
182
|
+
const utm = {
|
|
183
|
+
campaign: [queryString.utm_campaign].join('') || undefined,
|
|
184
|
+
content: [queryString.utm_content].join('') || undefined,
|
|
185
|
+
medium: [queryString.utm_medium].join('') || undefined,
|
|
186
|
+
source: [queryString.utm_source].join('') || undefined,
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
// It's expected that the header name "referer" is actually a misspelling of the word "referrer"
|
|
190
|
+
// req.referrer is used during fetching to determine the value of the Referer header of the request being made,
|
|
191
|
+
// used as a fallback
|
|
192
|
+
referrer: req.headers.referer || [''].concat(req.headers.referrer || '').join(''),
|
|
193
|
+
utm: utm,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
getPersonalizeExecutions(personalizeInfo, language) {
|
|
197
|
+
if (personalizeInfo.variantIds.length === 0) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
const results = [];
|
|
201
|
+
return personalizeInfo.variantIds.reduce((results, variantId) => {
|
|
202
|
+
if (variantId.includes('_')) {
|
|
203
|
+
// Component-level personalization in format "<ComponentID>_<VariantID>"
|
|
204
|
+
const componentId = variantId.split('_')[0];
|
|
205
|
+
const friendlyId = CdpHelper.getComponentFriendlyId(personalizeInfo.pageId, componentId, language, this.config.scope);
|
|
206
|
+
const execution = results.find((x) => x.friendlyId === friendlyId);
|
|
207
|
+
if (execution) {
|
|
208
|
+
execution.variantIds.push(variantId);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// The default/control variant (format "<ComponentID>_default") is also a valid value returned by the execution
|
|
212
|
+
const defaultVariant = `${componentId}${DEFAULT_VARIANT}`;
|
|
213
|
+
results.push({
|
|
214
|
+
friendlyId,
|
|
215
|
+
variantIds: [defaultVariant, variantId],
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
// Embedded (page-level) personalization in format "<VariantID>"
|
|
221
|
+
const friendlyId = CdpHelper.getPageFriendlyId(personalizeInfo.pageId, language, this.config.scope);
|
|
222
|
+
const execution = results.find((x) => x.friendlyId === friendlyId);
|
|
223
|
+
if (execution) {
|
|
224
|
+
execution.variantIds.push(variantId);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
results.push({
|
|
228
|
+
friendlyId,
|
|
229
|
+
variantIds: [variantId],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return results;
|
|
234
|
+
}, results);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PersonalizeHelper } from './PersonalizeHelper';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const mountainBikeVariant = {
|
|
2
|
+
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0',
|
|
3
|
+
componentName: 'ContentBlock',
|
|
4
|
+
dataSource: '20679cd4-356b-4452-b507-453beeb0be39',
|
|
5
|
+
fields: {
|
|
6
|
+
content: {
|
|
7
|
+
value: '<p><img src="https://edge-beta.sitecorecloud.io/ser-edge-personalization/media/JssNextWeb/Mountain-Bike.jpg?h=675&w=1200" style="width:1200px;height:675px;" /></p>',
|
|
8
|
+
},
|
|
9
|
+
heading: { value: 'Mountain Bike' },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
export const cityBikeVariant = {
|
|
13
|
+
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0',
|
|
14
|
+
componentName: 'ContentBlock',
|
|
15
|
+
dataSource: '36e02581-2056-4c55-a4d5-f4b700ba1ae2',
|
|
16
|
+
fields: {
|
|
17
|
+
content: {
|
|
18
|
+
value: '<p><img src="https://edge-beta.sitecorecloud.io/ser-edge-personalization/media/JssNextWeb/Mountain-Bike.jpg?h=675&w=1200" style="width:1200px;height:675px;" /></p>',
|
|
19
|
+
},
|
|
20
|
+
heading: { value: 'Mountain Bike' },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export const component_variant = {
|
|
24
|
+
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0',
|
|
25
|
+
componentName: 'ContentBlock',
|
|
26
|
+
dataSource: '36e02581-2056-4c55-a4d5-f4b700ba1ae2',
|
|
27
|
+
fields: {
|
|
28
|
+
content: {
|
|
29
|
+
value: '<p><img src="https://edge-beta.sitecorecloud.io/ser-edge-personalization/media/JssNextWeb/Hybrid-Bike.jpg?h=675&w=1200" style="width:1200px;height:675px;" /></p>',
|
|
30
|
+
},
|
|
31
|
+
heading: { value: 'Hybrid Bike' },
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const defaultRendering = {
|
|
35
|
+
uid: '0b6d23d8-c50e-4e79-9eca-317ec43e82b0',
|
|
36
|
+
componentName: 'ContentBlock',
|
|
37
|
+
dataSource: 'e020fb58-1be8-4537-aab8-67916452ecf2',
|
|
38
|
+
fields: { content: { value: '' }, heading: { value: 'Default Content' } },
|
|
39
|
+
experiences: {
|
|
40
|
+
'mountain-bike-audience': mountainBikeVariant,
|
|
41
|
+
'city-bike-audience': cityBikeVariant,
|
|
42
|
+
'componentid_variant-id': component_variant,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
export const getPersonalizeLayoutData = (variant, language, path) => {
|
|
46
|
+
const baseLayout = {
|
|
47
|
+
sitecore: {
|
|
48
|
+
context: {
|
|
49
|
+
pageEditing: false,
|
|
50
|
+
site: { name: 'JssNextWeb' },
|
|
51
|
+
visitorIdentificationTimestamp: 1038543,
|
|
52
|
+
language: language || 'en',
|
|
53
|
+
variantId: '',
|
|
54
|
+
itemPath: path || '/styleguide',
|
|
55
|
+
},
|
|
56
|
+
route: {
|
|
57
|
+
name: 'styleguide',
|
|
58
|
+
placeholders: {
|
|
59
|
+
main: {},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
switch (variant) {
|
|
65
|
+
case 'mountain-bike-audience':
|
|
66
|
+
baseLayout.sitecore.route.placeholders.main = [mountainBikeVariant];
|
|
67
|
+
baseLayout.sitecore.context.variantId = 'mountain-bike-audience';
|
|
68
|
+
break;
|
|
69
|
+
case 'city-bike-audience':
|
|
70
|
+
baseLayout.sitecore.route.placeholders.main = [cityBikeVariant];
|
|
71
|
+
baseLayout.sitecore.context.variantId = 'city-bike-audience';
|
|
72
|
+
break;
|
|
73
|
+
case 'componentid_variant-id':
|
|
74
|
+
baseLayout.sitecore.route.placeholders.main = [component_variant];
|
|
75
|
+
baseLayout.sitecore.context.variantId = '_default';
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
baseLayout.sitecore.route.placeholders.main = [defaultRendering];
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
return baseLayout;
|
|
82
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sitecore-jss/sitecore-jss-proxy",
|
|
3
|
-
"version": "22.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "22.3.1-canary.2",
|
|
4
|
+
"description": "Middlewares, utilities to work in a headless mode",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"build": "npm run clean && tsc -p tsconfig.json && tsc -p tsconfig-esm.json",
|
|
10
10
|
"clean": "del-cli dist types",
|
|
11
11
|
"lint": "eslint \"./src/**/*.ts\"",
|
|
12
|
-
"test": "mocha --require ts-node/register \"./src/**/*.test.ts\"",
|
|
12
|
+
"test": "mocha --require ts-node/register \"./src/**/*.test.ts\" --exit",
|
|
13
13
|
"prepublishOnly": "npm run build",
|
|
14
14
|
"coverage": "nyc npm test",
|
|
15
|
-
"generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --readme none --out ../../ref-docs/sitecore-jss-proxy src/index.ts --githubPages false"
|
|
15
|
+
"generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --outputFileStrategy Members --parametersFormat table --readme none --entryPoints src/personalize/index.ts --out ../../ref-docs/sitecore-jss-proxy src/index.ts --githubPages false"
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|
|
18
|
-
"node": ">=
|
|
18
|
+
"node": ">=22"
|
|
19
19
|
},
|
|
20
20
|
"author": {
|
|
21
21
|
"name": "Sitecore Corporation",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"url": "https://github.com/sitecore/jss/issues"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@sitecore-cloudsdk/core": "^0.4.1",
|
|
31
|
+
"@sitecore-cloudsdk/personalize": "^0.4.1",
|
|
32
|
+
"@sitecore-jss/sitecore-jss": "^22.3.1-canary.2",
|
|
30
33
|
"http-proxy-middleware": "^2.0.6",
|
|
31
34
|
"http-status-codes": "^2.2.0",
|
|
32
35
|
"set-cookie-parser": "^2.5.1"
|
|
@@ -35,18 +38,28 @@
|
|
|
35
38
|
"@types/chai": "^4.3.4",
|
|
36
39
|
"@types/express": "^4.17.17",
|
|
37
40
|
"@types/mocha": "^10.0.1",
|
|
38
|
-
"@types/node": "^
|
|
41
|
+
"@types/node": "^22.9.0",
|
|
42
|
+
"@types/proxyquire": "^1.3.31",
|
|
39
43
|
"@types/set-cookie-parser": "^2.4.2",
|
|
44
|
+
"@types/sinon": "^17.0.3",
|
|
45
|
+
"@types/supertest": "^6.0.2",
|
|
40
46
|
"chai": "^4.3.7",
|
|
41
47
|
"del-cli": "^5.0.0",
|
|
42
|
-
"eslint": "^8.
|
|
48
|
+
"eslint": "^8.56.0",
|
|
49
|
+
"express": "^4.19.2",
|
|
43
50
|
"mocha": "^10.2.0",
|
|
44
51
|
"nyc": "^15.1.0",
|
|
52
|
+
"proxyquire": "^2.1.3",
|
|
53
|
+
"sinon": "^17.0.1",
|
|
54
|
+
"supertest": "^7.0.0",
|
|
45
55
|
"ts-node": "^10.9.1",
|
|
46
|
-
"typescript": "~
|
|
56
|
+
"typescript": "~5.6.3"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"express": "^4.19.2"
|
|
47
60
|
},
|
|
48
61
|
"types": "types/index.d.ts",
|
|
49
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "5b35bbbe22b6ea21a1829950e371d54422ebf8fd",
|
|
50
63
|
"files": [
|
|
51
64
|
"dist",
|
|
52
65
|
"types"
|
package/types/index.d.ts
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { ProxyConfig, ServerBundle } from './ProxyConfig';
|
|
6
|
-
import { RouteUrlParser } from './RouteUrlParser';
|
|
7
|
-
export declare const removeEmptyAnalyticsCookie: (proxyResponse: IncomingMessage) => void;
|
|
8
|
-
/**
|
|
9
|
-
* @param {string} reqPath
|
|
10
|
-
* @param {Request} req
|
|
11
|
-
* @param {ProxyConfig} config
|
|
12
|
-
* @param {RouteUrlParser} parseRouteUrl
|
|
13
|
-
*/
|
|
14
|
-
export declare function rewriteRequestPath(reqPath: string, req: Request, config: ProxyConfig, parseRouteUrl?: RouteUrlParser): string;
|
|
15
|
-
/**
|
|
16
|
-
* @param {AppRenderer} renderer
|
|
17
|
-
* @param {ProxyConfig} config
|
|
18
|
-
* @param {RouteUrlParser} parseRouteUrl
|
|
19
|
-
*/
|
|
20
|
-
export default function scProxy(renderer: AppRenderer, config: ProxyConfig, parseRouteUrl: RouteUrlParser): RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>[];
|
|
21
|
-
export { ProxyConfig, ServerBundle };
|
|
1
|
+
export * from './middleware';
|
|
2
|
+
export * from './types';
|
|
3
|
+
export * from './personalize';
|
|
4
|
+
export { GRAPHQL_LAYOUT_QUERY_NAME } from '@sitecore-jss/sitecore-jss/layout';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import { Metadata } from '@sitecore-jss/sitecore-jss/utils';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the editing config endpoint
|
|
5
|
+
*/
|
|
6
|
+
export type EditingConfigEndpointOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Custom path for the endpoint. Default is `<routerPath>/config`
|
|
9
|
+
* @example
|
|
10
|
+
* { path: '/foo/config' } -> <routerPath>/foo/config
|
|
11
|
+
*/
|
|
12
|
+
path?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Components registered in the application
|
|
15
|
+
*/
|
|
16
|
+
components: string[] | Map<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Application metadata
|
|
19
|
+
*/
|
|
20
|
+
metadata: Metadata;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Middleware to handle editing config requests
|
|
24
|
+
* @param {EditingConfigEndpointOptions} config Configuration for the endpoint
|
|
25
|
+
* @returns {RequestHandler} Middleware function
|
|
26
|
+
*/
|
|
27
|
+
export declare const editingConfigMiddleware: (config: EditingConfigEndpointOptions) => (_req: Request, res: Response) => void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from 'express';
|
|
2
|
+
import { EditingConfigEndpointOptions } from './config';
|
|
3
|
+
import { EditingRenderEndpointOptions } from './render';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for the editing router
|
|
6
|
+
*/
|
|
7
|
+
export type EditingRouterConfig = {
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for the /config endpoint
|
|
10
|
+
*/
|
|
11
|
+
config: EditingConfigEndpointOptions;
|
|
12
|
+
/**
|
|
13
|
+
* Configuration for the /render endpoint
|
|
14
|
+
*/
|
|
15
|
+
render: EditingRenderEndpointOptions;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Middleware to handle editing requests
|
|
19
|
+
* @param {Request} req Request
|
|
20
|
+
* @param {Response} res Response
|
|
21
|
+
* @param {NextFunction} next Next function
|
|
22
|
+
*/
|
|
23
|
+
export declare const editingMiddleware: (req: Request, res: Response, next: NextFunction) => Promise<unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Creates a router for editing requests.
|
|
26
|
+
* Supports the following routes:
|
|
27
|
+
* - <routerPath>/render (GET) - renders a route
|
|
28
|
+
* - <routerPath>/config (GET) - returns the current application configuration
|
|
29
|
+
* @param {EditingRouterConfig} options Editing router configuration
|
|
30
|
+
* @returns {Router} Editing router
|
|
31
|
+
*/
|
|
32
|
+
export declare const editingRouter: (options: EditingRouterConfig) => import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { GraphQLRequestClientFactory } from '@sitecore-jss/sitecore-jss';
|
|
2
|
+
import { AppRenderer } from '../../types/AppRenderer';
|
|
3
|
+
import { Request, Response } from 'express';
|
|
4
|
+
import { RenderMetadataQueryParams } from '@sitecore-jss/sitecore-jss/editing';
|
|
5
|
+
import { PersonalizeHelper } from '../../personalize';
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the editing render endpoint
|
|
8
|
+
*/
|
|
9
|
+
export type EditingRenderEndpointOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* Custom path for the endpoint. Default is `<routerPath>/render`
|
|
12
|
+
* @example
|
|
13
|
+
* { path: '/foo/render' } -> <routerPath>/foo/render
|
|
14
|
+
*/
|
|
15
|
+
path?: string;
|
|
16
|
+
/**
|
|
17
|
+
* GraphQl Request Client Factory provided by the server bundle
|
|
18
|
+
*/
|
|
19
|
+
clientFactory: GraphQLRequestClientFactory;
|
|
20
|
+
/**
|
|
21
|
+
* The appRenderer will produce the requested route's html
|
|
22
|
+
*/
|
|
23
|
+
renderView: AppRenderer;
|
|
24
|
+
/**
|
|
25
|
+
* Personalize helper instance passed from proxy app
|
|
26
|
+
*/
|
|
27
|
+
personalizeHelper?: PersonalizeHelper;
|
|
28
|
+
};
|
|
29
|
+
type MetadataRequest = Request & {
|
|
30
|
+
query: RenderMetadataQueryParams;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Middleware to handle editing render requests
|
|
34
|
+
* @param {EditingRenderEndpointOptions} config for the endpoint
|
|
35
|
+
*/
|
|
36
|
+
export declare const editingRenderMiddleware: (config: EditingRenderEndpointOptions) => (req: MetadataRequest, res: Response) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Gets the Content-Security-Policy header value
|
|
39
|
+
* @returns {string} Content-Security-Policy header value
|
|
40
|
+
*/
|
|
41
|
+
export declare const getSCPHeader: () => string;
|
|
42
|
+
export {};
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import { IncomingMessage, ServerResponse, Agent } from 'http';
|
|
4
2
|
import { Agent as HttpsAgent } from 'https';
|
|
5
3
|
import { Options } from 'http-proxy-middleware';
|
|
6
|
-
import { AppRenderer } from '
|
|
7
|
-
import {
|
|
8
|
-
import { RouteUrlParser } from './RouteUrlParser';
|
|
4
|
+
import { AppRenderer, RenderResponse } from '../../types/AppRenderer';
|
|
5
|
+
import { RouteUrlParser } from '../../types/RouteUrlParser';
|
|
9
6
|
/** A reply from the Sitecore Layout Service */
|
|
10
7
|
export interface LayoutServiceData {
|
|
11
8
|
sitecore: {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IncomingMessage } from 'http';
|
|
2
|
+
import { Request, RequestHandler } from 'express';
|
|
3
|
+
import { AppRenderer } from '../../types/AppRenderer';
|
|
4
|
+
import { RouteUrlParser } from '../../types/RouteUrlParser';
|
|
5
|
+
import { ProxyConfig, ServerBundle } from './ProxyConfig';
|
|
6
|
+
export declare const removeEmptyAnalyticsCookie: (proxyResponse: IncomingMessage) => void;
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} reqPath
|
|
9
|
+
* @param {Request} req
|
|
10
|
+
* @param {ProxyConfig} config
|
|
11
|
+
* @param {RouteUrlParser} parseRouteUrl
|
|
12
|
+
*/
|
|
13
|
+
export declare function rewriteRequestPath(reqPath: string, req: Request, config: ProxyConfig, parseRouteUrl?: RouteUrlParser): string;
|
|
14
|
+
/**
|
|
15
|
+
* @param {AppRenderer} renderer
|
|
16
|
+
* @param {ProxyConfig} config
|
|
17
|
+
* @param {RouteUrlParser} parseRouteUrl
|
|
18
|
+
*/
|
|
19
|
+
export declare function middleware(renderer: AppRenderer, config: ProxyConfig, parseRouteUrl: RouteUrlParser): RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>[];
|
|
20
|
+
export { ProxyConfig, ServerBundle };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { LayoutServiceData } from '@sitecore-jss/sitecore-jss/layout';
|
|
2
|
+
import { PersonalizeInfo } from '@sitecore-jss/sitecore-jss/personalize';
|
|
3
|
+
import { IncomingHttpHeaders, IncomingMessage, OutgoingMessage } from 'http';
|
|
4
|
+
import { ExperienceParams, PersonalizeConfig, PersonalizeExecution } from '../types/personalize';
|
|
5
|
+
export declare class PersonalizeHelper {
|
|
6
|
+
protected config: PersonalizeConfig;
|
|
7
|
+
private personalizeService;
|
|
8
|
+
private defaultHostname;
|
|
9
|
+
constructor(config: PersonalizeConfig);
|
|
10
|
+
/**
|
|
11
|
+
* Performs personalize on layout data before a page is rendered
|
|
12
|
+
* @param {IncomingMessage} req Incoming request nodejs object
|
|
13
|
+
* @param {OutgoingMessage} res Outgoing response nodejs object
|
|
14
|
+
* @param {LayoutServiceData} layoutData layoutData for the page
|
|
15
|
+
* @returns layout data with personalization applied
|
|
16
|
+
*/
|
|
17
|
+
personalizeLayoutData: (req: IncomingMessage, res: OutgoingMessage, layoutData: LayoutServiceData) => Promise<LayoutServiceData>;
|
|
18
|
+
/**
|
|
19
|
+
* Init CloudSDK personalization on server side
|
|
20
|
+
* @param {IncomingMessage} request incoming nodejs request object
|
|
21
|
+
* @param {OutgoingMessage} response outgoing nodejs response object
|
|
22
|
+
* @param {string} hostname host for cookies. Usually a host header, or a fallback config
|
|
23
|
+
*/
|
|
24
|
+
protected initPersonalizeServer(request: IncomingMessage, response: OutgoingMessage, hostname: string): Promise<void>;
|
|
25
|
+
protected getVariantIds: (req: IncomingMessage, language: string, pathname: string) => Promise<string[]>;
|
|
26
|
+
protected getLanguage(layoutData: LayoutServiceData): string;
|
|
27
|
+
protected getHostHeader(req: IncomingMessage): string;
|
|
28
|
+
protected excludeRoute(pathname: string): boolean | undefined;
|
|
29
|
+
protected extractDebugHeaders(incomingHeaders: IncomingHttpHeaders): {
|
|
30
|
+
[key: string]: string | string[] | undefined;
|
|
31
|
+
};
|
|
32
|
+
protected personalize({ params, friendlyId, language, timeout, variantIds, }: {
|
|
33
|
+
params: ExperienceParams;
|
|
34
|
+
friendlyId: string;
|
|
35
|
+
language: string;
|
|
36
|
+
timeout?: number;
|
|
37
|
+
variantIds?: string[];
|
|
38
|
+
}, request: IncomingMessage): Promise<{
|
|
39
|
+
variantId: string;
|
|
40
|
+
}>;
|
|
41
|
+
protected getExperienceParams(req: IncomingMessage): ExperienceParams;
|
|
42
|
+
protected getPersonalizeExecutions(personalizeInfo: PersonalizeInfo, language: string): PersonalizeExecution[];
|
|
43
|
+
}
|