@graview/guest 0.0.0-stage → 0.1.2
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 +96 -0
- package/README.md +59 -2
- package/dist/guest.d.ts +46 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +98 -0
- package/dist/guest.js.map +1 -0
- package/dist/host/frame.d.ts +41 -0
- package/dist/host/frame.d.ts.map +1 -0
- package/dist/host/frame.js +116 -0
- package/dist/host/frame.js.map +1 -0
- package/dist/host/index.d.ts +9 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +5 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/react.d.ts +24 -0
- package/dist/host/react.d.ts.map +1 -0
- package/dist/host/react.js +59 -0
- package/dist/host/react.js.map +1 -0
- package/dist/host/session.d.ts +93 -0
- package/dist/host/session.d.ts.map +1 -0
- package/dist/host/session.js +156 -0
- package/dist/host/session.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +138 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +42 -0
- package/dist/protocol.js.map +1 -0
- package/dist/react.d.ts +17 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +18 -0
- package/dist/react.js.map +1 -0
- package/package.json +64 -3
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
|
-
#
|
|
1
|
+
# @graview/guest
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
package/dist/guest.d.ts
ADDED
|
@@ -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":"AAAA,OAAO,EAAE,qBAAqB,EAA8C,MAAM,eAAe,CAAC;AAuFlG,iEAAiE;AACjE,SAAS,QAAQ,CAAC,KAAa,EAAE,IAAY,EAAE,GAAiB;IAC9D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,OAAO,GAAG,EAAE;QACV,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAE,IAAI,EAAE,GAAG,IAAI;YAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QACjE,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QACxC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,kBAAkB,CAAC,SAAsB,EAAE,EAAE,MAAoB,IAAI,CAAC,GAAG;IACvF,OAAO;QACL,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,EAAE,GAAG,CAAC;QACnE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,IAAI,GAAG,EAAE,MAAM,CAAC,eAAe,IAAI,KAAK,EAAE,GAAG,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CACpE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAsB,OAA4B;IAC/E,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,kBAAkB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACnF,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI,KAAK,CAAC;IACrD,MAAM,GAAG,GAAG,QAAQ,OAAO,CAAC,IAAI,EAAW,CAAC;IAC5C,MAAM,KAAK,GAAe,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACjE,uGAAuG;IACvG,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAE3D,MAAM,KAAK,GAAG,CAAC,IAAa,EAAa,EAAE,CAAC,eAAe,CAAC,IAAI,CAAc,CAAC;IAE/E,MAAM,KAAK,GAAG,GAAe,EAAE;QAC7B,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACnE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;YAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACvC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjG,MAAM,KAAK,GAAgB,EAAE,CAAC;QAC9B,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;YACvB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBACtC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACxF,CAAC;QACH,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,GAAkC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3F,MAAM,IAAI,GAAe,KAAK,CAAC,kBAAkB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACtF,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI;YACtC,GAAG,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAgC,EAAE,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClI,CAAC,CAAC,CAAC;QACJ,OAAO;YACL,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,KAAK;YACL,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9E,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,IAAI;SACL,CAAC;IACJ,CAAC,CAAC;IAEF,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,IAAI,CAAC,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC;IAEF,mGAAmG;IACnG,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACvC,IAAI,OAAO;YAAE,OAAO;QACpB,OAAO,GAAG,IAAI,CAAC;QACf,cAAc,CAAC,GAAG,EAAE;YAClB,OAAO,GAAG,KAAK,CAAC;YAChB,IAAI,EAAE,CAAC;QACT,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,CAAC,EAAmB,EAAE,MAAoB,EAAE,OAAe,EAAE,EAAE;QAC5E,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAwB,CAAC,CAAC;IACzF,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,EAAmB,EAAE,IAAa,EAAE,IAAa,EAAE,EAAE;QAChE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,+DAA+D,CAAC,CAAC;QACjJ,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;YAAE,OAAO,MAAM,CAAC,EAAE,EAAE,cAAc,EAAE,4DAA4D,CAAC,CAAC;QACpH,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO,MAAM,CAAC,EAAE,EAAE,aAAa,EAAE,oCAAoC,CAAC,CAAC;QACrJ,IAAI,CAAC;YACH;;;;eAIG;YACH,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YACvF,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACxE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,qBAAqB;gBAAE,OAAO,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAChG,8FAA8F;YAC9F,OAAO,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,+BAA+B,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,OAAO,CAAC,IAAI;YACV,IAAI,QAAQ;gBAAE,OAAO;YACrB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;gBACvB,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC;gBACpD,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC;gBAAE,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACnI,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;gBAC5D,IAAI,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAE,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;oBACxD,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;gBACxB,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5F,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5E,OAAO;YACT,CAAC;YACD,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QACrB,CAAC;QACD,IAAI;QACJ,IAAI,KAAK;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO;YACL,QAAQ,GAAG,IAAI,CAAC;YAChB,GAAG,EAAE,CAAC;QACR,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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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"}
|
package/dist/protocol.js
ADDED
|
@@ -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"}
|
package/dist/react.d.ts
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.1.2",
|
|
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.2",
|
|
33
|
+
"@graview/react": "0.1.2"
|
|
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
|
}
|