@preprio/toolkit 0.5.0-rc.1 → 0.5.0-rc.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.
Files changed (50) hide show
  1. package/README.md +207 -10
  2. package/dist/astro/index.cjs +1174 -1
  3. package/dist/astro/index.d.cts +47 -1
  4. package/dist/astro/index.d.ts +47 -1
  5. package/dist/astro/index.js +1177 -1
  6. package/dist/chunk-42Z2VW7G.js +1161 -0
  7. package/dist/chunk-6WY3MWCX.js +246 -0
  8. package/dist/chunk-MDAV2FLM.js +26 -0
  9. package/dist/{chunk-4JNKYIEV.js → chunk-PP3O7RB4.js} +1370 -617
  10. package/dist/chunk-S3VT4UWM.js +75 -0
  11. package/dist/chunk-SXUDBGLZ.js +184 -0
  12. package/dist/chunk-URXSDJOU.js +356 -0
  13. package/dist/index.cjs +3113 -391
  14. package/dist/index.d.cts +240 -5
  15. package/dist/index.d.ts +240 -5
  16. package/dist/index.js +189 -149
  17. package/dist/live-preview-EADA3ZL4.js +18 -0
  18. package/dist/nextjs/components.cjs +1530 -361
  19. package/dist/nextjs/components.d.cts +1 -1
  20. package/dist/nextjs/components.d.ts +1 -1
  21. package/dist/nextjs/components.js +4 -1
  22. package/dist/nextjs/index.cjs +1065 -6
  23. package/dist/nextjs/index.d.cts +81 -3
  24. package/dist/nextjs/index.d.ts +81 -3
  25. package/dist/nextjs/index.js +1059 -7
  26. package/dist/nextjs/live-action.cjs +285 -0
  27. package/dist/nextjs/live-action.d.cts +12 -0
  28. package/dist/nextjs/live-action.d.ts +12 -0
  29. package/dist/nextjs/live-action.js +68 -0
  30. package/dist/nextjs/live.cjs +1838 -0
  31. package/dist/nextjs/live.d.cts +52 -0
  32. package/dist/nextjs/live.d.ts +52 -0
  33. package/dist/nextjs/live.js +265 -0
  34. package/dist/nuxt/index.cjs +1 -1
  35. package/dist/nuxt/index.js +1 -1
  36. package/dist/nuxt/live-preview-PTEC3GPC.js +1462 -0
  37. package/dist/nuxt/live.cjs +1662 -0
  38. package/dist/nuxt/live.d.cts +43 -0
  39. package/dist/nuxt/live.d.ts +43 -0
  40. package/dist/nuxt/live.js +94 -0
  41. package/dist/{pixel-e1fUWl2Y.d.cts → pixel-CX422dCO.d.cts} +18 -3
  42. package/dist/{pixel-e1fUWl2Y.d.ts → pixel-CX422dCO.d.ts} +18 -3
  43. package/dist/react/components.cjs +1530 -361
  44. package/dist/react/components.d.cts +17 -2
  45. package/dist/react/components.d.ts +17 -2
  46. package/dist/react/components.js +1530 -361
  47. package/dist/sveltekit/index.cjs +1 -1
  48. package/dist/sveltekit/index.js +1 -1
  49. package/package.json +12 -1
  50. package/src/astro/PreprToolbar.astro +7 -1
package/dist/index.js CHANGED
@@ -4,185 +4,220 @@ import {
4
4
  COOKIE_SEGMENT,
5
5
  COOKIE_UID,
6
6
  COOKIE_VARIANT,
7
+ HEADER_LIVE_RENDER,
7
8
  ONE_YEAR_SECONDS,
8
9
  PARAM_HIDE_BAR,
9
10
  PARAM_SEGMENT,
10
11
  PARAM_VARIANT,
11
12
  PreprToolbarElement,
12
- VERSION,
13
13
  createPreprPreview,
14
14
  createToolbarStore,
15
15
  definePreprToolbar,
16
16
  loadTrackingPixel,
17
- resolveFeatures,
18
17
  setTrackingParam,
19
18
  stegaClean,
20
19
  trackEvent
21
- } from "./chunk-4JNKYIEV.js";
20
+ } from "./chunk-PP3O7RB4.js";
21
+ import {
22
+ PreprError,
23
+ extractAccessToken,
24
+ getActiveSegmentFromHeaders,
25
+ getActiveVariantFromHeaders,
26
+ getPreprEnvironmentSegments,
27
+ getPreprHeadersFromHeaders,
28
+ getPreprUUIDFromHeaders,
29
+ getToolbarPropsFromHeaders,
30
+ validatePreprToken
31
+ } from "./chunk-SXUDBGLZ.js";
32
+ import {
33
+ VERSION,
34
+ resolveFeatures
35
+ } from "./chunk-MDAV2FLM.js";
36
+ import {
37
+ createLiveBridge,
38
+ createLiveStore
39
+ } from "./chunk-6WY3MWCX.js";
40
+ import "./chunk-URXSDJOU.js";
41
+ import {
42
+ normalizeQuery,
43
+ parsePatchLog
44
+ } from "./chunk-42Z2VW7G.js";
45
+ import {
46
+ createScopedLogger
47
+ } from "./chunk-S3VT4UWM.js";
22
48
 
23
- // src/core/server.ts
24
- var PreprError = class extends Error {
25
- constructor(message, code, context, originalError) {
26
- super(message);
27
- this.code = code;
28
- this.context = context;
29
- this.originalError = originalError;
30
- this.name = "PreprError";
31
- }
32
- };
33
- function getPreprUUIDFromHeaders(headers) {
34
- return headers.get("prepr-customer-id");
49
+ // src/core/preview-attrs.ts
50
+ function previewAttrs(target, options) {
51
+ if (!options.enabled || !target.id || !target.field) return {};
52
+ const attrs = {
53
+ "data-prepr-encoded": "",
54
+ "data-prepr-id": target.id,
55
+ "data-prepr-field": target.field
56
+ };
57
+ if (target.locale) attrs["data-prepr-locale"] = target.locale;
58
+ if (target.entryId) attrs["data-prepr-entry-id"] = target.entryId;
59
+ return attrs;
35
60
  }
36
- function getActiveSegmentFromHeaders(headers) {
37
- return headers.get("Prepr-Segments");
61
+
62
+ // src/core/live-preview/morph.ts
63
+ function morphElement(current, next) {
64
+ syncAttributes(current, next);
65
+ morphChildren(current, next);
38
66
  }
39
- function getActiveVariantFromHeaders(headers) {
40
- return headers.get("Prepr-ABtesting");
67
+ var SKIP_ATTR = "data-prepr-morph-skip";
68
+ var RUNTIME_ATTR_PREFIXES = ["data-prepr-"];
69
+ function isRuntimeAttribute(name) {
70
+ return RUNTIME_ATTR_PREFIXES.some((prefix) => name.startsWith(prefix));
41
71
  }
42
- var PREPR_HEADER_KEYS = [
43
- "prepr-customer-id",
44
- "Prepr-Segments",
45
- "Prepr-ABtesting",
46
- "Prepr-Preview-Bar",
47
- "Prepr-Context-utm_source",
48
- "Prepr-Context-utm_medium",
49
- "Prepr-Context-utm_term",
50
- "Prepr-Context-utm_content",
51
- "Prepr-Context-utm_campaign",
52
- "Prepr-Context-initial_referral",
53
- "Prepr-Visitor-IP",
54
- "Prepr-Hubspot-Id",
55
- "Prepr-Customer-Id-Created",
56
- "User-Agent",
57
- "Prepr-Package"
58
- ];
59
- function getPreprHeadersFromHeaders(headers) {
60
- const preprHeaders = {};
61
- const copy = (key) => {
62
- const value = headers.get(key);
63
- if (value !== null) {
64
- preprHeaders[key] = value;
72
+ function syncAttributes(current, next) {
73
+ for (const { name } of Array.from(current.attributes)) {
74
+ if (!next.hasAttribute(name) && !isRuntimeAttribute(name)) {
75
+ current.removeAttribute(name);
65
76
  }
66
- };
67
- for (const key of PREPR_HEADER_KEYS) {
68
- copy(key);
69
77
  }
70
- return preprHeaders;
71
- }
72
- function validatePreprToken(token) {
73
- if (!token) {
74
- throw new PreprError(
75
- "Token is required",
76
- "MISSING_TOKEN",
77
- "validatePreprToken"
78
- );
79
- }
80
- if (!token.startsWith("https://")) {
81
- throw new PreprError(
82
- "Token must be a valid HTTPS URL",
83
- "INVALID_TOKEN",
84
- "validatePreprToken"
85
- );
78
+ for (const { name, value } of Array.from(next.attributes)) {
79
+ if (current.getAttribute(name) !== value) current.setAttribute(name, value);
86
80
  }
87
81
  }
88
- function extractAccessToken(graphqlUrl) {
89
- let url;
90
- try {
91
- url = new URL(graphqlUrl);
92
- } catch (error) {
93
- throw new PreprError(
94
- "Token must be a valid URL",
95
- "INVALID_TOKEN",
96
- "extractAccessToken",
97
- error instanceof Error ? error : new Error(String(error))
98
- );
99
- }
100
- if (url.hostname !== "graphql.prepr.io") {
101
- throw new PreprError(
102
- "Token must be a graphql.prepr.io URL",
103
- "INVALID_TOKEN",
104
- "extractAccessToken"
105
- );
106
- }
107
- const pathParts = url.pathname.split("/");
108
- const token = pathParts[pathParts.length - 1];
109
- if (!token) {
110
- throw new PreprError(
111
- "Token URL is missing an access token segment",
112
- "INVALID_TOKEN",
113
- "extractAccessToken"
114
- );
115
- }
116
- return token;
82
+ function isSkipped(node) {
83
+ return node.nodeType === Node.ELEMENT_NODE && (node.hasAttribute(SKIP_ATTR) || node.localName === "prepr-toolbar");
117
84
  }
118
- async function getPreprEnvironmentSegments(token) {
119
- validatePreprToken(token);
120
- try {
121
- const response = await fetch(token, {
122
- headers: {
123
- "Prepr-Package": `@preprio/toolkit@${VERSION}`,
124
- // Duplicates the version in Prepr-Package. It keeps the User-Agent
125
- // format used by `@preprio/prepr-nextjs`, for Prepr API consumers that
126
- // still identify the toolbar by that string.
127
- "User-Agent": `Prepr-Preview-Bar/${VERSION}`,
128
- "Content-Type": "application/json"
129
- },
130
- method: "POST",
131
- body: JSON.stringify({
132
- query: `{
133
- _Segments {
134
- _id
135
- name
136
- }
137
- }`
138
- })
139
- });
140
- if (!response.ok) {
141
- throw new PreprError(
142
- `HTTP ${response.status}: ${response.statusText}`,
143
- "HTTP_ERROR",
144
- "getPreprEnvironmentSegments"
145
- );
146
- }
147
- const json = await response.json();
148
- if (!json || !json.data || !json.data._Segments) {
149
- throw new PreprError(
150
- "Invalid response format from Prepr API",
151
- "INVALID_RESPONSE",
152
- "getPreprEnvironmentSegments"
153
- );
154
- }
155
- return json.data._Segments;
156
- } catch (error) {
157
- if (error instanceof PreprError) {
158
- throw error;
85
+ function sameKind(a, b) {
86
+ if (a.nodeType !== b.nodeType) return false;
87
+ if (a.nodeType !== Node.ELEMENT_NODE) return true;
88
+ const ea = a;
89
+ const eb = b;
90
+ return ea.localName === eb.localName && ea.id === eb.id;
91
+ }
92
+ function morphChildren(current, next) {
93
+ const wanted = Array.from(next.childNodes).filter((n) => !isSkipped(n));
94
+ const existing = Array.from(current.childNodes).filter((n) => !isSkipped(n));
95
+ wanted.forEach((target, i) => {
96
+ const node = existing[i];
97
+ if (node && sameKind(node, target)) {
98
+ if (node.nodeType === Node.ELEMENT_NODE) {
99
+ const element = node;
100
+ if (element.localName === "script") return;
101
+ if (element.localName === "astro-island" && !element.isEqualNode(target)) {
102
+ element.replaceWith(adopt(target, current));
103
+ return;
104
+ }
105
+ morphElement(element, target);
106
+ } else if (node.nodeValue !== target.nodeValue) {
107
+ node.nodeValue = target.nodeValue;
108
+ }
109
+ return;
159
110
  }
160
- throw new PreprError(
161
- "Failed to fetch segments from Prepr API",
162
- "FETCH_ERROR",
163
- "getPreprEnvironmentSegments",
164
- error instanceof Error ? error : new Error(String(error))
165
- );
111
+ const replacement = adopt(target, current);
112
+ if (node) node.replaceWith(replacement);
113
+ else current.appendChild(replacement);
114
+ });
115
+ for (const extra of existing.slice(wanted.length)) extra.remove();
116
+ }
117
+ function adopt(node, parent) {
118
+ const copy = parent.ownerDocument.importNode(node, true);
119
+ if (copy.nodeType === Node.ELEMENT_NODE) {
120
+ const element = copy;
121
+ const scripts = element.localName === "script" ? [element] : Array.from(element.querySelectorAll("script"));
122
+ scripts.forEach((s) => s.setAttribute("data-prepr-morphed", ""));
166
123
  }
124
+ return copy;
167
125
  }
168
- async function getToolbarPropsFromHeaders(headers, token, features) {
169
- const resolved = resolveFeatures(features);
170
- let segments = [];
171
- if (resolved.segments) {
126
+
127
+ // src/core/live-preview/html-live.ts
128
+ var debug = createScopedLogger("live-preview:html");
129
+ var COMPLETE_DOCUMENT = /<\/html>\s*$/i;
130
+ var SYNC_DEBOUNCE_MS = 150;
131
+ function startHtmlLivePreview(options = {}) {
132
+ if (typeof window === "undefined" || window.self === window.top) {
133
+ return { stop() {
134
+ } };
135
+ }
136
+ const store = createLiveStore();
137
+ const bridge = createLiveBridge(store, {
138
+ allowedEditorOrigins: () => options.allowedEditorOrigins,
139
+ strictMode: () => options.strictMode
140
+ });
141
+ bridge.start();
142
+ let timer = null;
143
+ let inFlight = false;
144
+ let pending = null;
145
+ let stopped = false;
146
+ const render = async (log) => {
147
+ inFlight = true;
172
148
  try {
173
- segments = await getPreprEnvironmentSegments(token);
174
- } catch {
175
- segments = [];
149
+ const response = await fetch(window.location.href, {
150
+ method: log.length ? "POST" : "GET",
151
+ headers: log.length ? { "Content-Type": "application/json", [HEADER_LIVE_RENDER]: "1" } : {},
152
+ body: log.length ? JSON.stringify({ log }) : void 0,
153
+ credentials: "same-origin",
154
+ cache: "no-store"
155
+ });
156
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
157
+ const html = await response.text();
158
+ if (stopped) return;
159
+ if (!COMPLETE_DOCUMENT.test(html)) {
160
+ throw new Error("incomplete page; a component failed to render");
161
+ }
162
+ const next = new DOMParser().parseFromString(html, "text/html");
163
+ if (next.title && next.title !== document.title)
164
+ document.title = next.title;
165
+ morphElement(document.body, next.body);
166
+ } catch (error) {
167
+ debug.log("re-render failed", error);
168
+ } finally {
169
+ inFlight = false;
176
170
  }
177
- }
171
+ if (pending && !stopped) {
172
+ const queued = pending;
173
+ pending = null;
174
+ void render(queued);
175
+ }
176
+ };
177
+ const schedule = (log) => {
178
+ if (timer) clearTimeout(timer);
179
+ timer = setTimeout(() => {
180
+ timer = null;
181
+ if (inFlight) pending = log;
182
+ else void render(log);
183
+ }, SYNC_DEBOUNCE_MS);
184
+ };
185
+ const offLog = store.onLogChange((log) => {
186
+ if (log.length) schedule(log);
187
+ });
188
+ const offInvalidate = store.onInvalidate(() => schedule([]));
178
189
  return {
179
- activeSegment: resolved.segments ? getActiveSegmentFromHeaders(headers) : null,
180
- activeVariant: resolved.abTesting ? getActiveVariantFromHeaders(headers) : null,
181
- segments,
182
- data: segments
190
+ stop() {
191
+ stopped = true;
192
+ if (timer) clearTimeout(timer);
193
+ offLog();
194
+ offInvalidate();
195
+ bridge.stop();
196
+ }
183
197
  };
184
198
  }
185
199
 
200
+ // src/core/live-preview/live-request.ts
201
+ var MAX_BODY_BYTES = 1e6;
202
+ async function readLiveLogFromRequest(request) {
203
+ try {
204
+ if (request.method !== "POST") return [];
205
+ if (request.headers.get(HEADER_LIVE_RENDER) !== "1") return [];
206
+ const origin = request.headers.get("origin");
207
+ if (!origin || origin !== new URL(request.url).origin) return [];
208
+ const type = request.headers.get("content-type") ?? "";
209
+ if (!type.toLowerCase().startsWith("application/json")) return [];
210
+ const length = Number(request.headers.get("content-length") ?? "0");
211
+ if (length > MAX_BODY_BYTES) return [];
212
+ const text = await request.clone().text();
213
+ if (text.length > MAX_BODY_BYTES) return [];
214
+ const body = JSON.parse(text);
215
+ return parsePatchLog(body?.log);
216
+ } catch {
217
+ return [];
218
+ }
219
+ }
220
+
186
221
  // src/core/middleware.ts
187
222
  var UTM_PARAMS = [
188
223
  "utm_source",
@@ -352,9 +387,14 @@ export {
352
387
  getPreprUUIDFromHeaders,
353
388
  getToolbarPropsFromHeaders,
354
389
  loadTrackingPixel,
390
+ normalizeQuery,
391
+ parsePatchLog,
392
+ previewAttrs,
355
393
  processPreprRequest,
394
+ readLiveLogFromRequest,
356
395
  resolveFeatures,
357
396
  setTrackingParam,
397
+ startHtmlLivePreview,
358
398
  stegaClean,
359
399
  trackEvent,
360
400
  validatePreprToken
@@ -0,0 +1,18 @@
1
+ import {
2
+ createLiveBridge,
3
+ createLiveStore
4
+ } from "./chunk-6WY3MWCX.js";
5
+ import "./chunk-URXSDJOU.js";
6
+ import {
7
+ applyPatchLog,
8
+ normalizeQuery,
9
+ parsePatchLog
10
+ } from "./chunk-42Z2VW7G.js";
11
+ import "./chunk-S3VT4UWM.js";
12
+ export {
13
+ applyPatchLog,
14
+ createLiveBridge,
15
+ createLiveStore,
16
+ normalizeQuery,
17
+ parsePatchLog
18
+ };