@preprio/toolkit 0.2.0-beta.4 → 0.2.0-beta.6

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.
@@ -30,7 +30,7 @@ __export(astro_exports, {
30
30
  module.exports = __toCommonJS(astro_exports);
31
31
 
32
32
  // src/version.ts
33
- var VERSION = "0.2.0-beta.4";
33
+ var VERSION = "0.2.0-beta.6";
34
34
 
35
35
  // src/core/features.ts
36
36
  function isEnabled(config) {
@@ -193,6 +193,11 @@ var UTM_PARAMS = [
193
193
  "utm_content",
194
194
  "utm_campaign"
195
195
  ];
196
+ var MAX_HEADER_VALUE_LENGTH = 2048;
197
+ function sanitizeHeaderValue(value) {
198
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
199
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
200
+ }
196
201
  function serializeCookie(cookie) {
197
202
  const parts = [
198
203
  `${cookie.name}=${encodeURIComponent(cookie.value)}`,
@@ -224,6 +229,15 @@ function parseCookies(request) {
224
229
  function processPreprRequest(request, opts) {
225
230
  const requestHeaders = new Headers(request.headers);
226
231
  const responseCookies = [];
232
+ const inboundPreprHeaders = [];
233
+ requestHeaders.forEach((_value, key) => {
234
+ if (key.toLowerCase().startsWith("prepr-")) {
235
+ inboundPreprHeaders.push(key);
236
+ }
237
+ });
238
+ for (const key of inboundPreprHeaders) {
239
+ requestHeaders.delete(key);
240
+ }
227
241
  const requestCookies = parseCookies(request);
228
242
  const url = new URL(request.url);
229
243
  const searchParams = url.searchParams;
@@ -233,25 +247,28 @@ function processPreprRequest(request, opts) {
233
247
  for (const key of UTM_PARAMS) {
234
248
  const value = searchParams.get(key);
235
249
  if (value !== null) {
236
- requestHeaders.set(`Prepr-Context-${key}`, value);
250
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
237
251
  }
238
252
  }
239
253
  const referer = request.headers.get("referer");
240
254
  if (referer) {
241
- requestHeaders.set("Prepr-Context-initial_referral", referer);
255
+ requestHeaders.set(
256
+ "Prepr-Context-initial_referral",
257
+ sanitizeHeaderValue(referer)
258
+ );
242
259
  }
243
260
  const userAgent = request.headers.get("user-agent");
244
261
  if (userAgent) {
245
- requestHeaders.set("Prepr-User-Agent", userAgent);
262
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
246
263
  }
247
264
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
248
265
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
249
266
  if (ip) {
250
- requestHeaders.set("Prepr-Visitor-IP", ip);
267
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
251
268
  }
252
269
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
253
270
  if (hutkCookie) {
254
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
271
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
255
272
  }
256
273
  let uid = requestCookies.get(COOKIE_UID);
257
274
  if (!uid) {
@@ -291,16 +308,16 @@ function processPreprRequest(request, opts) {
291
308
  if (!isLivePreview) {
292
309
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
293
310
  if (segmentCookie) {
294
- requestHeaders.set("Prepr-Segments", segmentCookie);
311
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
295
312
  }
296
313
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
297
314
  if (abCookie) {
298
- requestHeaders.set("Prepr-ABtesting", abCookie);
315
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
299
316
  }
300
317
  }
301
318
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
302
319
  if (previewAb !== null) {
303
- requestHeaders.set("Prepr-ABtesting", previewAb);
320
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
304
321
  if (!isLivePreview) {
305
322
  responseCookies.push({
306
323
  name: COOKIE_VARIANT,
@@ -313,7 +330,7 @@ function processPreprRequest(request, opts) {
313
330
  }
314
331
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
315
332
  if (previewSegment !== null) {
316
- requestHeaders.set("Prepr-Segments", previewSegment);
333
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
317
334
  if (!isLivePreview) {
318
335
  responseCookies.push({
319
336
  name: COOKIE_SEGMENT,
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.2.0-beta.4";
2
+ var VERSION = "0.2.0-beta.6";
3
3
 
4
4
  // src/core/features.ts
5
5
  function isEnabled(config) {
@@ -162,6 +162,11 @@ var UTM_PARAMS = [
162
162
  "utm_content",
163
163
  "utm_campaign"
164
164
  ];
165
+ var MAX_HEADER_VALUE_LENGTH = 2048;
166
+ function sanitizeHeaderValue(value) {
167
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
168
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
169
+ }
165
170
  function serializeCookie(cookie) {
166
171
  const parts = [
167
172
  `${cookie.name}=${encodeURIComponent(cookie.value)}`,
@@ -193,6 +198,15 @@ function parseCookies(request) {
193
198
  function processPreprRequest(request, opts) {
194
199
  const requestHeaders = new Headers(request.headers);
195
200
  const responseCookies = [];
201
+ const inboundPreprHeaders = [];
202
+ requestHeaders.forEach((_value, key) => {
203
+ if (key.toLowerCase().startsWith("prepr-")) {
204
+ inboundPreprHeaders.push(key);
205
+ }
206
+ });
207
+ for (const key of inboundPreprHeaders) {
208
+ requestHeaders.delete(key);
209
+ }
196
210
  const requestCookies = parseCookies(request);
197
211
  const url = new URL(request.url);
198
212
  const searchParams = url.searchParams;
@@ -202,25 +216,28 @@ function processPreprRequest(request, opts) {
202
216
  for (const key of UTM_PARAMS) {
203
217
  const value = searchParams.get(key);
204
218
  if (value !== null) {
205
- requestHeaders.set(`Prepr-Context-${key}`, value);
219
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
206
220
  }
207
221
  }
208
222
  const referer = request.headers.get("referer");
209
223
  if (referer) {
210
- requestHeaders.set("Prepr-Context-initial_referral", referer);
224
+ requestHeaders.set(
225
+ "Prepr-Context-initial_referral",
226
+ sanitizeHeaderValue(referer)
227
+ );
211
228
  }
212
229
  const userAgent = request.headers.get("user-agent");
213
230
  if (userAgent) {
214
- requestHeaders.set("Prepr-User-Agent", userAgent);
231
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
215
232
  }
216
233
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
217
234
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
218
235
  if (ip) {
219
- requestHeaders.set("Prepr-Visitor-IP", ip);
236
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
220
237
  }
221
238
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
222
239
  if (hutkCookie) {
223
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
240
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
224
241
  }
225
242
  let uid = requestCookies.get(COOKIE_UID);
226
243
  if (!uid) {
@@ -260,16 +277,16 @@ function processPreprRequest(request, opts) {
260
277
  if (!isLivePreview) {
261
278
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
262
279
  if (segmentCookie) {
263
- requestHeaders.set("Prepr-Segments", segmentCookie);
280
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
264
281
  }
265
282
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
266
283
  if (abCookie) {
267
- requestHeaders.set("Prepr-ABtesting", abCookie);
284
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
268
285
  }
269
286
  }
270
287
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
271
288
  if (previewAb !== null) {
272
- requestHeaders.set("Prepr-ABtesting", previewAb);
289
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
273
290
  if (!isLivePreview) {
274
291
  responseCookies.push({
275
292
  name: COOKIE_VARIANT,
@@ -282,7 +299,7 @@ function processPreprRequest(request, opts) {
282
299
  }
283
300
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
284
301
  if (previewSegment !== null) {
285
- requestHeaders.set("Prepr-Segments", previewSegment);
302
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
286
303
  if (!isLivePreview) {
287
304
  responseCookies.push({
288
305
  name: COOKIE_SEGMENT,
@@ -1394,8 +1394,8 @@ function Panel({
1394
1394
  "aria-label": t3("common.toolbar"),
1395
1395
  children: [
1396
1396
  /* @__PURE__ */ u2(Header, { t: t3, onClose: handlers.onClose }),
1397
- /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-adaptive", children: [
1398
- /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: showAdaptive ? t3("adaptiveContent.adaptiveContent") : t3("adaptiveContent.enablePreview") }),
1397
+ showAdaptive && /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-adaptive", children: [
1398
+ /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: t3("adaptiveContent.adaptiveContent") }),
1399
1399
  /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1400
1400
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "preview-label", children: t3("adaptiveContent.enablePreview") }),
1401
1401
  /* @__PURE__ */ u2(PreviewSelector, { state, t: t3, handlers })
@@ -1909,7 +1909,10 @@ function createIframeBridge(store, options = {}) {
1909
1909
  };
1910
1910
  return {
1911
1911
  start() {
1912
- sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
1912
+ const features = store?.get().features;
1913
+ sendPreprEvent("loaded", features ? { features } : void 0, {
1914
+ allowUntrustedTarget: true
1915
+ });
1913
1916
  window.addEventListener("keydown", onKeyDown);
1914
1917
  window.addEventListener("message", onMessage);
1915
1918
  },
@@ -2026,6 +2029,14 @@ function defaultNavigation() {
2026
2029
  currentPath: () => window.location.pathname + window.location.search
2027
2030
  };
2028
2031
  }
2032
+ function safeEditUrl(href) {
2033
+ try {
2034
+ const url = new URL(href, window.location.href);
2035
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2036
+ } catch {
2037
+ return null;
2038
+ }
2039
+ }
2029
2040
  function buildSegments(data) {
2030
2041
  return [{ _id: "all_other_users", name: "All other users" }, ...data];
2031
2042
  }
@@ -2089,10 +2100,20 @@ function createPreprPreview(opts = {}) {
2089
2100
  // In the editor, ask the parent to focus the field instead of opening a new
2090
2101
  // tab. Standalone previews keep the window.open behaviour.
2091
2102
  onEdit: ({ href, origin, id, field }) => {
2103
+ const safeHref = href ? safeEditUrl(href) : null;
2104
+ if (href && !safeHref) {
2105
+ debug7.warn("ignored edit request with unsupported href scheme");
2106
+ return;
2107
+ }
2092
2108
  if (isIframe) {
2093
- sendPreprEvent("field_edit_requested", { href, origin, id, field });
2094
- } else if (href) {
2095
- window.open(href);
2109
+ sendPreprEvent("field_edit_requested", {
2110
+ href: safeHref ?? void 0,
2111
+ origin,
2112
+ id,
2113
+ field
2114
+ });
2115
+ } else if (safeHref) {
2116
+ window.open(safeHref, "_blank", "noopener,noreferrer");
2096
2117
  }
2097
2118
  }
2098
2119
  });
package/dist/index.cjs CHANGED
@@ -44,7 +44,7 @@ __export(src_exports, {
44
44
  module.exports = __toCommonJS(src_exports);
45
45
 
46
46
  // src/version.ts
47
- var VERSION = "0.2.0-beta.4";
47
+ var VERSION = "0.2.0-beta.6";
48
48
 
49
49
  // src/core/features.ts
50
50
  function isEnabled(config) {
@@ -355,7 +355,10 @@ function createIframeBridge(store, options = {}) {
355
355
  };
356
356
  return {
357
357
  start() {
358
- sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
358
+ const features = store?.get().features;
359
+ sendPreprEvent("loaded", features ? { features } : void 0, {
360
+ allowUntrustedTarget: true
361
+ });
359
362
  window.addEventListener("keydown", onKeyDown);
360
363
  window.addEventListener("message", onMessage);
361
364
  },
@@ -1736,8 +1739,8 @@ function Panel({
1736
1739
  "aria-label": t3("common.toolbar"),
1737
1740
  children: [
1738
1741
  /* @__PURE__ */ u2(Header, { t: t3, onClose: handlers.onClose }),
1739
- /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-adaptive", children: [
1740
- /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: showAdaptive ? t3("adaptiveContent.adaptiveContent") : t3("adaptiveContent.enablePreview") }),
1742
+ showAdaptive && /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-adaptive", children: [
1743
+ /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: t3("adaptiveContent.adaptiveContent") }),
1741
1744
  /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1742
1745
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "preview-label", children: t3("adaptiveContent.enablePreview") }),
1743
1746
  /* @__PURE__ */ u2(PreviewSelector, { state, t: t3, handlers })
@@ -2074,6 +2077,14 @@ function defaultNavigation() {
2074
2077
  currentPath: () => window.location.pathname + window.location.search
2075
2078
  };
2076
2079
  }
2080
+ function safeEditUrl(href) {
2081
+ try {
2082
+ const url = new URL(href, window.location.href);
2083
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2084
+ } catch {
2085
+ return null;
2086
+ }
2087
+ }
2077
2088
  function buildSegments(data) {
2078
2089
  return [{ _id: "all_other_users", name: "All other users" }, ...data];
2079
2090
  }
@@ -2137,10 +2148,20 @@ function createPreprPreview(opts = {}) {
2137
2148
  // In the editor, ask the parent to focus the field instead of opening a new
2138
2149
  // tab. Standalone previews keep the window.open behaviour.
2139
2150
  onEdit: ({ href, origin, id, field }) => {
2151
+ const safeHref = href ? safeEditUrl(href) : null;
2152
+ if (href && !safeHref) {
2153
+ debug7.warn("ignored edit request with unsupported href scheme");
2154
+ return;
2155
+ }
2140
2156
  if (isIframe) {
2141
- sendPreprEvent("field_edit_requested", { href, origin, id, field });
2142
- } else if (href) {
2143
- window.open(href);
2157
+ sendPreprEvent("field_edit_requested", {
2158
+ href: safeHref ?? void 0,
2159
+ origin,
2160
+ id,
2161
+ field
2162
+ });
2163
+ } else if (safeHref) {
2164
+ window.open(safeHref, "_blank", "noopener,noreferrer");
2144
2165
  }
2145
2166
  }
2146
2167
  });
@@ -2358,6 +2379,11 @@ var UTM_PARAMS = [
2358
2379
  "utm_content",
2359
2380
  "utm_campaign"
2360
2381
  ];
2382
+ var MAX_HEADER_VALUE_LENGTH = 2048;
2383
+ function sanitizeHeaderValue(value) {
2384
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
2385
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
2386
+ }
2361
2387
  function parseCookies(request) {
2362
2388
  const cookies = /* @__PURE__ */ new Map();
2363
2389
  const header = request.headers.get("cookie");
@@ -2375,6 +2401,15 @@ function parseCookies(request) {
2375
2401
  function processPreprRequest(request, opts) {
2376
2402
  const requestHeaders = new Headers(request.headers);
2377
2403
  const responseCookies = [];
2404
+ const inboundPreprHeaders = [];
2405
+ requestHeaders.forEach((_value, key) => {
2406
+ if (key.toLowerCase().startsWith("prepr-")) {
2407
+ inboundPreprHeaders.push(key);
2408
+ }
2409
+ });
2410
+ for (const key of inboundPreprHeaders) {
2411
+ requestHeaders.delete(key);
2412
+ }
2378
2413
  const requestCookies = parseCookies(request);
2379
2414
  const url = new URL(request.url);
2380
2415
  const searchParams = url.searchParams;
@@ -2384,25 +2419,28 @@ function processPreprRequest(request, opts) {
2384
2419
  for (const key of UTM_PARAMS) {
2385
2420
  const value = searchParams.get(key);
2386
2421
  if (value !== null) {
2387
- requestHeaders.set(`Prepr-Context-${key}`, value);
2422
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
2388
2423
  }
2389
2424
  }
2390
2425
  const referer = request.headers.get("referer");
2391
2426
  if (referer) {
2392
- requestHeaders.set("Prepr-Context-initial_referral", referer);
2427
+ requestHeaders.set(
2428
+ "Prepr-Context-initial_referral",
2429
+ sanitizeHeaderValue(referer)
2430
+ );
2393
2431
  }
2394
2432
  const userAgent = request.headers.get("user-agent");
2395
2433
  if (userAgent) {
2396
- requestHeaders.set("Prepr-User-Agent", userAgent);
2434
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
2397
2435
  }
2398
2436
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
2399
2437
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
2400
2438
  if (ip) {
2401
- requestHeaders.set("Prepr-Visitor-IP", ip);
2439
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
2402
2440
  }
2403
2441
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
2404
2442
  if (hutkCookie) {
2405
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
2443
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
2406
2444
  }
2407
2445
  let uid = requestCookies.get(COOKIE_UID);
2408
2446
  if (!uid) {
@@ -2442,16 +2480,16 @@ function processPreprRequest(request, opts) {
2442
2480
  if (!isLivePreview) {
2443
2481
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
2444
2482
  if (segmentCookie) {
2445
- requestHeaders.set("Prepr-Segments", segmentCookie);
2483
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
2446
2484
  }
2447
2485
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
2448
2486
  if (abCookie) {
2449
- requestHeaders.set("Prepr-ABtesting", abCookie);
2487
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
2450
2488
  }
2451
2489
  }
2452
2490
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
2453
2491
  if (previewAb !== null) {
2454
- requestHeaders.set("Prepr-ABtesting", previewAb);
2492
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
2455
2493
  if (!isLivePreview) {
2456
2494
  responseCookies.push({
2457
2495
  name: COOKIE_VARIANT,
@@ -2464,7 +2502,7 @@ function processPreprRequest(request, opts) {
2464
2502
  }
2465
2503
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
2466
2504
  if (previewSegment !== null) {
2467
- requestHeaders.set("Prepr-Segments", previewSegment);
2505
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
2468
2506
  if (!isLivePreview) {
2469
2507
  responseCookies.push({
2470
2508
  name: COOKIE_SEGMENT,
package/dist/index.d.cts CHANGED
@@ -5,7 +5,7 @@ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j
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.2.0-beta.4";
8
+ declare const VERSION = "0.2.0-beta.6";
9
9
 
10
10
  /**
11
11
  * Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j
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.2.0-beta.4";
8
+ declare const VERSION = "0.2.0-beta.6";
9
9
 
10
10
  /**
11
11
  * Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
package/dist/index.js CHANGED
@@ -17,10 +17,10 @@ import {
17
17
  setTrackingParam,
18
18
  stegaClean,
19
19
  trackEvent
20
- } from "./chunk-6OOGNVJ4.js";
20
+ } from "./chunk-C6YEXA5G.js";
21
21
 
22
22
  // src/version.ts
23
- var VERSION = "0.2.0-beta.4";
23
+ var VERSION = "0.2.0-beta.6";
24
24
 
25
25
  // src/core/server.ts
26
26
  var PreprError = class extends Error {
@@ -184,6 +184,11 @@ var UTM_PARAMS = [
184
184
  "utm_content",
185
185
  "utm_campaign"
186
186
  ];
187
+ var MAX_HEADER_VALUE_LENGTH = 2048;
188
+ function sanitizeHeaderValue(value) {
189
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
190
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
191
+ }
187
192
  function parseCookies(request) {
188
193
  const cookies = /* @__PURE__ */ new Map();
189
194
  const header = request.headers.get("cookie");
@@ -201,6 +206,15 @@ function parseCookies(request) {
201
206
  function processPreprRequest(request, opts) {
202
207
  const requestHeaders = new Headers(request.headers);
203
208
  const responseCookies = [];
209
+ const inboundPreprHeaders = [];
210
+ requestHeaders.forEach((_value, key) => {
211
+ if (key.toLowerCase().startsWith("prepr-")) {
212
+ inboundPreprHeaders.push(key);
213
+ }
214
+ });
215
+ for (const key of inboundPreprHeaders) {
216
+ requestHeaders.delete(key);
217
+ }
204
218
  const requestCookies = parseCookies(request);
205
219
  const url = new URL(request.url);
206
220
  const searchParams = url.searchParams;
@@ -210,25 +224,28 @@ function processPreprRequest(request, opts) {
210
224
  for (const key of UTM_PARAMS) {
211
225
  const value = searchParams.get(key);
212
226
  if (value !== null) {
213
- requestHeaders.set(`Prepr-Context-${key}`, value);
227
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
214
228
  }
215
229
  }
216
230
  const referer = request.headers.get("referer");
217
231
  if (referer) {
218
- requestHeaders.set("Prepr-Context-initial_referral", referer);
232
+ requestHeaders.set(
233
+ "Prepr-Context-initial_referral",
234
+ sanitizeHeaderValue(referer)
235
+ );
219
236
  }
220
237
  const userAgent = request.headers.get("user-agent");
221
238
  if (userAgent) {
222
- requestHeaders.set("Prepr-User-Agent", userAgent);
239
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
223
240
  }
224
241
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
225
242
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
226
243
  if (ip) {
227
- requestHeaders.set("Prepr-Visitor-IP", ip);
244
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
228
245
  }
229
246
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
230
247
  if (hutkCookie) {
231
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
248
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
232
249
  }
233
250
  let uid = requestCookies.get(COOKIE_UID);
234
251
  if (!uid) {
@@ -268,16 +285,16 @@ function processPreprRequest(request, opts) {
268
285
  if (!isLivePreview) {
269
286
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
270
287
  if (segmentCookie) {
271
- requestHeaders.set("Prepr-Segments", segmentCookie);
288
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
272
289
  }
273
290
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
274
291
  if (abCookie) {
275
- requestHeaders.set("Prepr-ABtesting", abCookie);
292
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
276
293
  }
277
294
  }
278
295
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
279
296
  if (previewAb !== null) {
280
- requestHeaders.set("Prepr-ABtesting", previewAb);
297
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
281
298
  if (!isLivePreview) {
282
299
  responseCookies.push({
283
300
  name: COOKIE_VARIANT,
@@ -290,7 +307,7 @@ function processPreprRequest(request, opts) {
290
307
  }
291
308
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
292
309
  if (previewSegment !== null) {
293
- requestHeaders.set("Prepr-Segments", previewSegment);
310
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
294
311
  if (!isLivePreview) {
295
312
  responseCookies.push({
296
313
  name: COOKIE_SEGMENT,
@@ -335,7 +335,10 @@ function createIframeBridge(store, options = {}) {
335
335
  };
336
336
  return {
337
337
  start() {
338
- sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
338
+ const features = store?.get().features;
339
+ sendPreprEvent("loaded", features ? { features } : void 0, {
340
+ allowUntrustedTarget: true
341
+ });
339
342
  window.addEventListener("keydown", onKeyDown);
340
343
  window.addEventListener("message", onMessage);
341
344
  },
@@ -1712,8 +1715,8 @@ function Panel({
1712
1715
  "aria-label": t3("common.toolbar"),
1713
1716
  children: [
1714
1717
  /* @__PURE__ */ u2(Header, { t: t3, onClose: handlers.onClose }),
1715
- /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-adaptive", children: [
1716
- /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: showAdaptive ? t3("adaptiveContent.adaptiveContent") : t3("adaptiveContent.enablePreview") }),
1718
+ showAdaptive && /* @__PURE__ */ u2("div", { class: "prepr-section", "data-prepr": "section-adaptive", children: [
1719
+ /* @__PURE__ */ u2("span", { class: "prepr-section-label", "data-prepr": "adaptive-label", children: t3("adaptiveContent.adaptiveContent") }),
1717
1720
  /* @__PURE__ */ u2("div", { class: "prepr-row", children: [
1718
1721
  /* @__PURE__ */ u2("h2", { class: "prepr-row-title", "data-prepr": "preview-label", children: t3("adaptiveContent.enablePreview") }),
1719
1722
  /* @__PURE__ */ u2(PreviewSelector, { state, t: t3, handlers })
@@ -2050,6 +2053,14 @@ function defaultNavigation() {
2050
2053
  currentPath: () => window.location.pathname + window.location.search
2051
2054
  };
2052
2055
  }
2056
+ function safeEditUrl(href) {
2057
+ try {
2058
+ const url = new URL(href, window.location.href);
2059
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2060
+ } catch {
2061
+ return null;
2062
+ }
2063
+ }
2053
2064
  function buildSegments(data) {
2054
2065
  return [{ _id: "all_other_users", name: "All other users" }, ...data];
2055
2066
  }
@@ -2113,10 +2124,20 @@ function createPreprPreview(opts = {}) {
2113
2124
  // In the editor, ask the parent to focus the field instead of opening a new
2114
2125
  // tab. Standalone previews keep the window.open behaviour.
2115
2126
  onEdit: ({ href, origin, id, field }) => {
2127
+ const safeHref = href ? safeEditUrl(href) : null;
2128
+ if (href && !safeHref) {
2129
+ debug7.warn("ignored edit request with unsupported href scheme");
2130
+ return;
2131
+ }
2116
2132
  if (isIframe) {
2117
- sendPreprEvent("field_edit_requested", { href, origin, id, field });
2118
- } else if (href) {
2119
- window.open(href);
2133
+ sendPreprEvent("field_edit_requested", {
2134
+ href: safeHref ?? void 0,
2135
+ origin,
2136
+ id,
2137
+ field
2138
+ });
2139
+ } else if (safeHref) {
2140
+ window.open(safeHref, "_blank", "noopener,noreferrer");
2120
2141
  }
2121
2142
  }
2122
2143
  });
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createPreprPreview,
4
4
  loadTrackingPixel
5
- } from "../chunk-6OOGNVJ4.js";
5
+ } from "../chunk-C6YEXA5G.js";
6
6
 
7
7
  // src/nextjs/components.ts
8
8
  import { usePathname, useRouter } from "next/navigation";