@sitecore-jss/sitecore-jss 0.1.0-beta.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/LICENSE.txt +202 -0
- package/README.md +7 -0
- package/dist/cjs/cache-client.js +54 -0
- package/dist/cjs/constants.js +12 -0
- package/dist/cjs/debug.js +43 -0
- package/dist/cjs/graphql/app-root-query.js +73 -0
- package/dist/cjs/graphql/graphql-edge-proxy.js +12 -0
- package/dist/cjs/graphql/index.js +11 -0
- package/dist/cjs/graphql/search-service.js +60 -0
- package/dist/cjs/graphql-request-client.js +106 -0
- package/dist/cjs/i18n/dictionary-service.js +45 -0
- package/dist/cjs/i18n/graphql-dictionary-service.js +125 -0
- package/dist/cjs/i18n/index.js +7 -0
- package/dist/cjs/index.js +36 -0
- package/dist/cjs/layout/content-styles.js +73 -0
- package/dist/cjs/layout/graphql-layout-service.js +84 -0
- package/dist/cjs/layout/index.js +18 -0
- package/dist/cjs/layout/layout-service.js +9 -0
- package/dist/cjs/layout/models.js +27 -0
- package/dist/cjs/layout/themes.js +79 -0
- package/dist/cjs/layout/utils.js +44 -0
- package/dist/cjs/media/index.js +24 -0
- package/dist/cjs/media/media-api.js +128 -0
- package/dist/cjs/models.js +2 -0
- package/dist/cjs/native-fetcher.js +183 -0
- package/dist/cjs/personalize/graphql-personalize-service.js +114 -0
- package/dist/cjs/personalize/index.js +12 -0
- package/dist/cjs/personalize/layout-personalizer.js +75 -0
- package/dist/cjs/personalize/utils.js +92 -0
- package/dist/cjs/site/graphql-error-pages-service.js +86 -0
- package/dist/cjs/site/graphql-redirects-service.js +103 -0
- package/dist/cjs/site/graphql-robots-service.js +81 -0
- package/dist/cjs/site/graphql-siteinfo-service.js +128 -0
- package/dist/cjs/site/graphql-sitemap-service.js +91 -0
- package/dist/cjs/site/index.js +22 -0
- package/dist/cjs/site/site-resolver.js +79 -0
- package/dist/cjs/site/utils.js +43 -0
- package/dist/cjs/utils/edit-frame.js +138 -0
- package/dist/cjs/utils/editing.js +122 -0
- package/dist/cjs/utils/env.js +26 -0
- package/dist/cjs/utils/index.js +25 -0
- package/dist/cjs/utils/is-server.js +10 -0
- package/dist/cjs/utils/timeout-promise.js +31 -0
- package/dist/cjs/utils/utils.js +70 -0
- package/dist/esm/cache-client.js +50 -0
- package/dist/esm/constants.js +9 -0
- package/dist/esm/debug.js +36 -0
- package/dist/esm/graphql/app-root-query.js +69 -0
- package/dist/esm/graphql/graphql-edge-proxy.js +8 -0
- package/dist/esm/graphql/index.js +4 -0
- package/dist/esm/graphql/search-service.js +56 -0
- package/dist/esm/graphql-request-client.js +99 -0
- package/dist/esm/i18n/dictionary-service.js +41 -0
- package/dist/esm/i18n/graphql-dictionary-service.js +118 -0
- package/dist/esm/i18n/index.js +2 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/layout/content-styles.js +65 -0
- package/dist/esm/layout/graphql-layout-service.js +77 -0
- package/dist/esm/layout/index.js +6 -0
- package/dist/esm/layout/layout-service.js +5 -0
- package/dist/esm/layout/models.js +24 -0
- package/dist/esm/layout/themes.js +74 -0
- package/dist/esm/layout/utils.js +39 -0
- package/dist/esm/media/index.js +2 -0
- package/dist/esm/media/media-api.js +117 -0
- package/dist/esm/models.js +1 -0
- package/dist/esm/native-fetcher.js +176 -0
- package/dist/esm/personalize/graphql-personalize-service.js +107 -0
- package/dist/esm/personalize/index.js +3 -0
- package/dist/esm/personalize/layout-personalizer.js +69 -0
- package/dist/esm/personalize/utils.js +85 -0
- package/dist/esm/site/graphql-error-pages-service.js +79 -0
- package/dist/esm/site/graphql-redirects-service.js +96 -0
- package/dist/esm/site/graphql-robots-service.js +74 -0
- package/dist/esm/site/graphql-siteinfo-service.js +121 -0
- package/dist/esm/site/graphql-sitemap-service.js +84 -0
- package/dist/esm/site/index.js +7 -0
- package/dist/esm/site/site-resolver.js +75 -0
- package/dist/esm/site/utils.js +37 -0
- package/dist/esm/utils/edit-frame.js +133 -0
- package/dist/esm/utils/editing.js +111 -0
- package/dist/esm/utils/env.js +22 -0
- package/dist/esm/utils/index.js +5 -0
- package/dist/esm/utils/is-server.js +8 -0
- package/dist/esm/utils/timeout-promise.js +28 -0
- package/dist/esm/utils/utils.js +61 -0
- package/graphql.d.ts +1 -0
- package/graphql.js +1 -0
- package/i18n.d.ts +1 -0
- package/i18n.js +1 -0
- package/layout.d.ts +1 -0
- package/layout.js +1 -0
- package/media.d.ts +1 -0
- package/media.js +1 -0
- package/package.json +71 -0
- package/personalize.d.ts +1 -0
- package/personalize.js +1 -0
- package/site.d.ts +1 -0
- package/site.js +1 -0
- package/types/cache-client.d.ts +64 -0
- package/types/constants.d.ts +6 -0
- package/types/debug.d.ts +26 -0
- package/types/graphql/app-root-query.d.ts +32 -0
- package/types/graphql/graphql-edge-proxy.d.ts +7 -0
- package/types/graphql/index.d.ts +4 -0
- package/types/graphql/search-service.d.ts +92 -0
- package/types/graphql-request-client.d.ts +88 -0
- package/types/i18n/dictionary-service.d.ts +56 -0
- package/types/i18n/graphql-dictionary-service.d.ts +65 -0
- package/types/i18n/index.d.ts +2 -0
- package/types/index.d.ts +6 -0
- package/types/layout/content-styles.d.ts +18 -0
- package/types/layout/graphql-layout-service.d.ts +59 -0
- package/types/layout/index.d.ts +6 -0
- package/types/layout/layout-service.d.ts +20 -0
- package/types/layout/models.d.ts +140 -0
- package/types/layout/themes.d.ts +11 -0
- package/types/layout/utils.d.ts +17 -0
- package/types/media/index.d.ts +2 -0
- package/types/media/media-api.d.ts +69 -0
- package/types/models.d.ts +6 -0
- package/types/native-fetcher.d.ts +92 -0
- package/types/personalize/graphql-personalize-service.d.ts +77 -0
- package/types/personalize/index.d.ts +3 -0
- package/types/personalize/layout-personalizer.d.ts +25 -0
- package/types/personalize/utils.d.ts +53 -0
- package/types/site/graphql-error-pages-service.d.ts +55 -0
- package/types/site/graphql-redirects-service.d.ts +66 -0
- package/types/site/graphql-robots-service.d.ts +47 -0
- package/types/site/graphql-siteinfo-service.d.ts +69 -0
- package/types/site/graphql-sitemap-service.d.ts +53 -0
- package/types/site/index.d.ts +7 -0
- package/types/site/site-resolver.d.ts +27 -0
- package/types/site/utils.d.ts +24 -0
- package/types/utils/edit-frame.d.ts +76 -0
- package/types/utils/editing.d.ts +58 -0
- package/types/utils/env.d.ts +7 -0
- package/types/utils/index.d.ts +5 -0
- package/types/utils/is-server.d.ts +6 -0
- package/types/utils/timeout-promise.d.ts +18 -0
- package/types/utils/utils.d.ts +18 -0
- package/utils.d.ts +1 -0
- package/utils.js +1 -0
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
21
|
+
import debuggers from './debug';
|
|
22
|
+
import TimeoutPromise from './utils/timeout-promise';
|
|
23
|
+
export class NativeDataFetcher {
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Implements a data fetcher.
|
|
29
|
+
* @param {string} url The URL to request (may include query string)
|
|
30
|
+
* @param {RequestInit} [options] Optional fetch options
|
|
31
|
+
* @returns {Promise<NativeDataFetcherResponse<T>>} response
|
|
32
|
+
*/
|
|
33
|
+
fetch(url, options = {}) {
|
|
34
|
+
var _a;
|
|
35
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
+
const _b = this.config, { debugger: debugOverride, fetch: fetchOverride } = _b, init = __rest(_b, ["debugger", "fetch"]);
|
|
37
|
+
const startTimestamp = Date.now();
|
|
38
|
+
const fetchImpl = fetchOverride || fetch;
|
|
39
|
+
const debug = debugOverride || debuggers.http;
|
|
40
|
+
// merge options from fetcher config and fetch call
|
|
41
|
+
const requestInit = this.getRequestInit(Object.assign(Object.assign({}, init), options));
|
|
42
|
+
const fetchWithOptionalTimeout = [fetchImpl(url, requestInit)];
|
|
43
|
+
if (init.timeout) {
|
|
44
|
+
this.abortTimeout = new TimeoutPromise(init.timeout);
|
|
45
|
+
fetchWithOptionalTimeout.push(this.abortTimeout.start);
|
|
46
|
+
}
|
|
47
|
+
// Note a goal here is to provide consistent debug logging and error handling
|
|
48
|
+
// as we do in GraphQLRequestClient
|
|
49
|
+
const { headers: reqHeaders } = requestInit, rest = __rest(requestInit, ["headers"]);
|
|
50
|
+
debug('request: %o', Object.assign({ url, headers: this.extractDebugHeaders(reqHeaders) }, rest));
|
|
51
|
+
const response = yield Promise.race(fetchWithOptionalTimeout)
|
|
52
|
+
.then((res) => {
|
|
53
|
+
var _a;
|
|
54
|
+
(_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear();
|
|
55
|
+
return res;
|
|
56
|
+
})
|
|
57
|
+
.catch((error) => {
|
|
58
|
+
var _a;
|
|
59
|
+
(_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear();
|
|
60
|
+
debug('request error: %o', error);
|
|
61
|
+
throw error;
|
|
62
|
+
});
|
|
63
|
+
// Note even an error status may send useful json data in response (which we want for logging)
|
|
64
|
+
let respData = undefined;
|
|
65
|
+
const isJson = (_a = response.headers.get('Content-Type')) === null || _a === void 0 ? void 0 : _a.includes('application/json');
|
|
66
|
+
if (isJson) {
|
|
67
|
+
respData = yield response.json().catch((error) => {
|
|
68
|
+
debug('response.json() error: %o', error);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// if not JSON, just read the response as text
|
|
73
|
+
respData = yield response.text().catch((error) => {
|
|
74
|
+
debug('response.text() error: %o', error);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const debugResponse = {
|
|
78
|
+
status: response.status,
|
|
79
|
+
statusText: response.statusText,
|
|
80
|
+
headers: this.extractDebugHeaders(response.headers),
|
|
81
|
+
url: response.url,
|
|
82
|
+
redirected: response.redirected,
|
|
83
|
+
data: respData,
|
|
84
|
+
};
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
debug('response error: %o', debugResponse);
|
|
87
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
88
|
+
}
|
|
89
|
+
debug('response in %dms: %o', Date.now() - startTimestamp, debugResponse);
|
|
90
|
+
return Object.assign(Object.assign({}, response), { data: respData });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Perform a GET request
|
|
95
|
+
* @param {string} url The URL to request (may include query string)
|
|
96
|
+
* @param {RequestInit} [options] Fetch options
|
|
97
|
+
* @returns {Promise<NativeDataFetcherResponse<T>>} response
|
|
98
|
+
*/
|
|
99
|
+
get(url, options = {}) {
|
|
100
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
101
|
+
return this.fetch(url, Object.assign({ method: 'GET' }, options));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Perform a POST request
|
|
106
|
+
* @param {string} url The URL to request (may include query string)
|
|
107
|
+
* @param {unknown} body The data to send with the request
|
|
108
|
+
* @param {RequestInit} [options] Fetch options
|
|
109
|
+
* @returns {Promise<NativeDataFetcherResponse<T>>} response
|
|
110
|
+
*/
|
|
111
|
+
post(url, body, options = {}) {
|
|
112
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
+
return this.fetch(url, Object.assign({ method: 'POST', body: JSON.stringify(body) }, options));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Perform a DELETE request
|
|
118
|
+
* @param {string} url The URL to request (may include query string)
|
|
119
|
+
* @param {RequestInit} [options] Fetch options
|
|
120
|
+
* @returns {Promise<NativeDataFetcherResponse<T>>} response
|
|
121
|
+
*/
|
|
122
|
+
delete(url, options = {}) {
|
|
123
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
return this.fetch(url, Object.assign({ method: 'DELETE' }, options));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Perform a PUT request
|
|
129
|
+
* @param {string} url The URL to request (may include query string)
|
|
130
|
+
* @param {unknown} body The data to send with the request
|
|
131
|
+
* @param {RequestInit} [options] Fetch options
|
|
132
|
+
* @returns {Promise<NativeDataFetcherResponse<T>>} response
|
|
133
|
+
*/
|
|
134
|
+
put(url, body, options = {}) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
return this.fetch(url, Object.assign({ method: 'PUT', body: JSON.stringify(body) }, options));
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Perform a HEAD request
|
|
141
|
+
* @param {string} url The URL to request (may include query string)
|
|
142
|
+
* @param {RequestInit} [options] Fetch options
|
|
143
|
+
* @returns {Promise<NativeDataFetcherResponse<T>>} response
|
|
144
|
+
*/
|
|
145
|
+
head(url, options = {}) {
|
|
146
|
+
return this.fetch(url, Object.assign({ method: 'HEAD' }, options));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Determines settings for the request
|
|
150
|
+
* @param {RequestInit} init Custom settings for request
|
|
151
|
+
* @returns {RequestInit} The final request settings
|
|
152
|
+
*/
|
|
153
|
+
getRequestInit(init = {}) {
|
|
154
|
+
const headers = new Headers(init.headers);
|
|
155
|
+
if (!init.method) {
|
|
156
|
+
init.method = init.body ? 'POST' : 'GET';
|
|
157
|
+
}
|
|
158
|
+
headers.set('Content-Type', 'application/json');
|
|
159
|
+
init.headers = headers;
|
|
160
|
+
return init;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Safely extract all headers for debug logging
|
|
164
|
+
* @param {HeadersInit} incomingHeaders Incoming headers
|
|
165
|
+
* @returns Object with headers as key/value pairs
|
|
166
|
+
*/
|
|
167
|
+
extractDebugHeaders(incomingHeaders = {}) {
|
|
168
|
+
const headers = {};
|
|
169
|
+
if (typeof (incomingHeaders === null || incomingHeaders === void 0 ? void 0 : incomingHeaders.forEach) !== 'string' && incomingHeaders.forEach) {
|
|
170
|
+
incomingHeaders === null || incomingHeaders === void 0 ? void 0 : incomingHeaders.forEach((value, key) => {
|
|
171
|
+
headers[key] = value;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return headers;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
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 debug from '../debug';
|
|
11
|
+
import { isTimeoutError } from '../utils';
|
|
12
|
+
import { CdpHelper } from './utils';
|
|
13
|
+
import { MemoryCacheClient } from '../cache-client';
|
|
14
|
+
export class GraphQLPersonalizeService {
|
|
15
|
+
/**
|
|
16
|
+
* Fetch personalize data using the Sitecore GraphQL endpoint.
|
|
17
|
+
* @param {GraphQLPersonalizeServiceConfig} config
|
|
18
|
+
*/
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.config.timeout = config.timeout || 400;
|
|
22
|
+
this.graphQLClient = this.getGraphQLClient();
|
|
23
|
+
this.cache = this.getCacheClient();
|
|
24
|
+
}
|
|
25
|
+
get query() {
|
|
26
|
+
return /* GraphQL */ `
|
|
27
|
+
query($siteName: String!, $language: String!, $itemPath: String!) {
|
|
28
|
+
layout(site: $siteName, routePath: $itemPath, language: $language) {
|
|
29
|
+
item {
|
|
30
|
+
id
|
|
31
|
+
version
|
|
32
|
+
personalization {
|
|
33
|
+
variantIds
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get personalize information for a route
|
|
42
|
+
* @param {string} itemPath page route
|
|
43
|
+
* @param {string} language language
|
|
44
|
+
* @param {string} siteName site name
|
|
45
|
+
* @returns {Promise<PersonalizeInfo | undefined>} the personalize information or undefined (if itemPath / language not found)
|
|
46
|
+
*/
|
|
47
|
+
getPersonalizeInfo(itemPath, language, siteName) {
|
|
48
|
+
var _a;
|
|
49
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
+
debug.personalize('fetching personalize info for %s %s %s', siteName, itemPath, language);
|
|
51
|
+
const cacheKey = this.getCacheKey(itemPath, language, siteName);
|
|
52
|
+
let data = this.cache.getCacheValue(cacheKey);
|
|
53
|
+
if (!data) {
|
|
54
|
+
try {
|
|
55
|
+
data = yield this.graphQLClient.request(this.query, {
|
|
56
|
+
siteName,
|
|
57
|
+
itemPath,
|
|
58
|
+
language,
|
|
59
|
+
});
|
|
60
|
+
this.cache.setCacheValue(cacheKey, data);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (isTimeoutError(error)) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return ((_a = data === null || data === void 0 ? void 0 : data.layout) === null || _a === void 0 ? void 0 : _a.item)
|
|
70
|
+
? {
|
|
71
|
+
// CDP expects content id format `embedded_[<scope>_]<id>_<lang>` (lowercase)
|
|
72
|
+
contentId: CdpHelper.getContentId(data.layout.item.id, language, this.config.scope),
|
|
73
|
+
variantIds: data.layout.item.personalization.variantIds,
|
|
74
|
+
}
|
|
75
|
+
: undefined;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Gets cache client implementation
|
|
80
|
+
* Override this method if custom cache needs to be used
|
|
81
|
+
* @returns CacheClient instance
|
|
82
|
+
*/
|
|
83
|
+
getCacheClient() {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
return new MemoryCacheClient({
|
|
86
|
+
cacheEnabled: (_a = this.config.cacheEnabled) !== null && _a !== void 0 ? _a : true,
|
|
87
|
+
cacheTimeout: (_b = this.config.cacheTimeout) !== null && _b !== void 0 ? _b : 10,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
getCacheKey(itemPath, language, siteName) {
|
|
91
|
+
return `${siteName}-${itemPath}-${language}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Gets a GraphQL client that can make requests to the API.
|
|
95
|
+
* @returns {GraphQLClient} implementation
|
|
96
|
+
*/
|
|
97
|
+
getGraphQLClient() {
|
|
98
|
+
if (!this.config.clientFactory) {
|
|
99
|
+
throw new Error('You should provide a clientFactory.');
|
|
100
|
+
}
|
|
101
|
+
return this.config.clientFactory({
|
|
102
|
+
debugger: debug.personalize,
|
|
103
|
+
fetch: this.config.fetch,
|
|
104
|
+
timeout: this.config.timeout,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply personalization to layout data. This will recursively go through all placeholders/components, check experiences nodes and replace default with object from specific experience.
|
|
3
|
+
* @param {LayoutServiceData} layout Layout data
|
|
4
|
+
* @param {string} variantId variant id
|
|
5
|
+
*/
|
|
6
|
+
export function personalizeLayout(layout, variantId) {
|
|
7
|
+
var _a;
|
|
8
|
+
// Add variantId to Sitecore context so that it is accessible here
|
|
9
|
+
layout.sitecore.context.variantId = variantId;
|
|
10
|
+
const placeholders = (_a = layout.sitecore.route) === null || _a === void 0 ? void 0 : _a.placeholders;
|
|
11
|
+
if (Object.keys(placeholders !== null && placeholders !== void 0 ? placeholders : {}).length === 0) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (placeholders) {
|
|
15
|
+
Object.keys(placeholders).forEach((placeholder) => {
|
|
16
|
+
placeholders[placeholder] = personalizePlaceholder(placeholders[placeholder], variantId);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
|
|
22
|
+
* @param {Array} components components within placeholder
|
|
23
|
+
* @param {string} variantId variant id
|
|
24
|
+
* @returns {Array<ComponentRendering | HtmlElementRendering>} components with personalization applied
|
|
25
|
+
*/
|
|
26
|
+
export function personalizePlaceholder(components, variantId) {
|
|
27
|
+
return components
|
|
28
|
+
.map((component) => {
|
|
29
|
+
const rendering = component;
|
|
30
|
+
if (rendering.experiences !== undefined) {
|
|
31
|
+
return personalizeComponent(rendering, variantId);
|
|
32
|
+
}
|
|
33
|
+
else if (rendering.placeholders) {
|
|
34
|
+
const placeholders = rendering.placeholders;
|
|
35
|
+
Object.keys(placeholders).forEach((placeholder) => {
|
|
36
|
+
placeholders[placeholder] = personalizePlaceholder(placeholders[placeholder], variantId);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return component;
|
|
40
|
+
})
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @param {ComponentRenderingWithExperiences} component component with experiences
|
|
45
|
+
* @param {string} variantId variant id
|
|
46
|
+
* @returns {ComponentRendering | null} component with personalization applied or null if hidden
|
|
47
|
+
*/
|
|
48
|
+
export function personalizeComponent(component, variantId) {
|
|
49
|
+
const variant = component.experiences[variantId];
|
|
50
|
+
if (variant === undefined && component.componentName === undefined) {
|
|
51
|
+
// DEFAULT IS HIDDEN
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
else if (variant && variant.componentName === null && variant.dataSource === null) {
|
|
55
|
+
// HIDDEN
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
else if (variant) {
|
|
59
|
+
component = variant;
|
|
60
|
+
}
|
|
61
|
+
if (!component.placeholders)
|
|
62
|
+
return component;
|
|
63
|
+
Object.keys(component === null || component === void 0 ? void 0 : component.placeholders).forEach((placeholder) => {
|
|
64
|
+
if (component.placeholders) {
|
|
65
|
+
component.placeholders[placeholder] = personalizePlaceholder(component.placeholders[placeholder], variantId);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return component;
|
|
69
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const DEFAULT_VARIANT = '_default';
|
|
2
|
+
export const VARIANT_PREFIX = '_variantId_';
|
|
3
|
+
/**
|
|
4
|
+
* Get a personalized rewrite path for given pathname
|
|
5
|
+
* @param {string} pathname the pathname
|
|
6
|
+
* @param {PersonalizedRewriteData} data the personalize data to include in the rewrite
|
|
7
|
+
* @returns {string} the rewrite path
|
|
8
|
+
*/
|
|
9
|
+
export function getPersonalizedRewrite(pathname, data) {
|
|
10
|
+
const path = pathname.startsWith('/') ? pathname : '/' + pathname;
|
|
11
|
+
return `/${VARIANT_PREFIX}${data.variantId}${path}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get personalize data from the rewrite path
|
|
15
|
+
* @param {string} pathname the pathname
|
|
16
|
+
* @returns {PersonalizedRewriteData} the personalize data from the rewrite
|
|
17
|
+
*/
|
|
18
|
+
export function getPersonalizedRewriteData(pathname) {
|
|
19
|
+
const data = {
|
|
20
|
+
variantId: DEFAULT_VARIANT,
|
|
21
|
+
};
|
|
22
|
+
const path = pathname.endsWith('/') ? pathname : pathname + '/';
|
|
23
|
+
const result = path.match(`${VARIANT_PREFIX}(.*?)\\/`);
|
|
24
|
+
if (result) {
|
|
25
|
+
data.variantId = result[1];
|
|
26
|
+
}
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Normalize a personalized rewrite path (remove personalize data)
|
|
31
|
+
* @param {string} pathname the pathname
|
|
32
|
+
* @returns {string} the pathname with personalize data removed
|
|
33
|
+
*/
|
|
34
|
+
export function normalizePersonalizedRewrite(pathname) {
|
|
35
|
+
if (!pathname.includes(VARIANT_PREFIX)) {
|
|
36
|
+
return pathname;
|
|
37
|
+
}
|
|
38
|
+
const result = pathname.match(`${VARIANT_PREFIX}.*?(?:\\/|$)`);
|
|
39
|
+
return result === null ? pathname : pathname.replace(result[0], '');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Static utility class for Sitecore CDP
|
|
43
|
+
*/
|
|
44
|
+
export class CdpHelper {
|
|
45
|
+
/**
|
|
46
|
+
* Gets the page variant id for CDP in the required format
|
|
47
|
+
* @param {string} pageId the page id
|
|
48
|
+
* @param {string} language the language
|
|
49
|
+
* @param {string} variantId the variant id
|
|
50
|
+
* @param {string} [scope] the scope value
|
|
51
|
+
* @returns {string} the formatted page variant id
|
|
52
|
+
*/
|
|
53
|
+
static getPageVariantId(pageId, language, variantId, scope) {
|
|
54
|
+
const formattedPageId = pageId.replace(/[{}-]/g, '');
|
|
55
|
+
const formattedLanguage = language.replace('-', '_');
|
|
56
|
+
const scopeId = scope ? `${this.normalizeScope(scope)}_` : '';
|
|
57
|
+
let formattedVariantId = variantId;
|
|
58
|
+
if (!variantId || variantId === DEFAULT_VARIANT) {
|
|
59
|
+
formattedVariantId = 'default';
|
|
60
|
+
}
|
|
61
|
+
return `${scopeId}${formattedPageId}_${formattedLanguage}_${formattedVariantId}`.toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Gets the content id for CDP in the required format `embedded_[<scope>_]<id>_<lang>`
|
|
65
|
+
* @param {string} pageId the page id
|
|
66
|
+
* @param {string} language the language
|
|
67
|
+
* @param {string} [scope] the scope value
|
|
68
|
+
* @returns {string} the content id
|
|
69
|
+
*/
|
|
70
|
+
static getContentId(pageId, language, scope) {
|
|
71
|
+
const formattedPageId = pageId.replace(/[{}-]/g, '');
|
|
72
|
+
const formattedLanguage = language.replace('-', '_');
|
|
73
|
+
const scopeId = scope ? `${this.normalizeScope(scope)}_` : '';
|
|
74
|
+
return `embedded_${scopeId}${formattedPageId}_${formattedLanguage}`.toLowerCase();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Normalizes the scope from the given string value
|
|
78
|
+
* Removes all non-alphanumeric characters
|
|
79
|
+
* @param {string} [scope] the scope value
|
|
80
|
+
* @returns {string} normalized scope value
|
|
81
|
+
*/
|
|
82
|
+
static normalizeScope(scope) {
|
|
83
|
+
return (scope === null || scope === void 0 ? void 0 : scope.replace(/[^a-zA-Z0-9]+/g, '')) || '';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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 { siteNameError } from '../constants';
|
|
11
|
+
import debug from '../debug';
|
|
12
|
+
// The default query for request error handling
|
|
13
|
+
const defaultQuery = /* GraphQL */ `
|
|
14
|
+
query ErrorPagesQuery($siteName: String!, $language: String!) {
|
|
15
|
+
site {
|
|
16
|
+
siteInfo(site: $siteName) {
|
|
17
|
+
errorHandling(language: $language) {
|
|
18
|
+
notFoundPage {
|
|
19
|
+
rendered
|
|
20
|
+
}
|
|
21
|
+
notFoundPagePath
|
|
22
|
+
serverErrorPage {
|
|
23
|
+
rendered
|
|
24
|
+
}
|
|
25
|
+
serverErrorPagePath
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
/**
|
|
32
|
+
* Service that fetch the error pages data using Sitecore's GraphQL API.
|
|
33
|
+
*/
|
|
34
|
+
export class GraphQLErrorPagesService {
|
|
35
|
+
/**
|
|
36
|
+
* Creates an instance of graphQL error pages service with the provided options
|
|
37
|
+
* @param {GraphQLErrorPagesServiceConfig} options instance
|
|
38
|
+
*/
|
|
39
|
+
constructor(options) {
|
|
40
|
+
this.options = options;
|
|
41
|
+
this.graphQLClient = this.getGraphQLClient();
|
|
42
|
+
}
|
|
43
|
+
get query() {
|
|
44
|
+
return defaultQuery;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Fetch list of error pages for the site
|
|
48
|
+
* @returns {ErrorPages} list of url's error pages
|
|
49
|
+
* @throws {Error} if the siteName is empty.
|
|
50
|
+
*/
|
|
51
|
+
fetchErrorPages() {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
const siteName = this.options.siteName;
|
|
54
|
+
const language = this.options.language;
|
|
55
|
+
if (!siteName) {
|
|
56
|
+
throw new Error(siteNameError);
|
|
57
|
+
}
|
|
58
|
+
return this.graphQLClient.request(this.query, {
|
|
59
|
+
siteName,
|
|
60
|
+
language,
|
|
61
|
+
})
|
|
62
|
+
.then((result) => result.site.siteInfo ? result.site.siteInfo.errorHandling : null)
|
|
63
|
+
.catch((e) => Promise.reject(e));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Gets a GraphQL client that can make requests to the API.
|
|
68
|
+
* @returns {GraphQLClient} implementation
|
|
69
|
+
*/
|
|
70
|
+
getGraphQLClient() {
|
|
71
|
+
if (!this.options.clientFactory) {
|
|
72
|
+
throw new Error('You should provide a clientFactory.');
|
|
73
|
+
}
|
|
74
|
+
return this.options.clientFactory({
|
|
75
|
+
debugger: debug.errorpages,
|
|
76
|
+
retries: this.options.retries,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
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 { siteNameError } from '../constants';
|
|
11
|
+
import debug from '../debug';
|
|
12
|
+
import { MemoryCacheClient } from '../cache-client';
|
|
13
|
+
export const REDIRECT_TYPE_301 = 'REDIRECT_301';
|
|
14
|
+
export const REDIRECT_TYPE_302 = 'REDIRECT_302';
|
|
15
|
+
export const REDIRECT_TYPE_SERVER_TRANSFER = 'SERVER_TRANSFER';
|
|
16
|
+
// The default query for request redirects of site
|
|
17
|
+
const defaultQuery = /* GraphQL */ `
|
|
18
|
+
query RedirectsQuery($siteName: String!) {
|
|
19
|
+
site {
|
|
20
|
+
siteInfo(site: $siteName) {
|
|
21
|
+
redirects {
|
|
22
|
+
pattern
|
|
23
|
+
target
|
|
24
|
+
redirectType
|
|
25
|
+
isQueryStringPreserved
|
|
26
|
+
locale
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
/**
|
|
33
|
+
* The GraphQLRedirectsService class is used to query the JSS redirects using Graphql endpoint
|
|
34
|
+
*/
|
|
35
|
+
export class GraphQLRedirectsService {
|
|
36
|
+
/**
|
|
37
|
+
* Creates an instance of graphQL redirects service with the provided options
|
|
38
|
+
* @param {GraphQLRedirectsServiceConfig} options instance
|
|
39
|
+
*/
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.options = options;
|
|
42
|
+
this.graphQLClient = this.getGraphQLClient();
|
|
43
|
+
this.cache = this.getCacheClient();
|
|
44
|
+
}
|
|
45
|
+
get query() {
|
|
46
|
+
return defaultQuery;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fetch an array of redirects from API
|
|
50
|
+
* @param {string} siteName site name
|
|
51
|
+
* @returns Promise<RedirectInfo[]>
|
|
52
|
+
* @throws {Error} if the siteName is empty.
|
|
53
|
+
*/
|
|
54
|
+
fetchRedirects(siteName) {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
if (!siteName) {
|
|
58
|
+
throw new Error(siteNameError);
|
|
59
|
+
}
|
|
60
|
+
const cacheKey = `redirects-${siteName}`;
|
|
61
|
+
let data = this.cache.getCacheValue(cacheKey);
|
|
62
|
+
if (!data) {
|
|
63
|
+
data = yield this.graphQLClient.request(this.query, {
|
|
64
|
+
siteName,
|
|
65
|
+
});
|
|
66
|
+
this.cache.setCacheValue(cacheKey, data);
|
|
67
|
+
}
|
|
68
|
+
return ((_b = (_a = data === null || data === void 0 ? void 0 : data.site) === null || _a === void 0 ? void 0 : _a.siteInfo) === null || _b === void 0 ? void 0 : _b.redirects) || [];
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Gets a GraphQL client that can make requests to the API.
|
|
73
|
+
* @returns {GraphQLClient} implementation
|
|
74
|
+
*/
|
|
75
|
+
getGraphQLClient() {
|
|
76
|
+
if (!this.options.clientFactory) {
|
|
77
|
+
throw new Error('You should provide a clientFactory.');
|
|
78
|
+
}
|
|
79
|
+
return this.options.clientFactory({
|
|
80
|
+
debugger: debug.redirects,
|
|
81
|
+
fetch: this.options.fetch,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Gets cache client implementation
|
|
86
|
+
* Override this method if custom cache needs to be used
|
|
87
|
+
* @returns CacheClient instance
|
|
88
|
+
*/
|
|
89
|
+
getCacheClient() {
|
|
90
|
+
var _a, _b;
|
|
91
|
+
return new MemoryCacheClient({
|
|
92
|
+
cacheEnabled: (_a = this.options.cacheEnabled) !== null && _a !== void 0 ? _a : true,
|
|
93
|
+
cacheTimeout: (_b = this.options.cacheTimeout) !== null && _b !== void 0 ? _b : 10,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|