@longstoryshort/vtt-sdk 0.1.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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/adapters/owlbear/index.d.ts +51 -0
- package/dist/adapters/owlbear/index.js +202 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +188 -0
- package/dist/types-CS2uTxCW.d.ts +207 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tldrpg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @longstoryshort/vtt-sdk
|
|
2
|
+
|
|
3
|
+
Embed a [longstoryshort.app](https://longstoryshort.app) character sheet in any virtual tabletop (VTT) via iframe and postMessage.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
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.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
OBR / Foundry / … ──► [bridge page, FOREIGN origin]
|
|
11
|
+
│ embeds iframe ↓ ← trust boundary (postMessage only)
|
|
12
|
+
└► [LSS sheet, lss origin]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install @longstoryshort/vtt-sdk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The Owlbear adapter is an optional peer dependency:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install @owlbear-rodeo/sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Packages
|
|
28
|
+
|
|
29
|
+
| Import | Contents |
|
|
30
|
+
|--------|----------|
|
|
31
|
+
| `@longstoryshort/vtt-sdk` | Core: types, `createSheetBridge`, `createSheetClient`, `createBridgeSheetSource`, `formatRollMessage` |
|
|
32
|
+
| `@longstoryshort/vtt-sdk/owlbear` | `OwlbearAdapter`, `syncObrref`, constants |
|
|
33
|
+
|
|
34
|
+
## Quick start — bridge side
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createSheetBridge, createBridgeSheetSource } from '@longstoryshort/vtt-sdk';
|
|
38
|
+
import { OwlbearAdapter } from '@longstoryshort/vtt-sdk/owlbear';
|
|
39
|
+
|
|
40
|
+
const adapter = new OwlbearAdapter();
|
|
41
|
+
const source = createBridgeSheetSource({
|
|
42
|
+
iframe: document.getElementById('sheet-frame') as HTMLIFrameElement,
|
|
43
|
+
allowedOrigins: ['https://longstoryshort.app'],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const dispose = createSheetBridge(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: 'DICE_ROLL', payload: { ... } });
|
|
67
|
+
|
|
68
|
+
// receive inbound commands from the bridge
|
|
69
|
+
const unsub = client.onEvent((event) => {
|
|
70
|
+
if (event.type === 'CAPABILITY_COMMAND') { /* handle damage, conditions, … */ }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// cleanup
|
|
74
|
+
client.dispose();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## iframe sandbox requirements
|
|
78
|
+
|
|
79
|
+
The bridge must embed the sheet iframe with at least these sandbox tokens:
|
|
80
|
+
|
|
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.
|
|
86
|
+
|
|
87
|
+
## Reference bridge
|
|
88
|
+
|
|
89
|
+
A ready-to-adapt React bridge page for Owlbear Rodeo lives in [`bridges/owlbear/index.tsx`](bridges/owlbear/index.tsx). It is NOT published to npm — deploy it as a separate page from your own infrastructure.
|
|
90
|
+
|
|
91
|
+
## Protocol events
|
|
92
|
+
|
|
93
|
+
| Type | Direction | Description |
|
|
94
|
+
|------|-----------|-------------|
|
|
95
|
+
| `DICE_ROLL` | sheet → bridge | A roll result |
|
|
96
|
+
| `MANIFEST` | sheet → bridge | Sheet capabilities at handshake |
|
|
97
|
+
| `HEALTH_CHANGED` | sheet → bridge | HP after an adjust/set |
|
|
98
|
+
| `CAPABILITY_COMMAND` | bridge → sheet | Narrow inbound ops (adjust HP, toggle condition, …) |
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { V as VTTAdapter, a as VTTUser, S as SheetEvent, N as NotifyVariant } from '../../types-CS2uTxCW.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Owlbear Rodeo implementation of {@link VTTAdapter}.
|
|
5
|
+
*
|
|
6
|
+
* Owlbear exposes no public dice API (its 3D roller is first-party and closed),
|
|
7
|
+
* so rendering a roll is our job: we broadcast a result for toasts/logs and add
|
|
8
|
+
* a transient label item over the roller's token — scene items are shared, so
|
|
9
|
+
* everyone at the table sees the floating number. All scene work is best-effort
|
|
10
|
+
* and degrades silently; the broadcast/notification path is the guaranteed core.
|
|
11
|
+
*/
|
|
12
|
+
declare class OwlbearAdapter implements VTTAdapter {
|
|
13
|
+
private sdk;
|
|
14
|
+
private obr;
|
|
15
|
+
private user;
|
|
16
|
+
private sessionId;
|
|
17
|
+
private readyPromise;
|
|
18
|
+
private disposed;
|
|
19
|
+
get isAvailable(): boolean;
|
|
20
|
+
ready(): Promise<boolean>;
|
|
21
|
+
private init;
|
|
22
|
+
private loadSdk;
|
|
23
|
+
getSessionId(): string | undefined;
|
|
24
|
+
getCurrentUser(): VTTUser | undefined;
|
|
25
|
+
broadcast(event: SheetEvent): void;
|
|
26
|
+
onEvent(handler: (event: SheetEvent) => void): () => void;
|
|
27
|
+
notify(message: string, variant?: NotifyVariant): void;
|
|
28
|
+
labelOverSelection(text: string, ttlMs?: number): Promise<boolean>;
|
|
29
|
+
dispose(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Keep the Owlbear handshake (`obrref`) reachable by the SDK across client-side
|
|
34
|
+
* navigation.
|
|
35
|
+
*
|
|
36
|
+
* The OBR SDK reads `obrref` from the URL once, at module-import time, and
|
|
37
|
+
* freezes `isAvailable` from it. But in-app links drop the param, and the host
|
|
38
|
+
* app may load a fresh SDK copy on the next page. So: when we see the param
|
|
39
|
+
* (entry page) we stash it; when it's missing (after navigation) we put it back
|
|
40
|
+
* via `replaceState` before the SDK loads.
|
|
41
|
+
*/
|
|
42
|
+
declare function syncObrref(): void;
|
|
43
|
+
|
|
44
|
+
/** Room-wide pub/sub channel for sheet events. Namespaced to avoid collisions. */
|
|
45
|
+
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;
|
|
50
|
+
|
|
51
|
+
export { BROADCAST_CHANNEL, DEFAULT_LABEL_TTL_MS, LABEL_METADATA_KEY, OwlbearAdapter, syncObrref };
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// src/adapters/owlbear/constants.ts
|
|
2
|
+
var BROADCAST_CHANNEL = "rodeo.lss/sheet-events";
|
|
3
|
+
var LABEL_METADATA_KEY = "rodeo.lss/label";
|
|
4
|
+
var DEFAULT_LABEL_TTL_MS = 4e3;
|
|
5
|
+
|
|
6
|
+
// src/adapters/owlbear/obrref.ts
|
|
7
|
+
var PARAM = "obrref";
|
|
8
|
+
var STORAGE_KEY = "lss-obrref";
|
|
9
|
+
var DEV = process.env["NODE_ENV"] !== "production";
|
|
10
|
+
function syncObrref() {
|
|
11
|
+
if (typeof window === "undefined") {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let url;
|
|
15
|
+
try {
|
|
16
|
+
url = new URL(window.location.href);
|
|
17
|
+
} catch {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const current = url.searchParams.get(PARAM);
|
|
21
|
+
if (current) {
|
|
22
|
+
try {
|
|
23
|
+
window.sessionStorage.setItem(STORAGE_KEY, current);
|
|
24
|
+
if (DEV) {
|
|
25
|
+
console.info("[LSS/OBR] obrref captured from URL");
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
let stashed = null;
|
|
32
|
+
try {
|
|
33
|
+
stashed = window.sessionStorage.getItem(STORAGE_KEY);
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
if (stashed) {
|
|
37
|
+
url.searchParams.set(PARAM, stashed);
|
|
38
|
+
window.history.replaceState(window.history.state, "", url.toString());
|
|
39
|
+
if (DEV) {
|
|
40
|
+
console.info("[LSS/OBR] obrref restored from stash");
|
|
41
|
+
}
|
|
42
|
+
} else if (DEV) {
|
|
43
|
+
console.warn("[LSS/OBR] obrref missing and no stash to restore");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/adapters/owlbear/OwlbearAdapter.ts
|
|
48
|
+
var DEV2 = process.env["NODE_ENV"] !== "production";
|
|
49
|
+
var NOTIFY_VARIANT = {
|
|
50
|
+
info: "INFO",
|
|
51
|
+
success: "SUCCESS",
|
|
52
|
+
warning: "WARNING",
|
|
53
|
+
error: "ERROR"
|
|
54
|
+
};
|
|
55
|
+
var OwlbearAdapter = class {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.sdk = null;
|
|
58
|
+
this.obr = null;
|
|
59
|
+
this.readyPromise = null;
|
|
60
|
+
this.disposed = false;
|
|
61
|
+
}
|
|
62
|
+
get isAvailable() {
|
|
63
|
+
return this.obr?.isAvailable ?? false;
|
|
64
|
+
}
|
|
65
|
+
ready() {
|
|
66
|
+
if (!this.readyPromise) {
|
|
67
|
+
this.readyPromise = this.init();
|
|
68
|
+
}
|
|
69
|
+
return this.readyPromise;
|
|
70
|
+
}
|
|
71
|
+
async init() {
|
|
72
|
+
if (typeof window === "undefined") {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
syncObrref();
|
|
76
|
+
const sdk = await this.loadSdk();
|
|
77
|
+
if (!sdk) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
this.sdk = sdk;
|
|
81
|
+
this.obr = sdk.default;
|
|
82
|
+
if (DEV2) {
|
|
83
|
+
console.info(
|
|
84
|
+
"[LSS/OBR] init \u2014 isAvailable:",
|
|
85
|
+
this.obr.isAvailable,
|
|
86
|
+
"| isReady:",
|
|
87
|
+
this.obr.isReady
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (!this.obr.isAvailable) {
|
|
91
|
+
if (DEV2) {
|
|
92
|
+
console.warn("[LSS/OBR] not embedded (origin empty) \u2014 obrref missing at SDK load.");
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (DEV2) {
|
|
97
|
+
console.info("[LSS/OBR] awaiting onReady (isReady =", this.obr.isReady, ")");
|
|
98
|
+
}
|
|
99
|
+
await new Promise((resolve) => {
|
|
100
|
+
this.obr.onReady(resolve);
|
|
101
|
+
});
|
|
102
|
+
if (DEV2) {
|
|
103
|
+
console.info("[LSS/OBR] onReady fired \u2014 fetching player\u2026");
|
|
104
|
+
}
|
|
105
|
+
if (this.disposed) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
this.sessionId = this.obr.room.id;
|
|
109
|
+
const [id, name, role] = await Promise.all([
|
|
110
|
+
this.obr.player.getId(),
|
|
111
|
+
this.obr.player.getName(),
|
|
112
|
+
this.obr.player.getRole()
|
|
113
|
+
]);
|
|
114
|
+
this.user = { id, name, role: role === "GM" ? "gm" : "player" };
|
|
115
|
+
if (DEV2) {
|
|
116
|
+
console.info("[LSS/OBR] ready \u2014 room:", this.sessionId, "user:", this.user);
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
async loadSdk() {
|
|
121
|
+
const host = window;
|
|
122
|
+
for (let i = 0; i < 10 && !host.__lssObrSdk; i += 1) {
|
|
123
|
+
await new Promise((resolve) => {
|
|
124
|
+
window.setTimeout(resolve, 50);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (host.__lssObrSdk) {
|
|
128
|
+
return host.__lssObrSdk;
|
|
129
|
+
}
|
|
130
|
+
if (DEV2) {
|
|
131
|
+
console.warn("[LSS/OBR] no preloaded SDK \u2014 importing now (may miss OBR_READY)");
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
return await import('@owlbear-rodeo/sdk');
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error("[LSS/OBR] SDK import failed:", error);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
getSessionId() {
|
|
141
|
+
return this.sessionId;
|
|
142
|
+
}
|
|
143
|
+
getCurrentUser() {
|
|
144
|
+
return this.user;
|
|
145
|
+
}
|
|
146
|
+
broadcast(event) {
|
|
147
|
+
void this.obr?.broadcast.sendMessage(BROADCAST_CHANNEL, event).catch(() => {
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
onEvent(handler) {
|
|
151
|
+
if (!this.obr) {
|
|
152
|
+
return () => {
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return this.obr.broadcast.onMessage(BROADCAST_CHANNEL, (message) => {
|
|
156
|
+
handler(message.data);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
notify(message, variant = "info") {
|
|
160
|
+
void this.obr?.notification.show(message, NOTIFY_VARIANT[variant]).catch(() => {
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async labelOverSelection(text, ttlMs = DEFAULT_LABEL_TTL_MS) {
|
|
164
|
+
const { obr, sdk } = this;
|
|
165
|
+
if (!obr || !sdk) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const selection = await obr.player.getSelection();
|
|
170
|
+
if (!selection || selection.length !== 1) {
|
|
171
|
+
if (DEV2) {
|
|
172
|
+
console.warn("[OwlbearAdapter] label skipped \u2014 selected tokens:", selection?.length ?? 0);
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const [token] = await obr.scene.items.getItems(selection);
|
|
177
|
+
if (!token) {
|
|
178
|
+
if (DEV2) {
|
|
179
|
+
console.warn("[OwlbearAdapter] label skipped \u2014 selected item not found in scene");
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
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();
|
|
184
|
+
await obr.scene.items.addItems([label]);
|
|
185
|
+
window.setTimeout(() => {
|
|
186
|
+
void obr.scene.items.deleteItems([label.id]).catch(() => {
|
|
187
|
+
});
|
|
188
|
+
}, ttlMs);
|
|
189
|
+
return true;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (DEV2) {
|
|
192
|
+
console.warn("[OwlbearAdapter] label error:", error);
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
dispose() {
|
|
198
|
+
this.disposed = true;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export { BROADCAST_CHANNEL, DEFAULT_LABEL_TTL_MS, LABEL_METADATA_KEY, OwlbearAdapter, syncObrref };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { b as SheetSource, V as VTTAdapter, c as SheetBridgeOptions, d as SheetClientOptions, e as SheetClient, C as CapabilityManifest, S as SheetEvent, M as MessageHost, D as DiceRollPayload, N as NotifyVariant } from './types-CS2uTxCW.js';
|
|
2
|
+
export { f as CapabilityDescriptor, g as CapabilityOpAddTag, h as CapabilityOpAdjust, i as CapabilityOpName, j as CapabilityOpRemoveTag, k as CapabilityOpRequestRoll, l as CapabilityOpSet, m as CapabilityOpToggle, n as CapabilityOperation, H as HealthChangedPayload, o as MessageTarget, p as SheetBridgeMessages, a as VTTUser, q as VTTUserRole } from './types-CS2uTxCW.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wires a host {@link SheetSource} to a {@link VTTAdapter}. This is the whole
|
|
6
|
+
* integration in one place, and it knows nothing about any specific sheet app or
|
|
7
|
+
* any specific VTT:
|
|
8
|
+
*
|
|
9
|
+
* - a roll on the sheet → local toast for the roller + broadcast to other
|
|
10
|
+
* clients + a transient label over the roller's selected token;
|
|
11
|
+
* - a roll broadcast by another client → local toast.
|
|
12
|
+
*
|
|
13
|
+
* Returns a dispose fn that tears down every subscription it created. The
|
|
14
|
+
* adapter is left untouched — it may be shared and longer-lived than the bridge.
|
|
15
|
+
*/
|
|
16
|
+
declare function createSheetBridge(source: SheetSource, adapter: VTTAdapter, options?: SheetBridgeOptions): () => void;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sheet-side half of the postMessage transport. The character sheet (running in
|
|
20
|
+
* its own origin, embedded by a VTT bridge in a foreign origin) uses this to push
|
|
21
|
+
* outbound events (rolls) to the bridge and to receive inbound commands — the
|
|
22
|
+
* frame boundary keeps the sheet's DOM, cookies, and token unreadable to the
|
|
23
|
+
* bridge, so only the explicit protocol crosses.
|
|
24
|
+
*
|
|
25
|
+
* Inbound is honored only from the same window we post to (the bridge) and,
|
|
26
|
+
* optionally, an origin allowlist. Returns a safe no-op client during SSR / when
|
|
27
|
+
* no message host is available. The counterpart on the bridge side is
|
|
28
|
+
* `createBridgeSheetSource`.
|
|
29
|
+
*/
|
|
30
|
+
declare function createSheetClient(options?: SheetClientOptions): SheetClient;
|
|
31
|
+
|
|
32
|
+
/** A reference to the embedded sheet iframe — only `contentWindow` is read (live). */
|
|
33
|
+
interface SheetFrameRef {
|
|
34
|
+
contentWindow: Window | null;
|
|
35
|
+
}
|
|
36
|
+
interface BridgeSheetSourceOptions {
|
|
37
|
+
/** The embedded sheet iframe. Its *live* `contentWindow` is the message peer. */
|
|
38
|
+
iframe: SheetFrameRef;
|
|
39
|
+
/** Window to listen on. Default: `window`. */
|
|
40
|
+
host?: MessageHost;
|
|
41
|
+
/** Honor inbound only from these origins (the sheet's origin). */
|
|
42
|
+
allowedOrigins?: string[];
|
|
43
|
+
/** Origin to post inbound commands to. Default `'*'`. */
|
|
44
|
+
targetOrigin?: string;
|
|
45
|
+
}
|
|
46
|
+
/** A `SheetSource` (for `createSheetBridge`) plus raw access and inbound `send`. */
|
|
47
|
+
interface BridgeSheetSource extends SheetSource {
|
|
48
|
+
/** Subscribe to the sheet's capability manifest (sent once at handshake). Returns an unsubscribe fn. */
|
|
49
|
+
onManifest(handler: (manifest: CapabilityManifest) => void): () => void;
|
|
50
|
+
/** Subscribe to every event coming from the sheet (not only rolls). */
|
|
51
|
+
onEvent(handler: (event: SheetEvent) => void): () => void;
|
|
52
|
+
/** Post an inbound command to the sheet (e.g. `CAPABILITY_COMMAND`). */
|
|
53
|
+
send(event: SheetEvent): void;
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Bridge-side half of the postMessage transport. Runs in the bridge frame (the
|
|
58
|
+
* VTT extension), listens to the embedded sheet iframe, and exposes a
|
|
59
|
+
* `SheetSource` so `createSheetBridge` can drive the VTT adapter — without the
|
|
60
|
+
* bridge ever touching the sheet's internals.
|
|
61
|
+
*
|
|
62
|
+
* `contentWindow` is read live on every message/send, so it survives the sheet
|
|
63
|
+
* iframe navigating or reloading (e.g. Gatsby dev's full-reload on navigation).
|
|
64
|
+
*/
|
|
65
|
+
declare function createBridgeSheetSource(options: BridgeSheetSourceOptions): BridgeSheetSource;
|
|
66
|
+
|
|
67
|
+
/** Toast text for a roll, e.g. "🎲 Alice: Longsword Attack — 18 💥". */
|
|
68
|
+
declare function formatRollMessage(payload: DiceRollPayload): string;
|
|
69
|
+
/** Maps a roll's crit state onto a toast variant. */
|
|
70
|
+
declare function rollVariant(payload: DiceRollPayload): NotifyVariant;
|
|
71
|
+
|
|
72
|
+
export { type BridgeSheetSource, type BridgeSheetSourceOptions, CapabilityManifest, DiceRollPayload, MessageHost, NotifyVariant, SheetBridgeOptions, SheetClient, SheetClientOptions, SheetEvent, type SheetFrameRef, SheetSource, VTTAdapter, createBridgeSheetSource, createSheetBridge, createSheetClient, formatRollMessage, rollVariant };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// src/formatRoll.ts
|
|
2
|
+
function formatRollMessage(payload) {
|
|
3
|
+
let crit = "";
|
|
4
|
+
if (payload.isCrit) {
|
|
5
|
+
if (payload.critKind === "success") {
|
|
6
|
+
crit = " \u{1F4A5}";
|
|
7
|
+
} else if (payload.critKind === "failure") {
|
|
8
|
+
crit = " \u{1F480}";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return `\u{1F3B2} ${payload.characterName}: ${payload.title} \u2014 ${payload.total}${crit}`;
|
|
12
|
+
}
|
|
13
|
+
function rollVariant(payload) {
|
|
14
|
+
if (payload.isCrit && payload.critKind === "success") {
|
|
15
|
+
return "success";
|
|
16
|
+
}
|
|
17
|
+
if (payload.isCrit && payload.critKind === "failure") {
|
|
18
|
+
return "warning";
|
|
19
|
+
}
|
|
20
|
+
return "info";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/createSheetBridge.ts
|
|
24
|
+
var DEFAULT_MESSAGES = {
|
|
25
|
+
connected: "\u{1F3B2} Sheet connected to the table",
|
|
26
|
+
labelHint: "No label placed \u2014 select exactly one of your tokens on the map"
|
|
27
|
+
};
|
|
28
|
+
function createSheetBridge(source, adapter, options = {}) {
|
|
29
|
+
const messages = { ...DEFAULT_MESSAGES, ...options.messages };
|
|
30
|
+
const cleanups = [];
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
cleanups.push(source.onRoll((roll) => {
|
|
33
|
+
if (!adapter.isAvailable) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
adapter.notify(formatRollMessage(roll), rollVariant(roll));
|
|
37
|
+
adapter.broadcast({ type: "DICE_ROLL", payload: roll });
|
|
38
|
+
void adapter.labelOverSelection(roll.total).then((placed) => {
|
|
39
|
+
if (!placed) {
|
|
40
|
+
adapter.notify(messages.labelHint, "warning");
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}));
|
|
44
|
+
void adapter.ready().then((available) => {
|
|
45
|
+
if (cancelled || !available) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
adapter.notify(messages.connected, "success");
|
|
49
|
+
cleanups.push(adapter.onEvent((event) => {
|
|
50
|
+
if (event.type !== "DICE_ROLL") {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
adapter.notify(formatRollMessage(event.payload), rollVariant(event.payload));
|
|
54
|
+
}));
|
|
55
|
+
});
|
|
56
|
+
return () => {
|
|
57
|
+
cancelled = true;
|
|
58
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/postMessageProtocol.ts
|
|
63
|
+
var MARKER = "__lssSheetSdk";
|
|
64
|
+
var VERSION = 1;
|
|
65
|
+
function wrapEnvelope(event) {
|
|
66
|
+
return { __lssSheetSdk: VERSION, event };
|
|
67
|
+
}
|
|
68
|
+
function readEnvelope(data) {
|
|
69
|
+
if (typeof data === "object" && data !== null) {
|
|
70
|
+
const record = data;
|
|
71
|
+
if (record[MARKER] === VERSION && "event" in record) {
|
|
72
|
+
return record.event;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/createSheetClient.ts
|
|
79
|
+
function createSheetClient(options = {}) {
|
|
80
|
+
const host = options.host ?? (typeof window !== "undefined" ? window : void 0);
|
|
81
|
+
const target = options.target ?? (typeof window !== "undefined" ? window.parent : void 0);
|
|
82
|
+
const targetOrigin = options.targetOrigin ?? "*";
|
|
83
|
+
const { allowedOrigins } = options;
|
|
84
|
+
if (!host) {
|
|
85
|
+
return { send: () => {
|
|
86
|
+
}, onEvent: () => () => {
|
|
87
|
+
}, dispose: () => {
|
|
88
|
+
} };
|
|
89
|
+
}
|
|
90
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
91
|
+
const listener = (event) => {
|
|
92
|
+
if (target && event.source !== target) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (allowedOrigins && !allowedOrigins.includes(event.origin)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const sheetEvent = readEnvelope(event.data);
|
|
99
|
+
if (!sheetEvent) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
handlers.forEach((handler) => handler(sheetEvent));
|
|
103
|
+
};
|
|
104
|
+
host.addEventListener("message", listener);
|
|
105
|
+
return {
|
|
106
|
+
send(event) {
|
|
107
|
+
if (!target) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
target.postMessage(wrapEnvelope(event), targetOrigin);
|
|
111
|
+
},
|
|
112
|
+
onEvent(handler) {
|
|
113
|
+
handlers.add(handler);
|
|
114
|
+
return () => {
|
|
115
|
+
handlers.delete(handler);
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
dispose() {
|
|
119
|
+
handlers.clear();
|
|
120
|
+
host.removeEventListener("message", listener);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/createBridgeSheetSource.ts
|
|
126
|
+
function createBridgeSheetSource(options) {
|
|
127
|
+
const host = options.host ?? (typeof window !== "undefined" ? window : void 0);
|
|
128
|
+
const targetOrigin = options.targetOrigin ?? "*";
|
|
129
|
+
const { iframe, allowedOrigins } = options;
|
|
130
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
131
|
+
const listener = (event) => {
|
|
132
|
+
if (event.source !== iframe.contentWindow) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (allowedOrigins && !allowedOrigins.includes(event.origin)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const sheetEvent = readEnvelope(event.data);
|
|
139
|
+
if (!sheetEvent) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
handlers.forEach((handler) => handler(sheetEvent));
|
|
143
|
+
};
|
|
144
|
+
if (host) {
|
|
145
|
+
host.addEventListener("message", listener);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
onRoll(handler) {
|
|
149
|
+
const wrapped = (event) => {
|
|
150
|
+
if (event.type === "DICE_ROLL") {
|
|
151
|
+
handler(event.payload);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
handlers.add(wrapped);
|
|
155
|
+
return () => {
|
|
156
|
+
handlers.delete(wrapped);
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
onManifest(handler) {
|
|
160
|
+
const wrapped = (event) => {
|
|
161
|
+
if (event.type === "MANIFEST") {
|
|
162
|
+
handler(event.payload);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
handlers.add(wrapped);
|
|
166
|
+
return () => {
|
|
167
|
+
handlers.delete(wrapped);
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
onEvent(handler) {
|
|
171
|
+
handlers.add(handler);
|
|
172
|
+
return () => {
|
|
173
|
+
handlers.delete(handler);
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
send(event) {
|
|
177
|
+
iframe.contentWindow?.postMessage(wrapEnvelope(event), targetOrigin);
|
|
178
|
+
},
|
|
179
|
+
dispose() {
|
|
180
|
+
handlers.clear();
|
|
181
|
+
if (host) {
|
|
182
|
+
host.removeEventListener("message", listener);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { createBridgeSheetSource, createSheetBridge, createSheetClient, formatRollMessage, rollVariant };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSS Sheet SDK — public types.
|
|
3
|
+
*
|
|
4
|
+
* This file must not import from any project outside this SDK directory —
|
|
5
|
+
* only from other SDK files and external dependencies.
|
|
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
|
+
/** A single dice roll made on a character sheet, normalized for any VTT. */
|
|
15
|
+
interface DiceRollPayload {
|
|
16
|
+
characterId: string;
|
|
17
|
+
characterName: string;
|
|
18
|
+
/** Human label, e.g. "Атака Длинный меч" or "Спасбросок Ловкость". */
|
|
19
|
+
title: string;
|
|
20
|
+
/** Dice notation as shown, e.g. "(1к20) + 5". */
|
|
21
|
+
formula: string;
|
|
22
|
+
/** Rolled breakdown, e.g. "(15) + 5". */
|
|
23
|
+
breakdown: string;
|
|
24
|
+
/** Final total as a string — supports advantage pairs like "18 | 7". */
|
|
25
|
+
total: string;
|
|
26
|
+
isCrit: boolean;
|
|
27
|
+
critKind?: 'success' | 'failure';
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
type CapabilityOpName = 'adjust' | 'set' | 'toggle' | 'add-tag' | 'remove-tag' | 'request-roll';
|
|
31
|
+
/**
|
|
32
|
+
* adjust: apply a signed delta to a numeric capability.
|
|
33
|
+
* Sign convention: negative delta = subtract (damage), positive delta = add (heal/gain).
|
|
34
|
+
* The sheet applies its own rules (temp HP absorption, resistances, clamping).
|
|
35
|
+
*/
|
|
36
|
+
interface CapabilityOpAdjust {
|
|
37
|
+
op: 'adjust';
|
|
38
|
+
capabilityId: string;
|
|
39
|
+
delta: number;
|
|
40
|
+
}
|
|
41
|
+
/** set: overwrite a capability value outright. */
|
|
42
|
+
interface CapabilityOpSet {
|
|
43
|
+
op: 'set';
|
|
44
|
+
capabilityId: string;
|
|
45
|
+
value: number | boolean | string;
|
|
46
|
+
}
|
|
47
|
+
/** toggle: flip a boolean capability. */
|
|
48
|
+
interface CapabilityOpToggle {
|
|
49
|
+
op: 'toggle';
|
|
50
|
+
capabilityId: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* add-tag: append a label to a tag-track capability (e.g. a condition).
|
|
54
|
+
* Idempotent — if the value is already present, the sheet must ignore the command.
|
|
55
|
+
*/
|
|
56
|
+
interface CapabilityOpAddTag {
|
|
57
|
+
op: 'add-tag';
|
|
58
|
+
capabilityId: string;
|
|
59
|
+
value: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* remove-tag: remove a label from a tag-track capability.
|
|
63
|
+
* Removes the first occurrence. Assumes a well-behaved set (no duplicates in DnD5e conditions).
|
|
64
|
+
*/
|
|
65
|
+
interface CapabilityOpRemoveTag {
|
|
66
|
+
op: 'remove-tag';
|
|
67
|
+
capabilityId: string;
|
|
68
|
+
value: string;
|
|
69
|
+
}
|
|
70
|
+
/** request-roll: ask the sheet to roll a d20 check against an optional DC. */
|
|
71
|
+
interface CapabilityOpRequestRoll {
|
|
72
|
+
op: 'request-roll';
|
|
73
|
+
capabilityId: string;
|
|
74
|
+
rollType: string;
|
|
75
|
+
dc?: number;
|
|
76
|
+
}
|
|
77
|
+
type CapabilityOperation = CapabilityOpAdjust | CapabilityOpSet | CapabilityOpToggle | CapabilityOpAddTag | CapabilityOpRemoveTag | CapabilityOpRequestRoll;
|
|
78
|
+
/** One capability entry in the sheet manifest. */
|
|
79
|
+
interface CapabilityDescriptor {
|
|
80
|
+
id: string;
|
|
81
|
+
operations: CapabilityOpName[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Capability manifest — the sheet's public declaration of what the host may do.
|
|
85
|
+
* Sent outbound by the sheet at handshake time; the host reads it to know which
|
|
86
|
+
* CAPABILITY_COMMAND operations are valid for this member.
|
|
87
|
+
*/
|
|
88
|
+
interface CapabilityManifest {
|
|
89
|
+
version: '1';
|
|
90
|
+
/** Short identifier for the sheet system, e.g. 'dnd5e' or 'anima'. */
|
|
91
|
+
sheetSystem: string;
|
|
92
|
+
capabilities: CapabilityDescriptor[];
|
|
93
|
+
}
|
|
94
|
+
/** Outbound fact: HP values after the sheet has applied an adjust/set command. */
|
|
95
|
+
interface HealthChangedPayload {
|
|
96
|
+
characterId: string;
|
|
97
|
+
current: number;
|
|
98
|
+
max: number;
|
|
99
|
+
temp: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Everything that crosses the sheet↔VTT boundary.
|
|
103
|
+
*
|
|
104
|
+
* Outbound (sheet → host): DICE_ROLL · MANIFEST · HEALTH_CHANGED
|
|
105
|
+
* Inbound (host → sheet): CAPABILITY_COMMAND
|
|
106
|
+
*/
|
|
107
|
+
type SheetEvent = {
|
|
108
|
+
type: 'DICE_ROLL';
|
|
109
|
+
payload: DiceRollPayload;
|
|
110
|
+
} | {
|
|
111
|
+
type: 'MANIFEST';
|
|
112
|
+
payload: CapabilityManifest;
|
|
113
|
+
} | {
|
|
114
|
+
type: 'HEALTH_CHANGED';
|
|
115
|
+
payload: HealthChangedPayload;
|
|
116
|
+
} | {
|
|
117
|
+
type: 'CAPABILITY_COMMAND';
|
|
118
|
+
payload: CapabilityOperation;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Implemented by the host (the character-sheet app) so the bridge stays
|
|
122
|
+
* agnostic of any specific app. The host normalizes its own roll representation
|
|
123
|
+
* into a {@link DiceRollPayload} and pushes it through `onRoll`.
|
|
124
|
+
*/
|
|
125
|
+
interface SheetSource {
|
|
126
|
+
/** Subscribe to rolls made on the sheet. Returns an unsubscribe fn. */
|
|
127
|
+
onRoll(handler: (roll: DiceRollPayload) => void): () => void;
|
|
128
|
+
}
|
|
129
|
+
/** Human-facing strings surfaced by the bridge — override to localize. */
|
|
130
|
+
interface SheetBridgeMessages {
|
|
131
|
+
/** Toast shown once the sheet connects to the table. */
|
|
132
|
+
connected: string;
|
|
133
|
+
/** Toast shown to the roller when a token label could not be placed. */
|
|
134
|
+
labelHint: string;
|
|
135
|
+
}
|
|
136
|
+
interface SheetBridgeOptions {
|
|
137
|
+
messages?: Partial<SheetBridgeMessages>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The seam every VTT implements. The sheet talks only to this interface; each
|
|
141
|
+
* table (Owlbear, Foundry, …) ships a thin adapter that maps its own SDK onto
|
|
142
|
+
* these methods.
|
|
143
|
+
*/
|
|
144
|
+
interface VTTAdapter {
|
|
145
|
+
/** True only when the page actually runs inside this VTT. */
|
|
146
|
+
readonly isAvailable: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Loads and handshakes with the VTT SDK. Resolves `true` once ready, or
|
|
149
|
+
* `false` if the page is not running inside this VTT. Safe to call multiple
|
|
150
|
+
* times — the work happens once.
|
|
151
|
+
*/
|
|
152
|
+
ready(): Promise<boolean>;
|
|
153
|
+
getSessionId(): string | undefined;
|
|
154
|
+
getCurrentUser(): VTTUser | undefined;
|
|
155
|
+
/** Send an event to every other client in the room (sender excluded). */
|
|
156
|
+
broadcast(event: SheetEvent): void;
|
|
157
|
+
/** Subscribe to events broadcast by other clients. Returns an unsubscribe fn. */
|
|
158
|
+
onEvent(handler: (event: SheetEvent) => void): () => void;
|
|
159
|
+
/** Local toast on this client. */
|
|
160
|
+
notify(message: string, variant?: NotifyVariant): void;
|
|
161
|
+
/**
|
|
162
|
+
* Float a transient text label over the player's currently selected token.
|
|
163
|
+
* Scene items are shared, so the label is visible to everyone at the table.
|
|
164
|
+
* Resolves `true` if a label was placed, `false` when there isn't exactly
|
|
165
|
+
* one token selected or the scene write was rejected (e.g. no permission).
|
|
166
|
+
*/
|
|
167
|
+
labelOverSelection(text: string, ttlMs?: number): Promise<boolean>;
|
|
168
|
+
/** Tear down listeners / SDK handlers. */
|
|
169
|
+
dispose(): void;
|
|
170
|
+
}
|
|
171
|
+
/** Minimal "listen for messages" surface — the real `window` satisfies it. */
|
|
172
|
+
interface MessageHost {
|
|
173
|
+
addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
174
|
+
removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
175
|
+
}
|
|
176
|
+
/** Minimal "post a message" surface — a real `Window` (e.g. `window.parent`) satisfies it. */
|
|
177
|
+
interface MessageTarget {
|
|
178
|
+
postMessage(message: unknown, targetOrigin: string): void;
|
|
179
|
+
}
|
|
180
|
+
interface SheetClientOptions {
|
|
181
|
+
/** Window to post outbound events to (the embedding bridge). Default: `window.parent`. */
|
|
182
|
+
target?: MessageTarget;
|
|
183
|
+
/** Window to listen on for inbound events. Default: `window`. */
|
|
184
|
+
host?: MessageHost;
|
|
185
|
+
/** If set, inbound events are honored only from these origins. */
|
|
186
|
+
allowedOrigins?: string[];
|
|
187
|
+
/**
|
|
188
|
+
* Origin to post outbound events to. Default `'*'`. Override with the bridge's
|
|
189
|
+
* origin once known — outbound carries only non-sensitive data, never the token.
|
|
190
|
+
*/
|
|
191
|
+
targetOrigin?: string;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Sheet-side half of the postMessage transport. The sheet (its own origin,
|
|
195
|
+
* embedded by a VTT bridge) emits outbound events and receives inbound ones
|
|
196
|
+
* without exposing DOM/cookies/token across the frame boundary.
|
|
197
|
+
*/
|
|
198
|
+
interface SheetClient {
|
|
199
|
+
/** Send an outbound event (e.g. a dice roll or manifest) to the embedding bridge. */
|
|
200
|
+
send(event: SheetEvent): void;
|
|
201
|
+
/** Subscribe to inbound events/commands from the bridge. Returns an unsubscribe fn. */
|
|
202
|
+
onEvent(handler: (event: SheetEvent) => void): () => void;
|
|
203
|
+
/** Remove the inbound listener. */
|
|
204
|
+
dispose(): void;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export type { CapabilityManifest as C, DiceRollPayload as D, HealthChangedPayload as H, MessageHost as M, NotifyVariant as N, SheetEvent as S, VTTAdapter as V, VTTUser as a, SheetSource as b, SheetBridgeOptions as c, SheetClientOptions as d, SheetClient as e, CapabilityDescriptor as f, CapabilityOpAddTag as g, CapabilityOpAdjust as h, CapabilityOpName as i, CapabilityOpRemoveTag as j, CapabilityOpRequestRoll as k, CapabilityOpSet as l, CapabilityOpToggle as m, CapabilityOperation as n, MessageTarget as o, SheetBridgeMessages as p, VTTUserRole as q };
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@longstoryshort/vtt-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Embed a longstoryshort.app character sheet in any virtual tabletop via iframe and postMessage",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./owlbear": {
|
|
15
|
+
"import": "./dist/adapters/owlbear/index.js",
|
|
16
|
+
"types": "./dist/adapters/owlbear/index.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"typesVersions": {
|
|
20
|
+
"*": {
|
|
21
|
+
"owlbear": ["./dist/adapters/owlbear/index.d.ts"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"lint": "eslint src"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@owlbear-rodeo/sdk": ">=3.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"@owlbear-rodeo/sdk": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@owlbear-rodeo/sdk": "^3.1.0",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
44
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
45
|
+
"eslint": "^9.0.0",
|
|
46
|
+
"tsup": "^8.0.0",
|
|
47
|
+
"typescript": "^5.5.0",
|
|
48
|
+
"vitest": "^2.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|