@zappie/sdk 0.1.15 → 0.1.16

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @zappie/sdk
2
2
 
3
+ ## 0.1.16
4
+
5
+ ### Patch Changes
6
+
7
+ - f980164: The template preview bridge talks only to an allowed Zappie editor. In draft and review mode, `TemplateSettingsProvider` posts `ready`, `route` and `scroll` to the origin of the editor framing the page instead of to every origin, and accepts `settings` only from that origin and window. The allowed editors are `EDITOR_ORIGINS`, exported from `@zappie/sdk/template` with the `editorOrigin` helper: the merchant and admin dashboards in production and staging, and their local dev servers. A page framed by anything else, or not framed at all, sends and accepts nothing. Settings messages with more than 200 values are dropped, as are keys over 100 characters, text over 10,000 characters and numbers that aren't finite. Live sites are unchanged.
8
+
3
9
  ## 0.1.15
4
10
 
5
11
  ### Patch Changes
package/dist/debug.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/debug.ts
2
- var VERSION = true ? "0.1.15" : "0.0.0-development";
2
+ var VERSION = true ? "0.1.16" : "0.0.0-development";
3
3
  function installDebug(client) {
4
4
  if (typeof window === "undefined" || client.mode !== "test") {
5
5
  return;
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { ZappieError, isZappieError } from "./errors.js";
10
10
  export * from "./models.js";
11
11
  import { format, money } from "./money.js";
12
12
  import { RestTransport } from "./transport/rest.js";
13
- var VERSION = true ? "0.1.15" : "0.0.0-development";
13
+ var VERSION = true ? "0.1.16" : "0.0.0-development";
14
14
  export {
15
15
  API_VERSION,
16
16
  RestTransport,
@@ -4,7 +4,18 @@ type TemplateSettingValue = string | number | boolean;
4
4
  type TemplateMode = "site" | "draft" | "review";
5
5
  /** Merge defaults, saved settings, and preview values for the current mode. */
6
6
  declare function templateSettings<T extends Record<string, unknown>>(defaults: T, saved: Record<string, TemplateSettingValue> | undefined, preview: Record<string, TemplateSettingValue> | null, mode: TemplateMode): T;
7
- /** Read primitive setting values from an editor message. */
7
+ /**
8
+ * The Zappie editors allowed to frame a draft or review preview and talk to it: the merchant
9
+ * dashboard (Studio), the admin dashboard (template review), and each one's local dev server.
10
+ * The preview bridge posts only to the editor framing it, and only when that editor is listed here.
11
+ */
12
+ declare const EDITOR_ORIGINS: readonly string[];
13
+ /**
14
+ * Find the origin of the editor framing this page. The browser's `ancestorOrigins` is used where
15
+ * it exists, and the referrer otherwise. Returns null unless that origin is in `EDITOR_ORIGINS`.
16
+ */
17
+ declare function editorOrigin(ancestorOrigins: ArrayLike<string> | null | undefined, referrer: string, allowed?: readonly string[]): string | null;
18
+ /** Read primitive setting values from an editor message. Oversized messages and values are dropped. */
8
19
  declare function settingsFromMessage(data: unknown): Record<string, TemplateSettingValue> | null;
9
20
  /** Cloudflare image widths shared by template sites. */
10
21
  declare const IMAGE_WIDTHS: readonly [320, 480, 640, 960, 1280, 1600];
@@ -13,4 +24,4 @@ declare function resized(src: string, width: number, quality?: number): string;
13
24
  /** Build the responsive Cloudflare image candidates used by a template. */
14
25
  declare function srcSet(src: string, quality?: number): string;
15
26
 
16
- export { IMAGE_WIDTHS, type TemplateMode, type TemplateSettingValue, resized, settingsFromMessage, srcSet, templateSettings };
27
+ export { EDITOR_ORIGINS, IMAGE_WIDTHS, type TemplateMode, type TemplateSettingValue, editorOrigin, resized, settingsFromMessage, srcSet, templateSettings };
@@ -8,6 +8,30 @@ function templateSettings(defaults, saved, preview, mode) {
8
8
  }
9
9
  return { ...defaults, ...saved };
10
10
  }
11
+ var EDITOR_ORIGINS = Object.freeze([
12
+ "https://dashboard.zappie.ai",
13
+ "https://dashboard.stg.zappie.ai",
14
+ "https://admin.zappie.ai",
15
+ "https://admin.stg.zappie.ai",
16
+ "http://localhost:5181",
17
+ "http://localhost:5180"
18
+ ]);
19
+ function editorOrigin(ancestorOrigins, referrer, allowed = EDITOR_ORIGINS) {
20
+ let origin = null;
21
+ if (ancestorOrigins && ancestorOrigins.length > 0) {
22
+ origin = ancestorOrigins[0] ?? null;
23
+ } else if (referrer) {
24
+ try {
25
+ origin = new URL(referrer).origin;
26
+ } catch {
27
+ origin = null;
28
+ }
29
+ }
30
+ return origin && allowed.includes(origin) ? origin : null;
31
+ }
32
+ var MAX_SETTINGS = 200;
33
+ var MAX_SETTING_KEY = 100;
34
+ var MAX_SETTING_TEXT = 1e4;
11
35
  function settingsFromMessage(data) {
12
36
  if (!data || typeof data !== "object") {
13
37
  return null;
@@ -16,9 +40,16 @@ function settingsFromMessage(data) {
16
40
  if (message.zappie !== "settings" || !message.values || typeof message.values !== "object" || Array.isArray(message.values)) {
17
41
  return null;
18
42
  }
43
+ const entries = Object.entries(message.values);
44
+ if (entries.length > MAX_SETTINGS) {
45
+ return null;
46
+ }
19
47
  const values = {};
20
- for (const [key, value] of Object.entries(message.values)) {
21
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
48
+ for (const [key, value] of entries) {
49
+ if (key.length > MAX_SETTING_KEY) {
50
+ continue;
51
+ }
52
+ if (typeof value === "string" && value.length <= MAX_SETTING_TEXT || typeof value === "number" && Number.isFinite(value) || typeof value === "boolean") {
22
53
  values[key] = value;
23
54
  }
24
55
  }
@@ -37,7 +68,9 @@ function srcSet(src, quality) {
37
68
  ).join(", ");
38
69
  }
39
70
  export {
71
+ EDITOR_ORIGINS,
40
72
  IMAGE_WIDTHS,
73
+ editorOrigin,
41
74
  resized,
42
75
  settingsFromMessage,
43
76
  srcSet,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/template/index.ts"],
4
- "sourcesContent": ["/** A value accepted by a template manifest and the live preview bridge. */\nexport type TemplateSettingValue = string | number | boolean;\n\n/** Where a template is running. Live sites never accept preview messages. */\nexport type TemplateMode = \"site\" | \"draft\" | \"review\";\n\n/** Merge defaults, saved settings, and preview values for the current mode. */\nexport function templateSettings<T extends Record<string, unknown>>(\n defaults: T,\n saved: Record<string, TemplateSettingValue> | undefined,\n preview: Record<string, TemplateSettingValue> | null,\n mode: TemplateMode,\n): T {\n if (mode === \"review\") {\n return { ...defaults, ...(preview ?? {}) };\n }\n if (mode === \"draft\") {\n return { ...defaults, ...saved, ...(preview ?? {}) };\n }\n return { ...defaults, ...saved };\n}\n\n/** Read primitive setting values from an editor message. */\nexport function settingsFromMessage(\n data: unknown,\n): Record<string, TemplateSettingValue> | null {\n if (!data || typeof data !== \"object\") {\n return null;\n }\n const message = data as { zappie?: unknown; values?: unknown };\n if (\n message.zappie !== \"settings\" ||\n !message.values ||\n typeof message.values !== \"object\" ||\n Array.isArray(message.values)\n ) {\n return null;\n }\n const values: Record<string, TemplateSettingValue> = {};\n for (const [key, value] of Object.entries(message.values)) {\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n values[key] = value;\n }\n }\n return values;\n}\n\n/** Cloudflare image widths shared by template sites. */\nexport const IMAGE_WIDTHS = [320, 480, 640, 960, 1280, 1600] as const;\n\n/** Build a same-origin Cloudflare resize URL for an external image. */\nexport function resized(src: string, width: number, quality = 80): string {\n if (!/^https?:\\/\\//.test(src)) {\n return src;\n }\n return `/cdn-cgi/image/width=${width},quality=${quality},format=auto,fit=scale-down/${src}`;\n}\n\n/** Build the responsive Cloudflare image candidates used by a template. */\nexport function srcSet(src: string, quality?: number): string {\n return IMAGE_WIDTHS.map(\n (width) => `${resized(src, width, quality)} ${width}w`,\n ).join(\", \");\n}\n"],
5
- "mappings": ";AAOO,SAAS,iBACd,UACA,OACA,SACA,MACG;AACH,MAAI,SAAS,UAAU;AACrB,WAAO,EAAE,GAAG,UAAU,GAAI,WAAW,CAAC,EAAG;AAAA,EAC3C;AACA,MAAI,SAAS,SAAS;AACpB,WAAO,EAAE,GAAG,UAAU,GAAG,OAAO,GAAI,WAAW,CAAC,EAAG;AAAA,EACrD;AACA,SAAO,EAAE,GAAG,UAAU,GAAG,MAAM;AACjC;AAGO,SAAS,oBACd,MAC6C;AAC7C,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,WAAO;AAAA,EACT;AACA,QAAM,UAAU;AAChB,MACE,QAAQ,WAAW,cACnB,CAAC,QAAQ,UACT,OAAO,QAAQ,WAAW,YAC1B,MAAM,QAAQ,QAAQ,MAAM,GAC5B;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAA+C,CAAC;AACtD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,MAAM,GAAG;AACzD,QACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WACjB;AACA,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAGO,IAAM,eAAe,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,IAAI;AAGpD,SAAS,QAAQ,KAAa,OAAe,UAAU,IAAY;AACxE,MAAI,CAAC,eAAe,KAAK,GAAG,GAAG;AAC7B,WAAO;AAAA,EACT;AACA,SAAO,wBAAwB,KAAK,YAAY,OAAO,+BAA+B,GAAG;AAC3F;AAGO,SAAS,OAAO,KAAa,SAA0B;AAC5D,SAAO,aAAa;AAAA,IAClB,CAAC,UAAU,GAAG,QAAQ,KAAK,OAAO,OAAO,CAAC,IAAI,KAAK;AAAA,EACrD,EAAE,KAAK,IAAI;AACb;",
4
+ "sourcesContent": ["/** A value accepted by a template manifest and the live preview bridge. */\nexport type TemplateSettingValue = string | number | boolean;\n\n/** Where a template is running. Live sites never accept preview messages. */\nexport type TemplateMode = \"site\" | \"draft\" | \"review\";\n\n/** Merge defaults, saved settings, and preview values for the current mode. */\nexport function templateSettings<T extends Record<string, unknown>>(\n defaults: T,\n saved: Record<string, TemplateSettingValue> | undefined,\n preview: Record<string, TemplateSettingValue> | null,\n mode: TemplateMode,\n): T {\n if (mode === \"review\") {\n return { ...defaults, ...(preview ?? {}) };\n }\n if (mode === \"draft\") {\n return { ...defaults, ...saved, ...(preview ?? {}) };\n }\n return { ...defaults, ...saved };\n}\n\n/**\n * The Zappie editors allowed to frame a draft or review preview and talk to it: the merchant\n * dashboard (Studio), the admin dashboard (template review), and each one's local dev server.\n * The preview bridge posts only to the editor framing it, and only when that editor is listed here.\n */\nexport const EDITOR_ORIGINS: readonly string[] = Object.freeze([\n \"https://dashboard.zappie.ai\",\n \"https://dashboard.stg.zappie.ai\",\n \"https://admin.zappie.ai\",\n \"https://admin.stg.zappie.ai\",\n \"http://localhost:5181\",\n \"http://localhost:5180\",\n]);\n\n/**\n * Find the origin of the editor framing this page. The browser's `ancestorOrigins` is used where\n * it exists, and the referrer otherwise. Returns null unless that origin is in `EDITOR_ORIGINS`.\n */\nexport function editorOrigin(\n ancestorOrigins: ArrayLike<string> | null | undefined,\n referrer: string,\n allowed: readonly string[] = EDITOR_ORIGINS,\n): string | null {\n let origin: string | null = null;\n if (ancestorOrigins && ancestorOrigins.length > 0) {\n origin = ancestorOrigins[0] ?? null;\n } else if (referrer) {\n try {\n origin = new URL(referrer).origin;\n } catch {\n origin = null;\n }\n }\n return origin && allowed.includes(origin) ? origin : null;\n}\n\nconst MAX_SETTINGS = 200;\nconst MAX_SETTING_KEY = 100;\nconst MAX_SETTING_TEXT = 10_000;\n\n/** Read primitive setting values from an editor message. Oversized messages and values are dropped. */\nexport function settingsFromMessage(\n data: unknown,\n): Record<string, TemplateSettingValue> | null {\n if (!data || typeof data !== \"object\") {\n return null;\n }\n const message = data as { zappie?: unknown; values?: unknown };\n if (\n message.zappie !== \"settings\" ||\n !message.values ||\n typeof message.values !== \"object\" ||\n Array.isArray(message.values)\n ) {\n return null;\n }\n const entries = Object.entries(message.values);\n if (entries.length > MAX_SETTINGS) {\n return null;\n }\n const values: Record<string, TemplateSettingValue> = {};\n for (const [key, value] of entries) {\n if (key.length > MAX_SETTING_KEY) {\n continue;\n }\n if (\n (typeof value === \"string\" && value.length <= MAX_SETTING_TEXT) ||\n (typeof value === \"number\" && Number.isFinite(value)) ||\n typeof value === \"boolean\"\n ) {\n values[key] = value;\n }\n }\n return values;\n}\n\n/** Cloudflare image widths shared by template sites. */\nexport const IMAGE_WIDTHS = [320, 480, 640, 960, 1280, 1600] as const;\n\n/** Build a same-origin Cloudflare resize URL for an external image. */\nexport function resized(src: string, width: number, quality = 80): string {\n if (!/^https?:\\/\\//.test(src)) {\n return src;\n }\n return `/cdn-cgi/image/width=${width},quality=${quality},format=auto,fit=scale-down/${src}`;\n}\n\n/** Build the responsive Cloudflare image candidates used by a template. */\nexport function srcSet(src: string, quality?: number): string {\n return IMAGE_WIDTHS.map(\n (width) => `${resized(src, width, quality)} ${width}w`,\n ).join(\", \");\n}\n"],
5
+ "mappings": ";AAOO,SAAS,iBACd,UACA,OACA,SACA,MACG;AACH,MAAI,SAAS,UAAU;AACrB,WAAO,EAAE,GAAG,UAAU,GAAI,WAAW,CAAC,EAAG;AAAA,EAC3C;AACA,MAAI,SAAS,SAAS;AACpB,WAAO,EAAE,GAAG,UAAU,GAAG,OAAO,GAAI,WAAW,CAAC,EAAG;AAAA,EACrD;AACA,SAAO,EAAE,GAAG,UAAU,GAAG,MAAM;AACjC;AAOO,IAAM,iBAAoC,OAAO,OAAO;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMM,SAAS,aACd,iBACA,UACA,UAA6B,gBACd;AACf,MAAI,SAAwB;AAC5B,MAAI,mBAAmB,gBAAgB,SAAS,GAAG;AACjD,aAAS,gBAAgB,CAAC,KAAK;AAAA,EACjC,WAAW,UAAU;AACnB,QAAI;AACF,eAAS,IAAI,IAAI,QAAQ,EAAE;AAAA,IAC7B,QAAQ;AACN,eAAS;AAAA,IACX;AAAA,EACF;AACA,SAAO,UAAU,QAAQ,SAAS,MAAM,IAAI,SAAS;AACvD;AAEA,IAAM,eAAe;AACrB,IAAM,kBAAkB;AACxB,IAAM,mBAAmB;AAGlB,SAAS,oBACd,MAC6C;AAC7C,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,WAAO;AAAA,EACT;AACA,QAAM,UAAU;AAChB,MACE,QAAQ,WAAW,cACnB,CAAC,QAAQ,UACT,OAAO,QAAQ,WAAW,YAC1B,MAAM,QAAQ,QAAQ,MAAM,GAC5B;AACA,WAAO;AAAA,EACT;AACA,QAAM,UAAU,OAAO,QAAQ,QAAQ,MAAM;AAC7C,MAAI,QAAQ,SAAS,cAAc;AACjC,WAAO;AAAA,EACT;AACA,QAAM,SAA+C,CAAC;AACtD,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,QAAI,IAAI,SAAS,iBAAiB;AAChC;AAAA,IACF;AACA,QACG,OAAO,UAAU,YAAY,MAAM,UAAU,oBAC7C,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KACnD,OAAO,UAAU,WACjB;AACA,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAGO,IAAM,eAAe,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,IAAI;AAGpD,SAAS,QAAQ,KAAa,OAAe,UAAU,IAAY;AACxE,MAAI,CAAC,eAAe,KAAK,GAAG,GAAG;AAC7B,WAAO;AAAA,EACT;AACA,SAAO,wBAAwB,KAAK,YAAY,OAAO,+BAA+B,GAAG;AAC3F;AAGO,SAAS,OAAO,KAAa,SAA0B;AAC5D,SAAO,aAAa;AAAA,IAClB,CAAC,UAAU,GAAG,QAAQ,KAAK,OAAO,OAAO,CAAC,IAAI,KAAK;AAAA,EACrD,EAAE,KAAK,IAAI;AACb;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@
3
3
  // src/template/react.tsx
4
4
  import { useStore } from "@zappie/sdk/react";
5
5
  import {
6
+ editorOrigin,
6
7
  settingsFromMessage,
7
8
  templateSettings
8
9
  } from "@zappie/sdk/template";
@@ -25,7 +26,14 @@ function TemplateSettingsProvider({
25
26
  if (mode === "site") {
26
27
  return;
27
28
  }
28
- const send = (message2) => window.parent.postMessage(message2, "*");
29
+ if (window.parent === window) {
30
+ return;
31
+ }
32
+ const parent = editorOrigin(location.ancestorOrigins, document.referrer);
33
+ if (!parent) {
34
+ return;
35
+ }
36
+ const send = (message2) => window.parent.postMessage(message2, parent);
29
37
  const route = () => send({ zappie: "route", path: location.pathname + location.search });
30
38
  const scroll = () => send({
31
39
  zappie: "scroll",
@@ -33,6 +41,9 @@ function TemplateSettingsProvider({
33
41
  height: document.documentElement.scrollHeight
34
42
  });
35
43
  const message = (event) => {
44
+ if (event.origin !== parent || event.source !== window.parent) {
45
+ return;
46
+ }
36
47
  const values = settingsFromMessage(event.data);
37
48
  if (values) {
38
49
  setPreview(values);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/template/react.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport { useStore } from \"@zappie/sdk/react\";\nimport {\n settingsFromMessage,\n templateSettings,\n type TemplateMode,\n type TemplateSettingValue,\n} from \"@zappie/sdk/template\";\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nconst Context = createContext<{\n mode: TemplateMode;\n preview: Record<string, TemplateSettingValue> | null;\n}>({ mode: \"site\", preview: null });\n\n/** Supply the host-controlled template mode and editor bridge to a storefront. */\nexport function TemplateSettingsProvider({\n mode,\n children,\n}: {\n mode: TemplateMode;\n children: ReactNode;\n}) {\n const [preview, setPreview] = useState<Record<\n string,\n TemplateSettingValue\n > | null>(null);\n useEffect(() => {\n setPreview(null);\n if (mode === \"site\") {\n return;\n }\n const send = (message: object) => window.parent.postMessage(message, \"*\");\n const route = () =>\n send({ zappie: \"route\", path: location.pathname + location.search });\n const scroll = () =>\n send({\n zappie: \"scroll\",\n y: window.scrollY,\n height: document.documentElement.scrollHeight,\n });\n const message = (event: MessageEvent) => {\n const values = settingsFromMessage(event.data);\n if (values) {\n setPreview(values);\n }\n };\n const pushState = history.pushState;\n const replaceState = history.replaceState;\n history.pushState = function (...args) {\n pushState.apply(this, args);\n route();\n };\n history.replaceState = function (...args) {\n replaceState.apply(this, args);\n route();\n };\n window.addEventListener(\"message\", message);\n window.addEventListener(\"popstate\", route);\n window.addEventListener(\"scroll\", scroll, { passive: true });\n send({ zappie: \"ready\" });\n route();\n scroll();\n return () => {\n window.removeEventListener(\"message\", message);\n window.removeEventListener(\"popstate\", route);\n window.removeEventListener(\"scroll\", scroll);\n history.pushState = pushState;\n history.replaceState = replaceState;\n };\n }, [mode]);\n const value = useMemo(() => ({ mode, preview }), [mode, preview]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n}\n\n/** Read effective merchant settings over the defaults declared by a template. */\nexport function useTemplateSettings<T extends Record<string, unknown>>(\n defaults: T,\n): T {\n const store = useStore();\n const { mode, preview } = useContext(Context);\n return templateSettings(\n defaults,\n store.data?.templateSettings,\n preview,\n mode,\n );\n}\n"],
5
- "mappings": ";;;AACA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAgEE;AA9DT,IAAM,UAAU,cAGb,EAAE,MAAM,QAAQ,SAAS,KAAK,CAAC;AAG3B,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGG;AACD,QAAM,CAAC,SAAS,UAAU,IAAI,SAGpB,IAAI;AACd,YAAU,MAAM;AACd,eAAW,IAAI;AACf,QAAI,SAAS,QAAQ;AACnB;AAAA,IACF;AACA,UAAM,OAAO,CAACA,aAAoB,OAAO,OAAO,YAAYA,UAAS,GAAG;AACxE,UAAM,QAAQ,MACZ,KAAK,EAAE,QAAQ,SAAS,MAAM,SAAS,WAAW,SAAS,OAAO,CAAC;AACrE,UAAM,SAAS,MACb,KAAK;AAAA,MACH,QAAQ;AAAA,MACR,GAAG,OAAO;AAAA,MACV,QAAQ,SAAS,gBAAgB;AAAA,IACnC,CAAC;AACH,UAAM,UAAU,CAAC,UAAwB;AACvC,YAAM,SAAS,oBAAoB,MAAM,IAAI;AAC7C,UAAI,QAAQ;AACV,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AACA,UAAM,YAAY,QAAQ;AAC1B,UAAM,eAAe,QAAQ;AAC7B,YAAQ,YAAY,YAAa,MAAM;AACrC,gBAAU,MAAM,MAAM,IAAI;AAC1B,YAAM;AAAA,IACR;AACA,YAAQ,eAAe,YAAa,MAAM;AACxC,mBAAa,MAAM,MAAM,IAAI;AAC7B,YAAM;AAAA,IACR;AACA,WAAO,iBAAiB,WAAW,OAAO;AAC1C,WAAO,iBAAiB,YAAY,KAAK;AACzC,WAAO,iBAAiB,UAAU,QAAQ,EAAE,SAAS,KAAK,CAAC;AAC3D,SAAK,EAAE,QAAQ,QAAQ,CAAC;AACxB,UAAM;AACN,WAAO;AACP,WAAO,MAAM;AACX,aAAO,oBAAoB,WAAW,OAAO;AAC7C,aAAO,oBAAoB,YAAY,KAAK;AAC5C,aAAO,oBAAoB,UAAU,MAAM;AAC3C,cAAQ,YAAY;AACpB,cAAQ,eAAe;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AACT,QAAM,QAAQ,QAAQ,OAAO,EAAE,MAAM,QAAQ,IAAI,CAAC,MAAM,OAAO,CAAC;AAChE,SAAO,oBAAC,QAAQ,UAAR,EAAiB,OAAe,UAAS;AACnD;AAGO,SAAS,oBACd,UACG;AACH,QAAM,QAAQ,SAAS;AACvB,QAAM,EAAE,MAAM,QAAQ,IAAI,WAAW,OAAO;AAC5C,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["\"use client\";\nimport { useStore } from \"@zappie/sdk/react\";\nimport {\n editorOrigin,\n settingsFromMessage,\n templateSettings,\n type TemplateMode,\n type TemplateSettingValue,\n} from \"@zappie/sdk/template\";\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nconst Context = createContext<{\n mode: TemplateMode;\n preview: Record<string, TemplateSettingValue> | null;\n}>({ mode: \"site\", preview: null });\n\n/** Supply the host-controlled template mode and editor bridge to a storefront. */\nexport function TemplateSettingsProvider({\n mode,\n children,\n}: {\n mode: TemplateMode;\n children: ReactNode;\n}) {\n const [preview, setPreview] = useState<Record<\n string,\n TemplateSettingValue\n > | null>(null);\n useEffect(() => {\n setPreview(null);\n if (mode === \"site\") {\n return;\n }\n if (window.parent === window) {\n return;\n }\n // Talk only to an allowed Zappie editor framing this page. Anything else gets silence.\n const parent = editorOrigin(location.ancestorOrigins, document.referrer);\n if (!parent) {\n return;\n }\n const send = (message: object) =>\n window.parent.postMessage(message, parent);\n const route = () =>\n send({ zappie: \"route\", path: location.pathname + location.search });\n const scroll = () =>\n send({\n zappie: \"scroll\",\n y: window.scrollY,\n height: document.documentElement.scrollHeight,\n });\n const message = (event: MessageEvent) => {\n if (event.origin !== parent || event.source !== window.parent) {\n return;\n }\n const values = settingsFromMessage(event.data);\n if (values) {\n setPreview(values);\n }\n };\n const pushState = history.pushState;\n const replaceState = history.replaceState;\n history.pushState = function (...args) {\n pushState.apply(this, args);\n route();\n };\n history.replaceState = function (...args) {\n replaceState.apply(this, args);\n route();\n };\n window.addEventListener(\"message\", message);\n window.addEventListener(\"popstate\", route);\n window.addEventListener(\"scroll\", scroll, { passive: true });\n send({ zappie: \"ready\" });\n route();\n scroll();\n return () => {\n window.removeEventListener(\"message\", message);\n window.removeEventListener(\"popstate\", route);\n window.removeEventListener(\"scroll\", scroll);\n history.pushState = pushState;\n history.replaceState = replaceState;\n };\n }, [mode]);\n const value = useMemo(() => ({ mode, preview }), [mode, preview]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n}\n\n/** Read effective merchant settings over the defaults declared by a template. */\nexport function useTemplateSettings<T extends Record<string, unknown>>(\n defaults: T,\n): T {\n const store = useStore();\n const { mode, preview } = useContext(Context);\n return templateSettings(\n defaults,\n store.data?.templateSettings,\n preview,\n mode,\n );\n}\n"],
5
+ "mappings": ";;;AACA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AA4EE;AA1ET,IAAM,UAAU,cAGb,EAAE,MAAM,QAAQ,SAAS,KAAK,CAAC;AAG3B,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGG;AACD,QAAM,CAAC,SAAS,UAAU,IAAI,SAGpB,IAAI;AACd,YAAU,MAAM;AACd,eAAW,IAAI;AACf,QAAI,SAAS,QAAQ;AACnB;AAAA,IACF;AACA,QAAI,OAAO,WAAW,QAAQ;AAC5B;AAAA,IACF;AAEA,UAAM,SAAS,aAAa,SAAS,iBAAiB,SAAS,QAAQ;AACvE,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,UAAM,OAAO,CAACA,aACZ,OAAO,OAAO,YAAYA,UAAS,MAAM;AAC3C,UAAM,QAAQ,MACZ,KAAK,EAAE,QAAQ,SAAS,MAAM,SAAS,WAAW,SAAS,OAAO,CAAC;AACrE,UAAM,SAAS,MACb,KAAK;AAAA,MACH,QAAQ;AAAA,MACR,GAAG,OAAO;AAAA,MACV,QAAQ,SAAS,gBAAgB;AAAA,IACnC,CAAC;AACH,UAAM,UAAU,CAAC,UAAwB;AACvC,UAAI,MAAM,WAAW,UAAU,MAAM,WAAW,OAAO,QAAQ;AAC7D;AAAA,MACF;AACA,YAAM,SAAS,oBAAoB,MAAM,IAAI;AAC7C,UAAI,QAAQ;AACV,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AACA,UAAM,YAAY,QAAQ;AAC1B,UAAM,eAAe,QAAQ;AAC7B,YAAQ,YAAY,YAAa,MAAM;AACrC,gBAAU,MAAM,MAAM,IAAI;AAC1B,YAAM;AAAA,IACR;AACA,YAAQ,eAAe,YAAa,MAAM;AACxC,mBAAa,MAAM,MAAM,IAAI;AAC7B,YAAM;AAAA,IACR;AACA,WAAO,iBAAiB,WAAW,OAAO;AAC1C,WAAO,iBAAiB,YAAY,KAAK;AACzC,WAAO,iBAAiB,UAAU,QAAQ,EAAE,SAAS,KAAK,CAAC;AAC3D,SAAK,EAAE,QAAQ,QAAQ,CAAC;AACxB,UAAM;AACN,WAAO;AACP,WAAO,MAAM;AACX,aAAO,oBAAoB,WAAW,OAAO;AAC7C,aAAO,oBAAoB,YAAY,KAAK;AAC5C,aAAO,oBAAoB,UAAU,MAAM;AAC3C,cAAQ,YAAY;AACpB,cAAQ,eAAe;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AACT,QAAM,QAAQ,QAAQ,OAAO,EAAE,MAAM,QAAQ,IAAI,CAAC,MAAM,OAAO,CAAC;AAChE,SAAO,oBAAC,QAAQ,UAAR,EAAiB,OAAe,UAAS;AACnD;AAGO,SAAS,oBACd,UACG;AACH,QAAM,QAAQ,SAAS;AACvB,QAAM,EAAE,MAAM,QAAQ,IAAI,WAAW,OAAO;AAC5C,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACF;",
6
6
  "names": ["message"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zappie/sdk",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Typed, framework-agnostic client for Zappie menus, carts, checkout, auth and events.",
5
5
  "type": "module",
6
6
  "sideEffects": false,