@preprio/toolkit 0.6.0-alpha.2 → 0.6.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -30
- package/dist/astro/{chunk-U7VUQL57.js → chunk-CUDYSNJD.js} +1 -1
- package/dist/astro/graphql.js +1 -1
- package/dist/astro/index.cjs +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/{chunk-DS3QA3HF.js → chunk-3YZANSVY.js} +1 -1
- package/dist/{chunk-7HSMUULV.js → chunk-FC53KN4H.js} +1 -1
- package/dist/{chunk-UYYTYSOD.js → chunk-UH2OK4XC.js} +1 -1
- package/dist/{chunk-VV4PRH7W.js → chunk-VY5D3IED.js} +1 -1
- package/dist/graphql/codegen.js +3 -3
- package/dist/graphql.js +3 -3
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/nextjs/chunk-5XGJJVGX.js +544 -0
- package/dist/nextjs/components.cjs +155 -2
- package/dist/nextjs/components.d.cts +31 -3
- package/dist/nextjs/components.d.ts +31 -3
- package/dist/nextjs/components.js +155 -3
- package/dist/nextjs/graphql.cjs +588 -50
- package/dist/nextjs/graphql.d.cts +95 -28
- package/dist/nextjs/graphql.d.ts +95 -28
- package/dist/nextjs/graphql.js +287 -50
- package/dist/nextjs/index.cjs +3 -1
- package/dist/nextjs/index.d.cts +32 -3
- package/dist/nextjs/index.d.ts +32 -3
- package/dist/nextjs/index.js +8 -313
- package/dist/nextjs/{server-CjpELYvq.d.cts → server-BL-sqZvu.d.cts} +1 -1
- package/dist/nextjs/{server-CjpELYvq.d.ts → server-BL-sqZvu.d.ts} +1 -1
- package/dist/nuxt/{chunk-GMWF6PQ5.js → chunk-KROR7RXW.js} +1 -1
- package/dist/nuxt/graphql.js +1 -1
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/react/components.cjs +1 -1
- package/dist/react/components.js +1 -1
- package/dist/sveltekit/{chunk-5SQUG2L4.js → chunk-INDMNPEM.js} +1 -1
- package/dist/sveltekit/graphql.js +1 -1
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/package.json +1 -1
- package/dist/nextjs/chunk-ZIUKEGEN.js +0 -231
package/dist/nextjs/graphql.cjs
CHANGED
|
@@ -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.3";
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
)
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
-
|
|
1030
|
-
for (const group of decided.keys()) groups.add(group);
|
|
1084
|
+
groups = /* @__PURE__ */ new Set();
|
|
1031
1085
|
byVariables.set(pageKey, groups);
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
1042
|
-
|
|
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
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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 =
|
|
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
|
-
|
|
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,
|