@preprio/toolkit 0.6.0-alpha.2 → 0.6.0-alpha.4

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.
Files changed (42) hide show
  1. package/README.md +46 -30
  2. package/dist/astro/{chunk-U7VUQL57.js → chunk-TLUVG5P7.js} +1 -1
  3. package/dist/astro/graphql.js +1 -1
  4. package/dist/astro/index.cjs +1 -1
  5. package/dist/astro/index.js +1 -1
  6. package/dist/{chunk-VV4PRH7W.js → chunk-EE3GXKCF.js} +1 -1
  7. package/dist/{chunk-7HSMUULV.js → chunk-MZW2RVGY.js} +1 -1
  8. package/dist/{chunk-UYYTYSOD.js → chunk-XQ4DDVSW.js} +1 -1
  9. package/dist/{chunk-DS3QA3HF.js → chunk-YNZUYENT.js} +1 -1
  10. package/dist/graphql/codegen.js +3 -3
  11. package/dist/graphql.js +3 -3
  12. package/dist/index.cjs +1 -1
  13. package/dist/index.d.cts +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +3 -3
  16. package/dist/nextjs/chunk-HP2SJJSL.js +544 -0
  17. package/dist/nextjs/components.cjs +166 -2
  18. package/dist/nextjs/components.d.cts +32 -4
  19. package/dist/nextjs/components.d.ts +32 -4
  20. package/dist/nextjs/components.js +169 -4
  21. package/dist/nextjs/graphql.cjs +588 -50
  22. package/dist/nextjs/graphql.d.cts +95 -28
  23. package/dist/nextjs/graphql.d.ts +95 -28
  24. package/dist/nextjs/graphql.js +287 -50
  25. package/dist/nextjs/index.cjs +3 -1
  26. package/dist/nextjs/index.d.cts +33 -3
  27. package/dist/nextjs/index.d.ts +33 -3
  28. package/dist/nextjs/index.js +8 -313
  29. package/dist/nextjs/{server-CjpELYvq.d.cts → server-BL-sqZvu.d.cts} +1 -1
  30. package/dist/nextjs/{server-CjpELYvq.d.ts → server-BL-sqZvu.d.ts} +1 -1
  31. package/dist/nuxt/{chunk-GMWF6PQ5.js → chunk-QVY3D5B2.js} +1 -1
  32. package/dist/nuxt/graphql.js +1 -1
  33. package/dist/nuxt/index.cjs +1 -1
  34. package/dist/nuxt/index.js +1 -1
  35. package/dist/react/components.cjs +1 -1
  36. package/dist/react/components.js +1 -1
  37. package/dist/sveltekit/{chunk-5SQUG2L4.js → chunk-VSN5EX3U.js} +1 -1
  38. package/dist/sveltekit/graphql.js +1 -1
  39. package/dist/sveltekit/index.cjs +1 -1
  40. package/dist/sveltekit/index.js +1 -1
  41. package/package.json +1 -1
  42. package/dist/nextjs/chunk-ZIUKEGEN.js +0 -231
@@ -25,6 +25,7 @@ __export(graphql_exports, {
25
25
  PreprError: () => PreprError,
26
26
  PreprStack: () => PreprStack,
27
27
  createPreprClient: () => createPreprClient2,
28
+ createPreprProxy: () => createPreprProxy,
28
29
  decisionsFrom: () => decisionsFrom,
29
30
  isPersonalizedResponse: () => isPersonalizedResponse,
30
31
  loadPreprSchema: () => loadPreprSchema,
@@ -36,6 +37,27 @@ __export(graphql_exports, {
36
37
  });
37
38
  module.exports = __toCommonJS(graphql_exports);
38
39
 
40
+ // src/version.ts
41
+ var VERSION = "0.6.0-alpha.4";
42
+
43
+ // src/core/features.ts
44
+ function isEnabled(config) {
45
+ if (typeof config === "boolean") return config;
46
+ return config?.enabled ?? true;
47
+ }
48
+ function resolveFeatures(features) {
49
+ return {
50
+ segments: isEnabled(features?.segments),
51
+ abTesting: isEnabled(features?.abTesting),
52
+ editMode: isEnabled(features?.editMode)
53
+ };
54
+ }
55
+ var ALL_FEATURES_ENABLED = Object.freeze({
56
+ segments: true,
57
+ abTesting: true,
58
+ editMode: true
59
+ });
60
+
39
61
  // src/core/server.ts
40
62
  var PreprError = class extends Error {
41
63
  constructor(message, code, context, originalError) {
@@ -965,6 +987,49 @@ async function loadPreprSchema(url, options = {}) {
965
987
  return schema;
966
988
  }
967
989
 
990
+ // src/core/refresh.ts
991
+ var PREPR_REFRESH_COOKIE = "prepr_refresh";
992
+ var PREPR_REFRESH_HEADER = "Prepr-Refresh";
993
+ var REFRESH_MAX_PATHS = 5;
994
+ var SAFE_PATH = /^\/(?![/\\])[\w\-./%]{0,200}$/;
995
+ function safePath(path) {
996
+ return SAFE_PATH.test(path) ? path : void 0;
997
+ }
998
+ function isFresh(at, freshFor, now = Date.now()) {
999
+ const age = now - at;
1000
+ return at > 0 && age >= 0 && age < freshFor;
1001
+ }
1002
+ function cleanEntries(entries) {
1003
+ const out = [];
1004
+ for (const entry of entries) {
1005
+ if (out.length === REFRESH_MAX_PATHS) break;
1006
+ if (safePath(entry.path) && !out.some((e) => e.path === entry.path)) {
1007
+ out.push(entry);
1008
+ }
1009
+ }
1010
+ return out;
1011
+ }
1012
+ function parseRefresh(raw) {
1013
+ return cleanEntries(
1014
+ (raw ?? "").split("|").map((pair) => {
1015
+ const split = pair.lastIndexOf("~");
1016
+ const at = Number(split < 0 ? "" : pair.slice(split + 1));
1017
+ return {
1018
+ path: split < 0 ? pair : pair.slice(0, split),
1019
+ at: Number.isFinite(at) && at > 0 ? Math.floor(at) : 0
1020
+ };
1021
+ })
1022
+ );
1023
+ }
1024
+ function serializeRefresh(entries) {
1025
+ return cleanEntries(entries).map((entry) => `${entry.path}~${Math.floor(entry.at)}`).join("|");
1026
+ }
1027
+ function parseRefreshPaths(header) {
1028
+ return cleanEntries(
1029
+ (header ?? "").split("|").map((path) => ({ path, at: 0 }))
1030
+ ).map((entry) => entry.path);
1031
+ }
1032
+
968
1033
  // src/nextjs/decisions.ts
969
1034
  var PREPR_DECISIONS_COOKIE = "prepr_decisions";
970
1035
  var DECISIONS_MAX_GROUPS = 40;
@@ -996,58 +1061,108 @@ function forwardedHeaders(request, response) {
996
1061
  }
997
1062
  return getPreprHeadersFromHeaders(headers2);
998
1063
  }
999
- async function resolveDecisionsWith(client, seenGroups, request, response, document2, variables, options) {
1000
- try {
1001
- if (options?.onlyIf && !await options.onlyIf()) return;
1002
- if (!await client.hasSegments(document2, variables)) return;
1003
- const headers2 = mergePreprHeaders(forwardedHeaders(request, response));
1004
- const stored = parseDecisions(
1005
- request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
1006
- );
1007
- const pageKey = `${JSON.stringify(variables ?? null)}`;
1008
- let byVariables = seenGroups.get(document2);
1009
- if (!byVariables) {
1010
- byVariables = /* @__PURE__ */ new Map();
1011
- seenGroups.set(document2, byVariables);
1012
- }
1013
- const known = byVariables.get(pageKey);
1014
- const isNewVisitor = headers2.get("Prepr-Customer-Id-Created") === "true";
1015
- const contextChanged = isNewVisitor || headers2.has("Prepr-Segments") || [...request.nextUrl.searchParams.keys()].some(
1016
- (key) => key.startsWith("utm_")
1017
- );
1018
- if (options?.reuse !== false && !contextChanged && known && [...known].every((group) => stored.has(group))) {
1019
- return;
1020
- }
1021
- const decided = await client.decide(document2, variables, {
1022
- topLevelOnly: true,
1023
- headers: headers2,
1024
- init: { signal: AbortSignal.timeout(options?.timeoutMs ?? 1e3) }
1025
- });
1026
- if (!known && byVariables.size >= SEEN_MAX_PAGES) {
1064
+ var preprClientInternals = /* @__PURE__ */ new WeakMap();
1065
+ var DEFAULT_FRESH_FOR_MS = 1e4;
1066
+ async function decidePage(client, seenGroups, headers2, document2, variables, timeoutMs = 1e3) {
1067
+ if (!await client.hasSegments(document2, variables)) return void 0;
1068
+ const decided = await client.decide(document2, variables, {
1069
+ topLevelOnly: true,
1070
+ headers: headers2,
1071
+ init: { signal: AbortSignal.timeout(timeoutMs) }
1072
+ });
1073
+ let byVariables = seenGroups.get(document2);
1074
+ if (!byVariables) {
1075
+ byVariables = /* @__PURE__ */ new Map();
1076
+ seenGroups.set(document2, byVariables);
1077
+ }
1078
+ const pageKey = JSON.stringify(variables ?? null);
1079
+ let groups = byVariables.get(pageKey);
1080
+ if (!groups) {
1081
+ if (byVariables.size >= SEEN_MAX_PAGES) {
1027
1082
  byVariables.delete(byVariables.keys().next().value);
1028
1083
  }
1029
- const groups = known ?? /* @__PURE__ */ new Set();
1030
- for (const group of decided.keys()) groups.add(group);
1084
+ groups = /* @__PURE__ */ new Set();
1031
1085
  byVariables.set(pageKey, groups);
1032
- const next = /* @__PURE__ */ new Map();
1033
- for (const [group, variants] of decided) {
1034
- if (SAFE_ID.test(group)) {
1086
+ }
1087
+ for (const group of decided.keys()) groups.add(group);
1088
+ return decided;
1089
+ }
1090
+ function writeDecisions(request, response, decided, paths) {
1091
+ const next = /* @__PURE__ */ new Map();
1092
+ for (const decisions of decided) {
1093
+ for (const [group, variants] of decisions) {
1094
+ if (SAFE_ID.test(group) && !next.has(group)) {
1035
1095
  next.set(
1036
1096
  group,
1037
1097
  variants.filter((v) => SAFE_ID.test(v))
1038
1098
  );
1039
1099
  }
1040
1100
  }
1041
- for (const [group, variants] of stored) {
1042
- if (!next.has(group)) next.set(group, variants);
1101
+ }
1102
+ for (const [group, variants] of parseDecisions(
1103
+ request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
1104
+ )) {
1105
+ if (!next.has(group)) next.set(group, variants);
1106
+ }
1107
+ const options = {
1108
+ path: "/",
1109
+ sameSite: "lax",
1110
+ httpOnly: false,
1111
+ secure: request.nextUrl.protocol === "https:"
1112
+ };
1113
+ if (next.size > 0) {
1114
+ response.cookies.set(
1115
+ PREPR_DECISIONS_COOKIE,
1116
+ serializeDecisions(next),
1117
+ options
1118
+ );
1119
+ }
1120
+ if (paths.length > 0) {
1121
+ const at = Date.now();
1122
+ response.cookies.set(
1123
+ PREPR_REFRESH_COOKIE,
1124
+ serializeRefresh([
1125
+ ...paths.map((path) => ({ path, at })),
1126
+ ...parseRefresh(request.cookies.get(PREPR_REFRESH_COOKIE)?.value)
1127
+ ]),
1128
+ options
1129
+ );
1130
+ }
1131
+ }
1132
+ function pagePath(request) {
1133
+ return request.nextUrl.basePath + request.nextUrl.pathname;
1134
+ }
1135
+ function contextChanged(request, headers2) {
1136
+ return headers2.get("Prepr-Customer-Id-Created") === "true" || headers2.has("Prepr-Segments") || [...request.nextUrl.searchParams.keys()].some(
1137
+ (key) => key.startsWith("utm_")
1138
+ );
1139
+ }
1140
+ async function resolveDecisionsWith(client, seenGroups, request, response, document2, variables, options) {
1141
+ try {
1142
+ if (options?.onlyIf && !await options.onlyIf()) return;
1143
+ const known = seenGroups.get(document2)?.get(JSON.stringify(variables ?? null));
1144
+ const path = pagePath(request);
1145
+ const headers2 = mergePreprHeaders(forwardedHeaders(request, response));
1146
+ const stored = parseDecisions(
1147
+ request.cookies.get(PREPR_DECISIONS_COOKIE)?.value
1148
+ );
1149
+ const decidedAt = parseRefresh(request.cookies.get(PREPR_REFRESH_COOKIE)?.value).find(
1150
+ (entry) => entry.path === path
1151
+ )?.at ?? 0;
1152
+ const fresh = isFresh(decidedAt, options?.freshFor ?? DEFAULT_FRESH_FOR_MS);
1153
+ if (fresh && known && !contextChanged(request, headers2) && [...known].every((group) => stored.has(group))) {
1154
+ return;
1043
1155
  }
1044
- if (next.size === 0) return;
1045
- response.cookies.set(PREPR_DECISIONS_COOKIE, serializeDecisions(next), {
1046
- path: "/",
1047
- sameSite: "lax",
1048
- // Read by the inline switch script before first paint.
1049
- httpOnly: false
1050
- });
1156
+ const decided = await decidePage(
1157
+ client,
1158
+ seenGroups,
1159
+ headers2,
1160
+ document2,
1161
+ variables,
1162
+ options?.timeoutMs
1163
+ );
1164
+ if (!decided || decided.size === 0) return;
1165
+ writeDecisions(request, response, [decided], [path]);
1051
1166
  } catch (error) {
1052
1167
  if (isFrameworkSignal(error)) throw error;
1053
1168
  }
@@ -1063,6 +1178,411 @@ async function getPreprHeaders() {
1063
1178
  return getPreprHeadersFromHeaders(h);
1064
1179
  }
1065
1180
 
1181
+ // src/nextjs/proxy.ts
1182
+ var import_server9 = require("next/server");
1183
+
1184
+ // src/core/constants.ts
1185
+ var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
1186
+ var COOKIE_SEGMENT = "Prepr-Segments";
1187
+ var COOKIE_VARIANT = "Prepr-ABtesting";
1188
+ var COOKIE_UID = "__prepr_uid";
1189
+ var COOKIE_HUBSPOT = "hubspotutk";
1190
+ var PARAM_SEGMENT = "prepr_preview_segment";
1191
+ var PARAM_VARIANT = "prepr_preview_ab";
1192
+ var PARAM_HIDE_BAR = "prepr_hide_bar";
1193
+ var ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
1194
+
1195
+ // src/nextjs/bucket.ts
1196
+ var PREPR_BUCKET_COOKIE = "prepr_ab_bucket";
1197
+ var isBucket = (value) => value === "A" || value === "B";
1198
+ function setBucketCookie(request, response, bucket) {
1199
+ response.cookies.set(PREPR_BUCKET_COOKIE, bucket, {
1200
+ path: "/",
1201
+ sameSite: "lax",
1202
+ // Readable on purpose: PreprStack's inline switch reads it before first
1203
+ // paint. It holds only "A" or "B".
1204
+ httpOnly: false,
1205
+ secure: request.nextUrl.protocol === "https:",
1206
+ maxAge: 60 * 60 * 24 * 365
1207
+ });
1208
+ }
1209
+ async function resolvePreprBucket(request, response, options) {
1210
+ const stored = request.cookies.get(PREPR_BUCKET_COOKIE)?.value;
1211
+ const forced = response.headers.get("x-middleware-request-prepr-abtesting") ?? request.cookies.get(COOKIE_VARIANT)?.value;
1212
+ if (options.preview && isBucket(forced)) {
1213
+ if (forced !== stored) setBucketCookie(request, response, forced);
1214
+ return forced;
1215
+ }
1216
+ if (isBucket(stored)) return stored;
1217
+ if (options.onlyIf) {
1218
+ const needed = await Promise.resolve().then(options.onlyIf).catch(() => true);
1219
+ if (!needed) return "A";
1220
+ }
1221
+ const customerId = request.cookies.get(COOKIE_UID)?.value ?? response.cookies.get(COOKIE_UID)?.value;
1222
+ if (!customerId) return "A";
1223
+ try {
1224
+ const res = await fetch(options.url, {
1225
+ method: "POST",
1226
+ headers: {
1227
+ "Content-Type": "application/json",
1228
+ "Prepr-Customer-Id": customerId
1229
+ },
1230
+ body: JSON.stringify({ query: "{ __typename }" }),
1231
+ cache: "no-store",
1232
+ signal: AbortSignal.timeout(options.timeoutMs ?? 1500)
1233
+ });
1234
+ const bucket = res.headers.get("x-prepr-customer-bucket");
1235
+ if (isBucket(bucket)) {
1236
+ setBucketCookie(request, response, bucket);
1237
+ return bucket;
1238
+ }
1239
+ } catch {
1240
+ }
1241
+ return "A";
1242
+ }
1243
+
1244
+ // src/nextjs/draft.ts
1245
+ var import_headers2 = require("next/headers");
1246
+ var import_navigation = require("next/navigation");
1247
+ var import_server7 = require("next/server");
1248
+ var DRAFT_COOKIE = "__prerender_bypass";
1249
+ var RETURN_PARAM = "prepr_draft";
1250
+ var DEFAULT_ROUTE = "/api/prepr/draft";
1251
+ function preprDraftRedirect(request, options) {
1252
+ if (!options.preview) return null;
1253
+ if (request.method !== "GET" && request.method !== "HEAD") return null;
1254
+ const url = request.nextUrl;
1255
+ if (url.pathname === (options.route ?? DEFAULT_ROUTE)) return null;
1256
+ if (request.cookies.has(DRAFT_COOKIE)) {
1257
+ if (!url.searchParams.has(RETURN_PARAM)) return null;
1258
+ const clean = url.clone();
1259
+ clean.searchParams.delete(RETURN_PARAM);
1260
+ return import_server7.NextResponse.redirect(clean, 307);
1261
+ }
1262
+ if (url.searchParams.has(RETURN_PARAM)) return null;
1263
+ const target = new URL(options.route ?? DEFAULT_ROUTE, request.url);
1264
+ target.searchParams.set("to", url.pathname + url.search);
1265
+ return import_server7.NextResponse.redirect(target, 307);
1266
+ }
1267
+
1268
+ // src/nextjs/middleware.ts
1269
+ var import_server8 = require("next/server");
1270
+
1271
+ // src/core/middleware.ts
1272
+ var UTM_PARAMS = [
1273
+ "utm_source",
1274
+ "utm_medium",
1275
+ "utm_term",
1276
+ "utm_content",
1277
+ "utm_campaign"
1278
+ ];
1279
+ var MAX_HEADER_VALUE_LENGTH = 2048;
1280
+ function sanitizeHeaderValue(value) {
1281
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
1282
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
1283
+ }
1284
+ function parseCookies(request) {
1285
+ const cookies2 = /* @__PURE__ */ new Map();
1286
+ const header = request.headers.get("cookie");
1287
+ if (!header) return cookies2;
1288
+ for (const part of header.split(";")) {
1289
+ const eq = part.indexOf("=");
1290
+ if (eq === -1) continue;
1291
+ const name2 = part.slice(0, eq).trim();
1292
+ const value = part.slice(eq + 1).trim();
1293
+ if (!name2) continue;
1294
+ try {
1295
+ cookies2.set(name2, decodeURIComponent(value));
1296
+ } catch {
1297
+ cookies2.set(name2, value);
1298
+ }
1299
+ }
1300
+ return cookies2;
1301
+ }
1302
+ function processFrameworkRequest(request, opts, framework) {
1303
+ const requestHeaders = new Headers(request.headers);
1304
+ const responseCookies = [];
1305
+ const inboundPreprHeaders = [];
1306
+ requestHeaders.forEach((_value, key) => {
1307
+ if (key.toLowerCase().startsWith("prepr-")) {
1308
+ inboundPreprHeaders.push(key);
1309
+ }
1310
+ });
1311
+ for (const key of inboundPreprHeaders) {
1312
+ requestHeaders.delete(key);
1313
+ }
1314
+ const requestCookies = parseCookies(request);
1315
+ const url = new URL(request.url);
1316
+ const searchParams = url.searchParams;
1317
+ const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
1318
+ const isSecure = forwardedProto ? forwardedProto === "https" : url.protocol === "https:";
1319
+ const crossSite = isSecure ? { sameSite: "None", secure: true } : {};
1320
+ for (const key of UTM_PARAMS) {
1321
+ const value = searchParams.get(key);
1322
+ if (value !== null) {
1323
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
1324
+ }
1325
+ }
1326
+ const referer = request.headers.get("referer");
1327
+ if (referer) {
1328
+ requestHeaders.set(
1329
+ "Prepr-Context-initial_referral",
1330
+ sanitizeHeaderValue(referer)
1331
+ );
1332
+ }
1333
+ const userAgent = request.headers.get("user-agent");
1334
+ if (userAgent) {
1335
+ requestHeaders.set("User-Agent", sanitizeHeaderValue(userAgent));
1336
+ }
1337
+ const packageParts = [`@preprio/toolkit@${opts?.version ?? VERSION}`];
1338
+ if (framework) packageParts.push(`framework=${framework}`);
1339
+ packageParts.push(`preview=${opts?.preview ? "true" : "false"}`);
1340
+ requestHeaders.set("Prepr-Package", packageParts.join("; "));
1341
+ const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
1342
+ if (ip) {
1343
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
1344
+ }
1345
+ const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
1346
+ if (hutkCookie) {
1347
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
1348
+ }
1349
+ let uid = sanitizeHeaderValue(requestCookies.get(COOKIE_UID) ?? "");
1350
+ if (!uid) {
1351
+ uid = crypto.randomUUID();
1352
+ responseCookies.push({
1353
+ name: COOKIE_UID,
1354
+ value: uid,
1355
+ maxAge: ONE_YEAR_SECONDS,
1356
+ path: "/"
1357
+ });
1358
+ requestHeaders.set("Prepr-Customer-Id-Created", "true");
1359
+ }
1360
+ requestHeaders.set("Prepr-Customer-Id", uid);
1361
+ if (!opts?.preview) {
1362
+ return { requestHeaders, responseCookies };
1363
+ }
1364
+ const features = resolveFeatures(opts?.features);
1365
+ const hasPreviewParams = features.segments && searchParams.has(PARAM_SEGMENT) || features.abTesting && searchParams.has(PARAM_VARIANT);
1366
+ if (!hasPreviewParams) {
1367
+ const previewModeCookie = requestCookies.get(COOKIE_PREVIEW_MODE);
1368
+ const previewModeEnabled = previewModeCookie !== "false";
1369
+ if (!previewModeEnabled) {
1370
+ return { requestHeaders, responseCookies };
1371
+ }
1372
+ }
1373
+ requestHeaders.set("Prepr-Preview-Bar", "true");
1374
+ const isLivePreview = searchParams.get(PARAM_HIDE_BAR) === "true";
1375
+ if (!isLivePreview && !requestCookies.has(COOKIE_PREVIEW_MODE)) {
1376
+ responseCookies.push({
1377
+ name: COOKIE_PREVIEW_MODE,
1378
+ value: "true",
1379
+ maxAge: ONE_YEAR_SECONDS,
1380
+ path: "/",
1381
+ ...crossSite
1382
+ });
1383
+ }
1384
+ if (!isLivePreview) {
1385
+ const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
1386
+ if (segmentCookie) {
1387
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
1388
+ }
1389
+ const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
1390
+ if (abCookie) {
1391
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
1392
+ }
1393
+ }
1394
+ const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
1395
+ if (previewAb !== null) {
1396
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
1397
+ if (!isLivePreview) {
1398
+ responseCookies.push({
1399
+ name: COOKIE_VARIANT,
1400
+ value: previewAb,
1401
+ maxAge: ONE_YEAR_SECONDS,
1402
+ path: "/",
1403
+ ...crossSite
1404
+ });
1405
+ }
1406
+ }
1407
+ const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
1408
+ if (previewSegment !== null) {
1409
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
1410
+ if (!isLivePreview) {
1411
+ responseCookies.push({
1412
+ name: COOKIE_SEGMENT,
1413
+ value: previewSegment,
1414
+ maxAge: ONE_YEAR_SECONDS,
1415
+ path: "/",
1416
+ ...crossSite
1417
+ });
1418
+ }
1419
+ }
1420
+ if (requestHeaders.has("Prepr-Segments")) {
1421
+ requestHeaders.delete("Prepr-Customer-Id");
1422
+ requestHeaders.delete("Prepr-Customer-Id-Created");
1423
+ }
1424
+ return { requestHeaders, responseCookies };
1425
+ }
1426
+
1427
+ // src/nextjs/middleware.ts
1428
+ function createPreprMiddleware(request, responseOrOptions, maybeOptions) {
1429
+ let chainedResponse;
1430
+ let options;
1431
+ if (responseOrOptions instanceof import_server8.NextResponse) {
1432
+ chainedResponse = responseOrOptions;
1433
+ options = maybeOptions;
1434
+ } else {
1435
+ options = responseOrOptions;
1436
+ }
1437
+ const { requestHeaders, responseCookies } = processFrameworkRequest(
1438
+ request,
1439
+ options,
1440
+ "nextjs"
1441
+ );
1442
+ const response = chainedResponse ?? import_server8.NextResponse.next({ request: { headers: requestHeaders } });
1443
+ if (chainedResponse) {
1444
+ const existing = response.headers.get("x-middleware-override-headers");
1445
+ const names = new Set(
1446
+ existing ? existing.split(",").map((name2) => name2.trim()).filter(Boolean) : []
1447
+ );
1448
+ requestHeaders.forEach((value, key) => {
1449
+ names.add(key);
1450
+ response.headers.set(`x-middleware-request-${key}`, value);
1451
+ });
1452
+ response.headers.set("x-middleware-override-headers", [...names].join(","));
1453
+ }
1454
+ for (const cookie of responseCookies) {
1455
+ response.cookies.set(cookie.name, cookie.value, {
1456
+ maxAge: cookie.maxAge,
1457
+ path: cookie.path,
1458
+ // Lower-cased: Next's cookie API expects 'none' | 'lax' | 'strict'.
1459
+ ...cookie.sameSite ? {
1460
+ sameSite: cookie.sameSite.toLowerCase()
1461
+ } : {},
1462
+ ...cookie.secure ? { secure: true } : {}
1463
+ });
1464
+ }
1465
+ return response;
1466
+ }
1467
+
1468
+ // src/nextjs/proxy.ts
1469
+ function isPagePath(pathname) {
1470
+ return !pathname.startsWith("/_next/") && !pathname.startsWith("/api/") && !/\.[^/]*$/.test(pathname);
1471
+ }
1472
+ function createPreprProxy(options) {
1473
+ const internals = preprClientInternals.get(options.client);
1474
+ if (!internals) {
1475
+ throw new PreprError(
1476
+ "createPreprProxy needs a client made by createPreprClient from @preprio/toolkit/nextjs/graphql",
1477
+ "CONTEXT_ERROR",
1478
+ "createPreprProxy"
1479
+ );
1480
+ }
1481
+ const { client, seenGroups, url } = internals;
1482
+ const preview = options.preview ?? false;
1483
+ const allowed = async (request) => {
1484
+ if (!options.onlyIf) return true;
1485
+ try {
1486
+ return await options.onlyIf(request);
1487
+ } catch {
1488
+ return false;
1489
+ }
1490
+ };
1491
+ const lookup = (target) => {
1492
+ try {
1493
+ return options.page(target) ?? null;
1494
+ } catch {
1495
+ return null;
1496
+ }
1497
+ };
1498
+ async function refresh(request, response, paths) {
1499
+ const answer = new import_server9.NextResponse(null, {
1500
+ status: 204,
1501
+ headers: { "Cache-Control": "no-store" }
1502
+ });
1503
+ for (const cookie of response.cookies.getAll()) answer.cookies.set(cookie);
1504
+ const site = request.headers.get("sec-fetch-site");
1505
+ if (request.method !== "GET" || site !== null && site !== "same-origin" || !await allowed(request)) {
1506
+ return answer;
1507
+ }
1508
+ const own = new Set(
1509
+ parseRefresh(request.cookies.get(PREPR_REFRESH_COOKIE)?.value).map(
1510
+ (entry) => entry.path
1511
+ )
1512
+ );
1513
+ const basePath = request.nextUrl.basePath;
1514
+ const headers2 = mergePreprHeaders(forwardedHeaders(request, response));
1515
+ const decided = await Promise.all(
1516
+ paths.map(async (path) => {
1517
+ if (!own.has(path) || !path.startsWith(basePath)) return void 0;
1518
+ const pathname = path.slice(basePath.length) || "/";
1519
+ if (!isPagePath(pathname)) return void 0;
1520
+ const target = lookup(new URL(pathname, request.url));
1521
+ if (!target) return void 0;
1522
+ try {
1523
+ const decisions = await decidePage(
1524
+ client,
1525
+ seenGroups,
1526
+ headers2,
1527
+ target.document,
1528
+ target.variables,
1529
+ options.timeoutMs
1530
+ );
1531
+ return decisions?.size ? [path, decisions] : void 0;
1532
+ } catch (error) {
1533
+ if (isFrameworkSignal(error)) throw error;
1534
+ return void 0;
1535
+ }
1536
+ })
1537
+ );
1538
+ const found = decided.filter((entry) => entry !== void 0);
1539
+ writeDecisions(
1540
+ request,
1541
+ answer,
1542
+ found.map(([, decisions]) => decisions),
1543
+ found.map(([path]) => path)
1544
+ );
1545
+ return answer;
1546
+ }
1547
+ return async (request) => {
1548
+ if (request.headers.has(PREPR_REFRESH_HEADER)) {
1549
+ return refresh(
1550
+ request,
1551
+ createPreprMiddleware(request, { preview, features: options.features }),
1552
+ parseRefreshPaths(request.headers.get(PREPR_REFRESH_HEADER))
1553
+ );
1554
+ }
1555
+ const target = isPagePath(request.nextUrl.pathname) ? lookup(request.nextUrl) : null;
1556
+ if (target && preview) {
1557
+ const draft = preprDraftRedirect(request, { preview });
1558
+ if (draft) return draft;
1559
+ }
1560
+ const response = createPreprMiddleware(request, {
1561
+ preview,
1562
+ features: options.features
1563
+ });
1564
+ if (target && await allowed(request)) {
1565
+ await Promise.all([
1566
+ resolvePreprBucket(request, response, {
1567
+ url,
1568
+ preview,
1569
+ onlyIf: () => options.client.hasAbTests(target.document, target.variables)
1570
+ }),
1571
+ resolveDecisionsWith(
1572
+ client,
1573
+ seenGroups,
1574
+ request,
1575
+ response,
1576
+ target.document,
1577
+ target.variables,
1578
+ { freshFor: options.freshFor, timeoutMs: options.timeoutMs }
1579
+ )
1580
+ ]);
1581
+ }
1582
+ return response;
1583
+ };
1584
+ }
1585
+
1066
1586
  // src/nextjs/stack.ts
1067
1587
  var import_react = require("react");
1068
1588
 
@@ -1089,7 +1609,11 @@ function applyPreprAbBucket() {
1089
1609
  state.__preprAbSheet.textContent = css;
1090
1610
  }
1091
1611
  }
1092
- function applyPreprDecisions() {
1612
+ function applyPreprDecisions(view) {
1613
+ const viewState = window;
1614
+ const current = view === void 0 ? location.pathname + location.search : view;
1615
+ if (viewState.__preprDecisionsView === current) return;
1616
+ viewState.__preprDecisionsView = current;
1093
1617
  const match = document.cookie.match(/(?:^|; )prepr_decisions=([^;]*)/);
1094
1618
  let raw;
1095
1619
  try {
@@ -1153,13 +1677,18 @@ function renderBuckets(items, render, options) {
1153
1677
  abTest,
1154
1678
  index,
1155
1679
  track,
1156
- placeholder = false
1680
+ placeholder = false,
1681
+ defaultVariant = true
1157
1682
  } = options;
1158
1683
  const renderVariant = (variant, key) => {
1159
1684
  const resolved = resolveVariants(items, { decisions, bucket: variant });
1160
- const content = render(resolved, { index, placeholder });
1161
- const variantKeys = track ? variantKeysOf(resolved) : [];
1162
1685
  const ab = abTest && !bucket ? variant : void 0;
1686
+ const content = render(resolved, {
1687
+ index,
1688
+ placeholder,
1689
+ defaultVariant: defaultVariant && (ab === void 0 || ab === "A")
1690
+ });
1691
+ const variantKeys = track ? variantKeysOf(resolved) : [];
1163
1692
  if (variantKeys.length > 0) {
1164
1693
  return (0, import_react.createElement)(
1165
1694
  import_components.PreprVariantTracking,
@@ -1197,7 +1726,8 @@ function renderAllVariants(block, render, options) {
1197
1726
  decisions: /* @__PURE__ */ new Map([[group, [variant]]]),
1198
1727
  abTest: false,
1199
1728
  index: block.index,
1200
- track: options.track
1729
+ track: options.track,
1730
+ defaultVariant: variant === PREPR_DEFAULT_VARIANT
1201
1731
  })
1202
1732
  )
1203
1733
  );
@@ -1230,7 +1760,7 @@ function PreprStack({
1230
1760
  bucket,
1231
1761
  nonce,
1232
1762
  trackVariants = false,
1233
- aboveTheFold = 0
1763
+ aboveTheFold = 1
1234
1764
  }) {
1235
1765
  decisions.catch(() => void 0);
1236
1766
  const blocks = splitStack(items);
@@ -1253,7 +1783,8 @@ function PreprStack({
1253
1783
  nonce,
1254
1784
  dangerouslySetInnerHTML: { __html: AB_SWITCH_SCRIPT }
1255
1785
  }),
1256
- (0, import_react.createElement)(import_components.PreprAbSwitch)
1786
+ (0, import_react.createElement)(import_components.PreprAbSwitch),
1787
+ hasDecided && (0, import_react.createElement)(import_components.PreprPersonalizationSwitch, { refresh: false })
1257
1788
  ),
1258
1789
  ...blocks.map((block) => {
1259
1790
  if (decidedInProxy(block)) {
@@ -1302,7 +1833,7 @@ function createPreprClient2(options) {
1302
1833
  ...coreOptions
1303
1834
  });
1304
1835
  const seenGroups = /* @__PURE__ */ new Map();
1305
- return {
1836
+ const nextClient = {
1306
1837
  async query(document2, variables, queryOptions = {}) {
1307
1838
  const { personalize = false, ...rest } = queryOptions;
1308
1839
  const init = rest.init;
@@ -1343,6 +1874,12 @@ function createPreprClient2(options) {
1343
1874
  options2
1344
1875
  )
1345
1876
  };
1877
+ preprClientInternals.set(nextClient, {
1878
+ client,
1879
+ seenGroups,
1880
+ url: coreOptions.url
1881
+ });
1882
+ return nextClient;
1346
1883
  }
1347
1884
  // Annotate the CommonJS export names for ESM import in node:
1348
1885
  0 && (module.exports = {
@@ -1351,6 +1888,7 @@ function createPreprClient2(options) {
1351
1888
  PreprError,
1352
1889
  PreprStack,
1353
1890
  createPreprClient,
1891
+ createPreprProxy,
1354
1892
  decisionsFrom,
1355
1893
  isPersonalizedResponse,
1356
1894
  loadPreprSchema,