@preprio/toolkit 0.1.0-beta.3 → 0.1.0-beta.5
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 +202 -0
- package/README.md +5 -1
- package/dist/astro/index.cjs +34 -10
- package/dist/astro/index.js +34 -10
- package/dist/{chunk-5JGPWQVA.js → chunk-NLQIRN5O.js} +84 -24
- package/dist/index.cjs +103 -28
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +20 -5
- package/dist/json-script.cjs +32 -0
- package/dist/json-script.d.cts +17 -0
- package/dist/json-script.d.ts +17 -0
- package/dist/json-script.js +7 -0
- package/dist/nextjs/components.cjs +84 -24
- package/dist/nextjs/components.js +1 -1
- package/dist/nextjs/index.cjs +23 -5
- package/dist/nextjs/index.js +23 -5
- package/dist/nuxt/index.cjs +34 -10
- package/dist/nuxt/index.js +34 -10
- package/dist/sveltekit/index.cjs +34 -10
- package/dist/sveltekit/index.js +34 -10
- package/package.json +52 -14
- package/src/astro/PreprToolbar.astro +2 -1
- package/src/astro/PreprTrackingPixel.astro +3 -1
package/dist/index.cjs
CHANGED
|
@@ -43,7 +43,7 @@ __export(src_exports, {
|
|
|
43
43
|
module.exports = __toCommonJS(src_exports);
|
|
44
44
|
|
|
45
45
|
// src/version.ts
|
|
46
|
-
var VERSION = "0.1.0-beta.
|
|
46
|
+
var VERSION = "0.1.0-beta.5";
|
|
47
47
|
|
|
48
48
|
// src/core/constants.ts
|
|
49
49
|
var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
|
|
@@ -67,16 +67,34 @@ function getCookie(name) {
|
|
|
67
67
|
}
|
|
68
68
|
function setCookie(name, value, options = {}) {
|
|
69
69
|
if (typeof document === "undefined") return;
|
|
70
|
-
const { maxAge, path = "/" } = options;
|
|
70
|
+
const { maxAge, path = "/", sameSite, secure } = options;
|
|
71
71
|
let cookie = `${name}=${encodeURIComponent(value)};path=${path}`;
|
|
72
72
|
if (typeof maxAge === "number") {
|
|
73
73
|
cookie += `;max-age=${maxAge}`;
|
|
74
74
|
}
|
|
75
|
+
if (sameSite) {
|
|
76
|
+
cookie += `;samesite=${sameSite}`;
|
|
77
|
+
}
|
|
78
|
+
if (secure) {
|
|
79
|
+
cookie += ";secure";
|
|
80
|
+
}
|
|
75
81
|
document.cookie = cookie;
|
|
76
82
|
}
|
|
77
|
-
function removeCookie(name, path = "/") {
|
|
83
|
+
function removeCookie(name, path = "/", options = {}) {
|
|
78
84
|
if (typeof document === "undefined") return;
|
|
79
|
-
|
|
85
|
+
const { sameSite, secure } = options;
|
|
86
|
+
let cookie = `${name}=;path=${path};expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
87
|
+
if (sameSite) {
|
|
88
|
+
cookie += `;samesite=${sameSite}`;
|
|
89
|
+
}
|
|
90
|
+
if (secure) {
|
|
91
|
+
cookie += ";secure";
|
|
92
|
+
}
|
|
93
|
+
document.cookie = cookie;
|
|
94
|
+
}
|
|
95
|
+
function crossSiteCookieOptions() {
|
|
96
|
+
if (typeof window === "undefined" || !window.isSecureContext) return {};
|
|
97
|
+
return { sameSite: "None", secure: true };
|
|
80
98
|
}
|
|
81
99
|
|
|
82
100
|
// src/core/locales/en.json
|
|
@@ -170,7 +188,11 @@ function t(key, locale, vars) {
|
|
|
170
188
|
}
|
|
171
189
|
|
|
172
190
|
// src/core/utils.ts
|
|
173
|
-
|
|
191
|
+
var trustedParentOrigin = null;
|
|
192
|
+
function setTrustedParentOrigin(origin) {
|
|
193
|
+
trustedParentOrigin = origin;
|
|
194
|
+
}
|
|
195
|
+
function sendPreprEvent(event, data, options) {
|
|
174
196
|
if (typeof window !== "undefined") {
|
|
175
197
|
const message = {
|
|
176
198
|
name: "prepr_preview_bar",
|
|
@@ -181,7 +203,11 @@ function sendPreprEvent(event, data) {
|
|
|
181
203
|
new CustomEvent("prepr_preview_bar", { detail: message })
|
|
182
204
|
);
|
|
183
205
|
if (window.parent && window.parent !== window) {
|
|
184
|
-
|
|
206
|
+
if (trustedParentOrigin) {
|
|
207
|
+
window.parent.postMessage(message, trustedParentOrigin);
|
|
208
|
+
} else if (options?.allowUntrustedTarget) {
|
|
209
|
+
window.parent.postMessage(message, "*");
|
|
210
|
+
}
|
|
185
211
|
}
|
|
186
212
|
}
|
|
187
213
|
}
|
|
@@ -267,7 +293,23 @@ function createElementCache(query, ttl = 1e3) {
|
|
|
267
293
|
}
|
|
268
294
|
|
|
269
295
|
// src/core/iframe-bridge.ts
|
|
270
|
-
|
|
296
|
+
var EDITOR_BASE_DOMAIN = "prepr.io";
|
|
297
|
+
function isAllowedEditorOrigin(origin, allowed) {
|
|
298
|
+
if (allowed) return allowed.includes(origin);
|
|
299
|
+
let url;
|
|
300
|
+
try {
|
|
301
|
+
url = new URL(origin);
|
|
302
|
+
} catch {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (url.protocol !== "https:" || url.port !== "") return false;
|
|
306
|
+
const suffix = `.${EDITOR_BASE_DOMAIN}`;
|
|
307
|
+
if (!url.hostname.endsWith(suffix)) return false;
|
|
308
|
+
const label = url.hostname.slice(0, -suffix.length);
|
|
309
|
+
return label.length > 0 && !label.includes(".");
|
|
310
|
+
}
|
|
311
|
+
function createIframeBridge(store, options = {}) {
|
|
312
|
+
const allowedOrigins = options.allowedEditorOrigins;
|
|
271
313
|
let parentOrigin = null;
|
|
272
314
|
const onKeyDown = (event) => {
|
|
273
315
|
const key = event.key.toLowerCase();
|
|
@@ -277,14 +319,16 @@ function createIframeBridge(store) {
|
|
|
277
319
|
const onMessage = (evt) => {
|
|
278
320
|
const data = evt?.data;
|
|
279
321
|
if (data?.event === "prepr:initVE" && !parentOrigin) {
|
|
322
|
+
if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) return;
|
|
280
323
|
parentOrigin = evt.origin;
|
|
324
|
+
setTrustedParentOrigin(parentOrigin);
|
|
281
325
|
if (data.scrollPosition != null) {
|
|
282
326
|
const top = data.scrollPosition;
|
|
283
327
|
setTimeout(() => window.scrollTo(0, top), 1);
|
|
284
328
|
}
|
|
285
329
|
store.set({ previewMode: true, editMode: data.editMode ?? true });
|
|
286
330
|
}
|
|
287
|
-
if (evt.origin !== parentOrigin) return;
|
|
331
|
+
if (!parentOrigin || evt.origin !== parentOrigin) return;
|
|
288
332
|
if (data?.event === "prepr:getScrollPosition") {
|
|
289
333
|
const currentScrollY = window.scrollY || document.documentElement.scrollTop;
|
|
290
334
|
sendPreprEvent("getScrollPosition", { value: currentScrollY });
|
|
@@ -292,13 +336,15 @@ function createIframeBridge(store) {
|
|
|
292
336
|
};
|
|
293
337
|
return {
|
|
294
338
|
start() {
|
|
295
|
-
sendPreprEvent("loaded");
|
|
339
|
+
sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
|
|
296
340
|
window.addEventListener("keydown", onKeyDown);
|
|
297
341
|
window.addEventListener("message", onMessage);
|
|
298
342
|
},
|
|
299
343
|
stop() {
|
|
300
344
|
window.removeEventListener("keydown", onKeyDown);
|
|
301
345
|
window.removeEventListener("message", onMessage);
|
|
346
|
+
parentOrigin = null;
|
|
347
|
+
setTrustedParentOrigin(null);
|
|
302
348
|
}
|
|
303
349
|
};
|
|
304
350
|
}
|
|
@@ -933,43 +979,55 @@ function createToolbarStore(initial = {}) {
|
|
|
933
979
|
}
|
|
934
980
|
|
|
935
981
|
// src/core/toolbar-change-handler.ts
|
|
936
|
-
|
|
982
|
+
function cookieOpts() {
|
|
983
|
+
return {
|
|
984
|
+
maxAge: ONE_YEAR_SECONDS,
|
|
985
|
+
path: "/",
|
|
986
|
+
...crossSiteCookieOptions()
|
|
987
|
+
};
|
|
988
|
+
}
|
|
937
989
|
function createChangeHandler(deps) {
|
|
938
990
|
const { store, stega, syncAutoClean } = deps;
|
|
939
|
-
function
|
|
991
|
+
function updateParams(patch) {
|
|
940
992
|
const [path, existing] = splitPath(deps.currentPath());
|
|
941
993
|
const params = new URLSearchParams(existing);
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
994
|
+
for (const [name, value] of Object.entries(patch)) {
|
|
995
|
+
if (value === null) {
|
|
996
|
+
params.delete(name);
|
|
997
|
+
} else {
|
|
998
|
+
params.set(name, value);
|
|
999
|
+
}
|
|
946
1000
|
}
|
|
947
1001
|
const query = params.toString();
|
|
948
1002
|
deps.navigate(query ? `${path}?${query}` : path);
|
|
949
1003
|
}
|
|
950
1004
|
return function handleChange(before, after) {
|
|
1005
|
+
const paramPatch = {};
|
|
951
1006
|
if (before.selectedSegment !== after.selectedSegment) {
|
|
952
1007
|
if (after.selectedSegment === null) {
|
|
953
|
-
removeCookie(COOKIE_SEGMENT);
|
|
1008
|
+
removeCookie(COOKIE_SEGMENT, "/", crossSiteCookieOptions());
|
|
954
1009
|
} else {
|
|
955
|
-
setCookie(COOKIE_SEGMENT, after.selectedSegment,
|
|
1010
|
+
setCookie(COOKIE_SEGMENT, after.selectedSegment, cookieOpts());
|
|
956
1011
|
}
|
|
957
|
-
|
|
1012
|
+
paramPatch[PARAM_SEGMENT] = after.selectedSegment;
|
|
958
1013
|
sendPreprEvent("segment_changed", {
|
|
959
1014
|
segment: after.selectedSegment ?? void 0
|
|
960
1015
|
});
|
|
961
1016
|
}
|
|
962
1017
|
if (before.selectedVariant !== after.selectedVariant) {
|
|
963
1018
|
if (after.selectedVariant === null) {
|
|
964
|
-
removeCookie(COOKIE_VARIANT);
|
|
1019
|
+
removeCookie(COOKIE_VARIANT, "/", crossSiteCookieOptions());
|
|
965
1020
|
} else {
|
|
966
|
-
setCookie(COOKIE_VARIANT, after.selectedVariant,
|
|
1021
|
+
setCookie(COOKIE_VARIANT, after.selectedVariant, cookieOpts());
|
|
967
1022
|
}
|
|
968
|
-
|
|
1023
|
+
paramPatch[PARAM_VARIANT] = after.selectedVariant;
|
|
969
1024
|
sendPreprEvent("variant_changed", {
|
|
970
1025
|
variant: after.selectedVariant ?? void 0
|
|
971
1026
|
});
|
|
972
1027
|
}
|
|
1028
|
+
if (Object.keys(paramPatch).length > 0) {
|
|
1029
|
+
updateParams(paramPatch);
|
|
1030
|
+
}
|
|
973
1031
|
if (before.editMode !== after.editMode) {
|
|
974
1032
|
if (after.editMode) {
|
|
975
1033
|
stega.start();
|
|
@@ -989,8 +1047,8 @@ function createChangeHandler(deps) {
|
|
|
989
1047
|
if (Object.keys(coupled).length > 0) {
|
|
990
1048
|
store.set(coupled);
|
|
991
1049
|
}
|
|
992
|
-
setCookie(COOKIE_PREVIEW_MODE, String(after.previewMode),
|
|
993
|
-
setCookie(COOKIE_TOOLBAR_OPEN, "false",
|
|
1050
|
+
setCookie(COOKIE_PREVIEW_MODE, String(after.previewMode), cookieOpts());
|
|
1051
|
+
setCookie(COOKIE_TOOLBAR_OPEN, "false", cookieOpts());
|
|
994
1052
|
sendPreprEvent("preview_mode_toggled", { previewMode: after.previewMode });
|
|
995
1053
|
syncAutoClean(after.previewMode);
|
|
996
1054
|
const [path, existing] = splitPath(deps.currentPath());
|
|
@@ -1005,7 +1063,7 @@ function createChangeHandler(deps) {
|
|
|
1005
1063
|
}
|
|
1006
1064
|
}
|
|
1007
1065
|
if (before.toolbarOpen !== after.toolbarOpen) {
|
|
1008
|
-
setCookie(COOKIE_TOOLBAR_OPEN, String(after.toolbarOpen),
|
|
1066
|
+
setCookie(COOKIE_TOOLBAR_OPEN, String(after.toolbarOpen), cookieOpts());
|
|
1009
1067
|
}
|
|
1010
1068
|
};
|
|
1011
1069
|
}
|
|
@@ -2075,7 +2133,9 @@ function createPreprToolbar(opts) {
|
|
|
2075
2133
|
});
|
|
2076
2134
|
syncAutoClean(store.get().previewMode);
|
|
2077
2135
|
const bridge = createIframeBridge(store);
|
|
2078
|
-
sendPreprEvent("getScrollPosition", { value: 0 }
|
|
2136
|
+
sendPreprEvent("getScrollPosition", { value: 0 }, {
|
|
2137
|
+
allowUntrustedTarget: true
|
|
2138
|
+
});
|
|
2079
2139
|
if (isIframe) {
|
|
2080
2140
|
bridge.start();
|
|
2081
2141
|
}
|
|
@@ -2271,7 +2331,11 @@ function processPreprRequest(request, opts) {
|
|
|
2271
2331
|
const requestHeaders = new Headers(request.headers);
|
|
2272
2332
|
const responseCookies = [];
|
|
2273
2333
|
const requestCookies = parseCookies(request);
|
|
2274
|
-
const
|
|
2334
|
+
const url = new URL(request.url);
|
|
2335
|
+
const searchParams = url.searchParams;
|
|
2336
|
+
const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
2337
|
+
const isSecure = forwardedProto ? forwardedProto === "https" : url.protocol === "https:";
|
|
2338
|
+
const crossSite = isSecure ? { sameSite: "None", secure: true } : {};
|
|
2275
2339
|
for (const key of UTM_PARAMS) {
|
|
2276
2340
|
const value = searchParams.get(key);
|
|
2277
2341
|
if (value !== null) {
|
|
@@ -2320,6 +2384,15 @@ function processPreprRequest(request, opts) {
|
|
|
2320
2384
|
}
|
|
2321
2385
|
requestHeaders.set("Prepr-Preview-Bar", "true");
|
|
2322
2386
|
const isLivePreview = searchParams.get(PARAM_HIDE_BAR) === "true";
|
|
2387
|
+
if (!isLivePreview && !requestCookies.has(COOKIE_PREVIEW_MODE)) {
|
|
2388
|
+
responseCookies.push({
|
|
2389
|
+
name: COOKIE_PREVIEW_MODE,
|
|
2390
|
+
value: "true",
|
|
2391
|
+
maxAge: ONE_YEAR_SECONDS,
|
|
2392
|
+
path: "/",
|
|
2393
|
+
...crossSite
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2323
2396
|
if (!isLivePreview) {
|
|
2324
2397
|
const segmentCookie = requestCookies.get(COOKIE_SEGMENT);
|
|
2325
2398
|
if (segmentCookie) {
|
|
@@ -2338,7 +2411,8 @@ function processPreprRequest(request, opts) {
|
|
|
2338
2411
|
name: COOKIE_VARIANT,
|
|
2339
2412
|
value: previewAb,
|
|
2340
2413
|
maxAge: ONE_YEAR_SECONDS,
|
|
2341
|
-
path: "/"
|
|
2414
|
+
path: "/",
|
|
2415
|
+
...crossSite
|
|
2342
2416
|
});
|
|
2343
2417
|
}
|
|
2344
2418
|
}
|
|
@@ -2350,7 +2424,8 @@ function processPreprRequest(request, opts) {
|
|
|
2350
2424
|
name: COOKIE_SEGMENT,
|
|
2351
2425
|
value: previewSegment,
|
|
2352
2426
|
maxAge: ONE_YEAR_SECONDS,
|
|
2353
|
-
path: "/"
|
|
2427
|
+
path: "/",
|
|
2428
|
+
...crossSite
|
|
2354
2429
|
});
|
|
2355
2430
|
}
|
|
2356
2431
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -5,7 +5,7 @@ export { f as PreprEnvironment, g as PreprEventType, h as PreprHeaderName, i as
|
|
|
5
5
|
* Package version; must match package.json. Lives in its own leaf module so
|
|
6
6
|
* core can import it without cycling through the barrel.
|
|
7
7
|
*/
|
|
8
|
-
declare const VERSION = "0.1.0-beta.
|
|
8
|
+
declare const VERSION = "0.1.0-beta.5";
|
|
9
9
|
|
|
10
10
|
interface ToolbarState {
|
|
11
11
|
locale: Locale;
|
|
@@ -160,6 +160,15 @@ interface CookieSpec {
|
|
|
160
160
|
value: string;
|
|
161
161
|
maxAge: number;
|
|
162
162
|
path: string;
|
|
163
|
+
/**
|
|
164
|
+
* Set to `'None'` for cookies that must survive the Prepr editor's
|
|
165
|
+
* cross-site preview iframe — browsers drop the browser-default `Lax`
|
|
166
|
+
* cookies there. Always paired with `secure`, which browsers require for
|
|
167
|
+
* `SameSite=None`.
|
|
168
|
+
*/
|
|
169
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
170
|
+
/** Adds `Secure`. Required by browsers alongside `SameSite=None`. */
|
|
171
|
+
secure?: boolean;
|
|
163
172
|
}
|
|
164
173
|
interface PreprMiddlewareResult {
|
|
165
174
|
requestHeaders: Headers;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { f as PreprEnvironment, g as PreprEventType, h as PreprHeaderName, i as
|
|
|
5
5
|
* Package version; must match package.json. Lives in its own leaf module so
|
|
6
6
|
* core can import it without cycling through the barrel.
|
|
7
7
|
*/
|
|
8
|
-
declare const VERSION = "0.1.0-beta.
|
|
8
|
+
declare const VERSION = "0.1.0-beta.5";
|
|
9
9
|
|
|
10
10
|
interface ToolbarState {
|
|
11
11
|
locale: Locale;
|
|
@@ -160,6 +160,15 @@ interface CookieSpec {
|
|
|
160
160
|
value: string;
|
|
161
161
|
maxAge: number;
|
|
162
162
|
path: string;
|
|
163
|
+
/**
|
|
164
|
+
* Set to `'None'` for cookies that must survive the Prepr editor's
|
|
165
|
+
* cross-site preview iframe — browsers drop the browser-default `Lax`
|
|
166
|
+
* cookies there. Always paired with `secure`, which browsers require for
|
|
167
|
+
* `SameSite=None`.
|
|
168
|
+
*/
|
|
169
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
170
|
+
/** Adds `Secure`. Required by browsers alongside `SameSite=None`. */
|
|
171
|
+
secure?: boolean;
|
|
163
172
|
}
|
|
164
173
|
interface PreprMiddlewareResult {
|
|
165
174
|
requestHeaders: Headers;
|
package/dist/index.js
CHANGED
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
setTrackingParam,
|
|
17
17
|
stegaClean,
|
|
18
18
|
trackEvent
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-NLQIRN5O.js";
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var VERSION = "0.1.0-beta.
|
|
22
|
+
var VERSION = "0.1.0-beta.5";
|
|
23
23
|
|
|
24
24
|
// src/core/server.ts
|
|
25
25
|
var PreprError = class extends Error {
|
|
@@ -198,7 +198,11 @@ function processPreprRequest(request, opts) {
|
|
|
198
198
|
const requestHeaders = new Headers(request.headers);
|
|
199
199
|
const responseCookies = [];
|
|
200
200
|
const requestCookies = parseCookies(request);
|
|
201
|
-
const
|
|
201
|
+
const url = new URL(request.url);
|
|
202
|
+
const searchParams = url.searchParams;
|
|
203
|
+
const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
204
|
+
const isSecure = forwardedProto ? forwardedProto === "https" : url.protocol === "https:";
|
|
205
|
+
const crossSite = isSecure ? { sameSite: "None", secure: true } : {};
|
|
202
206
|
for (const key of UTM_PARAMS) {
|
|
203
207
|
const value = searchParams.get(key);
|
|
204
208
|
if (value !== null) {
|
|
@@ -247,6 +251,15 @@ function processPreprRequest(request, opts) {
|
|
|
247
251
|
}
|
|
248
252
|
requestHeaders.set("Prepr-Preview-Bar", "true");
|
|
249
253
|
const isLivePreview = searchParams.get(PARAM_HIDE_BAR) === "true";
|
|
254
|
+
if (!isLivePreview && !requestCookies.has(COOKIE_PREVIEW_MODE)) {
|
|
255
|
+
responseCookies.push({
|
|
256
|
+
name: COOKIE_PREVIEW_MODE,
|
|
257
|
+
value: "true",
|
|
258
|
+
maxAge: ONE_YEAR_SECONDS,
|
|
259
|
+
path: "/",
|
|
260
|
+
...crossSite
|
|
261
|
+
});
|
|
262
|
+
}
|
|
250
263
|
if (!isLivePreview) {
|
|
251
264
|
const segmentCookie = requestCookies.get(COOKIE_SEGMENT);
|
|
252
265
|
if (segmentCookie) {
|
|
@@ -265,7 +278,8 @@ function processPreprRequest(request, opts) {
|
|
|
265
278
|
name: COOKIE_VARIANT,
|
|
266
279
|
value: previewAb,
|
|
267
280
|
maxAge: ONE_YEAR_SECONDS,
|
|
268
|
-
path: "/"
|
|
281
|
+
path: "/",
|
|
282
|
+
...crossSite
|
|
269
283
|
});
|
|
270
284
|
}
|
|
271
285
|
}
|
|
@@ -277,7 +291,8 @@ function processPreprRequest(request, opts) {
|
|
|
277
291
|
name: COOKIE_SEGMENT,
|
|
278
292
|
value: previewSegment,
|
|
279
293
|
maxAge: ONE_YEAR_SECONDS,
|
|
280
|
-
path: "/"
|
|
294
|
+
path: "/",
|
|
295
|
+
...crossSite
|
|
281
296
|
});
|
|
282
297
|
}
|
|
283
298
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/core/json-script.ts
|
|
21
|
+
var json_script_exports = {};
|
|
22
|
+
__export(json_script_exports, {
|
|
23
|
+
serializeForScriptTag: () => serializeForScriptTag
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(json_script_exports);
|
|
26
|
+
function serializeForScriptTag(value) {
|
|
27
|
+
return JSON.stringify(value ?? null).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
28
|
+
}
|
|
29
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
30
|
+
0 && (module.exports = {
|
|
31
|
+
serializeForScriptTag
|
|
32
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize a value for embedding inside `<script type="application/json">`.
|
|
3
|
+
*
|
|
4
|
+
* `JSON.stringify` does not escape `<`, `>` or `&`, so a string value
|
|
5
|
+
* containing `</script>` would close the tag early and let the remainder parse
|
|
6
|
+
* as HTML — stored XSS whenever any part of the payload is author-controlled
|
|
7
|
+
* (CMS segment names reach these components verbatim).
|
|
8
|
+
*
|
|
9
|
+
* Escaping `<` and `>` as `\uXXXX` keeps the output valid JSON that parses back
|
|
10
|
+
* to the identical value, while making an early tag close impossible. `&` is
|
|
11
|
+
* escaped too so the payload is also safe inside HTML-escaping contexts, and
|
|
12
|
+
* U+2028/U+2029 because they are literal line terminators in JS source but
|
|
13
|
+
* legal unescaped inside a JSON string.
|
|
14
|
+
*/
|
|
15
|
+
declare function serializeForScriptTag(value: unknown): string;
|
|
16
|
+
|
|
17
|
+
export { serializeForScriptTag };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize a value for embedding inside `<script type="application/json">`.
|
|
3
|
+
*
|
|
4
|
+
* `JSON.stringify` does not escape `<`, `>` or `&`, so a string value
|
|
5
|
+
* containing `</script>` would close the tag early and let the remainder parse
|
|
6
|
+
* as HTML — stored XSS whenever any part of the payload is author-controlled
|
|
7
|
+
* (CMS segment names reach these components verbatim).
|
|
8
|
+
*
|
|
9
|
+
* Escaping `<` and `>` as `\uXXXX` keeps the output valid JSON that parses back
|
|
10
|
+
* to the identical value, while making an early tag close impossible. `&` is
|
|
11
|
+
* escaped too so the payload is also safe inside HTML-escaping contexts, and
|
|
12
|
+
* U+2028/U+2029 because they are literal line terminators in JS source but
|
|
13
|
+
* legal unescaped inside a JSON string.
|
|
14
|
+
*/
|
|
15
|
+
declare function serializeForScriptTag(value: unknown): string;
|
|
16
|
+
|
|
17
|
+
export { serializeForScriptTag };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// src/core/json-script.ts
|
|
2
|
+
function serializeForScriptTag(value) {
|
|
3
|
+
return JSON.stringify(value ?? null).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
4
|
+
}
|
|
5
|
+
export {
|
|
6
|
+
serializeForScriptTag
|
|
7
|
+
};
|