@graview/guest 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +86 -0
  2. package/dist/build.d.ts +43 -0
  3. package/dist/build.d.ts.map +1 -0
  4. package/dist/build.js +117 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/channel.d.ts +51 -0
  7. package/dist/channel.d.ts.map +1 -0
  8. package/dist/channel.js +105 -0
  9. package/dist/channel.js.map +1 -0
  10. package/dist/guest.d.ts +2 -22
  11. package/dist/guest.d.ts.map +1 -1
  12. package/dist/guest.js +8 -89
  13. package/dist/guest.js.map +1 -1
  14. package/dist/host/frame.d.ts.map +1 -1
  15. package/dist/host/frame.js +1 -6
  16. package/dist/host/frame.js.map +1 -1
  17. package/dist/host/index.d.ts +3 -0
  18. package/dist/host/index.d.ts.map +1 -1
  19. package/dist/host/index.js.map +1 -1
  20. package/dist/host/kit.d.ts +85 -0
  21. package/dist/host/kit.d.ts.map +1 -0
  22. package/dist/host/kit.js +344 -0
  23. package/dist/host/kit.js.map +1 -0
  24. package/dist/host/nonce.d.ts +3 -0
  25. package/dist/host/nonce.d.ts.map +1 -0
  26. package/dist/host/nonce.js +7 -0
  27. package/dist/host/nonce.js.map +1 -0
  28. package/dist/host/react.d.ts +16 -5
  29. package/dist/host/react.d.ts.map +1 -1
  30. package/dist/host/react.js +31 -3
  31. package/dist/host/react.js.map +1 -1
  32. package/dist/host/session.d.ts +6 -0
  33. package/dist/host/session.d.ts.map +1 -1
  34. package/dist/host/session.js +4 -0
  35. package/dist/host/session.js.map +1 -1
  36. package/dist/host/worker.d.ts +96 -0
  37. package/dist/host/worker.d.ts.map +1 -0
  38. package/dist/host/worker.js +195 -0
  39. package/dist/host/worker.js.map +1 -0
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/kit.d.ts +202 -0
  43. package/dist/kit.d.ts.map +1 -0
  44. package/dist/kit.js +69 -0
  45. package/dist/kit.js.map +1 -0
  46. package/dist/protocol.d.ts +48 -1
  47. package/dist/protocol.d.ts.map +1 -1
  48. package/dist/protocol.js +20 -0
  49. package/dist/protocol.js.map +1 -1
  50. package/dist/worker/elements.d.ts +11 -0
  51. package/dist/worker/elements.d.ts.map +1 -0
  52. package/dist/worker/elements.js +28 -0
  53. package/dist/worker/elements.js.map +1 -0
  54. package/dist/worker/harden.d.ts +39 -0
  55. package/dist/worker/harden.d.ts.map +1 -0
  56. package/dist/worker/harden.js +175 -0
  57. package/dist/worker/harden.js.map +1 -0
  58. package/dist/worker/index.d.ts +32 -0
  59. package/dist/worker/index.d.ts.map +1 -0
  60. package/dist/worker/index.js +92 -0
  61. package/dist/worker/index.js.map +1 -0
  62. package/dist/worker/listeners.d.ts +19 -0
  63. package/dist/worker/listeners.d.ts.map +1 -0
  64. package/dist/worker/listeners.js +157 -0
  65. package/dist/worker/listeners.js.map +1 -0
  66. package/dist/worker/natives.d.ts +7 -0
  67. package/dist/worker/natives.d.ts.map +1 -0
  68. package/dist/worker/natives.js +12 -0
  69. package/dist/worker/natives.js.map +1 -0
  70. package/package.json +30 -7
package/README.md CHANGED
@@ -58,3 +58,89 @@ entries import nothing of the framework, so a guest bundle carries none of it.
58
58
  `edges` among them, `label`, `fidelity`, `cardinality`, `mode`, `selected`,
59
59
  `implicated`, `flagged` — and `acts`, the acts the viewer may run. A record
60
60
  the viewer may not see is in none of them.
61
+
62
+ ## In a worker
63
+
64
+ Where a frame cannot be nested — a chat's widget, whose sandbox will not
65
+ frame another origin — the same guest runs in a classic Web Worker the host
66
+ starts from a `blob:` URL, and draws in the host's page from a component kit.
67
+
68
+ ```ts
69
+ import { mountGuestWorker } from "@graview/guest/host/worker";
70
+
71
+ const guest = mountGuestWorker(element, { worker: { script }, view: "recipe-card", store, principal,
72
+ onFailure: (reason) => showTheTierOneCard(reason) }); // refused, silent, or budget
73
+
74
+ views.register("recipe", { fidelity: "full", cardinality: "one" },
75
+ guestView({ worker: { script }, name: "recipe-card" }));
76
+ ```
77
+
78
+ `mountGuestWorker` starts the worker without `type: "module"`, which a
79
+ `blob:` URL in an opaque origin cannot start, from the script's text or a
80
+ URL it is given. It speaks the frame's protocol: one `guest-ready`, answered
81
+ with a fresh nonce and a MessageChannel; the props as the viewer sees
82
+ them; acts applied as the viewer, `via: "view:<name>"`, under the same
83
+ limits. A second ready from the same worker is dropped. Once it is ready,
84
+ the host sends a heartbeat over the port and the worker's runtime answers
85
+ it; a worker that goes `limits.silentMs` (5 000 by default) without an
86
+ answer — a guest spinning in `while (true)` — is terminated, and
87
+ the host is told `silent`. A guest that is busy but yields is kept. What the guest draws
88
+ comes over the port as Remote DOM mutation records, and the host draws only
89
+ the kit (`GUEST_KIT`), with `createKitRenderer`. Both are
90
+ `@graview/guest/host/worker`, apart from the frame's host, so a page that
91
+ draws only frames loads none of it, and `guestView` fetches it only when it
92
+ draws a worker.
93
+
94
+ In the guest, `connectGuest` from `@graview/guest/worker` is the frame
95
+ guest's API with a `root` to draw into:
96
+
97
+ ```ts
98
+ import { connectGuest } from "@graview/guest/worker";
99
+
100
+ const guest = connectGuest();
101
+ guest.subscribe((props) => {
102
+ const card = document.createElement("gv-card");
103
+ const title = document.createElement("gv-title");
104
+ title.textContent = props.node?.label ?? "";
105
+ const cook = document.createElement("gv-button");
106
+ cook.textContent = "Cooked it";
107
+ cook.addEventListener("press", () => guest.act("mark-cooked", { recipeId: props.node!.id }));
108
+ card.append(title, cook);
109
+ guest.root.replaceChildren(card);
110
+ });
111
+ ```
112
+
113
+ The kit is one declaration for both sides: each component's host element,
114
+ typed properties, events and children. The worker's remote elements are made
115
+ from it, and the host draws from it alone. An element outside it, a property
116
+ or event it does not declare, a value of another type, and a link that is
117
+ not an absolute `https:` URL are not drawn, and `refused` says why. A link
118
+ opens with `rel="noopener noreferrer"` and no referrer, in a new tab unless
119
+ the kit says `target: "_self"`; a host that passes
120
+ `links: { origins: ["https://recipes.example"] }` draws a link only to one
121
+ of those origins, and any other with no `href`.
122
+
123
+ Before the guest runs, the worker entry hardens the worker's global: every
124
+ name outside `GUEST_GLOBALS` goes, from the global and every prototype on its
125
+ chain — the network, storage, channels, nested workers, importScripts,
126
+ `eval` and every function constructor — and what is left is frozen. A name
127
+ that will not go stops the worker before any guest code runs. `hardening`
128
+ says what was removed.
129
+
130
+ Build the guest with `buildGuestBundle` from `@graview/guest/build` (Node,
131
+ with esbuild installed): one strict classic script, the worker entry first
132
+ and the guest after it, with nothing to load at run time.
133
+
134
+ ```ts
135
+ import { buildGuestBundle } from "@graview/guest/build";
136
+
137
+ const { script, sha256 } = await buildGuestBundle({ entry: "src/recipe-card.ts" });
138
+ ```
139
+
140
+ A guest that writes `import()` or importScripts is refused at build, and
141
+ `checkGuestBundle` says the same of a script built elsewhere. Hardening holds
142
+ only for a bundle whose first module is the worker entry, so a host that
143
+ runs guests it did not build checks them, or better, builds them itself.
144
+
145
+ The worker entry carries Remote DOM (`@remote-dom/core` and its polyfill,
146
+ MIT, pinned); the frame guest and the host do not.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * What every guest bundle starts with: strict mode for the whole script —
3
+ * so no frame of the runtime hands its `this` or itself to a stack trace's
4
+ * reader — and the licences of what it carries.
5
+ */
6
+ export declare const GUEST_BUNDLE_BANNER = "\"use strict\";\n/*! @remote-dom/core and @remote-dom/polyfill \u2014 Copyright 2020-present, Shopify Inc. \u2014 MIT License.\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n * @graview/guest's worker runtime \u2014 Elastic License 2.0. */";
7
+ export interface BuildGuestOptions {
8
+ /** The guest's entry module, a path. It imports `connectGuest` from `@graview/guest/worker`. */
9
+ readonly entry: string;
10
+ /** Minify. True by default. */
11
+ readonly minify?: boolean;
12
+ /** Replace these names at build time, as esbuild's `define`. */
13
+ readonly define?: Readonly<Record<string, string>>;
14
+ }
15
+ export interface GuestBundle {
16
+ /** The script: hand it to `mountGuestWorker` as `{ script }`. */
17
+ readonly script: string;
18
+ /** Its length in bytes, as UTF-8. */
19
+ readonly bytes: number;
20
+ /** Its SHA-256, hex: what a host that serves bundles through a tool call can pin. */
21
+ readonly sha256: string;
22
+ }
23
+ /** Why a script is not a classic guest bundle. */
24
+ export declare class GuestBundleError extends Error {
25
+ readonly findings: readonly string[];
26
+ constructor(findings: readonly string[]);
27
+ }
28
+ /**
29
+ * What in a script would keep it from being a classic guest bundle, read
30
+ * from the script with its comments taken out: anything that loads code at
31
+ * run time (`import()`, `importScripts`, `import.meta`), and module syntax.
32
+ * A string that merely says `import(` is refused too: a bundle is checked
33
+ * as text, and a guest has no need to say it.
34
+ */
35
+ export declare function checkGuestBundle(script: string): Promise<readonly string[]>;
36
+ /**
37
+ * BUILD A GUEST'S BUNDLE: the worker entry first, then the guest's entry,
38
+ * as one strict IIFE with nothing to load at run time. A dynamic `import()`
39
+ * the guest writes is refused rather than kept, and so is anything
40
+ * `checkGuestBundle` finds in the result.
41
+ */
42
+ export declare function buildGuestBundle(options: BuildGuestOptions): Promise<GuestBundle>;
43
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,mtCAAwC,CAAC;AAEzE,MAAM,WAAW,iBAAiB;IAChC,gGAAgG;IAChG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,kDAAkD;AAClD,qBAAa,gBAAiB,SAAQ,KAAK;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE;gBAA3B,QAAQ,EAAE,SAAS,MAAM,EAAE;CAIjD;AAeD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAiBjF;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CA6BvF"}
package/dist/build.js ADDED
@@ -0,0 +1,117 @@
1
+ /*
2
+ * A GUEST BUNDLE, BUILT AS ONE CLASSIC SCRIPT (FR-71): `@graview/guest/build`.
3
+ *
4
+ * A chat's widget runs in an opaque origin (Claude) or one every widget
5
+ * shares (ChatGPT), and Chromium refuses a module worker from a `blob:` URL
6
+ * in an opaque origin. So a worker guest is a classic script: one IIFE,
7
+ * strict, with nothing to fetch at run time — no `import`, no `import()`,
8
+ * no `importScripts` — and the worker entry first, so the runtime is
9
+ * hardened (FR-70) before a line of the guest runs.
10
+ *
11
+ * `buildGuestBundle` makes one with esbuild, which a project installs (an
12
+ * optional peer: a host that only mounts guests needs none of this), and
13
+ * `checkGuestBundle` says what in a script would keep it from being one.
14
+ * This entry is Node's: it is how a guest is built, not what runs.
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ import { fileURLToPath } from "node:url";
18
+ import { Script } from "node:vm";
19
+ /** Remote DOM's licence, carried by every guest bundle, which carries Remote DOM. */
20
+ const REMOTE_DOM_NOTICE = `/*! @remote-dom/core and @remote-dom/polyfill — Copyright 2020-present, Shopify Inc. — MIT License.
21
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
23
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ * @graview/guest's worker runtime — Elastic License 2.0. */`;
25
+ /**
26
+ * What every guest bundle starts with: strict mode for the whole script —
27
+ * so no frame of the runtime hands its `this` or itself to a stack trace's
28
+ * reader — and the licences of what it carries.
29
+ */
30
+ export const GUEST_BUNDLE_BANNER = `"use strict";\n${REMOTE_DOM_NOTICE}`;
31
+ /** Why a script is not a classic guest bundle. */
32
+ export class GuestBundleError extends Error {
33
+ findings;
34
+ constructor(findings) {
35
+ super(`Not a classic guest bundle: ${findings.join("; ")}.`);
36
+ this.findings = findings;
37
+ this.name = "GuestBundleError";
38
+ }
39
+ }
40
+ async function esbuild() {
41
+ try {
42
+ return (await import("esbuild"));
43
+ }
44
+ catch {
45
+ throw new Error("buildGuestBundle needs esbuild: add it to the project's devDependencies.");
46
+ }
47
+ }
48
+ /** The worker entry this build of the package ships, beside this module. */
49
+ const RUNTIME = fileURLToPath(new URL("./worker/index.js", import.meta.url));
50
+ /**
51
+ * What in a script would keep it from being a classic guest bundle, read
52
+ * from the script with its comments taken out: anything that loads code at
53
+ * run time (`import()`, `importScripts`, `import.meta`), and module syntax.
54
+ * A string that merely says `import(` is refused too: a bundle is checked
55
+ * as text, and a guest has no need to say it.
56
+ */
57
+ export async function checkGuestBundle(script) {
58
+ const findings = [];
59
+ /* A classic script, as a worker without `type: "module"` will compile it: `import` and `export` statements, and `import.meta`, do not. */
60
+ try {
61
+ new Script(script);
62
+ }
63
+ catch (error) {
64
+ findings.push(`it is not a classic script: ${error.message.split("\n")[0]}`);
65
+ return findings;
66
+ }
67
+ const { transform } = await esbuild();
68
+ const code = (await transform(script, { loader: "js", minifyWhitespace: true, legalComments: "none" })).code;
69
+ if (/\bimport\s*\(/.test(code))
70
+ findings.push("it loads code at run time with import()");
71
+ /* What esbuild leaves of an import() or require() it could not resolve: a call that throws, and a guest that meant to load something. */
72
+ if (/Dynamic require of/.test(code))
73
+ findings.push("it loads code at run time with import() or require()");
74
+ if (/\bimportScripts\b/.test(code))
75
+ findings.push("it names importScripts");
76
+ if (!script.startsWith('"use strict";'))
77
+ findings.push('it does not start "use strict";');
78
+ return findings;
79
+ }
80
+ /**
81
+ * BUILD A GUEST'S BUNDLE: the worker entry first, then the guest's entry,
82
+ * as one strict IIFE with nothing to load at run time. A dynamic `import()`
83
+ * the guest writes is refused rather than kept, and so is anything
84
+ * `checkGuestBundle` finds in the result.
85
+ */
86
+ export async function buildGuestBundle(options) {
87
+ const { build } = await esbuild();
88
+ const result = await build({
89
+ stdin: {
90
+ /* The runtime first: it is evaluated, and the worker hardened, before any module of the guest's. */
91
+ contents: `import ${JSON.stringify(RUNTIME)};\nimport ${JSON.stringify(options.entry)};\n`,
92
+ resolveDir: process.cwd(),
93
+ loader: "js",
94
+ },
95
+ bundle: true,
96
+ format: "iife",
97
+ platform: "browser",
98
+ target: "es2022",
99
+ minify: options.minify ?? true,
100
+ banner: { js: GUEST_BUNDLE_BANNER },
101
+ legalComments: "none",
102
+ /* The guest's own import of the worker entry is the runtime already in the bundle: one module, one connection. */
103
+ alias: { "@graview/guest/worker": RUNTIME },
104
+ /* esbuild rewrites an import() it cannot resolve; with none supported, it refuses one instead. */
105
+ supported: { "dynamic-import": false },
106
+ define: { "process.env.NODE_ENV": '"production"', ...options.define },
107
+ write: false,
108
+ logLevel: "silent",
109
+ });
110
+ const script = result.outputFiles[0].text;
111
+ const findings = await checkGuestBundle(script);
112
+ if (findings.length > 0)
113
+ throw new GuestBundleError(findings);
114
+ const bytes = Buffer.byteLength(script, "utf8");
115
+ return { script, bytes, sha256: createHash("sha256").update(script).digest("hex") };
116
+ }
117
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { type GuestAnswer, type GuestEvent, type GuestProps, type GuestRequest } from "./protocol.js";
2
+ /**
3
+ * A GUEST'S ONE CHANNEL to the host that started it. It can read what the
4
+ * host pushed and ask; it cannot reach the store, and an act it asks for
5
+ * is the viewer's to be refused.
6
+ */
7
+ export interface Guest {
8
+ /** What the host last pushed, or undefined until the first push. */
9
+ readonly props: GuestProps | undefined;
10
+ /** Called with every push. Returns the unsubscribe. */
11
+ subscribe(listener: (props: GuestProps) => void): () => void;
12
+ /** Ask for an act by name. Resolves with the host's answer; never rejects for a refusal. */
13
+ act(name: string, args?: Readonly<Record<string, unknown>>): Promise<GuestAnswer>;
14
+ /** Ask the host to go to a record. A record the viewer may not see goes nowhere. */
15
+ navigate(to: string): void;
16
+ /** Ask for a height, in CSS pixels. */
17
+ size(height: number): void;
18
+ /** Ask for the height of an element whenever it changes. Returns the stop. In a worker, where nothing is laid out, it does nothing. */
19
+ autoSize(element?: Element): () => void;
20
+ close(): void;
21
+ }
22
+ /** A request before the nonce is put on it. */
23
+ export type Unsent = GuestRequest extends infer R ? (R extends GuestRequest ? Omit<R, "nonce"> : never) : never;
24
+ /**
25
+ * Where a guest's hello comes from, and where it says it is ready: the
26
+ * parent window for a frame, the worker's owner for a worker. Everything
27
+ * after the hello is the port's, and the same for both.
28
+ */
29
+ export interface GuestLink {
30
+ /** Start hearing messages that might be the hello. Returns the stop. */
31
+ listen(heard: (event: MessageEvent) => void): () => void;
32
+ /** Whether a message came from where a hello may come from. */
33
+ fromHost(event: MessageEvent): boolean;
34
+ /** Say ready. */
35
+ ready(message: {
36
+ readonly graview: "guest-ready";
37
+ readonly protocol: number;
38
+ }): void;
39
+ }
40
+ /**
41
+ * The guest's half of the protocol, whatever it runs in: say ready, take
42
+ * the first hello from the host, and talk only over the port that came
43
+ * with it, every request carrying the hello's nonce.
44
+ */
45
+ export declare function openGuest(link: GuestLink, hooks?: {
46
+ readonly onEvent?: (event: GuestEvent) => void;
47
+ }): {
48
+ readonly guest: Guest;
49
+ send(request: Unsent): void;
50
+ };
51
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IACvC,uDAAuD;IACvD,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC7D,4FAA4F;IAC5F,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClF,oFAAoF;IACpF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uCAAuC;IACvC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uIAAuI;IACvI,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,IAAI,CAAC;IACxC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,+CAA+C;AAC/C,MAAM,MAAM,MAAM,GAAG,YAAY,SAAS,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,YAAY,GAAG,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC;AAEhH;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,wEAAwE;IACxE,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACzD,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC;IACvC,iBAAiB;IACjB,KAAK,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACtF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,GAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;CAAO,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAwFjK"}
@@ -0,0 +1,105 @@
1
+ import { GUEST_PROTOCOL, isHostHello, } from "./protocol.js";
2
+ /**
3
+ * The guest's half of the protocol, whatever it runs in: say ready, take
4
+ * the first hello from the host, and talk only over the port that came
5
+ * with it, every request carrying the hello's nonce.
6
+ */
7
+ export function openGuest(link, hooks = {}) {
8
+ let port;
9
+ let nonce = "";
10
+ let props;
11
+ let next = 0;
12
+ const listeners = new Set();
13
+ const waiting = new Map();
14
+ const queued = [];
15
+ const send = (request) => {
16
+ if (!port)
17
+ queued.push(request);
18
+ else
19
+ port.postMessage({ ...request, nonce });
20
+ };
21
+ const fromHost = (event) => {
22
+ const message = event.data;
23
+ if (!message || typeof message !== "object")
24
+ return;
25
+ if (message.type === "props") {
26
+ props = message.props;
27
+ for (const listener of listeners)
28
+ listener(message.props);
29
+ }
30
+ else if (message.type === "answer") {
31
+ waiting.get(message.id)?.(message);
32
+ waiting.delete(message.id);
33
+ }
34
+ else if (message.type === "event" && typeof message.listener === "number") {
35
+ hooks.onEvent?.(message);
36
+ }
37
+ else if (message.type === "heartbeat" && typeof message.beat === "number") {
38
+ /* The runtime answers, so only a blocked event loop goes quiet. */
39
+ send({ type: "heartbeat", beat: message.beat });
40
+ }
41
+ };
42
+ let stop = () => { };
43
+ const hello = (event) => {
44
+ if (port)
45
+ return;
46
+ if (!link.fromHost(event))
47
+ return;
48
+ if (!isHostHello(event.data) || event.data.protocol !== GUEST_PROTOCOL)
49
+ return;
50
+ const given = event.ports[0];
51
+ if (!given)
52
+ return;
53
+ port = given;
54
+ nonce = event.data.nonce;
55
+ port.onmessage = fromHost;
56
+ port.start?.();
57
+ stop();
58
+ for (const request of queued.splice(0))
59
+ send(request);
60
+ };
61
+ stop = link.listen(hello);
62
+ link.ready({ graview: "guest-ready", protocol: GUEST_PROTOCOL });
63
+ let observer;
64
+ const guest = {
65
+ get props() {
66
+ return props;
67
+ },
68
+ subscribe(listener) {
69
+ listeners.add(listener);
70
+ return () => listeners.delete(listener);
71
+ },
72
+ act(name, args = {}) {
73
+ const id = (next += 1);
74
+ return new Promise((resolve) => {
75
+ waiting.set(id, resolve);
76
+ send({ type: "act", id, name, args });
77
+ });
78
+ },
79
+ navigate(to) {
80
+ send({ type: "navigate", to });
81
+ },
82
+ size(height) {
83
+ send({ type: "size", height });
84
+ },
85
+ autoSize(element) {
86
+ const target = element ?? globalThis.document?.documentElement;
87
+ const Observer = globalThis.ResizeObserver;
88
+ if (!target || !Observer || typeof target.getBoundingClientRect !== "function")
89
+ return () => { };
90
+ observer?.disconnect();
91
+ const watching = new Observer(() => guest.size(Math.ceil(target.getBoundingClientRect().height)));
92
+ watching.observe(target);
93
+ observer = watching;
94
+ return () => watching.disconnect();
95
+ },
96
+ close() {
97
+ observer?.disconnect();
98
+ stop();
99
+ port?.close();
100
+ listeners.clear();
101
+ },
102
+ };
103
+ return { guest, send };
104
+ }
105
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,WAAW,GAMZ,MAAM,eAAe,CAAC;AAwCvB;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAe,EAAE,QAA4D,EAAE;IACvG,IAAI,IAA6B,CAAC;IAClC,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,KAA6B,CAAC;IAClC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAC;IACzD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkD,CAAC;IAC1E,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,MAAM,IAAI,GAAG,CAAC,OAAe,EAAE,EAAE;QAC/B,IAAI,CAAC,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;YAC3B,IAAI,CAAC,WAAW,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,KAAmB,EAAE,EAAE;QACvC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAmB,CAAC;QAC1C,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO;QACpD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC7B,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;YACtB,KAAK,MAAM,QAAQ,IAAI,SAAS;gBAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;YACnC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7B,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC5E,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5E,mEAAmE;YACnE,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,IAAI,GAAG,GAAG,EAAE,GAAE,CAAC,CAAC;IACpB,MAAM,KAAK,GAAG,CAAC,KAAmB,EAAE,EAAE;QACpC,IAAI,IAAI;YAAE,OAAO;QACjB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO;QAClC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,cAAc;YAAE,OAAO;QAC/E,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,GAAG,KAAK,CAAC;QACb,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QACf,IAAI,EAAE,CAAC;QACP,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC,CAAC;IACF,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC;IAEjE,IAAI,QAA4C,CAAC;IACjD,MAAM,KAAK,GAAU;QACnB,IAAI,KAAK;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,SAAS,CAAC,QAAQ;YAChB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;QACD,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE;YACjB,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;YACvB,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,EAAE;gBAC1C,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;gBACzB,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC;QACL,CAAC;QACD,QAAQ,CAAC,EAAE;YACT,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,IAAI,CAAC,MAAM;YACT,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,QAAQ,CAAC,OAAO;YACd,MAAM,MAAM,GAAG,OAAO,IAAK,UAAsC,CAAC,QAAQ,EAAE,eAAe,CAAC;YAC5F,MAAM,QAAQ,GAAI,UAAyD,CAAC,cAAc,CAAC;YAC3F,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,IAAI,OAAO,MAAM,CAAC,qBAAqB,KAAK,UAAU;gBAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;YAChG,QAAQ,EAAE,UAAU,EAAE,CAAC;YACvB,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAClG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACzB,QAAQ,GAAG,QAAQ,CAAC;YACpB,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QACrC,CAAC;QACD,KAAK;YACH,QAAQ,EAAE,UAAU,EAAE,CAAC;YACvB,IAAI,EAAE,CAAC;YACP,IAAI,EAAE,KAAK,EAAE,CAAC;YACd,SAAS,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;KACF,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC"}
package/dist/guest.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type GuestAnswer, type GuestProps } from "./protocol.js";
1
+ import { type Guest } from "./channel.js";
2
2
  /** The window a guest runs in, as far as the SDK reads it. */
3
3
  interface GuestWindow {
4
4
  readonly parent: {
@@ -17,30 +17,10 @@ export interface ConnectGuestOptions {
17
17
  /** The window to connect from. The global one by default. */
18
18
  readonly window?: GuestWindow;
19
19
  }
20
- /**
21
- * A GUEST'S ONE CHANNEL to the page that framed it. It can read what the
22
- * host pushed and ask; it cannot reach the store, and an act it asks for
23
- * is the viewer's to be refused.
24
- */
25
- export interface Guest {
26
- /** What the host last pushed, or undefined until the first push. */
27
- readonly props: GuestProps | undefined;
28
- /** Called with every push. Returns the unsubscribe. */
29
- subscribe(listener: (props: GuestProps) => void): () => void;
30
- /** Ask for an act by name. Resolves with the host's answer; never rejects for a refusal. */
31
- act(name: string, args?: Readonly<Record<string, unknown>>): Promise<GuestAnswer>;
32
- /** Ask the host to go to a record. A record the viewer may not see goes nowhere. */
33
- navigate(to: string): void;
34
- /** Ask for a height, in CSS pixels. */
35
- size(height: number): void;
36
- /** Ask for the height of an element whenever it changes. Returns the stop. */
37
- autoSize(element?: Element): () => void;
38
- close(): void;
39
- }
20
+ export type { Guest };
40
21
  /**
41
22
  * Connect a guest view to its host: say it is ready, take the first hello
42
23
  * from the parent, and talk only over the port that came with it.
43
24
  */
44
25
  export declare function connectGuest(options?: ConnectGuestOptions): Guest;
45
- export {};
46
26
  //# sourceMappingURL=guest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,UAAU,EAGhB,MAAM,eAAe,CAAC;AAEvB,8DAA8D;AAC9D,UAAU,WAAW;IACnB,QAAQ,CAAC,MAAM,EAAE;QAAE,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACtF,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;IACjF,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IACvC,uDAAuD;IACvD,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC7D,4FAA4F;IAC5F,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClF,oFAAoF;IACpF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uCAAuC;IACvC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,IAAI,CAAC;IACxC,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,KAAK,CAoFrE"}
1
+ {"version":3,"file":"guest.d.ts","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAErD,8DAA8D;AAC9D,UAAU,WAAW;IACnB,QAAQ,CAAC,MAAM,EAAE;QAAE,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACtF,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;IACjF,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,YAAY,EAAE,KAAK,EAAE,CAAC;AAEtB;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,KAAK,CAUrE"}
package/dist/guest.js CHANGED
@@ -1,98 +1,17 @@
1
- import { GUEST_PROTOCOL, isHostHello, } from "./protocol.js";
1
+ import { openGuest } from "./channel.js";
2
2
  /**
3
3
  * Connect a guest view to its host: say it is ready, take the first hello
4
4
  * from the parent, and talk only over the port that came with it.
5
5
  */
6
6
  export function connectGuest(options = {}) {
7
7
  const at = options.window ?? globalThis;
8
- let port;
9
- let nonce = "";
10
- let props;
11
- let next = 0;
12
- const listeners = new Set();
13
- const waiting = new Map();
14
- const queued = [];
15
- const send = (request) => {
16
- if (!port)
17
- queued.push(request);
18
- else
19
- port.postMessage({ ...request, nonce });
20
- };
21
- const fromHost = (event) => {
22
- const message = event.data;
23
- if (!message || typeof message !== "object")
24
- return;
25
- if (message.type === "props") {
26
- props = message.props;
27
- for (const listener of listeners)
28
- listener(message.props);
29
- }
30
- else if (message.type === "answer") {
31
- waiting.get(message.id)?.(message);
32
- waiting.delete(message.id);
33
- }
34
- };
35
- const hello = (event) => {
36
- if (port)
37
- return;
38
- if (event.source !== at.parent)
39
- return;
40
- if (options.hostOrigin !== undefined && event.origin !== options.hostOrigin)
41
- return;
42
- if (!isHostHello(event.data) || event.data.protocol !== GUEST_PROTOCOL)
43
- return;
44
- const given = event.ports[0];
45
- if (!given)
46
- return;
47
- port = given;
48
- nonce = event.data.nonce;
49
- port.onmessage = fromHost;
50
- port.start?.();
51
- at.removeEventListener("message", hello);
52
- for (const request of queued.splice(0))
53
- send(request);
54
- };
55
- at.addEventListener("message", hello);
56
- at.parent?.postMessage({ graview: "guest-ready", protocol: GUEST_PROTOCOL }, "*");
57
- let observer;
58
- return {
59
- get props() {
60
- return props;
8
+ return openGuest({
9
+ listen(heard) {
10
+ at.addEventListener("message", heard);
11
+ return () => at.removeEventListener("message", heard);
61
12
  },
62
- subscribe(listener) {
63
- listeners.add(listener);
64
- return () => listeners.delete(listener);
65
- },
66
- act(name, args = {}) {
67
- const id = (next += 1);
68
- return new Promise((resolve) => {
69
- waiting.set(id, resolve);
70
- send({ type: "act", id, name, args });
71
- });
72
- },
73
- navigate(to) {
74
- send({ type: "navigate", to });
75
- },
76
- size(height) {
77
- send({ type: "size", height });
78
- },
79
- autoSize(element) {
80
- const target = element ?? globalThis.document?.documentElement;
81
- const Observer = globalThis.ResizeObserver;
82
- if (!target || !Observer)
83
- return () => { };
84
- observer?.disconnect();
85
- const watching = new Observer(() => this.size(Math.ceil(target.getBoundingClientRect().height)));
86
- watching.observe(target);
87
- observer = watching;
88
- return () => watching.disconnect();
89
- },
90
- close() {
91
- observer?.disconnect();
92
- at.removeEventListener("message", hello);
93
- port?.close();
94
- listeners.clear();
95
- },
96
- };
13
+ fromHost: (event) => event.source === at.parent && (options.hostOrigin === undefined || event.origin === options.hostOrigin),
14
+ ready: (message) => at.parent?.postMessage(message, "*"),
15
+ }).guest;
97
16
  }
98
17
  //# sourceMappingURL=guest.js.map
package/dist/guest.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"guest.js","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"guest.js","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAc,MAAM,cAAc,CAAC;AAsBrD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,UAA+B,EAAE;IAC5D,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,IAAK,UAAqC,CAAC;IACpE,OAAO,SAAS,CAAC;QACf,MAAM,CAAC,KAAK;YACV,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACtC,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACxD,CAAC;QACD,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAM,EAAE,CAAC,MAAkB,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC;QACzI,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC;KACzD,CAAC,CAAC,KAAK,CAAC;AACX,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../src/host/frame.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,EAAuD,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAE3I,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,SAAS;IACxD,6FAA6F;IAC7F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,iGAAiG;IACjG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,wCAAwC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,cAAc,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,0CAA0C;IAC1C,MAAM,IAAI,IAAI,CAAC;IACf,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;CACjB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,UAAU,CAgGvH"}
1
+ {"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../src/host/frame.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGjE,OAAO,EAAuD,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAE3I,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,SAAS;IACxD,6FAA6F;IAC7F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,iGAAiG;IACjG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,wCAAwC;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,cAAc,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,0CAA0C;IAC1C,MAAM,IAAI,IAAI,CAAC;IACf,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,UAAU,CAgGvH"}
@@ -1,11 +1,6 @@
1
1
  import { GUEST_PROTOCOL, GUEST_SANDBOX, OPAQUE_ORIGIN, isGuestReady } from "../protocol.js";
2
+ import { mintNonce } from "./nonce.js";
2
3
  import { createGuestHost, createGuestLimiter } from "./session.js";
3
- /** A nonce no other frame and no other load of this one shares. */
4
- function mintNonce() {
5
- const bytes = new Uint8Array(16);
6
- globalThis.crypto.getRandomValues(bytes);
7
- return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
8
- }
9
4
  /**
10
5
  * MOUNT A GUEST VIEW: an iframe sandboxed to scripts alone, so its origin
11
6
  * is opaque and nothing of the host's is reachable from it, and one