@grizzlee/react 0.3.1 → 0.4.0

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,3 +1,7 @@
1
+ ## 0.4.0
2
+
3
+ - `GrizzleeEmbed` and `mountGrizzleeEmbed` add `web-share` to the iframe's `allow` attribute, so **Share** opens the device share sheet inside the frame. Without that permission, or without a share sheet, the dashboard copies the invitation and link and shows "Copied".
4
+
1
5
  ## 0.3.1
2
6
 
3
7
  - Fix `GrizzleeNativeEmbed` crashing on its first URL with `Cannot read property 'decode' of undefined`. The package bundled `react-native-url-polyfill` with the ES module build of `punycode`, which has no `ucs2` export. The polyfill is now a dependency that the app's bundler resolves.
package/README.md CHANGED
@@ -47,7 +47,7 @@ const dashboard = mountGrizzleeEmbed(document.getElementById("dashboard")!, {
47
47
  dashboard.destroy();
48
48
  ```
49
49
 
50
- The helper accepts the same options except `style`. It appends an iframe and returns `{ destroy() }`. Both hosts use `allow="clipboard-write"`, `referrerPolicy="no-referrer"` and `loading="lazy"`. They accept messages only from the current URL's origin and their own iframe window. Neither sets `sandbox`. If you add it to a plain iframe, include `allow-scripts allow-same-origin allow-downloads`. Without `allow-same-origin`, the exchange sends `Origin: null` and fails. Without `allow-downloads`, QR downloads fail.
50
+ The helper accepts the same options except `style`. It appends an iframe and returns `{ destroy() }`. Both hosts use `allow="clipboard-write; web-share"`, `referrerPolicy="no-referrer"` and `loading="lazy"`. They accept messages only from the current URL's origin and their own iframe window. Neither sets `sandbox`. If you add it to a plain iframe, include `allow-scripts allow-same-origin allow-downloads`. Without `allow-same-origin`, the exchange sends `Origin: null` and fails. Without `allow-downloads`, QR downloads fail.
51
51
 
52
52
  ## React Native
53
53
 
package/embed.cjs CHANGED
@@ -118,7 +118,7 @@ function mountGrizzleeEmbed(container, options) {
118
118
  const frame = container.ownerDocument.createElement("iframe");
119
119
  frame.title = options.title ?? "Partner dashboard";
120
120
  if (options.className) frame.className = options.className;
121
- frame.allow = "clipboard-write";
121
+ frame.allow = "clipboard-write; web-share";
122
122
  frame.referrerPolicy = "no-referrer";
123
123
  frame.loading = "lazy";
124
124
  frame.style.width = "100%";
package/embed.cjs.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/embed.ts", "../src/protocol.ts", "../src/host.ts"],
4
- "sourcesContent": ["import {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\n/** Append a dashboard iframe. Destroy removes its listener and the iframe. */\nexport function mountGrizzleeEmbed(\n container: HTMLElement,\n options: GrizzleeEmbedOptions,\n): { destroy(): void } {\n const url = embedUrl(options.url);\n const frame = container.ownerDocument.createElement(\"iframe\");\n frame.title = options.title ?? \"Partner dashboard\";\n if (options.className) frame.className = options.className;\n frame.allow = \"clipboard-write\";\n frame.referrerPolicy = \"no-referrer\";\n frame.loading = \"lazy\";\n frame.style.width = \"100%\";\n frame.style.border = \"0\";\n frame.style.minHeight = `${minimumHeight(options.minHeight)}px`;\n frame.style.height = frame.style.minHeight;\n const stop = listenToEmbed(frame, () => options);\n frame.src = url.href;\n container.appendChild(frame);\n return {\n destroy() {\n stop();\n frame.remove();\n },\n };\n}\n", "export interface GrizzleeShare {\n programId: string;\n url: string;\n text: string;\n}\n\nexport function embedUrl(\n url: string,\n Url: new (value: string) => URL = URL,\n): URL {\n const parsed = new Url(url);\n if (\n ![\"https:\", \"http:\"].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password\n )\n throw new Error(\n \"Use an absolute HTTP or HTTPS embed URL without credentials\",\n );\n return parsed;\n}\n\nexport function parseShare(\n message: object,\n validateUrl: (value: string) => unknown = embedUrl,\n): GrizzleeShare | null {\n if (\n !(\"programId\" in message) ||\n typeof message.programId !== \"string\" ||\n !message.programId ||\n !(\"url\" in message) ||\n typeof message.url !== \"string\" ||\n !(\"text\" in message) ||\n typeof message.text !== \"string\"\n )\n return null;\n try {\n validateUrl(message.url);\n } catch {\n return null;\n }\n return { programId: message.programId, url: message.url, text: message.text };\n}\n", "import { embedUrl, parseShare, type GrizzleeShare } from \"./protocol\";\n\nexport { embedUrl } from \"./protocol\";\nexport type { GrizzleeShare } from \"./protocol\";\n\nexport interface GrizzleeEmbedOptions {\n /** An absolute HTTP or HTTPS start URL minted by the merchant's server. */\n url: string;\n /** Return a newly minted URL to reopen the dashboard. Handle errors here. */\n onSessionExpired?: () => string | void | Promise<string | void>;\n onReady?: () => void;\n /** Supplying this callback asks the frame to send share actions to the host. */\n onShare?: (share: GrizzleeShare) => void;\n title?: string;\n className?: string;\n /** Minimum height in CSS pixels, clamped to 120 through 10,000. Defaults to 320. */\n minHeight?: number;\n}\n\nconst MAX_HEIGHT = 10_000;\n\nexport function minimumHeight(height = 320): number {\n return Number.isFinite(height)\n ? Math.min(MAX_HEIGHT, Math.max(120, Math.ceil(height)))\n : 320;\n}\n\n/** Both hosts use this listener so origin checks and renewal behave the same way. */\nexport function listenToEmbed(\n frame: HTMLIFrameElement,\n options: () => GrizzleeEmbedOptions,\n): () => void {\n const hostWindow = frame.ownerDocument.defaultView;\n if (!hostWindow)\n throw new Error(\"The iframe must belong to a browser document\");\n let origin = embedUrl(options().url).origin;\n let active = true;\n let renewing = false;\n\n const receive = (event: MessageEvent<unknown>) => {\n if (\n !active ||\n event.origin !== origin ||\n !frame.contentWindow ||\n event.source !== frame.contentWindow\n )\n return;\n const message = event.data;\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) return;\n\n switch (message.type) {\n case \"grizzlee:ready\":\n frame.contentWindow.postMessage(\n { type: \"grizzlee:configure\", share: !!options().onShare },\n origin,\n );\n options().onReady?.();\n break;\n case \"grizzlee:share\": {\n const share = parseShare(message);\n if (share) options().onShare?.(share);\n break;\n }\n case \"grizzlee:resize\": {\n if (\n \"height\" in message &&\n typeof message.height === \"number\" &&\n Number.isSafeInteger(message.height) &&\n message.height > 0\n ) {\n const height = Math.min(\n MAX_HEIGHT,\n Math.max(minimumHeight(options().minHeight), message.height),\n );\n frame.style.height = `${height}px`;\n }\n break;\n }\n case \"grizzlee:session_expired\": {\n const renew = options().onSessionExpired;\n if (!renew || renewing) return;\n renewing = true;\n void (async () => {\n try {\n const url = await renew();\n if (!active || !url) return;\n const next = embedUrl(url);\n origin = next.origin;\n frame.style.height = `${minimumHeight(options().minHeight)}px`;\n frame.src = next.href;\n } catch {\n // Leave the expired dashboard in place. The merchant handles errors in its callback.\n } finally {\n renewing = false;\n }\n })();\n break;\n }\n }\n };\n\n hostWindow.addEventListener(\"message\", receive);\n return () => {\n active = false;\n hostWindow.removeEventListener(\"message\", receive);\n };\n}\n"],
4
+ "sourcesContent": ["import {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\n/** Append a dashboard iframe. Destroy removes its listener and the iframe. */\nexport function mountGrizzleeEmbed(\n container: HTMLElement,\n options: GrizzleeEmbedOptions,\n): { destroy(): void } {\n const url = embedUrl(options.url);\n const frame = container.ownerDocument.createElement(\"iframe\");\n frame.title = options.title ?? \"Partner dashboard\";\n if (options.className) frame.className = options.className;\n frame.allow = \"clipboard-write; web-share\";\n frame.referrerPolicy = \"no-referrer\";\n frame.loading = \"lazy\";\n frame.style.width = \"100%\";\n frame.style.border = \"0\";\n frame.style.minHeight = `${minimumHeight(options.minHeight)}px`;\n frame.style.height = frame.style.minHeight;\n const stop = listenToEmbed(frame, () => options);\n frame.src = url.href;\n container.appendChild(frame);\n return {\n destroy() {\n stop();\n frame.remove();\n },\n };\n}\n", "export interface GrizzleeShare {\n programId: string;\n url: string;\n text: string;\n}\n\nexport function embedUrl(\n url: string,\n Url: new (value: string) => URL = URL,\n): URL {\n const parsed = new Url(url);\n if (\n ![\"https:\", \"http:\"].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password\n )\n throw new Error(\n \"Use an absolute HTTP or HTTPS embed URL without credentials\",\n );\n return parsed;\n}\n\nexport function parseShare(\n message: object,\n validateUrl: (value: string) => unknown = embedUrl,\n): GrizzleeShare | null {\n if (\n !(\"programId\" in message) ||\n typeof message.programId !== \"string\" ||\n !message.programId ||\n !(\"url\" in message) ||\n typeof message.url !== \"string\" ||\n !(\"text\" in message) ||\n typeof message.text !== \"string\"\n )\n return null;\n try {\n validateUrl(message.url);\n } catch {\n return null;\n }\n return { programId: message.programId, url: message.url, text: message.text };\n}\n", "import { embedUrl, parseShare, type GrizzleeShare } from \"./protocol\";\n\nexport { embedUrl } from \"./protocol\";\nexport type { GrizzleeShare } from \"./protocol\";\n\nexport interface GrizzleeEmbedOptions {\n /** An absolute HTTP or HTTPS start URL minted by the merchant's server. */\n url: string;\n /** Return a newly minted URL to reopen the dashboard. Handle errors here. */\n onSessionExpired?: () => string | void | Promise<string | void>;\n onReady?: () => void;\n /** Supplying this callback asks the frame to send share actions to the host. */\n onShare?: (share: GrizzleeShare) => void;\n title?: string;\n className?: string;\n /** Minimum height in CSS pixels, clamped to 120 through 10,000. Defaults to 320. */\n minHeight?: number;\n}\n\nconst MAX_HEIGHT = 10_000;\n\nexport function minimumHeight(height = 320): number {\n return Number.isFinite(height)\n ? Math.min(MAX_HEIGHT, Math.max(120, Math.ceil(height)))\n : 320;\n}\n\n/** Both hosts use this listener so origin checks and renewal behave the same way. */\nexport function listenToEmbed(\n frame: HTMLIFrameElement,\n options: () => GrizzleeEmbedOptions,\n): () => void {\n const hostWindow = frame.ownerDocument.defaultView;\n if (!hostWindow)\n throw new Error(\"The iframe must belong to a browser document\");\n let origin = embedUrl(options().url).origin;\n let active = true;\n let renewing = false;\n\n const receive = (event: MessageEvent<unknown>) => {\n if (\n !active ||\n event.origin !== origin ||\n !frame.contentWindow ||\n event.source !== frame.contentWindow\n )\n return;\n const message = event.data;\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) return;\n\n switch (message.type) {\n case \"grizzlee:ready\":\n frame.contentWindow.postMessage(\n { type: \"grizzlee:configure\", share: !!options().onShare },\n origin,\n );\n options().onReady?.();\n break;\n case \"grizzlee:share\": {\n const share = parseShare(message);\n if (share) options().onShare?.(share);\n break;\n }\n case \"grizzlee:resize\": {\n if (\n \"height\" in message &&\n typeof message.height === \"number\" &&\n Number.isSafeInteger(message.height) &&\n message.height > 0\n ) {\n const height = Math.min(\n MAX_HEIGHT,\n Math.max(minimumHeight(options().minHeight), message.height),\n );\n frame.style.height = `${height}px`;\n }\n break;\n }\n case \"grizzlee:session_expired\": {\n const renew = options().onSessionExpired;\n if (!renew || renewing) return;\n renewing = true;\n void (async () => {\n try {\n const url = await renew();\n if (!active || !url) return;\n const next = embedUrl(url);\n origin = next.origin;\n frame.style.height = `${minimumHeight(options().minHeight)}px`;\n frame.src = next.href;\n } catch {\n // Leave the expired dashboard in place. The merchant handles errors in its callback.\n } finally {\n renewing = false;\n }\n })();\n break;\n }\n }\n };\n\n hostWindow.addEventListener(\"message\", receive);\n return () => {\n active = false;\n hostWindow.removeEventListener(\"message\", receive);\n };\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/embed.js CHANGED
@@ -92,7 +92,7 @@ function mountGrizzleeEmbed(container, options) {
92
92
  const frame = container.ownerDocument.createElement("iframe");
93
93
  frame.title = options.title ?? "Partner dashboard";
94
94
  if (options.className) frame.className = options.className;
95
- frame.allow = "clipboard-write";
95
+ frame.allow = "clipboard-write; web-share";
96
96
  frame.referrerPolicy = "no-referrer";
97
97
  frame.loading = "lazy";
98
98
  frame.style.width = "100%";
package/embed.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/protocol.ts", "../src/host.ts", "../src/embed.ts"],
4
- "sourcesContent": ["export interface GrizzleeShare {\n programId: string;\n url: string;\n text: string;\n}\n\nexport function embedUrl(\n url: string,\n Url: new (value: string) => URL = URL,\n): URL {\n const parsed = new Url(url);\n if (\n ![\"https:\", \"http:\"].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password\n )\n throw new Error(\n \"Use an absolute HTTP or HTTPS embed URL without credentials\",\n );\n return parsed;\n}\n\nexport function parseShare(\n message: object,\n validateUrl: (value: string) => unknown = embedUrl,\n): GrizzleeShare | null {\n if (\n !(\"programId\" in message) ||\n typeof message.programId !== \"string\" ||\n !message.programId ||\n !(\"url\" in message) ||\n typeof message.url !== \"string\" ||\n !(\"text\" in message) ||\n typeof message.text !== \"string\"\n )\n return null;\n try {\n validateUrl(message.url);\n } catch {\n return null;\n }\n return { programId: message.programId, url: message.url, text: message.text };\n}\n", "import { embedUrl, parseShare, type GrizzleeShare } from \"./protocol\";\n\nexport { embedUrl } from \"./protocol\";\nexport type { GrizzleeShare } from \"./protocol\";\n\nexport interface GrizzleeEmbedOptions {\n /** An absolute HTTP or HTTPS start URL minted by the merchant's server. */\n url: string;\n /** Return a newly minted URL to reopen the dashboard. Handle errors here. */\n onSessionExpired?: () => string | void | Promise<string | void>;\n onReady?: () => void;\n /** Supplying this callback asks the frame to send share actions to the host. */\n onShare?: (share: GrizzleeShare) => void;\n title?: string;\n className?: string;\n /** Minimum height in CSS pixels, clamped to 120 through 10,000. Defaults to 320. */\n minHeight?: number;\n}\n\nconst MAX_HEIGHT = 10_000;\n\nexport function minimumHeight(height = 320): number {\n return Number.isFinite(height)\n ? Math.min(MAX_HEIGHT, Math.max(120, Math.ceil(height)))\n : 320;\n}\n\n/** Both hosts use this listener so origin checks and renewal behave the same way. */\nexport function listenToEmbed(\n frame: HTMLIFrameElement,\n options: () => GrizzleeEmbedOptions,\n): () => void {\n const hostWindow = frame.ownerDocument.defaultView;\n if (!hostWindow)\n throw new Error(\"The iframe must belong to a browser document\");\n let origin = embedUrl(options().url).origin;\n let active = true;\n let renewing = false;\n\n const receive = (event: MessageEvent<unknown>) => {\n if (\n !active ||\n event.origin !== origin ||\n !frame.contentWindow ||\n event.source !== frame.contentWindow\n )\n return;\n const message = event.data;\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) return;\n\n switch (message.type) {\n case \"grizzlee:ready\":\n frame.contentWindow.postMessage(\n { type: \"grizzlee:configure\", share: !!options().onShare },\n origin,\n );\n options().onReady?.();\n break;\n case \"grizzlee:share\": {\n const share = parseShare(message);\n if (share) options().onShare?.(share);\n break;\n }\n case \"grizzlee:resize\": {\n if (\n \"height\" in message &&\n typeof message.height === \"number\" &&\n Number.isSafeInteger(message.height) &&\n message.height > 0\n ) {\n const height = Math.min(\n MAX_HEIGHT,\n Math.max(minimumHeight(options().minHeight), message.height),\n );\n frame.style.height = `${height}px`;\n }\n break;\n }\n case \"grizzlee:session_expired\": {\n const renew = options().onSessionExpired;\n if (!renew || renewing) return;\n renewing = true;\n void (async () => {\n try {\n const url = await renew();\n if (!active || !url) return;\n const next = embedUrl(url);\n origin = next.origin;\n frame.style.height = `${minimumHeight(options().minHeight)}px`;\n frame.src = next.href;\n } catch {\n // Leave the expired dashboard in place. The merchant handles errors in its callback.\n } finally {\n renewing = false;\n }\n })();\n break;\n }\n }\n };\n\n hostWindow.addEventListener(\"message\", receive);\n return () => {\n active = false;\n hostWindow.removeEventListener(\"message\", receive);\n };\n}\n", "import {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\n/** Append a dashboard iframe. Destroy removes its listener and the iframe. */\nexport function mountGrizzleeEmbed(\n container: HTMLElement,\n options: GrizzleeEmbedOptions,\n): { destroy(): void } {\n const url = embedUrl(options.url);\n const frame = container.ownerDocument.createElement(\"iframe\");\n frame.title = options.title ?? \"Partner dashboard\";\n if (options.className) frame.className = options.className;\n frame.allow = \"clipboard-write\";\n frame.referrerPolicy = \"no-referrer\";\n frame.loading = \"lazy\";\n frame.style.width = \"100%\";\n frame.style.border = \"0\";\n frame.style.minHeight = `${minimumHeight(options.minHeight)}px`;\n frame.style.height = frame.style.minHeight;\n const stop = listenToEmbed(frame, () => options);\n frame.src = url.href;\n container.appendChild(frame);\n return {\n destroy() {\n stop();\n frame.remove();\n },\n };\n}\n"],
4
+ "sourcesContent": ["export interface GrizzleeShare {\n programId: string;\n url: string;\n text: string;\n}\n\nexport function embedUrl(\n url: string,\n Url: new (value: string) => URL = URL,\n): URL {\n const parsed = new Url(url);\n if (\n ![\"https:\", \"http:\"].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password\n )\n throw new Error(\n \"Use an absolute HTTP or HTTPS embed URL without credentials\",\n );\n return parsed;\n}\n\nexport function parseShare(\n message: object,\n validateUrl: (value: string) => unknown = embedUrl,\n): GrizzleeShare | null {\n if (\n !(\"programId\" in message) ||\n typeof message.programId !== \"string\" ||\n !message.programId ||\n !(\"url\" in message) ||\n typeof message.url !== \"string\" ||\n !(\"text\" in message) ||\n typeof message.text !== \"string\"\n )\n return null;\n try {\n validateUrl(message.url);\n } catch {\n return null;\n }\n return { programId: message.programId, url: message.url, text: message.text };\n}\n", "import { embedUrl, parseShare, type GrizzleeShare } from \"./protocol\";\n\nexport { embedUrl } from \"./protocol\";\nexport type { GrizzleeShare } from \"./protocol\";\n\nexport interface GrizzleeEmbedOptions {\n /** An absolute HTTP or HTTPS start URL minted by the merchant's server. */\n url: string;\n /** Return a newly minted URL to reopen the dashboard. Handle errors here. */\n onSessionExpired?: () => string | void | Promise<string | void>;\n onReady?: () => void;\n /** Supplying this callback asks the frame to send share actions to the host. */\n onShare?: (share: GrizzleeShare) => void;\n title?: string;\n className?: string;\n /** Minimum height in CSS pixels, clamped to 120 through 10,000. Defaults to 320. */\n minHeight?: number;\n}\n\nconst MAX_HEIGHT = 10_000;\n\nexport function minimumHeight(height = 320): number {\n return Number.isFinite(height)\n ? Math.min(MAX_HEIGHT, Math.max(120, Math.ceil(height)))\n : 320;\n}\n\n/** Both hosts use this listener so origin checks and renewal behave the same way. */\nexport function listenToEmbed(\n frame: HTMLIFrameElement,\n options: () => GrizzleeEmbedOptions,\n): () => void {\n const hostWindow = frame.ownerDocument.defaultView;\n if (!hostWindow)\n throw new Error(\"The iframe must belong to a browser document\");\n let origin = embedUrl(options().url).origin;\n let active = true;\n let renewing = false;\n\n const receive = (event: MessageEvent<unknown>) => {\n if (\n !active ||\n event.origin !== origin ||\n !frame.contentWindow ||\n event.source !== frame.contentWindow\n )\n return;\n const message = event.data;\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) return;\n\n switch (message.type) {\n case \"grizzlee:ready\":\n frame.contentWindow.postMessage(\n { type: \"grizzlee:configure\", share: !!options().onShare },\n origin,\n );\n options().onReady?.();\n break;\n case \"grizzlee:share\": {\n const share = parseShare(message);\n if (share) options().onShare?.(share);\n break;\n }\n case \"grizzlee:resize\": {\n if (\n \"height\" in message &&\n typeof message.height === \"number\" &&\n Number.isSafeInteger(message.height) &&\n message.height > 0\n ) {\n const height = Math.min(\n MAX_HEIGHT,\n Math.max(minimumHeight(options().minHeight), message.height),\n );\n frame.style.height = `${height}px`;\n }\n break;\n }\n case \"grizzlee:session_expired\": {\n const renew = options().onSessionExpired;\n if (!renew || renewing) return;\n renewing = true;\n void (async () => {\n try {\n const url = await renew();\n if (!active || !url) return;\n const next = embedUrl(url);\n origin = next.origin;\n frame.style.height = `${minimumHeight(options().minHeight)}px`;\n frame.src = next.href;\n } catch {\n // Leave the expired dashboard in place. The merchant handles errors in its callback.\n } finally {\n renewing = false;\n }\n })();\n break;\n }\n }\n };\n\n hostWindow.addEventListener(\"message\", receive);\n return () => {\n active = false;\n hostWindow.removeEventListener(\"message\", receive);\n };\n}\n", "import {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\n/** Append a dashboard iframe. Destroy removes its listener and the iframe. */\nexport function mountGrizzleeEmbed(\n container: HTMLElement,\n options: GrizzleeEmbedOptions,\n): { destroy(): void } {\n const url = embedUrl(options.url);\n const frame = container.ownerDocument.createElement(\"iframe\");\n frame.title = options.title ?? \"Partner dashboard\";\n if (options.className) frame.className = options.className;\n frame.allow = \"clipboard-write; web-share\";\n frame.referrerPolicy = \"no-referrer\";\n frame.loading = \"lazy\";\n frame.style.width = \"100%\";\n frame.style.border = \"0\";\n frame.style.minHeight = `${minimumHeight(options.minHeight)}px`;\n frame.style.height = frame.style.minHeight;\n const stop = listenToEmbed(frame, () => options);\n frame.src = url.href;\n container.appendChild(frame);\n return {\n destroy() {\n stop();\n frame.remove();\n },\n };\n}\n"],
5
5
  "mappings": ";AAMO,SAAS,SACd,KACA,MAAkC,KAC7B;AACL,QAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,MACE,CAAC,CAAC,UAAU,OAAO,EAAE,SAAS,OAAO,QAAQ,KAC7C,OAAO,YACP,OAAO;AAEP,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AACF,SAAO;AACT;AAEO,SAAS,WACd,SACA,cAA0C,UACpB;AACtB,MACE,EAAE,eAAe,YACjB,OAAO,QAAQ,cAAc,YAC7B,CAAC,QAAQ,aACT,EAAE,SAAS,YACX,OAAO,QAAQ,QAAQ,YACvB,EAAE,UAAU,YACZ,OAAO,QAAQ,SAAS;AAExB,WAAO;AACT,MAAI;AACF,gBAAY,QAAQ,GAAG;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,EAAE,WAAW,QAAQ,WAAW,KAAK,QAAQ,KAAK,MAAM,QAAQ,KAAK;AAC9E;;;ACvBA,IAAM,aAAa;AAEZ,SAAS,cAAc,SAAS,KAAa;AAClD,SAAO,OAAO,SAAS,MAAM,IACzB,KAAK,IAAI,YAAY,KAAK,IAAI,KAAK,KAAK,KAAK,MAAM,CAAC,CAAC,IACrD;AACN;AAGO,SAAS,cACd,OACA,SACY;AACZ,QAAM,aAAa,MAAM,cAAc;AACvC,MAAI,CAAC;AACH,UAAM,IAAI,MAAM,8CAA8C;AAChE,MAAI,SAAS,SAAS,QAAQ,EAAE,GAAG,EAAE;AACrC,MAAI,SAAS;AACb,MAAI,WAAW;AAEf,QAAM,UAAU,CAAC,UAAiC;AAChD,QACE,CAAC,UACD,MAAM,WAAW,UACjB,CAAC,MAAM,iBACP,MAAM,WAAW,MAAM;AAEvB;AACF,UAAM,UAAU,MAAM;AACtB,QAAI,CAAC,WAAW,OAAO,YAAY,YAAY,EAAE,UAAU,SAAU;AAErE,YAAQ,QAAQ,MAAM;AAAA,MACpB,KAAK;AACH,cAAM,cAAc;AAAA,UAClB,EAAE,MAAM,sBAAsB,OAAO,CAAC,CAAC,QAAQ,EAAE,QAAQ;AAAA,UACzD;AAAA,QACF;AACA,gBAAQ,EAAE,UAAU;AACpB;AAAA,MACF,KAAK,kBAAkB;AACrB,cAAM,QAAQ,WAAW,OAAO;AAChC,YAAI,MAAO,SAAQ,EAAE,UAAU,KAAK;AACpC;AAAA,MACF;AAAA,MACA,KAAK,mBAAmB;AACtB,YACE,YAAY,WACZ,OAAO,QAAQ,WAAW,YAC1B,OAAO,cAAc,QAAQ,MAAM,KACnC,QAAQ,SAAS,GACjB;AACA,gBAAM,SAAS,KAAK;AAAA,YAClB;AAAA,YACA,KAAK,IAAI,cAAc,QAAQ,EAAE,SAAS,GAAG,QAAQ,MAAM;AAAA,UAC7D;AACA,gBAAM,MAAM,SAAS,GAAG,MAAM;AAAA,QAChC;AACA;AAAA,MACF;AAAA,MACA,KAAK,4BAA4B;AAC/B,cAAM,QAAQ,QAAQ,EAAE;AACxB,YAAI,CAAC,SAAS,SAAU;AACxB,mBAAW;AACX,cAAM,YAAY;AAChB,cAAI;AACF,kBAAM,MAAM,MAAM,MAAM;AACxB,gBAAI,CAAC,UAAU,CAAC,IAAK;AACrB,kBAAM,OAAO,SAAS,GAAG;AACzB,qBAAS,KAAK;AACd,kBAAM,MAAM,SAAS,GAAG,cAAc,QAAQ,EAAE,SAAS,CAAC;AAC1D,kBAAM,MAAM,KAAK;AAAA,UACnB,QAAQ;AAAA,UAER,UAAE;AACA,uBAAW;AAAA,UACb;AAAA,QACF,GAAG;AACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,aAAW,iBAAiB,WAAW,OAAO;AAC9C,SAAO,MAAM;AACX,aAAS;AACT,eAAW,oBAAoB,WAAW,OAAO;AAAA,EACnD;AACF;;;AChGO,SAAS,mBACd,WACA,SACqB;AACrB,QAAM,MAAM,SAAS,QAAQ,GAAG;AAChC,QAAM,QAAQ,UAAU,cAAc,cAAc,QAAQ;AAC5D,QAAM,QAAQ,QAAQ,SAAS;AAC/B,MAAI,QAAQ,UAAW,OAAM,YAAY,QAAQ;AACjD,QAAM,QAAQ;AACd,QAAM,iBAAiB;AACvB,QAAM,UAAU;AAChB,QAAM,MAAM,QAAQ;AACpB,QAAM,MAAM,SAAS;AACrB,QAAM,MAAM,YAAY,GAAG,cAAc,QAAQ,SAAS,CAAC;AAC3D,QAAM,MAAM,SAAS,MAAM,MAAM;AACjC,QAAM,OAAO,cAAc,OAAO,MAAM,OAAO;AAC/C,QAAM,MAAM,IAAI;AAChB,YAAU,YAAY,KAAK;AAC3B,SAAO;AAAA,IACL,UAAU;AACR,WAAK;AACL,YAAM,OAAO;AAAA,IACf;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/index.cjs CHANGED
@@ -121,7 +121,7 @@ function mountGrizzleeEmbed(container, options) {
121
121
  const frame = container.ownerDocument.createElement("iframe");
122
122
  frame.title = options.title ?? "Partner dashboard";
123
123
  if (options.className) frame.className = options.className;
124
- frame.allow = "clipboard-write";
124
+ frame.allow = "clipboard-write; web-share";
125
125
  frame.referrerPolicy = "no-referrer";
126
126
  frame.loading = "lazy";
127
127
  frame.style.width = "100%";
@@ -170,7 +170,7 @@ function GrizzleeEmbed(props) {
170
170
  height,
171
171
  minHeight: height
172
172
  },
173
- allow: "clipboard-write",
173
+ allow: "clipboard-write; web-share",
174
174
  referrerPolicy: "no-referrer",
175
175
  loading: "lazy"
176
176
  });
package/index.cjs.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/protocol.ts", "../src/host.ts", "../src/embed.ts"],
4
- "sourcesContent": ["\"use client\";\n\nimport {\n createElement,\n useEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n} from \"react\";\nimport {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport { mountGrizzleeEmbed } from \"./embed\";\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\nexport interface GrizzleeEmbedProps extends GrizzleeEmbedOptions {\n style?: CSSProperties;\n}\n\n/** Render a dashboard without putting a server key in the browser. */\nexport function GrizzleeEmbed(props: GrizzleeEmbedProps): ReactElement {\n const frame = useRef<HTMLIFrameElement>(null);\n const current = useRef(props);\n const url = embedUrl(props.url).href;\n const height = minimumHeight(props.minHeight);\n\n useEffect(() => {\n current.current = props;\n });\n useEffect(() => {\n frame.current?.contentWindow?.postMessage(\n { type: \"grizzlee:configure\", share: !!props.onShare },\n embedUrl(url).origin,\n );\n }, [url, props.onShare]);\n useEffect(() => {\n if (frame.current)\n return listenToEmbed(frame.current, () => current.current);\n }, [url]);\n\n return createElement(\"iframe\", {\n ref: frame,\n src: url,\n title: props.title ?? \"Partner dashboard\",\n className: props.className,\n style: {\n width: \"100%\",\n border: 0,\n ...props.style,\n height,\n minHeight: height,\n },\n allow: \"clipboard-write\",\n referrerPolicy: \"no-referrer\",\n loading: \"lazy\",\n });\n}\n", "export interface GrizzleeShare {\n programId: string;\n url: string;\n text: string;\n}\n\nexport function embedUrl(\n url: string,\n Url: new (value: string) => URL = URL,\n): URL {\n const parsed = new Url(url);\n if (\n ![\"https:\", \"http:\"].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password\n )\n throw new Error(\n \"Use an absolute HTTP or HTTPS embed URL without credentials\",\n );\n return parsed;\n}\n\nexport function parseShare(\n message: object,\n validateUrl: (value: string) => unknown = embedUrl,\n): GrizzleeShare | null {\n if (\n !(\"programId\" in message) ||\n typeof message.programId !== \"string\" ||\n !message.programId ||\n !(\"url\" in message) ||\n typeof message.url !== \"string\" ||\n !(\"text\" in message) ||\n typeof message.text !== \"string\"\n )\n return null;\n try {\n validateUrl(message.url);\n } catch {\n return null;\n }\n return { programId: message.programId, url: message.url, text: message.text };\n}\n", "import { embedUrl, parseShare, type GrizzleeShare } from \"./protocol\";\n\nexport { embedUrl } from \"./protocol\";\nexport type { GrizzleeShare } from \"./protocol\";\n\nexport interface GrizzleeEmbedOptions {\n /** An absolute HTTP or HTTPS start URL minted by the merchant's server. */\n url: string;\n /** Return a newly minted URL to reopen the dashboard. Handle errors here. */\n onSessionExpired?: () => string | void | Promise<string | void>;\n onReady?: () => void;\n /** Supplying this callback asks the frame to send share actions to the host. */\n onShare?: (share: GrizzleeShare) => void;\n title?: string;\n className?: string;\n /** Minimum height in CSS pixels, clamped to 120 through 10,000. Defaults to 320. */\n minHeight?: number;\n}\n\nconst MAX_HEIGHT = 10_000;\n\nexport function minimumHeight(height = 320): number {\n return Number.isFinite(height)\n ? Math.min(MAX_HEIGHT, Math.max(120, Math.ceil(height)))\n : 320;\n}\n\n/** Both hosts use this listener so origin checks and renewal behave the same way. */\nexport function listenToEmbed(\n frame: HTMLIFrameElement,\n options: () => GrizzleeEmbedOptions,\n): () => void {\n const hostWindow = frame.ownerDocument.defaultView;\n if (!hostWindow)\n throw new Error(\"The iframe must belong to a browser document\");\n let origin = embedUrl(options().url).origin;\n let active = true;\n let renewing = false;\n\n const receive = (event: MessageEvent<unknown>) => {\n if (\n !active ||\n event.origin !== origin ||\n !frame.contentWindow ||\n event.source !== frame.contentWindow\n )\n return;\n const message = event.data;\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) return;\n\n switch (message.type) {\n case \"grizzlee:ready\":\n frame.contentWindow.postMessage(\n { type: \"grizzlee:configure\", share: !!options().onShare },\n origin,\n );\n options().onReady?.();\n break;\n case \"grizzlee:share\": {\n const share = parseShare(message);\n if (share) options().onShare?.(share);\n break;\n }\n case \"grizzlee:resize\": {\n if (\n \"height\" in message &&\n typeof message.height === \"number\" &&\n Number.isSafeInteger(message.height) &&\n message.height > 0\n ) {\n const height = Math.min(\n MAX_HEIGHT,\n Math.max(minimumHeight(options().minHeight), message.height),\n );\n frame.style.height = `${height}px`;\n }\n break;\n }\n case \"grizzlee:session_expired\": {\n const renew = options().onSessionExpired;\n if (!renew || renewing) return;\n renewing = true;\n void (async () => {\n try {\n const url = await renew();\n if (!active || !url) return;\n const next = embedUrl(url);\n origin = next.origin;\n frame.style.height = `${minimumHeight(options().minHeight)}px`;\n frame.src = next.href;\n } catch {\n // Leave the expired dashboard in place. The merchant handles errors in its callback.\n } finally {\n renewing = false;\n }\n })();\n break;\n }\n }\n };\n\n hostWindow.addEventListener(\"message\", receive);\n return () => {\n active = false;\n hostWindow.removeEventListener(\"message\", receive);\n };\n}\n", "import {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\n/** Append a dashboard iframe. Destroy removes its listener and the iframe. */\nexport function mountGrizzleeEmbed(\n container: HTMLElement,\n options: GrizzleeEmbedOptions,\n): { destroy(): void } {\n const url = embedUrl(options.url);\n const frame = container.ownerDocument.createElement(\"iframe\");\n frame.title = options.title ?? \"Partner dashboard\";\n if (options.className) frame.className = options.className;\n frame.allow = \"clipboard-write\";\n frame.referrerPolicy = \"no-referrer\";\n frame.loading = \"lazy\";\n frame.style.width = \"100%\";\n frame.style.border = \"0\";\n frame.style.minHeight = `${minimumHeight(options.minHeight)}px`;\n frame.style.height = frame.style.minHeight;\n const stop = listenToEmbed(frame, () => options);\n frame.src = url.href;\n container.appendChild(frame);\n return {\n destroy() {\n stop();\n frame.remove();\n },\n };\n}\n"],
4
+ "sourcesContent": ["\"use client\";\n\nimport {\n createElement,\n useEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n} from \"react\";\nimport {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport { mountGrizzleeEmbed } from \"./embed\";\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\nexport interface GrizzleeEmbedProps extends GrizzleeEmbedOptions {\n style?: CSSProperties;\n}\n\n/** Render a dashboard without putting a server key in the browser. */\nexport function GrizzleeEmbed(props: GrizzleeEmbedProps): ReactElement {\n const frame = useRef<HTMLIFrameElement>(null);\n const current = useRef(props);\n const url = embedUrl(props.url).href;\n const height = minimumHeight(props.minHeight);\n\n useEffect(() => {\n current.current = props;\n });\n useEffect(() => {\n frame.current?.contentWindow?.postMessage(\n { type: \"grizzlee:configure\", share: !!props.onShare },\n embedUrl(url).origin,\n );\n }, [url, props.onShare]);\n useEffect(() => {\n if (frame.current)\n return listenToEmbed(frame.current, () => current.current);\n }, [url]);\n\n return createElement(\"iframe\", {\n ref: frame,\n src: url,\n title: props.title ?? \"Partner dashboard\",\n className: props.className,\n style: {\n width: \"100%\",\n border: 0,\n ...props.style,\n height,\n minHeight: height,\n },\n allow: \"clipboard-write; web-share\",\n referrerPolicy: \"no-referrer\",\n loading: \"lazy\",\n });\n}\n", "export interface GrizzleeShare {\n programId: string;\n url: string;\n text: string;\n}\n\nexport function embedUrl(\n url: string,\n Url: new (value: string) => URL = URL,\n): URL {\n const parsed = new Url(url);\n if (\n ![\"https:\", \"http:\"].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password\n )\n throw new Error(\n \"Use an absolute HTTP or HTTPS embed URL without credentials\",\n );\n return parsed;\n}\n\nexport function parseShare(\n message: object,\n validateUrl: (value: string) => unknown = embedUrl,\n): GrizzleeShare | null {\n if (\n !(\"programId\" in message) ||\n typeof message.programId !== \"string\" ||\n !message.programId ||\n !(\"url\" in message) ||\n typeof message.url !== \"string\" ||\n !(\"text\" in message) ||\n typeof message.text !== \"string\"\n )\n return null;\n try {\n validateUrl(message.url);\n } catch {\n return null;\n }\n return { programId: message.programId, url: message.url, text: message.text };\n}\n", "import { embedUrl, parseShare, type GrizzleeShare } from \"./protocol\";\n\nexport { embedUrl } from \"./protocol\";\nexport type { GrizzleeShare } from \"./protocol\";\n\nexport interface GrizzleeEmbedOptions {\n /** An absolute HTTP or HTTPS start URL minted by the merchant's server. */\n url: string;\n /** Return a newly minted URL to reopen the dashboard. Handle errors here. */\n onSessionExpired?: () => string | void | Promise<string | void>;\n onReady?: () => void;\n /** Supplying this callback asks the frame to send share actions to the host. */\n onShare?: (share: GrizzleeShare) => void;\n title?: string;\n className?: string;\n /** Minimum height in CSS pixels, clamped to 120 through 10,000. Defaults to 320. */\n minHeight?: number;\n}\n\nconst MAX_HEIGHT = 10_000;\n\nexport function minimumHeight(height = 320): number {\n return Number.isFinite(height)\n ? Math.min(MAX_HEIGHT, Math.max(120, Math.ceil(height)))\n : 320;\n}\n\n/** Both hosts use this listener so origin checks and renewal behave the same way. */\nexport function listenToEmbed(\n frame: HTMLIFrameElement,\n options: () => GrizzleeEmbedOptions,\n): () => void {\n const hostWindow = frame.ownerDocument.defaultView;\n if (!hostWindow)\n throw new Error(\"The iframe must belong to a browser document\");\n let origin = embedUrl(options().url).origin;\n let active = true;\n let renewing = false;\n\n const receive = (event: MessageEvent<unknown>) => {\n if (\n !active ||\n event.origin !== origin ||\n !frame.contentWindow ||\n event.source !== frame.contentWindow\n )\n return;\n const message = event.data;\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) return;\n\n switch (message.type) {\n case \"grizzlee:ready\":\n frame.contentWindow.postMessage(\n { type: \"grizzlee:configure\", share: !!options().onShare },\n origin,\n );\n options().onReady?.();\n break;\n case \"grizzlee:share\": {\n const share = parseShare(message);\n if (share) options().onShare?.(share);\n break;\n }\n case \"grizzlee:resize\": {\n if (\n \"height\" in message &&\n typeof message.height === \"number\" &&\n Number.isSafeInteger(message.height) &&\n message.height > 0\n ) {\n const height = Math.min(\n MAX_HEIGHT,\n Math.max(minimumHeight(options().minHeight), message.height),\n );\n frame.style.height = `${height}px`;\n }\n break;\n }\n case \"grizzlee:session_expired\": {\n const renew = options().onSessionExpired;\n if (!renew || renewing) return;\n renewing = true;\n void (async () => {\n try {\n const url = await renew();\n if (!active || !url) return;\n const next = embedUrl(url);\n origin = next.origin;\n frame.style.height = `${minimumHeight(options().minHeight)}px`;\n frame.src = next.href;\n } catch {\n // Leave the expired dashboard in place. The merchant handles errors in its callback.\n } finally {\n renewing = false;\n }\n })();\n break;\n }\n }\n };\n\n hostWindow.addEventListener(\"message\", receive);\n return () => {\n active = false;\n hostWindow.removeEventListener(\"message\", receive);\n };\n}\n", "import {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\n/** Append a dashboard iframe. Destroy removes its listener and the iframe. */\nexport function mountGrizzleeEmbed(\n container: HTMLElement,\n options: GrizzleeEmbedOptions,\n): { destroy(): void } {\n const url = embedUrl(options.url);\n const frame = container.ownerDocument.createElement(\"iframe\");\n frame.title = options.title ?? \"Partner dashboard\";\n if (options.className) frame.className = options.className;\n frame.allow = \"clipboard-write; web-share\";\n frame.referrerPolicy = \"no-referrer\";\n frame.loading = \"lazy\";\n frame.style.width = \"100%\";\n frame.style.border = \"0\";\n frame.style.minHeight = `${minimumHeight(options.minHeight)}px`;\n frame.style.height = frame.style.minHeight;\n const stop = listenToEmbed(frame, () => options);\n frame.src = url.href;\n container.appendChild(frame);\n return {\n destroy() {\n stop();\n frame.remove();\n },\n };\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/index.js CHANGED
@@ -101,7 +101,7 @@ function mountGrizzleeEmbed(container, options) {
101
101
  const frame = container.ownerDocument.createElement("iframe");
102
102
  frame.title = options.title ?? "Partner dashboard";
103
103
  if (options.className) frame.className = options.className;
104
- frame.allow = "clipboard-write";
104
+ frame.allow = "clipboard-write; web-share";
105
105
  frame.referrerPolicy = "no-referrer";
106
106
  frame.loading = "lazy";
107
107
  frame.style.width = "100%";
@@ -150,7 +150,7 @@ function GrizzleeEmbed(props) {
150
150
  height,
151
151
  minHeight: height
152
152
  },
153
- allow: "clipboard-write",
153
+ allow: "clipboard-write; web-share",
154
154
  referrerPolicy: "no-referrer",
155
155
  loading: "lazy"
156
156
  });
package/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/protocol.ts", "../src/host.ts", "../src/embed.ts"],
4
- "sourcesContent": ["\"use client\";\n\nimport {\n createElement,\n useEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n} from \"react\";\nimport {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport { mountGrizzleeEmbed } from \"./embed\";\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\nexport interface GrizzleeEmbedProps extends GrizzleeEmbedOptions {\n style?: CSSProperties;\n}\n\n/** Render a dashboard without putting a server key in the browser. */\nexport function GrizzleeEmbed(props: GrizzleeEmbedProps): ReactElement {\n const frame = useRef<HTMLIFrameElement>(null);\n const current = useRef(props);\n const url = embedUrl(props.url).href;\n const height = minimumHeight(props.minHeight);\n\n useEffect(() => {\n current.current = props;\n });\n useEffect(() => {\n frame.current?.contentWindow?.postMessage(\n { type: \"grizzlee:configure\", share: !!props.onShare },\n embedUrl(url).origin,\n );\n }, [url, props.onShare]);\n useEffect(() => {\n if (frame.current)\n return listenToEmbed(frame.current, () => current.current);\n }, [url]);\n\n return createElement(\"iframe\", {\n ref: frame,\n src: url,\n title: props.title ?? \"Partner dashboard\",\n className: props.className,\n style: {\n width: \"100%\",\n border: 0,\n ...props.style,\n height,\n minHeight: height,\n },\n allow: \"clipboard-write\",\n referrerPolicy: \"no-referrer\",\n loading: \"lazy\",\n });\n}\n", "export interface GrizzleeShare {\n programId: string;\n url: string;\n text: string;\n}\n\nexport function embedUrl(\n url: string,\n Url: new (value: string) => URL = URL,\n): URL {\n const parsed = new Url(url);\n if (\n ![\"https:\", \"http:\"].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password\n )\n throw new Error(\n \"Use an absolute HTTP or HTTPS embed URL without credentials\",\n );\n return parsed;\n}\n\nexport function parseShare(\n message: object,\n validateUrl: (value: string) => unknown = embedUrl,\n): GrizzleeShare | null {\n if (\n !(\"programId\" in message) ||\n typeof message.programId !== \"string\" ||\n !message.programId ||\n !(\"url\" in message) ||\n typeof message.url !== \"string\" ||\n !(\"text\" in message) ||\n typeof message.text !== \"string\"\n )\n return null;\n try {\n validateUrl(message.url);\n } catch {\n return null;\n }\n return { programId: message.programId, url: message.url, text: message.text };\n}\n", "import { embedUrl, parseShare, type GrizzleeShare } from \"./protocol\";\n\nexport { embedUrl } from \"./protocol\";\nexport type { GrizzleeShare } from \"./protocol\";\n\nexport interface GrizzleeEmbedOptions {\n /** An absolute HTTP or HTTPS start URL minted by the merchant's server. */\n url: string;\n /** Return a newly minted URL to reopen the dashboard. Handle errors here. */\n onSessionExpired?: () => string | void | Promise<string | void>;\n onReady?: () => void;\n /** Supplying this callback asks the frame to send share actions to the host. */\n onShare?: (share: GrizzleeShare) => void;\n title?: string;\n className?: string;\n /** Minimum height in CSS pixels, clamped to 120 through 10,000. Defaults to 320. */\n minHeight?: number;\n}\n\nconst MAX_HEIGHT = 10_000;\n\nexport function minimumHeight(height = 320): number {\n return Number.isFinite(height)\n ? Math.min(MAX_HEIGHT, Math.max(120, Math.ceil(height)))\n : 320;\n}\n\n/** Both hosts use this listener so origin checks and renewal behave the same way. */\nexport function listenToEmbed(\n frame: HTMLIFrameElement,\n options: () => GrizzleeEmbedOptions,\n): () => void {\n const hostWindow = frame.ownerDocument.defaultView;\n if (!hostWindow)\n throw new Error(\"The iframe must belong to a browser document\");\n let origin = embedUrl(options().url).origin;\n let active = true;\n let renewing = false;\n\n const receive = (event: MessageEvent<unknown>) => {\n if (\n !active ||\n event.origin !== origin ||\n !frame.contentWindow ||\n event.source !== frame.contentWindow\n )\n return;\n const message = event.data;\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) return;\n\n switch (message.type) {\n case \"grizzlee:ready\":\n frame.contentWindow.postMessage(\n { type: \"grizzlee:configure\", share: !!options().onShare },\n origin,\n );\n options().onReady?.();\n break;\n case \"grizzlee:share\": {\n const share = parseShare(message);\n if (share) options().onShare?.(share);\n break;\n }\n case \"grizzlee:resize\": {\n if (\n \"height\" in message &&\n typeof message.height === \"number\" &&\n Number.isSafeInteger(message.height) &&\n message.height > 0\n ) {\n const height = Math.min(\n MAX_HEIGHT,\n Math.max(minimumHeight(options().minHeight), message.height),\n );\n frame.style.height = `${height}px`;\n }\n break;\n }\n case \"grizzlee:session_expired\": {\n const renew = options().onSessionExpired;\n if (!renew || renewing) return;\n renewing = true;\n void (async () => {\n try {\n const url = await renew();\n if (!active || !url) return;\n const next = embedUrl(url);\n origin = next.origin;\n frame.style.height = `${minimumHeight(options().minHeight)}px`;\n frame.src = next.href;\n } catch {\n // Leave the expired dashboard in place. The merchant handles errors in its callback.\n } finally {\n renewing = false;\n }\n })();\n break;\n }\n }\n };\n\n hostWindow.addEventListener(\"message\", receive);\n return () => {\n active = false;\n hostWindow.removeEventListener(\"message\", receive);\n };\n}\n", "import {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\n/** Append a dashboard iframe. Destroy removes its listener and the iframe. */\nexport function mountGrizzleeEmbed(\n container: HTMLElement,\n options: GrizzleeEmbedOptions,\n): { destroy(): void } {\n const url = embedUrl(options.url);\n const frame = container.ownerDocument.createElement(\"iframe\");\n frame.title = options.title ?? \"Partner dashboard\";\n if (options.className) frame.className = options.className;\n frame.allow = \"clipboard-write\";\n frame.referrerPolicy = \"no-referrer\";\n frame.loading = \"lazy\";\n frame.style.width = \"100%\";\n frame.style.border = \"0\";\n frame.style.minHeight = `${minimumHeight(options.minHeight)}px`;\n frame.style.height = frame.style.minHeight;\n const stop = listenToEmbed(frame, () => options);\n frame.src = url.href;\n container.appendChild(frame);\n return {\n destroy() {\n stop();\n frame.remove();\n },\n };\n}\n"],
4
+ "sourcesContent": ["\"use client\";\n\nimport {\n createElement,\n useEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n} from \"react\";\nimport {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport { mountGrizzleeEmbed } from \"./embed\";\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\nexport interface GrizzleeEmbedProps extends GrizzleeEmbedOptions {\n style?: CSSProperties;\n}\n\n/** Render a dashboard without putting a server key in the browser. */\nexport function GrizzleeEmbed(props: GrizzleeEmbedProps): ReactElement {\n const frame = useRef<HTMLIFrameElement>(null);\n const current = useRef(props);\n const url = embedUrl(props.url).href;\n const height = minimumHeight(props.minHeight);\n\n useEffect(() => {\n current.current = props;\n });\n useEffect(() => {\n frame.current?.contentWindow?.postMessage(\n { type: \"grizzlee:configure\", share: !!props.onShare },\n embedUrl(url).origin,\n );\n }, [url, props.onShare]);\n useEffect(() => {\n if (frame.current)\n return listenToEmbed(frame.current, () => current.current);\n }, [url]);\n\n return createElement(\"iframe\", {\n ref: frame,\n src: url,\n title: props.title ?? \"Partner dashboard\",\n className: props.className,\n style: {\n width: \"100%\",\n border: 0,\n ...props.style,\n height,\n minHeight: height,\n },\n allow: \"clipboard-write; web-share\",\n referrerPolicy: \"no-referrer\",\n loading: \"lazy\",\n });\n}\n", "export interface GrizzleeShare {\n programId: string;\n url: string;\n text: string;\n}\n\nexport function embedUrl(\n url: string,\n Url: new (value: string) => URL = URL,\n): URL {\n const parsed = new Url(url);\n if (\n ![\"https:\", \"http:\"].includes(parsed.protocol) ||\n parsed.username ||\n parsed.password\n )\n throw new Error(\n \"Use an absolute HTTP or HTTPS embed URL without credentials\",\n );\n return parsed;\n}\n\nexport function parseShare(\n message: object,\n validateUrl: (value: string) => unknown = embedUrl,\n): GrizzleeShare | null {\n if (\n !(\"programId\" in message) ||\n typeof message.programId !== \"string\" ||\n !message.programId ||\n !(\"url\" in message) ||\n typeof message.url !== \"string\" ||\n !(\"text\" in message) ||\n typeof message.text !== \"string\"\n )\n return null;\n try {\n validateUrl(message.url);\n } catch {\n return null;\n }\n return { programId: message.programId, url: message.url, text: message.text };\n}\n", "import { embedUrl, parseShare, type GrizzleeShare } from \"./protocol\";\n\nexport { embedUrl } from \"./protocol\";\nexport type { GrizzleeShare } from \"./protocol\";\n\nexport interface GrizzleeEmbedOptions {\n /** An absolute HTTP or HTTPS start URL minted by the merchant's server. */\n url: string;\n /** Return a newly minted URL to reopen the dashboard. Handle errors here. */\n onSessionExpired?: () => string | void | Promise<string | void>;\n onReady?: () => void;\n /** Supplying this callback asks the frame to send share actions to the host. */\n onShare?: (share: GrizzleeShare) => void;\n title?: string;\n className?: string;\n /** Minimum height in CSS pixels, clamped to 120 through 10,000. Defaults to 320. */\n minHeight?: number;\n}\n\nconst MAX_HEIGHT = 10_000;\n\nexport function minimumHeight(height = 320): number {\n return Number.isFinite(height)\n ? Math.min(MAX_HEIGHT, Math.max(120, Math.ceil(height)))\n : 320;\n}\n\n/** Both hosts use this listener so origin checks and renewal behave the same way. */\nexport function listenToEmbed(\n frame: HTMLIFrameElement,\n options: () => GrizzleeEmbedOptions,\n): () => void {\n const hostWindow = frame.ownerDocument.defaultView;\n if (!hostWindow)\n throw new Error(\"The iframe must belong to a browser document\");\n let origin = embedUrl(options().url).origin;\n let active = true;\n let renewing = false;\n\n const receive = (event: MessageEvent<unknown>) => {\n if (\n !active ||\n event.origin !== origin ||\n !frame.contentWindow ||\n event.source !== frame.contentWindow\n )\n return;\n const message = event.data;\n if (!message || typeof message !== \"object\" || !(\"type\" in message)) return;\n\n switch (message.type) {\n case \"grizzlee:ready\":\n frame.contentWindow.postMessage(\n { type: \"grizzlee:configure\", share: !!options().onShare },\n origin,\n );\n options().onReady?.();\n break;\n case \"grizzlee:share\": {\n const share = parseShare(message);\n if (share) options().onShare?.(share);\n break;\n }\n case \"grizzlee:resize\": {\n if (\n \"height\" in message &&\n typeof message.height === \"number\" &&\n Number.isSafeInteger(message.height) &&\n message.height > 0\n ) {\n const height = Math.min(\n MAX_HEIGHT,\n Math.max(minimumHeight(options().minHeight), message.height),\n );\n frame.style.height = `${height}px`;\n }\n break;\n }\n case \"grizzlee:session_expired\": {\n const renew = options().onSessionExpired;\n if (!renew || renewing) return;\n renewing = true;\n void (async () => {\n try {\n const url = await renew();\n if (!active || !url) return;\n const next = embedUrl(url);\n origin = next.origin;\n frame.style.height = `${minimumHeight(options().minHeight)}px`;\n frame.src = next.href;\n } catch {\n // Leave the expired dashboard in place. The merchant handles errors in its callback.\n } finally {\n renewing = false;\n }\n })();\n break;\n }\n }\n };\n\n hostWindow.addEventListener(\"message\", receive);\n return () => {\n active = false;\n hostWindow.removeEventListener(\"message\", receive);\n };\n}\n", "import {\n embedUrl,\n listenToEmbed,\n minimumHeight,\n type GrizzleeEmbedOptions,\n} from \"./host\";\n\nexport type { GrizzleeEmbedOptions, GrizzleeShare } from \"./host\";\n\n/** Append a dashboard iframe. Destroy removes its listener and the iframe. */\nexport function mountGrizzleeEmbed(\n container: HTMLElement,\n options: GrizzleeEmbedOptions,\n): { destroy(): void } {\n const url = embedUrl(options.url);\n const frame = container.ownerDocument.createElement(\"iframe\");\n frame.title = options.title ?? \"Partner dashboard\";\n if (options.className) frame.className = options.className;\n frame.allow = \"clipboard-write; web-share\";\n frame.referrerPolicy = \"no-referrer\";\n frame.loading = \"lazy\";\n frame.style.width = \"100%\";\n frame.style.border = \"0\";\n frame.style.minHeight = `${minimumHeight(options.minHeight)}px`;\n frame.style.height = frame.style.minHeight;\n const stop = listenToEmbed(frame, () => options);\n frame.src = url.href;\n container.appendChild(frame);\n return {\n destroy() {\n stop();\n frame.remove();\n },\n };\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grizzlee/react",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"