@graview/guest 0.0.0-stage → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,96 @@
1
+ Elastic License 2.0
2
+
3
+ ## Acceptance
4
+
5
+ By using the software, you agree to all of the terms and conditions below.
6
+
7
+ ## Copyright License
8
+
9
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
10
+ non-sublicensable, non-transferable license to use, copy, distribute, make
11
+ available, and prepare derivative works of the software, in each case subject
12
+ to the limitations and conditions below.
13
+
14
+ ## Limitations
15
+
16
+ You may not provide the software to third parties as a hosted or managed
17
+ service, where the service provides users with access to any substantial set
18
+ of the features or functionality of the software.
19
+
20
+ You may not move, change, disable, or circumvent the license key
21
+ functionality in the software, and you may not remove or obscure any
22
+ functionality in the software that is protected by the license key.
23
+
24
+ You may not alter, remove, or obscure any licensing, copyright, or other
25
+ notices of the licensor in the software. Any use of the licensor's trademarks
26
+ is subject to applicable law.
27
+
28
+ ## Patents
29
+
30
+ The licensor grants you a license, under any patent claims the licensor can
31
+ license, or becomes able to license, to make, have made, use, sell, offer for
32
+ sale, import and have imported the software, in each case subject to the
33
+ limitations and conditions in this license. This license does not cover any
34
+ patent claims that you cause to be infringed by modifications or additions to
35
+ the software. If you or your company make any written claim that the software
36
+ infringes or contributes to infringement of any patent, your patent license
37
+ for the software granted under these terms ends immediately. If your company
38
+ makes such a claim, your patent license ends immediately for work on behalf
39
+ of your company.
40
+
41
+ ## Notices
42
+
43
+ You must ensure that anyone who gets a copy of any part of the software from
44
+ you also gets a copy of these terms.
45
+
46
+ If you modify the software, you must include in any modified copies of the
47
+ software prominent notices stating that you have modified the software.
48
+
49
+ ## No Other Rights
50
+
51
+ These terms do not imply any licenses other than those expressly granted in
52
+ these terms.
53
+
54
+ ## Termination
55
+
56
+ If you use the software in violation of these terms, such use is not
57
+ licensed, and your licenses will automatically terminate. If the licensor
58
+ provides you with a notice of your violation, and you cease all violation of
59
+ this license no later than 30 days after you receive that notice, your
60
+ licenses will be reinstated retroactively. However, if you violate these
61
+ terms after such reinstatement, any additional violation of these terms will
62
+ cause your licenses to terminate automatically and permanently.
63
+
64
+ ## No Liability
65
+
66
+ *As far as the law allows, the software comes as is, without any warranty or
67
+ condition, and the licensor will not be liable to you for any damages arising
68
+ out of these terms or the use or nature of the software, under any kind of
69
+ legal claim.*
70
+
71
+ ## Definitions
72
+
73
+ The **licensor** is the entity offering these terms, and the **software** is
74
+ the software the licensor makes available under these terms, including any
75
+ portion of it.
76
+
77
+ **you** refers to the individual or entity agreeing to these terms.
78
+
79
+ **your company** is any legal entity, sole proprietorship, or other kind of
80
+ organization that you work for, plus all organizations that have control
81
+ over, are under the control of, or are under common control with that
82
+ organization. **control** means ownership of substantially all the assets of
83
+ an entity, or the power to direct its management and policies by vote,
84
+ contract, or otherwise. Control can be direct or indirect.
85
+
86
+ **your licenses** are all the licenses granted to you for the software under
87
+ these terms.
88
+
89
+ **use** means anything you do with the software requiring one of your
90
+ licenses.
91
+
92
+ **trademark** means trademarks, service marks, and similar rights.
93
+
94
+ ---
95
+
96
+ Copyright 2025-2026 En Dash Consulting
package/README.md CHANGED
@@ -1,3 +1,60 @@
1
- # Temporary Holding Version
1
+ # @graview/guest
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A view somebody else wrote — a recipe card, a bespoke dashboard, React or
4
+ plain script — drawn in an iframe that can see only what the viewer may see
5
+ and can only ask. The host pushes the view's props as the viewer sees them;
6
+ the guest asks for an act by name, and the host applies it as the viewer, so
7
+ the policy refuses on the guest's behalf exactly as it would refuse a click.
8
+ The rail says the act came through the view.
9
+
10
+ ## In the host
11
+
12
+ ```ts
13
+ import { guestView, mountGuestView } from "@graview/guest/host";
14
+
15
+ views.register("recipe", { fidelity: "full", cardinality: "one" },
16
+ guestView({ url: "https://cards.example/recipe.html", name: "recipe-card" }));
17
+
18
+ // Or without React, into any element:
19
+ const frame = mountGuestView(element, { url, view: "recipe-card", store, principal, input: () => ({ node: { id } }) });
20
+ frame.update(); // the input moved
21
+ frame.dispose();
22
+ ```
23
+
24
+ `mountGuestView` gives the frame `sandbox="allow-scripts"` and never
25
+ `allow-same-origin`, so its origin is opaque and no cookie, storage or store
26
+ of the host's is reachable from it. A `guest-ready` is answered only from
27
+ that frame's own window and from the opaque origin, with a fresh nonce and a
28
+ MessageChannel; every request over the port carries the nonce. Acts are
29
+ rate-limited per frame (30 a minute by default, `limits`), and a flood of
30
+ messages past 120 a second is dropped unread. `createGuestHost` is the same
31
+ host without the DOM. Where the frame is served from, and its CSP, are the
32
+ host's business: a separate registrable domain with `connect-src 'none'` is
33
+ the shape the protocol assumes. It matters where the host's session rides a
34
+ cookie: in Firefox and WebKit a guest with no CSP can send a credentialed
35
+ request to the host that carries a cookie without SameSite. It cannot
36
+ read the answer, but the request is made. Set the cookie SameSite=Lax or
37
+ Strict, or serve guests with `connect-src 'none'`
38
+ (`scripts/guest-sandbox.mjs` checks both in all three engines).
39
+
40
+ ## In the guest
41
+
42
+ ```ts
43
+ import { connectGuest } from "@graview/guest";
44
+
45
+ const guest = connectGuest();
46
+ guest.subscribe((props) => render(props.node, props.acts));
47
+ const answer = await guest.act("mark-cooked", { recipeId: props.node.id });
48
+ if (!answer.ok) show(answer.message); // the policy's own sentence
49
+ guest.navigate(id);
50
+ guest.autoSize();
51
+ ```
52
+
53
+ In React, `useGuest()` from `@graview/guest/react` is the same connection:
54
+ `{ props, act, navigate, size }`, re-rendering on every push. The guest
55
+ entries import nothing of the framework, so a guest bundle carries none of it.
56
+
57
+ `GuestProps` is the plain-data half of `ViewProps` — `node`, `nodes`, the
58
+ `edges` among them, `label`, `fidelity`, `cardinality`, `mode`, `selected`,
59
+ `implicated`, `flagged` — and `acts`, the acts the viewer may run. A record
60
+ the viewer may not see is in none of them.
@@ -0,0 +1,46 @@
1
+ import { type GuestAnswer, type GuestProps } from "./protocol.js";
2
+ /** The window a guest runs in, as far as the SDK reads it. */
3
+ interface GuestWindow {
4
+ readonly parent: {
5
+ postMessage(message: unknown, targetOrigin: string): void;
6
+ } | null;
7
+ addEventListener(type: "message", listener: (event: MessageEvent) => void): void;
8
+ removeEventListener(type: "message", listener: (event: MessageEvent) => void): void;
9
+ }
10
+ export interface ConnectGuestOptions {
11
+ /**
12
+ * The host's origin, when the guest knows it: a hello from any other
13
+ * origin is ignored. Without it the hello is still taken only from the
14
+ * parent window, which is the frame's host by construction.
15
+ */
16
+ readonly hostOrigin?: string;
17
+ /** The window to connect from. The global one by default. */
18
+ readonly window?: GuestWindow;
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
+ }
40
+ /**
41
+ * Connect a guest view to its host: say it is ready, take the first hello
42
+ * from the parent, and talk only over the port that came with it.
43
+ */
44
+ export declare function connectGuest(options?: ConnectGuestOptions): Guest;
45
+ export {};
46
+ //# sourceMappingURL=guest.d.ts.map
@@ -0,0 +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"}
package/dist/guest.js ADDED
@@ -0,0 +1,98 @@
1
+ import { GUEST_PROTOCOL, isHostHello, } from "./protocol.js";
2
+ /**
3
+ * Connect a guest view to its host: say it is ready, take the first hello
4
+ * from the parent, and talk only over the port that came with it.
5
+ */
6
+ export function connectGuest(options = {}) {
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;
61
+ },
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
+ };
97
+ }
98
+ //# sourceMappingURL=guest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guest.js","sourceRoot":"","sources":["../src/guest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { AnySchema, Principal, Store } from "@graview/core";
2
+ import { type GuestLimits, type GuestStats, type GuestViewInput } from "./session.js";
3
+ export interface MountGuestViewOptions<S extends AnySchema> {
4
+ /** Where the guest's code is served. Where that is, and its CSP, are the host's business. */
5
+ readonly url: string;
6
+ /** The view's registered name: what the rail says an act came through (`via: "view:<name>"`). */
7
+ readonly view: string;
8
+ readonly store: Store<S>;
9
+ /** The viewer: what is pushed is what they may see, and what is asked for is applied as them. */
10
+ readonly principal: Principal;
11
+ /** What is drawn where the guest is. */
12
+ readonly input?: () => GuestViewInput;
13
+ readonly onNavigate?: (id: string) => void;
14
+ /** The guest asked for a height. By default the frame takes it. */
15
+ readonly onSize?: (height: number) => void;
16
+ readonly limits?: GuestLimits;
17
+ /** The frame's accessible name. The view's name by default. */
18
+ readonly title?: string;
19
+ }
20
+ export interface GuestFrame {
21
+ readonly iframe: HTMLIFrameElement;
22
+ /** Push again: the view's input moved. */
23
+ update(): void;
24
+ /** What this frame has been refused or had dropped, across every document it has loaded. */
25
+ readonly stats: Readonly<GuestStats>;
26
+ dispose(): void;
27
+ }
28
+ /**
29
+ * MOUNT A GUEST VIEW: an iframe sandboxed to scripts alone, so its origin
30
+ * is opaque and nothing of the host's is reachable from it, and one
31
+ * MessageChannel handed over when it says it is ready.
32
+ *
33
+ * A `guest-ready` is answered only when it comes from this frame's own
34
+ * window and from the opaque origin — a frame that somehow had an origin of
35
+ * its own is not a guest. Each one gets a fresh nonce and port, and the one
36
+ * before is closed, so a document the frame navigated away from keeps
37
+ * nothing. Requests ride the port with the nonce, and the host applies an
38
+ * act as the viewer, never as the guest.
39
+ */
40
+ export declare function mountGuestView<S extends AnySchema>(element: HTMLElement, options: MountGuestViewOptions<S>): GuestFrame;
41
+ //# sourceMappingURL=frame.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,116 @@
1
+ import { GUEST_PROTOCOL, GUEST_SANDBOX, OPAQUE_ORIGIN, isGuestReady } from "../protocol.js";
2
+ 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
+ /**
10
+ * MOUNT A GUEST VIEW: an iframe sandboxed to scripts alone, so its origin
11
+ * is opaque and nothing of the host's is reachable from it, and one
12
+ * MessageChannel handed over when it says it is ready.
13
+ *
14
+ * A `guest-ready` is answered only when it comes from this frame's own
15
+ * window and from the opaque origin — a frame that somehow had an origin of
16
+ * its own is not a guest. Each one gets a fresh nonce and port, and the one
17
+ * before is closed, so a document the frame navigated away from keeps
18
+ * nothing. Requests ride the port with the nonce, and the host applies an
19
+ * act as the viewer, never as the guest.
20
+ */
21
+ export function mountGuestView(element, options) {
22
+ const document = element.ownerDocument;
23
+ const window = document.defaultView;
24
+ const iframe = document.createElement("iframe");
25
+ iframe.setAttribute("sandbox", GUEST_SANDBOX);
26
+ iframe.setAttribute("referrerpolicy", "no-referrer");
27
+ /* No camera, microphone, geolocation, payment or fullscreen: a guest asks the host, it does not get features. */
28
+ iframe.setAttribute("allow", "");
29
+ iframe.setAttribute("title", options.title ?? options.view);
30
+ iframe.setAttribute("data-guest-view", options.view);
31
+ iframe.style.border = "0";
32
+ iframe.style.width = "100%";
33
+ iframe.style.display = "block";
34
+ iframe.src = options.url;
35
+ const stats = { applied: 0, refused: 0, dropped: 0 };
36
+ /* The frame's, not a session's: saying "ready" again buys a new nonce, never a new allowance. */
37
+ const limiter = createGuestLimiter(options.limits);
38
+ let session;
39
+ let port;
40
+ let counted = { applied: 0, refused: 0, dropped: 0 };
41
+ const tally = () => {
42
+ if (!session)
43
+ return;
44
+ const now = session.stats;
45
+ stats.applied += now.applied - counted.applied;
46
+ stats.refused += now.refused - counted.refused;
47
+ stats.dropped += now.dropped - counted.dropped;
48
+ counted = { ...now };
49
+ };
50
+ const end = () => {
51
+ tally();
52
+ session?.dispose();
53
+ port?.close();
54
+ session = undefined;
55
+ port = undefined;
56
+ counted = { applied: 0, refused: 0, dropped: 0 };
57
+ };
58
+ const onMessage = (event) => {
59
+ /* Only this frame's window, and only the opaque origin: anything else is not this guest. */
60
+ if (event.source === null || event.source !== iframe.contentWindow)
61
+ return;
62
+ if (event.origin !== OPAQUE_ORIGIN || !limiter.message()) {
63
+ stats.dropped += 1;
64
+ return;
65
+ }
66
+ if (!isGuestReady(event.data) || event.data.protocol !== GUEST_PROTOCOL) {
67
+ stats.dropped += 1;
68
+ return;
69
+ }
70
+ end();
71
+ const nonce = mintNonce();
72
+ const channel = new MessageChannel();
73
+ port = channel.port1;
74
+ const live = createGuestHost({
75
+ store: options.store,
76
+ principal: options.principal,
77
+ view: options.view,
78
+ nonce,
79
+ send: (message) => channel.port1.postMessage(message),
80
+ ...(options.input ? { input: options.input } : {}),
81
+ ...(options.onNavigate ? { onNavigate: options.onNavigate } : {}),
82
+ onSize: options.onSize ?? ((height) => (iframe.style.height = `${height}px`)),
83
+ ...(options.limits ? { limits: options.limits } : {}),
84
+ limiter,
85
+ });
86
+ session = live;
87
+ channel.port1.onmessage = (message) => {
88
+ live.receive(message.data);
89
+ tally();
90
+ };
91
+ const hello = { graview: "host-hello", protocol: GUEST_PROTOCOL, nonce, view: options.view };
92
+ /*
93
+ * "*" because an opaque origin cannot be named as a target. It goes to
94
+ * this frame's window alone, and carries nothing but the port and the
95
+ * nonce: what the viewer may see comes over the port.
96
+ */
97
+ iframe.contentWindow.postMessage(hello, "*", [channel.port2]);
98
+ live.push();
99
+ };
100
+ window.addEventListener("message", onMessage);
101
+ element.appendChild(iframe);
102
+ return {
103
+ iframe,
104
+ update: () => session?.push(),
105
+ get stats() {
106
+ tally();
107
+ return stats;
108
+ },
109
+ dispose() {
110
+ end();
111
+ window.removeEventListener("message", onMessage);
112
+ iframe.remove();
113
+ },
114
+ };
115
+ }
116
+ //# sourceMappingURL=frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.js","sourceRoot":"","sources":["../../src/host/frame.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { mountGuestView } from "./frame.js";
2
+ export type { GuestFrame, MountGuestViewOptions } from "./frame.js";
3
+ export { createGuestHost, createGuestLimiter } from "./session.js";
4
+ export type { GuestHost, GuestHostOptions, GuestLimiter, GuestLimits, GuestStats, GuestViewInput } from "./session.js";
5
+ export { guestView } from "./react.js";
6
+ export type { GuestViewOptions } from "./react.js";
7
+ export { GUEST_PROTOCOL, GUEST_SANDBOX, OPAQUE_ORIGIN } from "../protocol.js";
8
+ export type { GuestProps } from "../protocol.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACnE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9E,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { mountGuestView } from "./frame.js";
2
+ export { createGuestHost, createGuestLimiter } from "./session.js";
3
+ export { guestView } from "./react.js";
4
+ export { GUEST_PROTOCOL, GUEST_SANDBOX, OPAQUE_ORIGIN } from "../protocol.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEnE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { AnySchema } from "@graview/core";
2
+ import { type ViewComponent } from "@graview/react";
3
+ import type { GuestLimits } from "./session.js";
4
+ export interface GuestViewOptions {
5
+ /** Where the guest's code is served. */
6
+ readonly url: string;
7
+ /** The view's name: what the rail says an act came through. */
8
+ readonly name: string;
9
+ /** Where the frame first stands before the guest asks for a height. 160px by default. */
10
+ readonly height?: number;
11
+ readonly limits?: GuestLimits;
12
+ }
13
+ /**
14
+ * A VIEW SOMEBODY ELSE WROTE, registered like any other:
15
+ *
16
+ * views.register("recipe", { fidelity: "full", cardinality: "one" },
17
+ * guestView({ url: "https://recipes.example/card.html", name: "recipe-card" }));
18
+ *
19
+ * It draws an iframe sandboxed to scripts alone, pushes the view's props as
20
+ * the seat at the keyboard sees them, applies what the guest asks for as
21
+ * that seat, and goes to a record by selecting it.
22
+ */
23
+ export declare function guestView(options: GuestViewOptions): ViewComponent<AnySchema>;
24
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/host/react.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAc,KAAK,aAAa,EAAkB,MAAM,gBAAgB,CAAC;AAGhF,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,cAAc,CAAC;AAEhE,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAcD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CA+B7E"}
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useGraview } from "@graview/react";
3
+ import { useEffect, useRef } from "react";
4
+ import { mountGuestView } from "./frame.js";
5
+ const inputOf = (props) => ({
6
+ ...(props.node ? { node: { id: props.node.id } } : {}),
7
+ ...(props.nodes ? { nodes: props.nodes.map((node) => ({ id: node.id })) } : {}),
8
+ ...(props.label !== undefined ? { label: props.label } : {}),
9
+ fidelity: props.fidelity,
10
+ cardinality: props.cardinality,
11
+ mode: props.mode,
12
+ selected: props.selected,
13
+ ...(props.implicated ? { implicated: props.implicated } : {}),
14
+ ...(props.flagged ? { flagged: props.flagged } : {}),
15
+ });
16
+ /**
17
+ * A VIEW SOMEBODY ELSE WROTE, registered like any other:
18
+ *
19
+ * views.register("recipe", { fidelity: "full", cardinality: "one" },
20
+ * guestView({ url: "https://recipes.example/card.html", name: "recipe-card" }));
21
+ *
22
+ * It draws an iframe sandboxed to scripts alone, pushes the view's props as
23
+ * the seat at the keyboard sees them, applies what the guest asks for as
24
+ * that seat, and goes to a record by selecting it.
25
+ */
26
+ export function guestView(options) {
27
+ function GuestView(props) {
28
+ const { store, principal, setSelection } = useGraview();
29
+ const holder = useRef(null);
30
+ const frame = useRef(null);
31
+ const latest = useRef(props);
32
+ latest.current = props;
33
+ useEffect(() => {
34
+ const element = holder.current;
35
+ if (!element)
36
+ return;
37
+ const mounted = mountGuestView(element, {
38
+ url: options.url,
39
+ view: options.name,
40
+ store,
41
+ principal,
42
+ input: () => inputOf(latest.current),
43
+ onNavigate: (id) => setSelection([id]),
44
+ ...(options.limits ? { limits: options.limits } : {}),
45
+ });
46
+ mounted.iframe.style.height = `${options.height ?? 160}px`;
47
+ frame.current = mounted;
48
+ return () => {
49
+ mounted.dispose();
50
+ frame.current = null;
51
+ };
52
+ }, [store, principal, setSelection]);
53
+ useEffect(() => frame.current?.update(), [props]);
54
+ return _jsx("div", { ref: holder, "data-guest": options.name });
55
+ }
56
+ GuestView.displayName = `GuestView(${options.name})`;
57
+ return GuestView;
58
+ }
59
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/host/react.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import { type AnySchema, type Principal, type Store } from "@graview/core";
2
+ import type { GuestProps, HostMessage } from "../protocol.js";
3
+ /**
4
+ * What the host knows of the place a guest view is drawn: `ViewProps` as
5
+ * the framework hands any view, by id. The host reads every record again
6
+ * from the store as the viewer sees it, so an id here the viewer may not
7
+ * see goes no further.
8
+ */
9
+ export interface GuestViewInput {
10
+ readonly node?: {
11
+ readonly id: string;
12
+ };
13
+ readonly nodes?: readonly {
14
+ readonly id: string;
15
+ }[];
16
+ readonly label?: string;
17
+ readonly fidelity?: GuestProps["fidelity"];
18
+ readonly cardinality?: GuestProps["cardinality"];
19
+ readonly mode?: GuestProps["mode"];
20
+ readonly selected?: boolean;
21
+ readonly implicated?: readonly string[];
22
+ readonly flagged?: readonly string[];
23
+ }
24
+ /** How much a frame may ask, per frame. Past either, the host stops listening until the window moves. */
25
+ export interface GuestLimits {
26
+ /** Acts a frame may ask for in `actWindowMs`. 30 by default. */
27
+ readonly acts?: number;
28
+ /** 60 000 (a minute) by default. */
29
+ readonly actWindowMs?: number;
30
+ /** Messages of any kind a frame may send in `messageWindowMs`; the rest are dropped unread. 120 by default. */
31
+ readonly messages?: number;
32
+ /** 1 000 by default. */
33
+ readonly messageWindowMs?: number;
34
+ /** The tallest a guest may ask to be, in CSS pixels. 4 000 by default. */
35
+ readonly maxHeight?: number;
36
+ }
37
+ export interface GuestHostOptions<S extends AnySchema> {
38
+ /** The store, or the store as the viewer sees it: the host reads it through `seenBy(principal)` either way. */
39
+ readonly store: Store<S>;
40
+ /** The viewer. Every act the guest asks for is applied as them, and only what they may see is pushed. */
41
+ readonly principal: Principal;
42
+ /** The view's registered name. Acts it asks for are recorded `via: "view:<name>"`. */
43
+ readonly view: string;
44
+ /** This frame's nonce. A request without it is dropped. */
45
+ readonly nonce: string;
46
+ /** Where messages to the guest go: the host's end of the frame's port. */
47
+ send(message: HostMessage): void;
48
+ /** What is drawn where the guest is, read on every push. */
49
+ readonly input?: () => GuestViewInput;
50
+ /** The guest asked to go to a record the viewer may see. */
51
+ readonly onNavigate?: (id: string) => void;
52
+ /** The guest asked for a height. */
53
+ readonly onSize?: (height: number) => void;
54
+ readonly limits?: GuestLimits;
55
+ /**
56
+ * The frame's limiter, when the frame outlives this session: a guest
57
+ * that says it is ready again gets a new nonce, not a new allowance.
58
+ */
59
+ readonly limiter?: GuestLimiter;
60
+ /** The clock, for tests. */
61
+ readonly now?: () => number;
62
+ }
63
+ /** How much one frame may still ask: each call spends one, and says whether there was one to spend. */
64
+ export interface GuestLimiter {
65
+ act(): boolean;
66
+ message(): boolean;
67
+ }
68
+ /** What a frame has been refused, for a host that wants to show or log it. */
69
+ export interface GuestStats {
70
+ /** Acts applied. */
71
+ applied: number;
72
+ /** Acts asked for and not applied, for any reason. */
73
+ refused: number;
74
+ /** Messages dropped unread: a wrong nonce, a shape the protocol does not know, or past the message limit. */
75
+ dropped: number;
76
+ }
77
+ export interface GuestHost {
78
+ /** Hand the host one message from the frame's port. */
79
+ receive(data: unknown): void;
80
+ /** Push what the viewer sees now. Called on every change to the store, and by a host when the view's input moves. */
81
+ push(): void;
82
+ readonly stats: Readonly<GuestStats>;
83
+ dispose(): void;
84
+ }
85
+ /** One frame's allowance, per `GuestLimits`. */
86
+ export declare function createGuestLimiter(limits?: GuestLimits, now?: () => number): GuestLimiter;
87
+ /**
88
+ * THE HOST'S HALF OF ONE FRAME, without the DOM: what to push, and what to
89
+ * make of what comes back. `mountGuestView` wires it to an iframe; a test
90
+ * can drive it with plain objects.
91
+ */
92
+ export declare function createGuestHost<S extends AnySchema>(options: GuestHostOptions<S>): GuestHost;
93
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/host/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ import { PermissionDeniedError } from "@graview/core";
2
+ /** A count over a sliding window: true while under the limit. */
3
+ function windowed(limit, span, now) {
4
+ const times = [];
5
+ return () => {
6
+ const at = now();
7
+ while (times.length > 0 && times[0] <= at - span)
8
+ times.shift();
9
+ if (times.length >= limit)
10
+ return false;
11
+ times.push(at);
12
+ return true;
13
+ };
14
+ }
15
+ /** One frame's allowance, per `GuestLimits`. */
16
+ export function createGuestLimiter(limits = {}, now = Date.now) {
17
+ return {
18
+ act: windowed(limits.acts ?? 30, limits.actWindowMs ?? 60_000, now),
19
+ message: windowed(limits.messages ?? 120, limits.messageWindowMs ?? 1_000, now),
20
+ };
21
+ }
22
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23
+ /**
24
+ * THE HOST'S HALF OF ONE FRAME, without the DOM: what to push, and what to
25
+ * make of what comes back. `mountGuestView` wires it to an iframe; a test
26
+ * can drive it with plain objects.
27
+ */
28
+ export function createGuestHost(options) {
29
+ const limiter = options.limiter ?? createGuestLimiter(options.limits, options.now);
30
+ const maxHeight = options.limits?.maxHeight ?? 4_000;
31
+ const via = `view:${options.view}`;
32
+ const stats = { applied: 0, refused: 0, dropped: 0 };
33
+ /* Read through the viewer's sight every time: the store moves, and what they may see moves with it. */
34
+ const seen = () => options.store.seenBy(options.principal);
35
+ const plain = (node) => structuredClone(node);
36
+ const props = () => {
37
+ const store = seen();
38
+ const graph = store.graph;
39
+ const input = options.input?.() ?? {};
40
+ const node = input.node ? graph.getNode(input.node.id) : undefined;
41
+ const nodes = input.nodes?.flatMap((member) => {
42
+ const found = graph.getNode(member.id);
43
+ return found ? [found] : [];
44
+ });
45
+ const shown = new Set([...(node ? [node.id] : []), ...(nodes ?? []).map((member) => member.id)]);
46
+ const edges = [];
47
+ for (const id of shown) {
48
+ for (const edge of graph.outEdges(id)) {
49
+ if (shown.has(edge.to))
50
+ edges.push({ kind: edge.kind, from: edge.from, to: edge.to });
51
+ }
52
+ }
53
+ const visible = (ids) => ids?.filter((id) => graph.has(id));
54
+ const acts = store.permittedMutations(options.principal).map((mutation) => ({
55
+ name: mutation.name,
56
+ title: mutation.title ?? mutation.name,
57
+ ...(mutation.description !== undefined ? { description: mutation.description } : {}),
58
+ ...(mutation.subject ? { subject: { kinds: mutation.subject.kinds, arg: mutation.subject.arg } } : {}),
59
+ }));
60
+ return {
61
+ view: options.view,
62
+ ...(node ? { node: plain(node) } : {}),
63
+ ...(nodes ? { nodes: nodes.map(plain) } : {}),
64
+ edges,
65
+ ...(input.label !== undefined ? { label: input.label } : {}),
66
+ ...(input.fidelity !== undefined ? { fidelity: input.fidelity } : {}),
67
+ ...(input.cardinality !== undefined ? { cardinality: input.cardinality } : {}),
68
+ ...(input.mode !== undefined ? { mode: input.mode } : {}),
69
+ ...(input.selected !== undefined ? { selected: input.selected } : {}),
70
+ ...(input.implicated ? { implicated: visible(input.implicated) } : {}),
71
+ ...(input.flagged ? { flagged: visible(input.flagged) } : {}),
72
+ acts,
73
+ };
74
+ };
75
+ let disposed = false;
76
+ const push = () => {
77
+ if (!disposed)
78
+ options.send({ type: "props", props: props() });
79
+ };
80
+ /* Every change to the store is a push, coalesced: a batch of five ops is one message, not five. */
81
+ let pending = false;
82
+ const off = options.store.subscribe(() => {
83
+ if (pending)
84
+ return;
85
+ pending = true;
86
+ queueMicrotask(() => {
87
+ pending = false;
88
+ push();
89
+ });
90
+ });
91
+ const answer = (id, reason, message) => {
92
+ stats.refused += 1;
93
+ options.send({ type: "answer", id, ok: false, reason, message });
94
+ };
95
+ const act = (id, name, args) => {
96
+ if (typeof name !== "string" || !isRecord(args))
97
+ return answer(id, "malformed", "An act is asked for by name, with its arguments as an object.");
98
+ if (!limiter.act())
99
+ return answer(id, "rate-limited", "Too many acts asked for at once; this one was not applied.");
100
+ if (!options.store.allMutations().some((mutation) => mutation.name === name))
101
+ return answer(id, "unknown-act", "There is no act by that name here.");
102
+ try {
103
+ /*
104
+ * THE VIEWER'S CLICK, NOT THE GUEST'S: the store judges the viewer's
105
+ * principal, refuses a record they may not see before any grant is
106
+ * read, and the op says it came through this view.
107
+ */
108
+ const result = options.store.apply({ name, args }, { author: options.principal, via });
109
+ stats.applied += 1;
110
+ options.send({ type: "answer", id, ok: true, intent: result.intent });
111
+ }
112
+ catch (error) {
113
+ if (error instanceof PermissionDeniedError)
114
+ return answer(id, "refused", error.refusal.message);
115
+ /* An act's own error can name what it read; the guest is told only that it did not happen. */
116
+ return answer(id, "failed", "The act could not be applied.");
117
+ }
118
+ };
119
+ return {
120
+ receive(data) {
121
+ if (disposed)
122
+ return;
123
+ if (!limiter.message()) {
124
+ stats.dropped += 1;
125
+ return;
126
+ }
127
+ if (!isRecord(data) || data.nonce !== options.nonce) {
128
+ stats.dropped += 1;
129
+ return;
130
+ }
131
+ if (data.type === "act" && (typeof data.id === "string" || typeof data.id === "number"))
132
+ return act(data.id, data.name, data.args);
133
+ if (data.type === "navigate" && typeof data.to === "string") {
134
+ if (seen().graph.has(data.to))
135
+ options.onNavigate?.(data.to);
136
+ else
137
+ stats.dropped += 1;
138
+ return;
139
+ }
140
+ if (data.type === "size" && typeof data.height === "number" && Number.isFinite(data.height)) {
141
+ options.onSize?.(Math.max(0, Math.min(maxHeight, Math.round(data.height))));
142
+ return;
143
+ }
144
+ stats.dropped += 1;
145
+ },
146
+ push,
147
+ get stats() {
148
+ return stats;
149
+ },
150
+ dispose() {
151
+ disposed = true;
152
+ off();
153
+ },
154
+ };
155
+ }
156
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/host/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { connectGuest } from "./guest.js";
2
+ export type { ConnectGuestOptions, Guest } from "./guest.js";
3
+ export { GUEST_PROTOCOL, GUEST_SANDBOX, OPAQUE_ORIGIN, isGuestReady, isHostHello } from "./protocol.js";
4
+ export type { GuestAct, GuestAnswer, GuestEdge, GuestNode, GuestProps, GuestReady, GuestRefusal, GuestRequest, HostHello, HostMessage, } from "./protocol.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACxG,YAAY,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,WAAW,GACZ,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { connectGuest } from "./guest.js";
2
+ export { GUEST_PROTOCOL, GUEST_SANDBOX, OPAQUE_ORIGIN, isGuestReady, isHostHello } from "./protocol.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * THE GUEST-VIEW PROTOCOL (FR-04): what a host and a view written by
3
+ * somebody the viewer does not trust may say to each other.
4
+ *
5
+ * The guest runs in `<iframe sandbox="allow-scripts">` and never with
6
+ * `allow-same-origin`, so its origin is opaque ("null"): no cookie, no
7
+ * storage, no store, no token of the host's is reachable from it. It has
8
+ * one way out, a MessageChannel the host hands it once it says it is ready:
9
+ *
10
+ * guest → parent { graview: "guest-ready", protocol } (window.postMessage, once per document)
11
+ * host → guest { graview: "host-hello", protocol, nonce, view } (window.postMessage, with the port)
12
+ * host → guest { type: "props", props } (over the port, whenever what it sees moves)
13
+ * guest → host { type: "act", nonce, id, name, args } (over the port: ask for an act)
14
+ * guest → host { type: "navigate", nonce, to } (over the port: go to a record)
15
+ * guest → host { type: "size", nonce, height } (over the port: the height it wants)
16
+ * host → guest { type: "answer", id, ok, … } (over the port: what became of an act)
17
+ *
18
+ * The host answers a `guest-ready` only from its own frame's window and
19
+ * only from the opaque origin, and every request over the port carries the
20
+ * nonce of that hello. What the guest is pushed is the store as the viewer
21
+ * sees it; an act it asks for is applied under the viewer's principal, so
22
+ * the policy refuses on its behalf exactly as it would refuse a click.
23
+ *
24
+ * This module has no imports: a guest bundle carries it and nothing of the
25
+ * framework.
26
+ */
27
+ /** Moves only when a guest of the previous protocol can no longer be served. */
28
+ export declare const GUEST_PROTOCOL = 1;
29
+ /** The only sandbox token a guest frame is given. Never `allow-same-origin`. */
30
+ export declare const GUEST_SANDBOX = "allow-scripts";
31
+ /** The origin a sandboxed frame without `allow-same-origin` posts from. */
32
+ export declare const OPAQUE_ORIGIN = "null";
33
+ /** A record as a guest receives it: plain data, only ever one the viewer may see. */
34
+ export interface GuestNode {
35
+ readonly id: string;
36
+ readonly kind: string;
37
+ readonly label?: string;
38
+ readonly [field: string]: unknown;
39
+ }
40
+ /** A link between two records the viewer may both see. */
41
+ export interface GuestEdge {
42
+ readonly kind: string;
43
+ readonly from: string;
44
+ readonly to: string;
45
+ }
46
+ /** An act the viewer may run, by name, as the guest may ask for it. */
47
+ export interface GuestAct {
48
+ readonly name: string;
49
+ readonly title: string;
50
+ readonly description?: string;
51
+ /** The kinds the act is done to, when it is done to a record. */
52
+ readonly subject?: {
53
+ readonly kinds: readonly string[] | "*";
54
+ readonly arg: string;
55
+ };
56
+ }
57
+ /**
58
+ * WHAT A GUEST VIEW IS HANDED: the plain-data half of `ViewProps`, read
59
+ * from the store as the viewer sees it. A record the viewer may not see is
60
+ * not in it — not as a node, a member, an edge or an id in `implicated`.
61
+ */
62
+ export interface GuestProps {
63
+ /** The view's registered name: what the rail says an act came through. */
64
+ readonly view: string;
65
+ /** The record, for a `one` view. Absent when the viewer may not see it. */
66
+ readonly node?: GuestNode;
67
+ /** The members, for a `many` view. */
68
+ readonly nodes?: readonly GuestNode[];
69
+ /** The links among the records above. */
70
+ readonly edges: readonly GuestEdge[];
71
+ readonly label?: string;
72
+ readonly fidelity?: "full" | "summary" | "glyph";
73
+ readonly cardinality?: "one" | "many";
74
+ readonly mode?: "scene" | "fullscreen";
75
+ readonly selected?: boolean;
76
+ readonly implicated?: readonly string[];
77
+ readonly flagged?: readonly string[];
78
+ /** The acts the viewer may run here: what the guest may ask for. */
79
+ readonly acts: readonly GuestAct[];
80
+ }
81
+ /** Why the host did not apply an act the guest asked for. */
82
+ export type GuestRefusal =
83
+ /** The policy refused it, in the policy's sentence: the same answer a click would get. */
84
+ "refused"
85
+ /** More acts than the frame is allowed in the window. */
86
+ | "rate-limited"
87
+ /** No act by that name. */
88
+ | "unknown-act"
89
+ /** The request was not one the protocol knows. */
90
+ | "malformed"
91
+ /** The act ran and threw: its arguments did not fit, or its own rule said no. */
92
+ | "failed";
93
+ export type GuestAnswer = {
94
+ readonly type: "answer";
95
+ readonly id: string | number;
96
+ readonly ok: true;
97
+ readonly intent: string;
98
+ } | {
99
+ readonly type: "answer";
100
+ readonly id: string | number;
101
+ readonly ok: false;
102
+ readonly reason: GuestRefusal;
103
+ readonly message: string;
104
+ };
105
+ export interface GuestReady {
106
+ readonly graview: "guest-ready";
107
+ readonly protocol: number;
108
+ }
109
+ export interface HostHello {
110
+ readonly graview: "host-hello";
111
+ readonly protocol: number;
112
+ readonly nonce: string;
113
+ readonly view: string;
114
+ }
115
+ export type HostMessage = {
116
+ readonly type: "props";
117
+ readonly props: GuestProps;
118
+ } | GuestAnswer;
119
+ export type GuestRequest = {
120
+ readonly type: "act";
121
+ readonly nonce: string;
122
+ readonly id: string | number;
123
+ readonly name: string;
124
+ readonly args: Readonly<Record<string, unknown>>;
125
+ } | {
126
+ readonly type: "navigate";
127
+ readonly nonce: string;
128
+ readonly to: string;
129
+ } | {
130
+ readonly type: "size";
131
+ readonly nonce: string;
132
+ readonly height: number;
133
+ };
134
+ /** Whether a window message is a guest saying it is ready. */
135
+ export declare function isGuestReady(data: unknown): data is GuestReady;
136
+ /** Whether a window message is the host's hello. */
137
+ export declare function isHostHello(data: unknown): data is HostHello;
138
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * THE GUEST-VIEW PROTOCOL (FR-04): what a host and a view written by
3
+ * somebody the viewer does not trust may say to each other.
4
+ *
5
+ * The guest runs in `<iframe sandbox="allow-scripts">` and never with
6
+ * `allow-same-origin`, so its origin is opaque ("null"): no cookie, no
7
+ * storage, no store, no token of the host's is reachable from it. It has
8
+ * one way out, a MessageChannel the host hands it once it says it is ready:
9
+ *
10
+ * guest → parent { graview: "guest-ready", protocol } (window.postMessage, once per document)
11
+ * host → guest { graview: "host-hello", protocol, nonce, view } (window.postMessage, with the port)
12
+ * host → guest { type: "props", props } (over the port, whenever what it sees moves)
13
+ * guest → host { type: "act", nonce, id, name, args } (over the port: ask for an act)
14
+ * guest → host { type: "navigate", nonce, to } (over the port: go to a record)
15
+ * guest → host { type: "size", nonce, height } (over the port: the height it wants)
16
+ * host → guest { type: "answer", id, ok, … } (over the port: what became of an act)
17
+ *
18
+ * The host answers a `guest-ready` only from its own frame's window and
19
+ * only from the opaque origin, and every request over the port carries the
20
+ * nonce of that hello. What the guest is pushed is the store as the viewer
21
+ * sees it; an act it asks for is applied under the viewer's principal, so
22
+ * the policy refuses on its behalf exactly as it would refuse a click.
23
+ *
24
+ * This module has no imports: a guest bundle carries it and nothing of the
25
+ * framework.
26
+ */
27
+ /** Moves only when a guest of the previous protocol can no longer be served. */
28
+ export const GUEST_PROTOCOL = 1;
29
+ /** The only sandbox token a guest frame is given. Never `allow-same-origin`. */
30
+ export const GUEST_SANDBOX = "allow-scripts";
31
+ /** The origin a sandboxed frame without `allow-same-origin` posts from. */
32
+ export const OPAQUE_ORIGIN = "null";
33
+ /** Whether a window message is a guest saying it is ready. */
34
+ export function isGuestReady(data) {
35
+ return typeof data === "object" && data !== null && data.graview === "guest-ready";
36
+ }
37
+ /** Whether a window message is the host's hello. */
38
+ export function isHostHello(data) {
39
+ const hello = data;
40
+ return typeof data === "object" && data !== null && hello.graview === "host-hello" && typeof hello.nonce === "string";
41
+ }
42
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,gFAAgF;AAChF,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC;AAuFpC,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAK,IAAmB,CAAC,OAAO,KAAK,aAAa,CAAC;AACrG,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,MAAM,KAAK,GAAG,IAAiB,CAAC;IAChC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,YAAY,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC;AACxH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { type ConnectGuestOptions, type Guest } from "./guest.js";
2
+ import type { GuestProps } from "./protocol.js";
3
+ /** The one connection this frame has, made on first use. */
4
+ export declare function guest(options?: ConnectGuestOptions): Guest;
5
+ /**
6
+ * WHAT THE HOST PUSHED, in a React guest: undefined until the first push,
7
+ * then every push re-renders. `act`, `navigate` and `size` are the
8
+ * connection's own.
9
+ */
10
+ export declare function useGuest(options?: ConnectGuestOptions): {
11
+ readonly props: GuestProps | undefined;
12
+ readonly act: Guest["act"];
13
+ readonly navigate: Guest["navigate"];
14
+ readonly size: Guest["size"];
15
+ };
16
+ export type { Guest, GuestProps };
17
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,mBAAmB,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIhD,4DAA4D;AAC5D,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,KAAK,CAE1D;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG;IACvD,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC9B,CAQA;AAED,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC"}
package/dist/react.js ADDED
@@ -0,0 +1,18 @@
1
+ import { useSyncExternalStore } from "react";
2
+ import { connectGuest } from "./guest.js";
3
+ let shared;
4
+ /** The one connection this frame has, made on first use. */
5
+ export function guest(options) {
6
+ return (shared ??= connectGuest(options));
7
+ }
8
+ /**
9
+ * WHAT THE HOST PUSHED, in a React guest: undefined until the first push,
10
+ * then every push re-renders. `act`, `navigate` and `size` are the
11
+ * connection's own.
12
+ */
13
+ export function useGuest(options) {
14
+ const connection = guest(options);
15
+ const props = useSyncExternalStore((changed) => connection.subscribe(changed), () => connection.props, () => undefined);
16
+ return { props, act: connection.act, navigate: connection.navigate, size: connection.size };
17
+ }
18
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAwC,MAAM,YAAY,CAAC;AAGhF,IAAI,MAAyB,CAAC;AAE9B,4DAA4D;AAC5D,MAAM,UAAU,KAAK,CAAC,OAA6B;IACjD,OAAO,CAAC,MAAM,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,OAA6B;IAMpD,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,oBAAoB,CAChC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,EAC1C,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EACtB,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC;AAC9F,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "@graview/guest",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.3",
4
+ "type": "module",
5
+ "description": "Guest views: somebody else's React or plain script in a sandboxed frame, shown only what the viewer may see, able only to ask for acts the viewer's seat then applies.",
6
+ "keywords": [
7
+ "graview",
8
+ "context-graph",
9
+ "typed-graph",
10
+ "interface",
11
+ "iframe",
12
+ "sandbox",
13
+ "guest-view"
14
+ ],
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./react": {
23
+ "types": "./dist/react.d.ts",
24
+ "default": "./dist/react.js"
25
+ },
26
+ "./host": {
27
+ "types": "./dist/host/index.d.ts",
28
+ "default": "./dist/host/index.js"
29
+ }
30
+ },
31
+ "dependencies": {
32
+ "@graview/core": "0.1.3",
33
+ "@graview/react": "0.1.3"
34
+ },
35
+ "peerDependencies": {
36
+ "react": ">=18"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "react": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "README.md"
46
+ ],
47
+ "sideEffects": false,
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/en-dash-consulting/graview.git",
51
+ "directory": "packages/guest"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "license": "Elastic-2.0",
57
+ "homepage": "https://graview.dev/docs/packages/guest.html",
58
+ "bugs": {
59
+ "url": "https://github.com/en-dash-consulting/graview/issues"
60
+ },
61
+ "engines": {
62
+ "node": ">=22"
63
+ },
64
+ "scripts": {
65
+ "build": "tsc -b"
66
+ }
6
67
  }