@longstoryshort/vtt-sdk 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # @longstoryshort/vtt-sdk
2
2
 
3
- Embed a [longstoryshort.app](https://longstoryshort.app) character sheet in any virtual tabletop (VTT) via iframe and postMessage.
3
+ Embed a [longstoryshort.app](https://longstoryshort.app) character sheet in any virtual tabletop via iframe and postMessage.
4
4
 
5
5
  ## How it works
6
6
 
7
- The SDK implements a postMessage-based protocol that lets a VTT extension (the "bridge") embed the LSS sheet in a nested iframe and receive dice rolls, manifests, and other events — without ever reading the sheet's DOM, cookies, or auth token.
7
+ The sheet and your VTT run at different origins. They communicate only via `window.postMessage` your code never reads the sheet's DOM, cookies, or auth token.
8
8
 
9
9
  ```
10
- OBR / Foundry / … ──► [bridge page, FOREIGN origin]
11
- embeds iframe ↓ ← trust boundary (postMessage only)
12
- └► [LSS sheet, lss origin]
10
+ Your page (your origin)
11
+ └── LSS sheet iframe (longstoryshort.app)
12
+ └── postMessage ──→ your page
13
13
  ```
14
14
 
15
15
  ## Installation
@@ -18,7 +18,7 @@ OBR / Foundry / … ──► [bridge page, FOREIGN origin]
18
18
  npm install @longstoryshort/vtt-sdk
19
19
  ```
20
20
 
21
- The Owlbear adapter is an optional peer dependency:
21
+ For Owlbear Rodeo, also install the peer dependency:
22
22
 
23
23
  ```sh
24
24
  npm install @owlbear-rodeo/sdk
@@ -28,82 +28,64 @@ npm install @owlbear-rodeo/sdk
28
28
 
29
29
  | Import | Contents |
30
30
  |--------|----------|
31
- | `@longstoryshort/vtt-sdk` | Core: types, `createRollBridge`, `createSheetClient`, `createBridgeSheetSource`, `formatRollMessage` |
32
- | `@longstoryshort/vtt-sdk/owlbear` | `OwlbearAdapter`, `syncObrref`, constants |
31
+ | `@longstoryshort/vtt-sdk` | Core: protocol types, `createBridgeSheetSource`, `createSheetClient`, `formatRollMessage`, `SHEET_IFRAME_SANDBOX` |
32
+ | `@longstoryshort/vtt-sdk/owlbear` | `OwlbearAdapter`, `ObrAdapter`, OBR bootstrap helpers |
33
33
 
34
- ## Quick start — bridge side
34
+ ## Quick start
35
35
 
36
36
  ```ts
37
- import { createRollBridge, createBridgeSheetSource } from '@longstoryshort/vtt-sdk';
38
- import { OwlbearAdapter } from '@longstoryshort/vtt-sdk/owlbear';
37
+ import { createBridgeSheetSource, SHEET_IFRAME_SANDBOX, formatRollMessage, rollVariant } from '@longstoryshort/vtt-sdk';
39
38
 
40
- const adapter = new OwlbearAdapter();
39
+ // Embed the sheet
40
+ const iframe = document.createElement('iframe');
41
+ iframe.src = 'https://longstoryshort.app/iframe/characters/list/';
42
+ iframe.setAttribute('sandbox', SHEET_IFRAME_SANDBOX);
43
+ iframe.setAttribute('allow', 'clipboard-write');
44
+ iframe.style.cssText = 'border:none;width:100%;height:100vh;display:block';
45
+ document.body.appendChild(iframe);
46
+
47
+ // Receive rolls
41
48
  const source = createBridgeSheetSource({
42
- iframe: document.getElementById('sheet-frame') as HTMLIFrameElement,
49
+ iframe,
43
50
  allowedOrigins: ['https://longstoryshort.app'],
44
51
  });
45
52
 
46
- const dispose = createRollBridge(source, adapter, {
47
- messages: {
48
- connected: '🎲 Sheet connected',
49
- labelHint: 'Select exactly one token to place a roll label',
50
- },
51
- });
52
-
53
- // later:
54
- dispose();
55
- source.dispose();
56
- ```
57
-
58
- ## Quick start — sheet side
59
-
60
- ```ts
61
- import { createSheetClient } from '@longstoryshort/vtt-sdk';
62
-
63
- const client = createSheetClient();
64
-
65
- // emit a roll to the bridge
66
- client.send({ type: 'dnd:roll', payload: { ... } });
67
-
68
- // receive inbound commands from the bridge
69
- const unsub = client.onEvent((event) => {
70
- if (event.type === 'dnd:command') { /* handle damage, conditions, … */ }
53
+ source.onRoll((roll) => {
54
+ // wire to your VTT — notification, chat, peer broadcast, etc.
55
+ console.log(formatRollMessage(roll), rollVariant(roll));
71
56
  });
72
57
 
73
58
  // cleanup
74
- client.dispose();
59
+ source.dispose();
75
60
  ```
76
61
 
77
- ## iframe sandbox requirements
78
-
79
- The bridge must embed the sheet iframe with at least these sandbox tokens:
62
+ ## Documentation
80
63
 
81
- ```
82
- sandbox="allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms allow-modals"
83
- ```
84
-
85
- `allow-same-origin` is required so the sheet can read its auth cookie and access localStorage. Without it the sheet gets an opaque origin and auth breaks.
64
+ - [SDK guide](docs/sdk-guide.md) — sandbox requirements, receiving events, protocol reference, utilities. Start here regardless of your VTT architecture.
65
+ - [Bridge guide](docs/bridge-guide.md) for VTTs that use an extension/plugin model and need a separate static bridge page. Includes the full annotated OBR bridge and `OwlbearAdapter` reference.
86
66
 
87
67
  ## Reference bridge — Owlbear Rodeo (D&D 5e)
88
68
 
89
- A deployable vanilla-TS bridge for Owlbear Rodeo lives in [`bridges/dnd/`](bridges/dnd/). It is automatically deployed to GitHub Pages on every push to `master`.
69
+ A deployable bridge for Owlbear Rodeo lives in [`bridges/dnd/`](bridges/dnd/). Deployed automatically to GitHub Pages on every push to `master`.
90
70
 
91
71
  **Live manifest:** `https://bridge.longstoryshort.app/dnd/obr/manifest.json`
92
72
 
93
- To install the extension in OBR: Extensions → Add extension → paste the manifest URL above.
73
+ To install in OBR: Extensions → Add extension → paste the manifest URL above.
74
+
75
+ ## Bridge template
94
76
 
95
- To adapt for your own VTT: copy `bridges/dnd/src/main.ts`, swap `OwlbearAdapter` for your own `VTTAdapter` implementation, and deploy as a static page.
77
+ [`bridges/_template/`](bridges/_template/) is a minimal copy-and-modify skeleton iframe embed + `createBridgeSheetSource` + `TODO` comments for your VTT's APIs, ready to build with Vite.
96
78
 
97
79
  ## Protocol events
98
80
 
99
81
  | Type | Status | Direction | Description |
100
82
  |------|--------|-----------|-------------|
101
- | `dnd:roll` | ✅ stable | sheet → host | A roll result |
102
- | `dnd:manifest` | 🧪 reserved | sheet → host | Sheet capabilities at handshake |
103
- | `dnd:health` | 🧪 reserved | sheet → host | HP after an adjust/set |
104
- | `dnd:command` | 🧪 reserved | host → sheet | Narrow inbound ops (adjust HP, toggle condition, …) |
83
+ | `dnd:roll` | ✅ stable | sheet → host | A roll result |
84
+ | `dnd:manifest` | 🧪 reserved | sheet → host | Sheet capabilities at handshake |
85
+ | `dnd:health` | 🧪 reserved | sheet → host | HP after an adjust/set |
86
+ | `dnd:command` | 🧪 reserved | host → sheet | Inbound ops (adjust HP, toggle condition, …) |
105
87
 
106
- Reserved events are typed and functional the sheet implements them — but the bridge-side wiring is considered experimental API and may change. Wire them directly via `BridgeSheetSource.onEvent` rather than through `createRollBridge`.
88
+ Reserved events are typed and wired end-to-end, but the bridge-side API is experimental. Subscribe via `source.onEvent` directly.
107
89
 
108
90
  ## License
109
91
 
@@ -1,17 +1,35 @@
1
- import { V as VTTAdapter, a as VTTUser, S as SheetEvent, N as NotifyVariant } from '../../types-w2_82sqo.js';
1
+ import { S as SheetEvent, N as NotifyVariant } from '../../formatRoll-BhFkInCu.js';
2
2
  import * as _owlbear_rodeo_sdk from '@owlbear-rodeo/sdk';
3
3
 
4
+ interface ObrPlayer {
5
+ id: string;
6
+ name: string;
7
+ role: 'gm' | 'player';
8
+ }
9
+ /** Public contract of {@link OwlbearAdapter} — use this type when you need to reference the adapter without importing the class. */
10
+ interface ObrAdapter {
11
+ readonly isAvailable: boolean;
12
+ ready(): Promise<boolean>;
13
+ getSessionId(): string | undefined;
14
+ getCurrentUser(): ObrPlayer | undefined;
15
+ notify(message: string, variant?: 'info' | 'success' | 'warning' | 'error'): void;
16
+ broadcast(event: SheetEvent): void;
17
+ onEvent(handler: (event: SheetEvent) => void): () => void;
18
+ getRoomMetadata(): Promise<Record<string, unknown>>;
19
+ onRoomMetadataChange(handler: () => void): () => void;
20
+ dispose(): void;
21
+ }
22
+
4
23
  /**
5
- * Owlbear Rodeo implementation of {@link VTTAdapter}.
24
+ * Owlbear Rodeo bridge helper.
6
25
  *
7
26
  * Owlbear exposes no public dice API (its 3D roller is first-party and closed),
8
- * so rendering a roll is our job: we broadcast a result for toasts/logs and add
9
- * a transient label item over the roller's token — scene items are shared, so
27
+ * so rendering a roll is the bridge's job: broadcast a result for toasts/logs and
28
+ * add a transient label item over the roller's token — scene items are shared, so
10
29
  * everyone at the table sees the floating number. All scene work is best-effort
11
30
  * and degrades silently; the broadcast/notification path is the guaranteed core.
12
31
  */
13
- declare class OwlbearAdapter implements VTTAdapter {
14
- private sdk;
32
+ declare class OwlbearAdapter implements ObrAdapter {
15
33
  private obr;
16
34
  private user;
17
35
  private sessionId;
@@ -21,11 +39,12 @@ declare class OwlbearAdapter implements VTTAdapter {
21
39
  ready(): Promise<boolean>;
22
40
  private init;
23
41
  getSessionId(): string | undefined;
24
- getCurrentUser(): VTTUser | undefined;
42
+ getCurrentUser(): ObrPlayer | undefined;
25
43
  broadcast(event: SheetEvent): void;
26
44
  onEvent(handler: (event: SheetEvent) => void): () => void;
27
45
  notify(message: string, variant?: NotifyVariant): void;
28
- labelOverSelection(text: string, ttlMs?: number): Promise<boolean>;
46
+ getRoomMetadata(): Promise<Record<string, unknown>>;
47
+ onRoomMetadataChange(handler: () => void): () => void;
29
48
  dispose(): void;
30
49
  }
31
50
 
@@ -43,10 +62,8 @@ declare function syncObrref(): void;
43
62
 
44
63
  /** Room-wide pub/sub channel for sheet events. Namespaced to avoid collisions. */
45
64
  declare const BROADCAST_CHANNEL = "rodeo.lss/sheet-events";
46
- /** Marks scene items we create (transient roll labels) so we own/clean them. */
47
- declare const LABEL_METADATA_KEY = "rodeo.lss/label";
48
- /** How long a roll label floats over a token before it is removed. */
49
- declare const DEFAULT_LABEL_TTL_MS = 4000;
65
+ /** OBR room metadata key false when Vortex is connected (logger active), true otherwise. */
66
+ declare const NOTIFY_ROLLS_KEY = "rodeo.lss/notify-rolls";
50
67
 
51
68
  interface ObrLike {
52
69
  onReady(cb: () => void): void;
@@ -80,4 +97,4 @@ declare function preloadObrSdk(sdk: OwlbearSdk): void;
80
97
  */
81
98
  declare function loadObrSdk(): Promise<OwlbearSdk | null>;
82
99
 
83
- export { BROADCAST_CHANNEL, DEFAULT_LABEL_TTL_MS, LABEL_METADATA_KEY, type ObrPreloadWindow, OwlbearAdapter, loadObrSdk, preloadObrSdk, syncObrref, whenObrReady };
100
+ export { BROADCAST_CHANNEL, NOTIFY_ROLLS_KEY, type ObrAdapter, type ObrPlayer, type ObrPreloadWindow, OwlbearAdapter, loadObrSdk, preloadObrSdk, syncObrref, whenObrReady };
@@ -1,7 +1,6 @@
1
1
  // src/adapters/owlbear/constants.ts
2
2
  var BROADCAST_CHANNEL = "rodeo.lss/sheet-events";
3
- var LABEL_METADATA_KEY = "rodeo.lss/label";
4
- var DEFAULT_LABEL_TTL_MS = 4e3;
3
+ var NOTIFY_ROLLS_KEY = "rodeo.lss/notify-rolls";
5
4
 
6
5
  // src/adapters/owlbear/obrref.ts
7
6
  var PARAM = "obrref";
@@ -90,7 +89,6 @@ var NOTIFY_VARIANT = {
90
89
  };
91
90
  var OwlbearAdapter = class {
92
91
  constructor() {
93
- this.sdk = null;
94
92
  this.obr = null;
95
93
  this.readyPromise = null;
96
94
  this.disposed = false;
@@ -113,7 +111,6 @@ var OwlbearAdapter = class {
113
111
  if (!sdk) {
114
112
  return false;
115
113
  }
116
- this.sdk = sdk;
117
114
  this.obr = sdk.default;
118
115
  if (DEV3) {
119
116
  console.info(
@@ -174,43 +171,21 @@ var OwlbearAdapter = class {
174
171
  void this.obr?.notification.show(message, NOTIFY_VARIANT[variant]).catch(() => {
175
172
  });
176
173
  }
177
- async labelOverSelection(text, ttlMs = DEFAULT_LABEL_TTL_MS) {
178
- const { obr, sdk } = this;
179
- if (!obr || !sdk) {
180
- return false;
181
- }
182
- try {
183
- const selection = await obr.player.getSelection();
184
- if (!selection || selection.length !== 1) {
185
- if (DEV3) {
186
- console.warn("[OwlbearAdapter] label skipped \u2014 selected tokens:", selection?.length ?? 0);
187
- }
188
- return false;
189
- }
190
- const [token] = await obr.scene.items.getItems(selection);
191
- if (!token) {
192
- if (DEV3) {
193
- console.warn("[OwlbearAdapter] label skipped \u2014 selected item not found in scene");
194
- }
195
- return false;
196
- }
197
- const label = sdk.buildLabel().plainText(text).position(token.position).attachedTo(token.id).pointerHeight(0).disableHit(true).locked(true).layer("TEXT").metadata({ [LABEL_METADATA_KEY]: true }).build();
198
- await obr.scene.items.addItems([label]);
199
- window.setTimeout(() => {
200
- void obr.scene.items.deleteItems([label.id]).catch(() => {
201
- });
202
- }, ttlMs);
203
- return true;
204
- } catch (error) {
205
- if (DEV3) {
206
- console.warn("[OwlbearAdapter] label error:", error);
207
- }
208
- return false;
209
- }
174
+ getRoomMetadata() {
175
+ if (!this.obr) return Promise.resolve({});
176
+ return this.obr.room.getMetadata().then(
177
+ (meta) => meta,
178
+ () => ({})
179
+ );
180
+ }
181
+ onRoomMetadataChange(handler) {
182
+ if (!this.obr) return () => {
183
+ };
184
+ return this.obr.room.onMetadataChange(handler);
210
185
  }
211
186
  dispose() {
212
187
  this.disposed = true;
213
188
  }
214
189
  };
215
190
 
216
- export { BROADCAST_CHANNEL, DEFAULT_LABEL_TTL_MS, LABEL_METADATA_KEY, OwlbearAdapter, loadObrSdk, preloadObrSdk, syncObrref, whenObrReady };
191
+ export { BROADCAST_CHANNEL, NOTIFY_ROLLS_KEY, OwlbearAdapter, loadObrSdk, preloadObrSdk, syncObrref, whenObrReady };
@@ -4,13 +4,6 @@
4
4
  * This file must not import from any project outside this SDK directory —
5
5
  * only from other SDK files and external dependencies.
6
6
  */
7
- type VTTUserRole = 'gm' | 'player';
8
- interface VTTUser {
9
- id: string;
10
- name: string;
11
- role: VTTUserRole;
12
- }
13
- type NotifyVariant = 'info' | 'success' | 'warning' | 'error';
14
7
  /** A single dice roll made on a character sheet, normalized for any VTT. */
15
8
  interface DiceRollPayload {
16
9
  characterId: string;
@@ -140,48 +133,6 @@ interface SheetSource {
140
133
  /** Subscribe to rolls made on the sheet. Returns an unsubscribe fn. */
141
134
  onRoll(handler: (roll: DiceRollPayload) => void): () => void;
142
135
  }
143
- /** Human-facing strings surfaced by the bridge — override to localize. */
144
- interface RollBridgeMessages {
145
- /** Toast shown once the sheet connects to the table. */
146
- connected: string;
147
- /** Toast shown to the roller when a token label could not be placed. */
148
- labelHint: string;
149
- }
150
- interface RollBridgeOptions {
151
- messages?: Partial<RollBridgeMessages>;
152
- }
153
- /**
154
- * The seam every VTT implements. The sheet talks only to this interface; each
155
- * table (Owlbear, Foundry, …) ships a thin adapter that maps its own SDK onto
156
- * these methods.
157
- */
158
- interface VTTAdapter {
159
- /** True only when the page actually runs inside this VTT. */
160
- readonly isAvailable: boolean;
161
- /**
162
- * Loads and handshakes with the VTT SDK. Resolves `true` once ready, or
163
- * `false` if the page is not running inside this VTT. Safe to call multiple
164
- * times — the work happens once.
165
- */
166
- ready(): Promise<boolean>;
167
- getSessionId(): string | undefined;
168
- getCurrentUser(): VTTUser | undefined;
169
- /** Send an event to every other client in the room (sender excluded). */
170
- broadcast(event: SheetEvent): void;
171
- /** Subscribe to events broadcast by other clients. Returns an unsubscribe fn. */
172
- onEvent(handler: (event: SheetEvent) => void): () => void;
173
- /** Local toast on this client. */
174
- notify(message: string, variant?: NotifyVariant): void;
175
- /**
176
- * Float a transient text label over the player's currently selected token.
177
- * Scene items are shared, so the label is visible to everyone at the table.
178
- * Resolves `true` if a label was placed, `false` when there isn't exactly
179
- * one token selected or the scene write was rejected (e.g. no permission).
180
- */
181
- labelOverSelection(text: string, ttlMs?: number): Promise<boolean>;
182
- /** Tear down listeners / SDK handlers. */
183
- dispose(): void;
184
- }
185
136
  /** Minimal "listen for messages" surface — the real `window` satisfies it. */
186
137
  interface MessageHost {
187
138
  addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
@@ -218,4 +169,10 @@ interface SheetClient {
218
169
  dispose(): void;
219
170
  }
220
171
 
221
- export type { CapabilityManifest as C, DiceRollPayload as D, HealthChangedPayload as H, MessageHost as M, NotifyVariant as N, RollBridgeOptions as R, SheetEvent as S, VTTAdapter as V, VTTUser as a, SheetSource as b, SheetClientOptions as c, SheetClient as d, CapabilityDescriptor as e, CapabilityOpAddTag as f, CapabilityOpAdjust as g, CapabilityOpName as h, CapabilityOpRemoveTag as i, CapabilityOpRequestRoll as j, CapabilityOpSet as k, CapabilityOpToggle as l, CapabilityOperation as m, MessageTarget as n, RollBridgeMessages as o, VTTUserRole as p };
172
+ type NotifyVariant = 'info' | 'success' | 'warning' | 'error';
173
+ /** Toast text for a roll, e.g. "🎲 Alice: Longsword Attack — 18 💥". */
174
+ declare function formatRollMessage(payload: DiceRollPayload): string;
175
+ /** Maps a roll's crit state onto a toast variant. */
176
+ declare function rollVariant(payload: DiceRollPayload): NotifyVariant;
177
+
178
+ export { type CapabilityDescriptor as C, type DiceRollPayload as D, type HealthChangedPayload as H, type MessageHost as M, type NotifyVariant as N, type SheetEvent as S, type SheetClientOptions as a, type SheetClient as b, type SheetSource as c, type CapabilityManifest as d, type CapabilityOpAddTag as e, type CapabilityOpAdjust as f, type CapabilityOpName as g, type CapabilityOpRemoveTag as h, type CapabilityOpRequestRoll as i, type CapabilityOpSet as j, type CapabilityOpToggle as k, type CapabilityOperation as l, type MessageTarget as m, formatRollMessage as n, rollVariant as r };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as SheetSource, V as VTTAdapter, R as RollBridgeOptions, c as SheetClientOptions, d as SheetClient, C as CapabilityManifest, S as SheetEvent, M as MessageHost, D as DiceRollPayload, N as NotifyVariant } from './types-w2_82sqo.js';
2
- export { e as CapabilityDescriptor, f as CapabilityOpAddTag, g as CapabilityOpAdjust, h as CapabilityOpName, i as CapabilityOpRemoveTag, j as CapabilityOpRequestRoll, k as CapabilityOpSet, l as CapabilityOpToggle, m as CapabilityOperation, H as HealthChangedPayload, n as MessageTarget, o as RollBridgeMessages, a as VTTUser, p as VTTUserRole } from './types-w2_82sqo.js';
1
+ import { a as SheetClientOptions, b as SheetClient, c as SheetSource, S as SheetEvent, M as MessageHost } from './formatRoll-BhFkInCu.js';
2
+ export { C as CapabilityDescriptor, d as CapabilityManifest, e as CapabilityOpAddTag, f as CapabilityOpAdjust, g as CapabilityOpName, h as CapabilityOpRemoveTag, i as CapabilityOpRequestRoll, j as CapabilityOpSet, k as CapabilityOpToggle, l as CapabilityOperation, D as DiceRollPayload, H as HealthChangedPayload, m as MessageTarget, N as NotifyVariant, n as formatRollMessage, r as rollVariant } from './formatRoll-BhFkInCu.js';
3
3
 
4
4
  /**
5
5
  * Minimum sandbox tokens required on the sheet iframe.
@@ -8,23 +8,6 @@ export { e as CapabilityDescriptor, f as CapabilityOpAddTag, g as CapabilityOpAd
8
8
  */
9
9
  declare const SHEET_IFRAME_SANDBOX = "allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms allow-modals";
10
10
 
11
- /**
12
- * The default roll bridge — one opinionated policy, not the full protocol.
13
- *
14
- * Wires {@link SheetSource} → {@link VTTAdapter} for the `dnd:roll` event only:
15
- * - a roll on the sheet → local toast for the roller + broadcast to other
16
- * clients + a transient label over the roller's selected token;
17
- * - a roll broadcast by another client → local toast.
18
- *
19
- * Inbound capability wiring (`dnd:command`, `dnd:manifest`, `dnd:health`) is
20
- * deliberately out of scope here — wire those directly via
21
- * {@link BridgeSheetSource.onEvent} when your bridge needs them.
22
- *
23
- * Returns a dispose fn that tears down every subscription it created. The
24
- * adapter is left untouched — it may be shared and longer-lived than the bridge.
25
- */
26
- declare function createRollBridge(source: SheetSource, adapter: VTTAdapter, options?: RollBridgeOptions): () => void;
27
-
28
11
  /**
29
12
  * Sheet-side half of the postMessage transport. The character sheet (running in
30
13
  * its own origin, embedded by a VTT bridge in a foreign origin) uses this to push
@@ -53,11 +36,9 @@ interface BridgeSheetSourceOptions {
53
36
  /** Origin to post inbound commands to. Default `'*'`. */
54
37
  targetOrigin?: string;
55
38
  }
56
- /** A `SheetSource` (for `createRollBridge`) plus raw access and inbound `send`. */
39
+ /** Bridge-side source: `onRoll` convenience + full `onEvent` access + inbound `send`. */
57
40
  interface BridgeSheetSource extends SheetSource {
58
- /** Subscribe to the sheet's capability manifest (sent once at handshake). Returns an unsubscribe fn. */
59
- onManifest(handler: (manifest: CapabilityManifest) => void): () => void;
60
- /** Subscribe to every event coming from the sheet (not only rolls). */
41
+ /** Subscribe to every event coming from the sheet. Returns an unsubscribe fn. */
61
42
  onEvent(handler: (event: SheetEvent) => void): () => void;
62
43
  /** Post an inbound command to the sheet (e.g. `dnd:command`). */
63
44
  send(event: SheetEvent): void;
@@ -65,18 +46,12 @@ interface BridgeSheetSource extends SheetSource {
65
46
  }
66
47
  /**
67
48
  * Bridge-side half of the postMessage transport. Runs in the bridge frame (the
68
- * VTT extension), listens to the embedded sheet iframe, and exposes a
69
- * `SheetSource` so `createRollBridge` can drive the VTT adapter without the
70
- * bridge ever touching the sheet's internals.
49
+ * VTT extension), listens to the embedded sheet iframe, and delivers typed
50
+ * `SheetEvent`s without the bridge ever touching the sheet's internals.
71
51
  *
72
52
  * `contentWindow` is read live on every message/send, so it survives the sheet
73
53
  * iframe navigating or reloading (e.g. Gatsby dev's full-reload on navigation).
74
54
  */
75
55
  declare function createBridgeSheetSource(options: BridgeSheetSourceOptions): BridgeSheetSource;
76
56
 
77
- /** Toast text for a roll, e.g. "🎲 Alice: Longsword Attack 18 💥". */
78
- declare function formatRollMessage(payload: DiceRollPayload): string;
79
- /** Maps a roll's crit state onto a toast variant. */
80
- declare function rollVariant(payload: DiceRollPayload): NotifyVariant;
81
-
82
- export { type BridgeSheetSource, type BridgeSheetSourceOptions, CapabilityManifest, DiceRollPayload, MessageHost, NotifyVariant, RollBridgeOptions, SHEET_IFRAME_SANDBOX, SheetClient, SheetClientOptions, SheetEvent, type SheetFrameRef, SheetSource, VTTAdapter, createBridgeSheetSource, createRollBridge, createSheetClient, formatRollMessage, rollVariant };
57
+ export { type BridgeSheetSource, type BridgeSheetSourceOptions, MessageHost, SHEET_IFRAME_SANDBOX, SheetClient, SheetClientOptions, SheetEvent, type SheetFrameRef, SheetSource, createBridgeSheetSource, createSheetClient };
package/dist/index.js CHANGED
@@ -1,67 +1,6 @@
1
1
  // src/constants.ts
2
2
  var SHEET_IFRAME_SANDBOX = "allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms allow-modals";
3
3
 
4
- // src/formatRoll.ts
5
- function formatRollMessage(payload) {
6
- let crit = "";
7
- if (payload.isCrit) {
8
- if (payload.critKind === "success") {
9
- crit = " \u{1F4A5}";
10
- } else if (payload.critKind === "failure") {
11
- crit = " \u{1F480}";
12
- }
13
- }
14
- return `\u{1F3B2} ${payload.characterName}: ${payload.title} \u2014 ${payload.total}${crit}`;
15
- }
16
- function rollVariant(payload) {
17
- if (payload.isCrit && payload.critKind === "success") {
18
- return "success";
19
- }
20
- if (payload.isCrit && payload.critKind === "failure") {
21
- return "warning";
22
- }
23
- return "info";
24
- }
25
-
26
- // src/createRollBridge.ts
27
- var DEFAULT_MESSAGES = {
28
- connected: "\u{1F3B2} Sheet connected to the table",
29
- labelHint: "No label placed \u2014 select exactly one of your tokens on the map"
30
- };
31
- function createRollBridge(source, adapter, options = {}) {
32
- const messages = { ...DEFAULT_MESSAGES, ...options.messages };
33
- const cleanups = [];
34
- let cancelled = false;
35
- cleanups.push(source.onRoll((roll) => {
36
- if (!adapter.isAvailable) {
37
- return;
38
- }
39
- adapter.notify(formatRollMessage(roll), rollVariant(roll));
40
- adapter.broadcast({ type: "dnd:roll", payload: roll });
41
- void adapter.labelOverSelection(roll.total).then((placed) => {
42
- if (!placed) {
43
- adapter.notify(messages.labelHint, "warning");
44
- }
45
- });
46
- }));
47
- void adapter.ready().then((available) => {
48
- if (cancelled || !available) {
49
- return;
50
- }
51
- adapter.notify(messages.connected, "success");
52
- cleanups.push(adapter.onEvent((event) => {
53
- if (event.type !== "dnd:roll") {
54
- return;
55
- }
56
- adapter.notify(formatRollMessage(event.payload), rollVariant(event.payload));
57
- }));
58
- });
59
- return () => {
60
- cancelled = true;
61
- cleanups.forEach((cleanup) => cleanup());
62
- };
63
- }
64
-
65
4
  // src/postMessageProtocol.ts
66
5
  var MARKER = "__lssSheetSdk";
67
6
  var VERSION = 2;
@@ -159,17 +98,6 @@ function createBridgeSheetSource(options) {
159
98
  handlers.delete(wrapped);
160
99
  };
161
100
  },
162
- onManifest(handler) {
163
- const wrapped = (event) => {
164
- if (event.type === "dnd:manifest") {
165
- handler(event.payload);
166
- }
167
- };
168
- handlers.add(wrapped);
169
- return () => {
170
- handlers.delete(wrapped);
171
- };
172
- },
173
101
  onEvent(handler) {
174
102
  handlers.add(handler);
175
103
  return () => {
@@ -188,4 +116,26 @@ function createBridgeSheetSource(options) {
188
116
  };
189
117
  }
190
118
 
191
- export { SHEET_IFRAME_SANDBOX, createBridgeSheetSource, createRollBridge, createSheetClient, formatRollMessage, rollVariant };
119
+ // src/formatRoll.ts
120
+ function formatRollMessage(payload) {
121
+ let crit = "";
122
+ if (payload.isCrit) {
123
+ if (payload.critKind === "success") {
124
+ crit = " \u{1F4A5}";
125
+ } else if (payload.critKind === "failure") {
126
+ crit = " \u{1F480}";
127
+ }
128
+ }
129
+ return `\u{1F3B2} ${payload.characterName}: ${payload.title} \u2014 ${payload.total}${crit}`;
130
+ }
131
+ function rollVariant(payload) {
132
+ if (payload.isCrit && payload.critKind === "success") {
133
+ return "success";
134
+ }
135
+ if (payload.isCrit && payload.critKind === "failure") {
136
+ return "warning";
137
+ }
138
+ return "info";
139
+ }
140
+
141
+ export { SHEET_IFRAME_SANDBOX, createBridgeSheetSource, createSheetClient, formatRollMessage, rollVariant };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longstoryshort/vtt-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Embed a longstoryshort.app character sheet in any virtual tabletop via iframe and postMessage",
5
5
  "license": "MIT",
6
6
  "type": "module",