@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 +4 -0
- package/README.md +1 -1
- package/embed.cjs +1 -1
- package/embed.cjs.map +1 -1
- package/embed.js +1 -1
- package/embed.js.map +1 -1
- package/index.cjs +2 -2
- package/index.cjs.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/package.json +1 -1
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": "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",
|
|
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
|
}
|