@t3ams/sdk 0.1.4

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.
@@ -0,0 +1,204 @@
1
+ import { HostApi, Transport } from "@novasamatech/host-api";
2
+
3
+ //#region src/protocol.d.ts
4
+ declare const T3AMS_GAME_PROTOCOL_VERSION: 1;
5
+ declare const T3AMS_GAME_MAX_DATA_BYTES: number;
6
+ type T3amsJson = null | boolean | number | string | readonly T3amsJson[] | {
7
+ readonly [key: string]: T3amsJson;
8
+ };
9
+ type GameStatus = "lobby" | "playing" | "finished" | "abandoned";
10
+ type HostContext = {
11
+ readonly embedding: "nested";
12
+ readonly capability: "delegated-host";
13
+ } | {
14
+ readonly embedding: "t3ams";
15
+ readonly capability: "none";
16
+ };
17
+ type GameStateUpdate = Exclude<GameStatus, "abandoned">;
18
+ /**
19
+ * Preset marketplace tags a game may declare. Hosts surface these for
20
+ * discovery filters ("Multiplayer", "Action", …); games pass a subset in
21
+ * their config/meta.
22
+ */
23
+ declare const T3AMS_GAME_TAGS: readonly ["multiplayer", "single-player", "co-op", "party", "action", "racing", "arcade", "puzzle", "strategy", "trivia", "word", "casual"];
24
+ type T3amsGameTag = (typeof T3AMS_GAME_TAGS)[number];
25
+ declare function isT3amsGameTag(value: unknown): value is T3amsGameTag;
26
+ interface T3amsPlayer {
27
+ readonly id: string;
28
+ readonly xid: string;
29
+ readonly name: string;
30
+ readonly avatarUrl?: string;
31
+ }
32
+ interface T3amsSessionInfo {
33
+ readonly sessionId: string;
34
+ readonly hostSeatId: string;
35
+ readonly role: "host" | "guest";
36
+ readonly conversationKind: "channel" | "group" | "dm";
37
+ readonly initialStatus: GameStatus;
38
+ }
39
+ interface T3amsGameMeta {
40
+ readonly id: string;
41
+ readonly name: string;
42
+ readonly description?: string;
43
+ readonly iconUrl?: string;
44
+ /** Marketplace cover image; falls back to a host-rendered placeholder. */
45
+ readonly coverImageUrl?: string;
46
+ /** Preset discovery tags; unknown tags are rejected by validation. */
47
+ readonly tags?: readonly T3amsGameTag[];
48
+ /** Minimum players needed before the game can start; hosts surface this on invite cards. */
49
+ readonly minPlayers?: number;
50
+ /** Join cap enforced by the host while the session is in the lobby. */
51
+ readonly maxPlayers?: number;
52
+ }
53
+ interface T3amsTurn {
54
+ readonly seatId: string;
55
+ readonly prompt?: string;
56
+ }
57
+ type HostToGameMessage = {
58
+ readonly t3ams: 1;
59
+ readonly type: "hello";
60
+ readonly protocolVersion: 1;
61
+ readonly session: T3amsSessionInfo;
62
+ readonly player: T3amsPlayer;
63
+ readonly roster: readonly T3amsPlayer[];
64
+ readonly game?: T3amsGameMeta;
65
+ readonly hostContext?: HostContext;
66
+ } | {
67
+ readonly t3ams: 1;
68
+ readonly type: "message";
69
+ readonly from: string;
70
+ readonly data: T3amsJson;
71
+ } | {
72
+ readonly t3ams: 1;
73
+ readonly type: "roster";
74
+ readonly roster: readonly T3amsPlayer[];
75
+ } | {
76
+ readonly t3ams: 1;
77
+ readonly type: "close";
78
+ };
79
+ type GameToHostMessage = {
80
+ readonly t3ams: 1;
81
+ readonly type: "ready";
82
+ readonly protocolVersion: 1;
83
+ } | {
84
+ readonly t3ams: 1;
85
+ readonly type: "send";
86
+ readonly data: T3amsJson;
87
+ } | {
88
+ readonly t3ams: 1;
89
+ readonly type: "state";
90
+ readonly status: GameStateUpdate;
91
+ readonly detail?: string;
92
+ readonly turn?: T3amsTurn;
93
+ } | {
94
+ readonly t3ams: 1;
95
+ readonly type: "result";
96
+ readonly summary: string;
97
+ } | {
98
+ readonly t3ams: 1;
99
+ readonly type: "exit";
100
+ };
101
+ declare function isT3amsJson(data: unknown): data is T3amsJson;
102
+ declare function parseHostToGameMessage(data: unknown): HostToGameMessage | null;
103
+ declare function parseGameToHostMessage(data: unknown): GameToHostMessage | null;
104
+ //#endregion
105
+ //#region src/client.d.ts
106
+ /** True when the game is running inside a frame (the t3ams host embeds it in an iframe), so it should probe for a host via the ready/hello handshake. Top-level pages run standalone. */
107
+ declare function isInT3ams(): boolean;
108
+ interface T3amsGameSession<TData extends T3amsJson = T3amsJson> {
109
+ readonly session: T3amsSessionInfo;
110
+ readonly hostContext: HostContext | null;
111
+ readonly player: T3amsPlayer;
112
+ readonly roster: readonly T3amsPlayer[];
113
+ readonly meta: T3amsGameMeta | null;
114
+ send(data: TData): void;
115
+ onMessage(cb: (from: string, data: TData) => void): () => void;
116
+ onRoster(cb: (roster: readonly T3amsPlayer[]) => void): () => void;
117
+ onClose(cb: () => void): () => void;
118
+ setState(status: GameStateUpdate, detail?: string): void;
119
+ setTurn(turn: T3amsTurn | null): void;
120
+ onPlayerJoin(cb: (player: T3amsPlayer) => void): () => void;
121
+ onPlayerLeave(cb: (player: T3amsPlayer) => void): () => void;
122
+ reportResult(summary: string): void;
123
+ exit(): void;
124
+ }
125
+ declare function connectT3ams<TData extends T3amsJson = T3amsJson>(timeoutMs?: number): Promise<T3amsGameSession<TData>>;
126
+ //#endregion
127
+ //#region src/session-store.d.ts
128
+ interface T3amsConfig {
129
+ readonly name: string;
130
+ readonly description?: string;
131
+ readonly iconUrl?: string;
132
+ /** Minimum players needed before the game can start; optional, display-only. */
133
+ readonly minPlayers?: number;
134
+ /** Preferred join cap; optional, display-only. */
135
+ readonly maxPlayers?: number;
136
+ readonly timeoutMs?: number;
137
+ }
138
+ type T3amsStatus = "connecting" | "ready" | "error" | "closed" | "skipped";
139
+ interface T3amsSnapshot {
140
+ readonly status: T3amsStatus;
141
+ readonly error: string | null;
142
+ readonly session: T3amsSessionInfo | null;
143
+ readonly player: T3amsPlayer | null;
144
+ readonly meta: T3amsGameMeta | null;
145
+ readonly roster: readonly T3amsPlayer[];
146
+ readonly state: GameStateUpdate;
147
+ readonly turn: T3amsTurn | null;
148
+ }
149
+ declare function configureT3ams(configuration: T3amsConfig): void;
150
+ declare function ensureT3amsConnection(): void;
151
+ declare function retryT3amsConnection(): void;
152
+ declare function getT3amsSession(): T3amsGameSession | null;
153
+ declare function subscribeT3ams(listener: () => void): () => void;
154
+ declare function getT3amsSnapshot(): T3amsSnapshot;
155
+ declare function t3amsSetState(status: GameStateUpdate, detail?: string): void;
156
+ declare function t3amsSetTurn(turn: T3amsTurn | null): void;
157
+ declare function t3amsSend(data: T3amsJson): void;
158
+ declare function t3amsReportResult(summary: string): void;
159
+ declare function t3amsExit(): void;
160
+ //#endregion
161
+ //#region src/capabilities.d.ts
162
+ interface DelegatedChildSessionRequest {
163
+ readonly requestId: string;
164
+ readonly gameId: string;
165
+ readonly gameOrigin: string;
166
+ readonly port: MessagePort;
167
+ }
168
+ interface DelegatedChildSessionInfo {
169
+ readonly sessionId: string;
170
+ readonly gameId: string;
171
+ readonly gameOrigin: string;
172
+ }
173
+ interface GameCapabilityBridgeOptions {
174
+ readonly iframe: HTMLIFrameElement;
175
+ readonly gameId: string;
176
+ readonly gameOrigin: string;
177
+ readonly openDelegatedChildSession: (request: DelegatedChildSessionRequest) => Promise<DelegatedChildSessionInfo>;
178
+ readonly onUnavailable: (reason: "unsupported" | "rejected" | "timeout") => void;
179
+ readonly onReady: (session: DelegatedChildSessionInfo) => void;
180
+ }
181
+ interface GameCapabilityBridge {
182
+ start(): Promise<void>;
183
+ close(): void;
184
+ dispose(): void;
185
+ }
186
+ interface DelegatedHostPortOptions {
187
+ readonly parentOrigin?: string;
188
+ readonly timeoutMs?: number;
189
+ }
190
+ /**
191
+ * Wait for the one host capability port attached to this game document.
192
+ * The port is accepted only from the immediate parent and pinned origin.
193
+ */
194
+ declare function waitForDelegatedHostPort(gameId: string, options?: DelegatedHostPortOptions): Promise<MessagePort>;
195
+ declare function createGameCapabilityBridge(options: GameCapabilityBridgeOptions): GameCapabilityBridge;
196
+ //#endregion
197
+ //#region src/host-transport.d.ts
198
+ /** Returns the delegated host transport while running inside a t3ams game. */
199
+ declare function getT3amsHostTransport(): Transport | undefined;
200
+ /** Returns the delegated host API while running inside a t3ams game. */
201
+ declare function getT3amsHostApi(): HostApi | undefined;
202
+ //#endregion
203
+ export { type DelegatedChildSessionInfo, type DelegatedChildSessionRequest, type DelegatedHostPortOptions, type GameCapabilityBridge, type GameCapabilityBridgeOptions, type GameStateUpdate, type GameStatus, type GameToHostMessage, type HostContext, type HostToGameMessage, T3AMS_GAME_MAX_DATA_BYTES, T3AMS_GAME_PROTOCOL_VERSION, T3AMS_GAME_TAGS, type T3amsConfig, type T3amsGameMeta, type T3amsGameSession, type T3amsGameTag, type T3amsJson, type T3amsPlayer, type T3amsSessionInfo, type T3amsSnapshot, type T3amsStatus, type T3amsTurn, configureT3ams, connectT3ams, createGameCapabilityBridge, ensureT3amsConnection, getT3amsHostApi, getT3amsHostTransport, getT3amsSession, getT3amsSnapshot, isInT3ams, isT3amsGameTag, isT3amsJson, parseGameToHostMessage, parseHostToGameMessage, retryT3amsConnection, subscribeT3ams, t3amsExit, t3amsReportResult, t3amsSend, t3amsSetState, t3amsSetTurn, waitForDelegatedHostPort };
204
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/protocol.ts","../src/client.ts","../src/session-store.ts","../src/capabilities.ts","../src/host-transport.ts"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,800 @@
1
+ import { createDefaultLogger, createHostApi, createTransport } from "@novasamatech/host-api";
2
+
3
+ //#region src/protocol.ts
4
+ const T3AMS_GAME_PROTOCOL_VERSION = 1;
5
+ const T3AMS_GAME_MAX_DATA_BYTES = 12 * 1024;
6
+ const MAX_NAME_LENGTH = 100;
7
+ const MAX_TEXT_LENGTH = 280;
8
+ const CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F-\u009F]/;
9
+ /**
10
+ * Preset marketplace tags a game may declare. Hosts surface these for
11
+ * discovery filters ("Multiplayer", "Action", …); games pass a subset in
12
+ * their config/meta.
13
+ */
14
+ const T3AMS_GAME_TAGS = [
15
+ "multiplayer",
16
+ "single-player",
17
+ "co-op",
18
+ "party",
19
+ "action",
20
+ "racing",
21
+ "arcade",
22
+ "puzzle",
23
+ "strategy",
24
+ "trivia",
25
+ "word",
26
+ "casual"
27
+ ];
28
+ function isT3amsGameTag(value) {
29
+ return T3AMS_GAME_TAGS.includes(value);
30
+ }
31
+ function isT3amsJson(data) {
32
+ return checkJsonValue(data, /* @__PURE__ */ new Set());
33
+ }
34
+ function checkJsonValue(data, ancestors) {
35
+ if (data === null) return true;
36
+ const kind = typeof data;
37
+ if (kind === "boolean" || kind === "string") return true;
38
+ if (kind === "number") return Number.isFinite(data);
39
+ if (kind !== "object") return false;
40
+ if (ancestors.has(data)) return false;
41
+ if (Array.isArray(data)) {
42
+ ancestors.add(data);
43
+ try {
44
+ return data.every((item) => checkJsonValue(item, ancestors));
45
+ } finally {
46
+ ancestors.delete(data);
47
+ }
48
+ }
49
+ const proto = Object.getPrototypeOf(data);
50
+ if (proto !== Object.prototype && proto !== null) return false;
51
+ ancestors.add(data);
52
+ try {
53
+ return Object.keys(data).every((key) => checkJsonValue(data[key], ancestors));
54
+ } finally {
55
+ ancestors.delete(data);
56
+ }
57
+ }
58
+ function jsonByteLength(value) {
59
+ return new TextEncoder().encode(JSON.stringify(value)).length;
60
+ }
61
+ function isRecord(value) {
62
+ return typeof value === "object" && value !== null && !Array.isArray(value);
63
+ }
64
+ function isBoundedText(value, maxLength) {
65
+ return typeof value === "string" && value.length <= maxLength && !CONTROL_CHAR_PATTERN.test(value);
66
+ }
67
+ function isValidName(value) {
68
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_NAME_LENGTH;
69
+ }
70
+ function isValidPlayer(value) {
71
+ if (!isRecord(value)) return false;
72
+ const { id, xid, name, avatarUrl } = value;
73
+ if (typeof id !== "string" || id.length === 0) return false;
74
+ if (typeof xid !== "string" || xid.length === 0) return false;
75
+ if (!isValidName(name)) return false;
76
+ if (avatarUrl !== void 0 && typeof avatarUrl !== "string") return false;
77
+ return true;
78
+ }
79
+ function isValidRoster(value) {
80
+ return Array.isArray(value) && value.every(isValidPlayer);
81
+ }
82
+ function isGameStatus(value) {
83
+ return value === "lobby" || value === "playing" || value === "finished" || value === "abandoned";
84
+ }
85
+ function isGameStateUpdate(value) {
86
+ return value === "lobby" || value === "playing" || value === "finished";
87
+ }
88
+ function isValidHostContext(value) {
89
+ if (!isRecord(value)) return false;
90
+ const keys = Object.keys(value);
91
+ if (keys.length !== 2 || !keys.includes("embedding") || !keys.includes("capability")) return false;
92
+ return value["embedding"] === "nested" && value["capability"] === "delegated-host" || value["embedding"] === "t3ams" && value["capability"] === "none";
93
+ }
94
+ function isValidSessionInfo(value) {
95
+ if (!isRecord(value)) return false;
96
+ const { sessionId, hostSeatId, role, conversationKind, initialStatus } = value;
97
+ if (typeof sessionId !== "string" || sessionId.length === 0) return false;
98
+ if (typeof hostSeatId !== "string" || hostSeatId.length === 0) return false;
99
+ if (role !== "host" && role !== "guest") return false;
100
+ if (conversationKind !== "channel" && conversationKind !== "group" && conversationKind !== "dm") return false;
101
+ if (!isGameStatus(initialStatus)) return false;
102
+ return true;
103
+ }
104
+ function isValidTurn(value) {
105
+ if (!isRecord(value)) return false;
106
+ const { seatId, prompt } = value;
107
+ if (typeof seatId !== "string" || seatId.length === 0 || seatId.length > MAX_NAME_LENGTH) return false;
108
+ if (prompt !== void 0 && !isBoundedText(prompt, MAX_NAME_LENGTH)) return false;
109
+ return true;
110
+ }
111
+ function isValidUrlField(value) {
112
+ return typeof value === "string" && value.length <= 2048 && !CONTROL_CHAR_PATTERN.test(value);
113
+ }
114
+ function isValidGameMeta(value) {
115
+ if (!isRecord(value)) return false;
116
+ const { id, name, description, iconUrl, coverImageUrl, tags, minPlayers, maxPlayers } = value;
117
+ if (!isValidName(id)) return false;
118
+ if (!isValidName(name)) return false;
119
+ if (description !== void 0 && !isBoundedText(description, MAX_TEXT_LENGTH)) return false;
120
+ if (iconUrl !== void 0 && !isValidUrlField(iconUrl)) return false;
121
+ if (coverImageUrl !== void 0 && !isValidUrlField(coverImageUrl)) return false;
122
+ if (tags !== void 0 && (!Array.isArray(tags) || tags.length > T3AMS_GAME_TAGS.length || !tags.every(isT3amsGameTag))) return false;
123
+ for (const count of [minPlayers, maxPlayers]) {
124
+ if (count === void 0) continue;
125
+ if (typeof count !== "number" || !Number.isInteger(count) || count < 1) return false;
126
+ }
127
+ if (typeof minPlayers === "number" && typeof maxPlayers === "number" && minPlayers > maxPlayers) return false;
128
+ return true;
129
+ }
130
+ function parseHostToGameMessage(data) {
131
+ if (!isRecord(data)) return null;
132
+ if (data["t3ams"] !== 1) return null;
133
+ switch (data["type"]) {
134
+ case "hello": {
135
+ if (data["protocolVersion"] !== T3AMS_GAME_PROTOCOL_VERSION) return null;
136
+ const session = data["session"];
137
+ const player = data["player"];
138
+ const roster = data["roster"];
139
+ if (!isValidSessionInfo(session)) return null;
140
+ if (!isValidPlayer(player)) return null;
141
+ if (!isValidRoster(roster)) return null;
142
+ const game = data["game"];
143
+ const hostContext = data["hostContext"];
144
+ if (hostContext !== void 0 && !isValidHostContext(hostContext)) return null;
145
+ return {
146
+ t3ams: 1,
147
+ type: "hello",
148
+ protocolVersion: 1,
149
+ session,
150
+ player,
151
+ roster,
152
+ ...isValidGameMeta(game) ? { game } : {},
153
+ ...hostContext !== void 0 ? { hostContext } : {}
154
+ };
155
+ }
156
+ case "message": {
157
+ const from = data["from"];
158
+ const value = data["data"];
159
+ if (typeof from !== "string" || from.length === 0) return null;
160
+ if (!isT3amsJson(value)) return null;
161
+ return {
162
+ t3ams: 1,
163
+ type: "message",
164
+ from,
165
+ data: value
166
+ };
167
+ }
168
+ case "roster": {
169
+ const roster = data["roster"];
170
+ if (!isValidRoster(roster)) return null;
171
+ return {
172
+ t3ams: 1,
173
+ type: "roster",
174
+ roster
175
+ };
176
+ }
177
+ case "close": return {
178
+ t3ams: 1,
179
+ type: "close"
180
+ };
181
+ default: return null;
182
+ }
183
+ }
184
+ function parseGameToHostMessage(data) {
185
+ if (!isRecord(data)) return null;
186
+ if (data["t3ams"] !== 1) return null;
187
+ switch (data["type"]) {
188
+ case "ready":
189
+ if (data["protocolVersion"] !== T3AMS_GAME_PROTOCOL_VERSION) return null;
190
+ return {
191
+ t3ams: 1,
192
+ type: "ready",
193
+ protocolVersion: 1
194
+ };
195
+ case "send": {
196
+ const value = data["data"];
197
+ if (!isT3amsJson(value)) return null;
198
+ if (jsonByteLength(value) > T3AMS_GAME_MAX_DATA_BYTES) return null;
199
+ return {
200
+ t3ams: 1,
201
+ type: "send",
202
+ data: value
203
+ };
204
+ }
205
+ case "state": {
206
+ const status = data["status"];
207
+ if (!isGameStateUpdate(status)) return null;
208
+ const detail = data["detail"];
209
+ if (detail !== void 0 && !isBoundedText(detail, MAX_TEXT_LENGTH)) return null;
210
+ const turn = data["turn"];
211
+ return {
212
+ t3ams: 1,
213
+ type: "state",
214
+ status,
215
+ ...detail !== void 0 ? { detail } : {},
216
+ ...isValidTurn(turn) ? { turn } : {}
217
+ };
218
+ }
219
+ case "result": {
220
+ const summary = data["summary"];
221
+ if (!isBoundedText(summary, MAX_TEXT_LENGTH)) return null;
222
+ return {
223
+ t3ams: 1,
224
+ type: "result",
225
+ summary
226
+ };
227
+ }
228
+ case "exit": return {
229
+ t3ams: 1,
230
+ type: "exit"
231
+ };
232
+ default: return null;
233
+ }
234
+ }
235
+
236
+ //#endregion
237
+ //#region src/client.ts
238
+ const READY_RETRY_MS = 250;
239
+ /** True when the game is running inside a frame (the t3ams host embeds it in an iframe), so it should probe for a host via the ready/hello handshake. Top-level pages run standalone. */
240
+ function isInT3ams() {
241
+ try {
242
+ return window.self !== window.top;
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
247
+ function connectT3ams(timeoutMs = 1e4) {
248
+ const { promise, resolve, reject } = Promise.withResolvers();
249
+ let connected = false;
250
+ let settled = false;
251
+ let hostOrigin = null;
252
+ let roster = [];
253
+ const messageCbs = /* @__PURE__ */ new Set();
254
+ const rosterCbs = /* @__PURE__ */ new Set();
255
+ const closeCbs = /* @__PURE__ */ new Set();
256
+ const playerJoinCbs = /* @__PURE__ */ new Set();
257
+ const playerLeaveCbs = /* @__PURE__ */ new Set();
258
+ function post(message) {
259
+ if (hostOrigin === null) return;
260
+ window.parent.postMessage(message, hostOrigin);
261
+ }
262
+ function pingReady() {
263
+ window.parent.postMessage({
264
+ t3ams: 1,
265
+ type: "ready",
266
+ protocolVersion: T3AMS_GAME_PROTOCOL_VERSION
267
+ }, "*");
268
+ }
269
+ function handleMessage(event) {
270
+ if (event.source !== window.parent) return;
271
+ const parsed = parseHostToGameMessage(event.data);
272
+ if (parsed === null) return;
273
+ if (!connected) {
274
+ if (parsed.type !== "hello") return;
275
+ connected = true;
276
+ settled = true;
277
+ hostOrigin = event.origin;
278
+ roster = parsed.roster;
279
+ const initialStatus = parsed.session.initialStatus;
280
+ let phaseStatus = initialStatus === "playing" || initialStatus === "finished" ? initialStatus : "lobby";
281
+ let phaseDetail;
282
+ let phaseTurn = null;
283
+ const meta = parsed.game ?? null;
284
+ function postState() {
285
+ post({
286
+ t3ams: 1,
287
+ type: "state",
288
+ status: phaseStatus,
289
+ ...phaseDetail !== void 0 ? { detail: phaseDetail } : {},
290
+ ...phaseTurn !== null ? { turn: phaseTurn } : {}
291
+ });
292
+ }
293
+ clearInterval(readyTimer);
294
+ clearTimeout(timeoutTimer);
295
+ resolve({
296
+ session: parsed.session,
297
+ hostContext: parsed.hostContext ?? null,
298
+ player: parsed.player,
299
+ get meta() {
300
+ return meta;
301
+ },
302
+ get roster() {
303
+ return roster;
304
+ },
305
+ send(data) {
306
+ post({
307
+ t3ams: 1,
308
+ type: "send",
309
+ data
310
+ });
311
+ },
312
+ onMessage(cb) {
313
+ messageCbs.add(cb);
314
+ return () => messageCbs.delete(cb);
315
+ },
316
+ onRoster(cb) {
317
+ rosterCbs.add(cb);
318
+ return () => rosterCbs.delete(cb);
319
+ },
320
+ onClose(cb) {
321
+ closeCbs.add(cb);
322
+ return () => closeCbs.delete(cb);
323
+ },
324
+ onPlayerJoin(cb) {
325
+ playerJoinCbs.add(cb);
326
+ return () => playerJoinCbs.delete(cb);
327
+ },
328
+ onPlayerLeave(cb) {
329
+ playerLeaveCbs.add(cb);
330
+ return () => playerLeaveCbs.delete(cb);
331
+ },
332
+ setState(status, detail) {
333
+ phaseStatus = status;
334
+ phaseDetail = detail;
335
+ postState();
336
+ },
337
+ setTurn(turn) {
338
+ phaseTurn = turn;
339
+ postState();
340
+ },
341
+ reportResult(summary) {
342
+ post({
343
+ t3ams: 1,
344
+ type: "result",
345
+ summary
346
+ });
347
+ },
348
+ exit() {
349
+ post({
350
+ t3ams: 1,
351
+ type: "exit"
352
+ });
353
+ }
354
+ });
355
+ return;
356
+ }
357
+ switch (parsed.type) {
358
+ case "hello": return;
359
+ case "message":
360
+ for (const cb of messageCbs) cb(parsed.from, parsed.data);
361
+ return;
362
+ case "roster": {
363
+ const previous = roster;
364
+ const next = parsed.roster;
365
+ const previousIds = new Set(previous.map((p) => p.id));
366
+ const nextIds = new Set(next.map((p) => p.id));
367
+ for (const p of next) if (!previousIds.has(p.id)) for (const cb of playerJoinCbs) cb(p);
368
+ for (const p of previous) if (!nextIds.has(p.id)) for (const cb of playerLeaveCbs) cb(p);
369
+ roster = next;
370
+ for (const cb of rosterCbs) cb(roster);
371
+ return;
372
+ }
373
+ case "close":
374
+ for (const cb of closeCbs) cb();
375
+ return;
376
+ }
377
+ }
378
+ window.addEventListener("message", handleMessage);
379
+ pingReady();
380
+ const readyTimer = setInterval(pingReady, READY_RETRY_MS);
381
+ const timeoutTimer = setTimeout(() => {
382
+ if (settled) return;
383
+ settled = true;
384
+ clearInterval(readyTimer);
385
+ window.removeEventListener("message", handleMessage);
386
+ reject(/* @__PURE__ */ new Error("t3ams handshake timed out"));
387
+ }, timeoutMs);
388
+ return promise;
389
+ }
390
+
391
+ //#endregion
392
+ //#region src/session-store.ts
393
+ let session = null;
394
+ let config = null;
395
+ let started = false;
396
+ const listeners = /* @__PURE__ */ new Set();
397
+ let snapshot = {
398
+ status: isInT3ams() ? "connecting" : "skipped",
399
+ error: null,
400
+ session: null,
401
+ player: null,
402
+ meta: null,
403
+ roster: [],
404
+ state: "lobby",
405
+ turn: null
406
+ };
407
+ function update(patch) {
408
+ snapshot = {
409
+ ...snapshot,
410
+ ...patch
411
+ };
412
+ for (const listener of listeners) listener();
413
+ }
414
+ function configureT3ams(configuration) {
415
+ config = configuration;
416
+ }
417
+ function ensureT3amsConnection() {
418
+ if (!isInT3ams() || started) return;
419
+ started = true;
420
+ connectT3ams(config?.timeoutMs ?? 3e3).then((connected) => {
421
+ session = connected;
422
+ const fallbackMeta = config === null ? null : {
423
+ id: "",
424
+ name: config.name,
425
+ ...config.description !== void 0 ? { description: config.description } : {},
426
+ ...config.iconUrl !== void 0 ? { iconUrl: config.iconUrl } : {},
427
+ ...config.minPlayers !== void 0 ? { minPlayers: config.minPlayers } : {},
428
+ ...config.maxPlayers !== void 0 ? { maxPlayers: config.maxPlayers } : {}
429
+ };
430
+ const initialStatus = connected.session.initialStatus;
431
+ const state = initialStatus === "playing" || initialStatus === "finished" ? initialStatus : "lobby";
432
+ update({
433
+ status: "ready",
434
+ error: null,
435
+ session: connected.session,
436
+ player: connected.player,
437
+ meta: connected.meta ?? fallbackMeta,
438
+ roster: connected.roster,
439
+ state
440
+ });
441
+ connected.onRoster((roster) => update({ roster }));
442
+ connected.onClose(() => update({ status: "closed" }));
443
+ }).catch(() => {
444
+ update({
445
+ status: "skipped",
446
+ error: null
447
+ });
448
+ });
449
+ }
450
+ function retryT3amsConnection() {
451
+ started = false;
452
+ session = null;
453
+ update({
454
+ status: isInT3ams() ? "connecting" : "skipped",
455
+ error: null,
456
+ session: null,
457
+ player: null,
458
+ meta: null,
459
+ roster: [],
460
+ turn: null
461
+ });
462
+ ensureT3amsConnection();
463
+ }
464
+ function getT3amsSession() {
465
+ return session;
466
+ }
467
+ function subscribeT3ams(listener) {
468
+ listeners.add(listener);
469
+ return () => {
470
+ listeners.delete(listener);
471
+ };
472
+ }
473
+ function getT3amsSnapshot() {
474
+ return snapshot;
475
+ }
476
+ function t3amsSetState(status, detail) {
477
+ if (session === null) return;
478
+ session.setState(status, detail);
479
+ update({ state: status });
480
+ }
481
+ function t3amsSetTurn(turn) {
482
+ if (session === null) return;
483
+ session.setTurn(turn);
484
+ update({ turn });
485
+ }
486
+ function t3amsSend(data) {
487
+ if (session === null) return;
488
+ session.send(data);
489
+ }
490
+ function t3amsReportResult(summary) {
491
+ if (session === null) return;
492
+ session.reportResult(summary);
493
+ }
494
+ function t3amsExit() {
495
+ if (session === null) return;
496
+ session.exit();
497
+ }
498
+
499
+ //#endregion
500
+ //#region src/capabilities.ts
501
+ function randomId() {
502
+ if (globalThis.crypto?.randomUUID != null) return globalThis.crypto.randomUUID();
503
+ const bytes = new Uint8Array(16);
504
+ if (globalThis.crypto?.getRandomValues != null) globalThis.crypto.getRandomValues(bytes);
505
+ else for (let index = 0; index < bytes.length; index += 1) bytes[index] = Math.floor(Math.random() * 256);
506
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
507
+ }
508
+ function resolvedOrigin(value) {
509
+ try {
510
+ return new URL(value).origin === value ? value : null;
511
+ } catch {
512
+ return null;
513
+ }
514
+ }
515
+ /**
516
+ * Resolve once the game document announces it is loaded and listening at
517
+ * `gameOrigin`, so the port is transferred to a settled origin (a transfer to a
518
+ * still-navigating iframe is silently dropped by the browser). Resolves `false`
519
+ * on timeout.
520
+ */
521
+ function waitForGameReady(iframe, gameOrigin, timeoutMs) {
522
+ const { promise, resolve } = Promise.withResolvers();
523
+ if (typeof window === "undefined") {
524
+ resolve(false);
525
+ return promise;
526
+ }
527
+ let settled = false;
528
+ const finish = (value) => {
529
+ if (settled) return;
530
+ settled = true;
531
+ clearTimeout(timer);
532
+ window.removeEventListener("message", onMessage);
533
+ resolve(value);
534
+ };
535
+ const onMessage = (event) => {
536
+ if (event.source !== iframe.contentWindow || event.origin !== gameOrigin) return;
537
+ const data = event.data;
538
+ if (typeof data === "object" && data !== null && "t3amsHost" in data && data.t3amsHost === 1 && "type" in data && data.type === "delegated-session-ready") finish(true);
539
+ };
540
+ const timer = setTimeout(() => finish(false), timeoutMs);
541
+ window.addEventListener("message", onMessage);
542
+ return promise;
543
+ }
544
+ function isDelegatedSessionPortMessage(value, gameId) {
545
+ if (typeof value !== "object" || value === null) return false;
546
+ const message = value;
547
+ return message.t3amsHost === 1 && message.type === "delegated-session-port" && message.protocolVersion === 1 && message.gameId === gameId && typeof message.sessionId === "string" && typeof MessagePort !== "undefined" && message.port instanceof MessagePort;
548
+ }
549
+ function isDelegatedSessionUnavailableMessage(value) {
550
+ if (typeof value !== "object" || value === null) return false;
551
+ const message = value;
552
+ return message.t3amsHost === 1 && message.type === "delegated-session-unavailable" && message.protocolVersion === 1 && (message.reason === "unsupported" || message.reason === "rejected" || message.reason === "timeout");
553
+ }
554
+ function findParentOrigin() {
555
+ if (typeof document !== "undefined" && document.referrer !== "") try {
556
+ return new URL(document.referrer).origin;
557
+ } catch {}
558
+ const ancestors = window.location.ancestorOrigins;
559
+ return ancestors != null && ancestors.length > 0 ? ancestors[0] ?? null : null;
560
+ }
561
+ /**
562
+ * Wait for the one host capability port attached to this game document.
563
+ * The port is accepted only from the immediate parent and pinned origin.
564
+ */
565
+ function waitForDelegatedHostPort(gameId, options = {}) {
566
+ const { promise, resolve, reject } = Promise.withResolvers();
567
+ if (typeof window === "undefined" || window.parent === window) {
568
+ reject(/* @__PURE__ */ new Error("Delegated host port is unavailable outside an iframe"));
569
+ return promise;
570
+ }
571
+ const parentOrigin = options.parentOrigin ?? findParentOrigin();
572
+ if (parentOrigin == null) {
573
+ reject(/* @__PURE__ */ new Error("Unable to resolve the delegated host origin"));
574
+ return promise;
575
+ }
576
+ let settled = false;
577
+ const timeout = window.setTimeout(() => finish(/* @__PURE__ */ new Error("Timed out waiting for delegated host port")), options.timeoutMs ?? 5e3);
578
+ const finish = (error, port) => {
579
+ if (settled) {
580
+ port?.close();
581
+ return;
582
+ }
583
+ settled = true;
584
+ window.clearTimeout(timeout);
585
+ window.removeEventListener("message", onMessage);
586
+ if (error != null) reject(error);
587
+ else resolve(port);
588
+ };
589
+ const onMessage = (event) => {
590
+ if (event.source !== window.parent || event.origin !== parentOrigin) return;
591
+ if (isDelegatedSessionUnavailableMessage(event.data)) {
592
+ finish(/* @__PURE__ */ new Error(`Delegated host unavailable: ${event.data.reason}`));
593
+ return;
594
+ }
595
+ if (!isDelegatedSessionPortMessage(event.data, gameId)) return;
596
+ finish(void 0, event.data.port);
597
+ };
598
+ window.addEventListener("message", onMessage);
599
+ return promise;
600
+ }
601
+ function createGameCapabilityBridge(options) {
602
+ let disposed = false;
603
+ let closed = false;
604
+ let portToHost = null;
605
+ let portToGame = null;
606
+ let session = null;
607
+ const closePorts = () => {
608
+ portToHost?.close();
609
+ portToGame?.close();
610
+ portToHost = null;
611
+ portToGame = null;
612
+ };
613
+ const notifyUnavailable = (reason) => {
614
+ try {
615
+ options.iframe.contentWindow?.postMessage({
616
+ t3amsHost: 1,
617
+ type: "delegated-session-unavailable",
618
+ protocolVersion: 1,
619
+ reason
620
+ }, options.gameOrigin);
621
+ } catch {}
622
+ options.onUnavailable(reason);
623
+ };
624
+ return {
625
+ async start() {
626
+ if (disposed || closed || session != null) return;
627
+ const target = options.iframe.contentWindow;
628
+ const origin = resolvedOrigin(options.gameOrigin);
629
+ if (target == null || origin == null) {
630
+ notifyUnavailable("unsupported");
631
+ return;
632
+ }
633
+ const channel = new MessageChannel();
634
+ portToHost = channel.port1;
635
+ portToGame = channel.port2;
636
+ const requestId = `t3ams-cap-v1:${options.gameId}:${randomId()}`;
637
+ try {
638
+ const [gameReady, accepted] = await Promise.all([waitForGameReady(options.iframe, origin, 1e4), options.openDelegatedChildSession({
639
+ requestId,
640
+ gameId: options.gameId,
641
+ gameOrigin: origin,
642
+ port: channel.port1
643
+ })]);
644
+ session = accepted;
645
+ const liveTarget = options.iframe.contentWindow;
646
+ if (disposed || closed || liveTarget == null) {
647
+ closePorts();
648
+ return;
649
+ }
650
+ if (!gameReady) {
651
+ closePorts();
652
+ notifyUnavailable("timeout");
653
+ return;
654
+ }
655
+ liveTarget.postMessage({
656
+ t3amsHost: 1,
657
+ type: "delegated-session-port",
658
+ protocolVersion: 1,
659
+ sessionId: session.sessionId,
660
+ gameId: session.gameId,
661
+ port: channel.port2
662
+ }, origin, [channel.port2]);
663
+ portToGame = null;
664
+ options.onReady(session);
665
+ } catch (error) {
666
+ closePorts();
667
+ notifyUnavailable(error instanceof Error && error.message.includes("Timed out") ? "timeout" : "rejected");
668
+ }
669
+ },
670
+ close() {
671
+ if (closed) return;
672
+ closed = true;
673
+ closePorts();
674
+ session = null;
675
+ },
676
+ dispose() {
677
+ if (disposed) return;
678
+ disposed = true;
679
+ closed = true;
680
+ closePorts();
681
+ session = null;
682
+ }
683
+ };
684
+ }
685
+
686
+ //#endregion
687
+ //#region src/host-transport.ts
688
+ const T3AMS_HOST_PROTOCOL_VERSION = 1;
689
+ function isDelegatedBootstrapMessage(data) {
690
+ if (typeof data !== "object" || data === null) return false;
691
+ const message = data;
692
+ if (message["t3amsHost"] !== T3AMS_HOST_PROTOCOL_VERSION || message["protocolVersion"] !== T3AMS_HOST_PROTOCOL_VERSION) return false;
693
+ if (message["type"] === "delegated-session-port") return typeof message["sessionId"] === "string" && typeof message["gameId"] === "string" && typeof MessagePort !== "undefined" && message["port"] instanceof MessagePort;
694
+ return message["type"] === "delegated-session-unavailable" && (message["reason"] === "unsupported" || message["reason"] === "rejected" || message["reason"] === "timeout");
695
+ }
696
+ function detach(view) {
697
+ const copy = new Uint8Array(view.byteLength);
698
+ copy.set(view);
699
+ return copy;
700
+ }
701
+ function createDelegatedProvider() {
702
+ let pinnedOrigin = (() => {
703
+ if (typeof document !== "undefined" && document.referrer !== "") try {
704
+ return new URL(document.referrer).origin;
705
+ } catch {
706
+ return null;
707
+ }
708
+ const ancestors = window.location.ancestorOrigins;
709
+ return ancestors != null && ancestors.length > 0 ? ancestors[0] ?? null : null;
710
+ })();
711
+ const subscribers = /* @__PURE__ */ new Set();
712
+ let port = null;
713
+ let closed = false;
714
+ let readyTimer = null;
715
+ const closeSession = () => {
716
+ closed = true;
717
+ port?.close();
718
+ port = null;
719
+ };
720
+ const handleBootstrap = (event) => {
721
+ if (event.source !== window.parent) return;
722
+ if (pinnedOrigin === null) pinnedOrigin = event.origin;
723
+ else if (event.origin !== pinnedOrigin) return;
724
+ if (!isDelegatedBootstrapMessage(event.data)) return;
725
+ if (event.data.type === "delegated-session-unavailable") {
726
+ closeSession();
727
+ return;
728
+ }
729
+ if (port !== null) {
730
+ port.onmessage = null;
731
+ port.close();
732
+ }
733
+ closed = false;
734
+ port = event.data.port;
735
+ if (readyTimer !== null) {
736
+ clearInterval(readyTimer);
737
+ readyTimer = null;
738
+ }
739
+ port.onmessage = (portEvent) => {
740
+ if (!(portEvent.data instanceof Uint8Array)) return;
741
+ const detached = detach(portEvent.data);
742
+ for (const subscriber of subscribers) subscriber(detached);
743
+ };
744
+ };
745
+ window.addEventListener("message", handleBootstrap);
746
+ const announceReady = () => {
747
+ if (port !== null || typeof window === "undefined") return;
748
+ window.parent.postMessage({
749
+ t3amsHost: 1,
750
+ type: "delegated-session-ready",
751
+ protocolVersion: 1
752
+ }, pinnedOrigin ?? "*");
753
+ };
754
+ if (typeof window !== "undefined" && isInT3ams()) {
755
+ announceReady();
756
+ readyTimer = setInterval(announceReady, 250);
757
+ }
758
+ return {
759
+ logger: createDefaultLogger("t3ams-delegated"),
760
+ isCorrectEnvironment: () => isInT3ams(),
761
+ postMessage(message) {
762
+ if (closed || !port) return;
763
+ const detached = detach(message);
764
+ port.postMessage(detached, [detached.buffer]);
765
+ },
766
+ subscribe(callback) {
767
+ subscribers.add(callback);
768
+ return () => subscribers.delete(callback);
769
+ },
770
+ dispose() {
771
+ subscribers.clear();
772
+ window.removeEventListener("message", handleBootstrap);
773
+ if (readyTimer !== null) {
774
+ clearInterval(readyTimer);
775
+ readyTimer = null;
776
+ }
777
+ closeSession();
778
+ }
779
+ };
780
+ }
781
+ const eagerDelegatedProvider = typeof window !== "undefined" && isInT3ams() ? createDelegatedProvider() : void 0;
782
+ let delegatedTransport;
783
+ let delegatedHostApi;
784
+ /** Returns the delegated host transport while running inside a t3ams game. */
785
+ function getT3amsHostTransport() {
786
+ if (typeof window === "undefined" || !isInT3ams()) return void 0;
787
+ delegatedTransport ??= createTransport(eagerDelegatedProvider ?? createDelegatedProvider());
788
+ return delegatedTransport;
789
+ }
790
+ /** Returns the delegated host API while running inside a t3ams game. */
791
+ function getT3amsHostApi() {
792
+ const transport = getT3amsHostTransport();
793
+ if (transport === void 0) return void 0;
794
+ delegatedHostApi ??= createHostApi(transport);
795
+ return delegatedHostApi;
796
+ }
797
+
798
+ //#endregion
799
+ export { T3AMS_GAME_MAX_DATA_BYTES, T3AMS_GAME_PROTOCOL_VERSION, T3AMS_GAME_TAGS, configureT3ams, connectT3ams, createGameCapabilityBridge, ensureT3amsConnection, getT3amsHostApi, getT3amsHostTransport, getT3amsSession, getT3amsSnapshot, isInT3ams, isT3amsGameTag, isT3amsJson, parseGameToHostMessage, parseHostToGameMessage, retryT3amsConnection, subscribeT3ams, t3amsExit, t3amsReportResult, t3amsSend, t3amsSetState, t3amsSetTurn, waitForDelegatedHostPort };
800
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/protocol.ts","../src/client.ts","../src/session-store.ts","../src/capabilities.ts","../src/host-transport.ts"],"sourcesContent":["export const T3AMS_GAME_PROTOCOL_VERSION = 1 as const;\nexport const T3AMS_GAME_MAX_DATA_BYTES = 12 * 1024;\n\nconst MAX_NAME_LENGTH = 100;\nconst MAX_TEXT_LENGTH = 280;\nconst CONTROL_CHAR_PATTERN = /[\\u0000-\\u001F\\u007F-\\u009F]/;\n\nexport type T3amsJson =\n | null\n | boolean\n | number\n | string\n | readonly T3amsJson[]\n | { readonly [key: string]: T3amsJson };\n\nexport type GameStatus = \"lobby\" | \"playing\" | \"finished\" | \"abandoned\";\nexport type HostContext =\n | { readonly embedding: \"nested\"; readonly capability: \"delegated-host\" }\n | { readonly embedding: \"t3ams\"; readonly capability: \"none\" };\n\nexport type GameStateUpdate = Exclude<GameStatus, \"abandoned\">;\n/**\n * Preset marketplace tags a game may declare. Hosts surface these for\n * discovery filters (\"Multiplayer\", \"Action\", …); games pass a subset in\n * their config/meta.\n */\nexport const T3AMS_GAME_TAGS = [\n \"multiplayer\",\n \"single-player\",\n \"co-op\",\n \"party\",\n \"action\",\n \"racing\",\n \"arcade\",\n \"puzzle\",\n \"strategy\",\n \"trivia\",\n \"word\",\n \"casual\",\n] as const;\n\nexport type T3amsGameTag = (typeof T3AMS_GAME_TAGS)[number];\n\nexport function isT3amsGameTag(value: unknown): value is T3amsGameTag {\n return (T3AMS_GAME_TAGS as readonly unknown[]).includes(value);\n}\n\nexport interface T3amsPlayer {\n readonly id: string;\n readonly xid: string;\n readonly name: string;\n readonly avatarUrl?: string;\n}\n\nexport interface T3amsSessionInfo {\n readonly sessionId: string;\n readonly hostSeatId: string;\n readonly role: \"host\" | \"guest\";\n readonly conversationKind: \"channel\" | \"group\" | \"dm\";\n readonly initialStatus: GameStatus;\n}\n\nexport interface T3amsGameMeta {\n readonly id: string;\n readonly name: string;\n readonly description?: string;\n readonly iconUrl?: string;\n /** Marketplace cover image; falls back to a host-rendered placeholder. */\n readonly coverImageUrl?: string;\n /** Preset discovery tags; unknown tags are rejected by validation. */\n readonly tags?: readonly T3amsGameTag[];\n /** Minimum players needed before the game can start; hosts surface this on invite cards. */\n readonly minPlayers?: number;\n /** Join cap enforced by the host while the session is in the lobby. */\n readonly maxPlayers?: number;\n}\n\nexport interface T3amsTurn {\n readonly seatId: string;\n readonly prompt?: string;\n}\n\nexport type HostToGameMessage =\n | {\n readonly t3ams: 1;\n readonly type: \"hello\";\n readonly protocolVersion: 1;\n readonly session: T3amsSessionInfo;\n readonly player: T3amsPlayer;\n readonly roster: readonly T3amsPlayer[];\n readonly game?: T3amsGameMeta;\n readonly hostContext?: HostContext;\n }\n | {\n readonly t3ams: 1;\n readonly type: \"message\";\n readonly from: string;\n readonly data: T3amsJson;\n }\n | {\n readonly t3ams: 1;\n readonly type: \"roster\";\n readonly roster: readonly T3amsPlayer[];\n }\n | { readonly t3ams: 1; readonly type: \"close\" };\n\nexport type GameToHostMessage =\n | { readonly t3ams: 1; readonly type: \"ready\"; readonly protocolVersion: 1 }\n | { readonly t3ams: 1; readonly type: \"send\"; readonly data: T3amsJson }\n | {\n readonly t3ams: 1;\n readonly type: \"state\";\n readonly status: GameStateUpdate;\n readonly detail?: string;\n readonly turn?: T3amsTurn;\n }\n | { readonly t3ams: 1; readonly type: \"result\"; readonly summary: string }\n | { readonly t3ams: 1; readonly type: \"exit\" };\n\nexport function isT3amsJson(data: unknown): data is T3amsJson {\n return checkJsonValue(data, new Set());\n}\n\nfunction checkJsonValue(data: unknown, ancestors: Set<unknown>): boolean {\n if (data === null) return true;\n const kind = typeof data;\n if (kind === \"boolean\" || kind === \"string\") return true;\n if (kind === \"number\") return Number.isFinite(data);\n if (kind !== \"object\") return false;\n if (ancestors.has(data)) return false;\n\n if (Array.isArray(data)) {\n ancestors.add(data);\n try {\n return data.every((item) => checkJsonValue(item, ancestors));\n } finally {\n ancestors.delete(data);\n }\n }\n\n const proto = Object.getPrototypeOf(data);\n if (proto !== Object.prototype && proto !== null) return false;\n\n ancestors.add(data);\n try {\n return Object.keys(data as Record<string, unknown>).every((key) =>\n checkJsonValue((data as Record<string, unknown>)[key], ancestors),\n );\n } finally {\n ancestors.delete(data);\n }\n}\n\nfunction jsonByteLength(value: T3amsJson): number {\n return new TextEncoder().encode(JSON.stringify(value)).length;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBoundedText(value: unknown, maxLength: number): value is string {\n return (\n typeof value === \"string\" &&\n value.length <= maxLength &&\n !CONTROL_CHAR_PATTERN.test(value)\n );\n}\n\nfunction isValidName(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n value.length > 0 &&\n value.length <= MAX_NAME_LENGTH\n );\n}\n\nfunction isValidPlayer(value: unknown): value is T3amsPlayer {\n if (!isRecord(value)) return false;\n const { id, xid, name, avatarUrl } = value;\n if (typeof id !== \"string\" || id.length === 0) return false;\n if (typeof xid !== \"string\" || xid.length === 0) return false;\n if (!isValidName(name)) return false;\n if (avatarUrl !== undefined && typeof avatarUrl !== \"string\") return false;\n return true;\n}\n\nfunction isValidRoster(value: unknown): value is readonly T3amsPlayer[] {\n return Array.isArray(value) && value.every(isValidPlayer);\n}\n\nfunction isGameStatus(value: unknown): value is GameStatus {\n return (\n value === \"lobby\" ||\n value === \"playing\" ||\n value === \"finished\" ||\n value === \"abandoned\"\n );\n}\n\nfunction isGameStateUpdate(value: unknown): value is GameStateUpdate {\n return value === \"lobby\" || value === \"playing\" || value === \"finished\";\n}\nfunction isValidHostContext(value: unknown): value is HostContext {\n if (!isRecord(value)) return false;\n const keys = Object.keys(value);\n if (\n keys.length !== 2 ||\n !keys.includes(\"embedding\") ||\n !keys.includes(\"capability\")\n )\n return false;\n return (\n (value[\"embedding\"] === \"nested\" &&\n value[\"capability\"] === \"delegated-host\") ||\n (value[\"embedding\"] === \"t3ams\" && value[\"capability\"] === \"none\")\n );\n}\n\nfunction isValidSessionInfo(value: unknown): value is T3amsSessionInfo {\n if (!isRecord(value)) return false;\n const { sessionId, hostSeatId, role, conversationKind, initialStatus } =\n value;\n if (typeof sessionId !== \"string\" || sessionId.length === 0) return false;\n if (typeof hostSeatId !== \"string\" || hostSeatId.length === 0) return false;\n if (role !== \"host\" && role !== \"guest\") return false;\n if (\n conversationKind !== \"channel\" &&\n conversationKind !== \"group\" &&\n conversationKind !== \"dm\"\n )\n return false;\n if (!isGameStatus(initialStatus)) return false;\n return true;\n}\n\nfunction isValidTurn(value: unknown): value is T3amsTurn {\n if (!isRecord(value)) return false;\n const { seatId, prompt } = value;\n if (\n typeof seatId !== \"string\" ||\n seatId.length === 0 ||\n seatId.length > MAX_NAME_LENGTH\n )\n return false;\n if (prompt !== undefined && !isBoundedText(prompt, MAX_NAME_LENGTH))\n return false;\n return true;\n}\n\nfunction isValidUrlField(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n value.length <= 2048 &&\n !CONTROL_CHAR_PATTERN.test(value)\n );\n}\n\n\nfunction isValidGameMeta(value: unknown): value is T3amsGameMeta {\n if (!isRecord(value)) return false;\n const { id, name, description, iconUrl, coverImageUrl, tags, minPlayers, maxPlayers } = value;\n if (!isValidName(id)) return false;\n if (!isValidName(name)) return false;\n if (description !== undefined && !isBoundedText(description, MAX_TEXT_LENGTH))\n return false;\n if (iconUrl !== undefined && !isValidUrlField(iconUrl)) return false;\n if (coverImageUrl !== undefined && !isValidUrlField(coverImageUrl))\n return false;\n if (\n tags !== undefined &&\n (!Array.isArray(tags) ||\n tags.length > T3AMS_GAME_TAGS.length ||\n !tags.every(isT3amsGameTag))\n )\n return false;\n for (const count of [minPlayers, maxPlayers]) {\n if (count === undefined) continue;\n if (typeof count !== \"number\" || !Number.isInteger(count) || count < 1)\n return false;\n }\n if (typeof minPlayers === \"number\" && typeof maxPlayers === \"number\" && minPlayers > maxPlayers)\n return false;\n return true;\n}\n\nexport function parseHostToGameMessage(\n data: unknown,\n): HostToGameMessage | null {\n if (!isRecord(data)) return null;\n if (data[\"t3ams\"] !== 1) return null;\n\n switch (data[\"type\"]) {\n case \"hello\": {\n if (data[\"protocolVersion\"] !== T3AMS_GAME_PROTOCOL_VERSION) return null;\n const session = data[\"session\"];\n const player = data[\"player\"];\n const roster = data[\"roster\"];\n if (!isValidSessionInfo(session)) return null;\n if (!isValidPlayer(player)) return null;\n if (!isValidRoster(roster)) return null;\n const game = data[\"game\"];\n const hostContext = data[\"hostContext\"];\n if (hostContext !== undefined && !isValidHostContext(hostContext))\n return null;\n return {\n t3ams: 1,\n type: \"hello\",\n protocolVersion: 1,\n session,\n player,\n roster,\n ...(isValidGameMeta(game) ? { game } : {}),\n ...(hostContext !== undefined ? { hostContext } : {}),\n };\n }\n case \"message\": {\n const from = data[\"from\"];\n const value = data[\"data\"];\n if (typeof from !== \"string\" || from.length === 0) return null;\n if (!isT3amsJson(value)) return null;\n return { t3ams: 1, type: \"message\", from, data: value };\n }\n case \"roster\": {\n const roster = data[\"roster\"];\n if (!isValidRoster(roster)) return null;\n return { t3ams: 1, type: \"roster\", roster };\n }\n case \"close\":\n return { t3ams: 1, type: \"close\" };\n default:\n return null;\n }\n}\n\nexport function parseGameToHostMessage(\n data: unknown,\n): GameToHostMessage | null {\n if (!isRecord(data)) return null;\n if (data[\"t3ams\"] !== 1) return null;\n\n switch (data[\"type\"]) {\n case \"ready\": {\n if (data[\"protocolVersion\"] !== T3AMS_GAME_PROTOCOL_VERSION) return null;\n return { t3ams: 1, type: \"ready\", protocolVersion: 1 };\n }\n case \"send\": {\n const value = data[\"data\"];\n if (!isT3amsJson(value)) return null;\n if (jsonByteLength(value) > T3AMS_GAME_MAX_DATA_BYTES) return null;\n return { t3ams: 1, type: \"send\", data: value };\n }\n case \"state\": {\n const status = data[\"status\"];\n if (!isGameStateUpdate(status)) return null;\n const detail = data[\"detail\"];\n if (detail !== undefined && !isBoundedText(detail, MAX_TEXT_LENGTH))\n return null;\n const turn = data[\"turn\"];\n return {\n t3ams: 1,\n type: \"state\",\n status,\n ...(detail !== undefined ? { detail } : {}),\n ...(isValidTurn(turn) ? { turn } : {}),\n };\n }\n case \"result\": {\n const summary = data[\"summary\"];\n if (!isBoundedText(summary, MAX_TEXT_LENGTH)) return null;\n return { t3ams: 1, type: \"result\", summary };\n }\n case \"exit\":\n return { t3ams: 1, type: \"exit\" };\n default:\n return null;\n }\n}\n","import {\n T3AMS_GAME_PROTOCOL_VERSION,\n parseHostToGameMessage,\n type GameStateUpdate,\n type HostContext,\n type GameToHostMessage,\n type T3amsJson,\n type T3amsPlayer,\n type T3amsSessionInfo,\n type T3amsGameMeta,\n type T3amsTurn,\n} from \"./protocol.js\";\n\nconst READY_RETRY_MS = 250;\n\n/** True when the game is running inside a frame (the t3ams host embeds it in an iframe), so it should probe for a host via the ready/hello handshake. Top-level pages run standalone. */\nexport function isInT3ams(): boolean {\n try {\n return window.self !== window.top;\n } catch {\n return false;\n }\n}\n\nexport interface T3amsGameSession<TData extends T3amsJson = T3amsJson> {\n readonly session: T3amsSessionInfo;\n readonly hostContext: HostContext | null;\n\n readonly player: T3amsPlayer;\n readonly roster: readonly T3amsPlayer[];\n readonly meta: T3amsGameMeta | null;\n send(data: TData): void;\n onMessage(cb: (from: string, data: TData) => void): () => void;\n onRoster(cb: (roster: readonly T3amsPlayer[]) => void): () => void;\n onClose(cb: () => void): () => void;\n setState(status: GameStateUpdate, detail?: string): void;\n setTurn(turn: T3amsTurn | null): void;\n onPlayerJoin(cb: (player: T3amsPlayer) => void): () => void;\n onPlayerLeave(cb: (player: T3amsPlayer) => void): () => void;\n reportResult(summary: string): void;\n exit(): void;\n}\n\nexport function connectT3ams<TData extends T3amsJson = T3amsJson>(\n timeoutMs = 10_000,\n): Promise<T3amsGameSession<TData>> {\n const { promise, resolve, reject } =\n Promise.withResolvers<T3amsGameSession<TData>>();\n\n let connected = false;\n let settled = false;\n let hostOrigin: string | null = null;\n let roster: readonly T3amsPlayer[] = [];\n const messageCbs = new Set<(from: string, data: TData) => void>();\n const rosterCbs = new Set<(roster: readonly T3amsPlayer[]) => void>();\n const closeCbs = new Set<() => void>();\n const playerJoinCbs = new Set<(player: T3amsPlayer) => void>();\n const playerLeaveCbs = new Set<(player: T3amsPlayer) => void>();\n\n function post(message: GameToHostMessage): void {\n if (hostOrigin === null) return;\n window.parent.postMessage(message, hostOrigin);\n }\n\n function pingReady(): void {\n window.parent.postMessage(\n { t3ams: 1, type: \"ready\", protocolVersion: T3AMS_GAME_PROTOCOL_VERSION },\n \"*\",\n );\n }\n\n function handleMessage(event: MessageEvent): void {\n if (event.source !== window.parent) return;\n const parsed = parseHostToGameMessage(event.data);\n if (parsed === null) return;\n\n if (!connected) {\n if (parsed.type !== \"hello\") return;\n connected = true;\n settled = true;\n hostOrigin = event.origin;\n roster = parsed.roster;\n const initialStatus = parsed.session.initialStatus;\n let phaseStatus: GameStateUpdate =\n initialStatus === \"playing\" || initialStatus === \"finished\"\n ? initialStatus\n : \"lobby\";\n let phaseDetail: string | undefined;\n let phaseTurn: T3amsTurn | null = null;\n const meta: T3amsGameMeta | null = parsed.game ?? null;\n function postState(): void {\n post({\n t3ams: 1,\n type: \"state\",\n status: phaseStatus,\n ...(phaseDetail !== undefined ? { detail: phaseDetail } : {}),\n ...(phaseTurn !== null ? { turn: phaseTurn } : {}),\n });\n }\n clearInterval(readyTimer);\n clearTimeout(timeoutTimer);\n resolve({\n session: parsed.session,\n hostContext: parsed.hostContext ?? null,\n\n player: parsed.player,\n get meta() {\n return meta;\n },\n get roster() {\n return roster;\n },\n send(data) {\n post({ t3ams: 1, type: \"send\", data });\n },\n onMessage(cb) {\n messageCbs.add(cb as (from: string, data: T3amsJson) => void);\n return () =>\n messageCbs.delete(cb as (from: string, data: T3amsJson) => void);\n },\n onRoster(cb) {\n rosterCbs.add(cb);\n return () => rosterCbs.delete(cb);\n },\n onClose(cb) {\n closeCbs.add(cb);\n return () => closeCbs.delete(cb);\n },\n onPlayerJoin(cb) {\n playerJoinCbs.add(cb);\n return () => playerJoinCbs.delete(cb);\n },\n onPlayerLeave(cb) {\n playerLeaveCbs.add(cb);\n return () => playerLeaveCbs.delete(cb);\n },\n setState(status, detail) {\n phaseStatus = status;\n phaseDetail = detail;\n postState();\n },\n setTurn(turn) {\n phaseTurn = turn;\n postState();\n },\n reportResult(summary) {\n post({ t3ams: 1, type: \"result\", summary });\n },\n exit() {\n post({ t3ams: 1, type: \"exit\" });\n },\n });\n return;\n }\n\n switch (parsed.type) {\n case \"hello\":\n return;\n case \"message\":\n for (const cb of messageCbs) cb(parsed.from, parsed.data as TData);\n return;\n case \"roster\": {\n const previous = roster;\n const next = parsed.roster;\n const previousIds = new Set(previous.map((p) => p.id));\n const nextIds = new Set(next.map((p) => p.id));\n for (const p of next)\n if (!previousIds.has(p.id)) for (const cb of playerJoinCbs) cb(p);\n for (const p of previous)\n if (!nextIds.has(p.id)) for (const cb of playerLeaveCbs) cb(p);\n roster = next;\n for (const cb of rosterCbs) cb(roster);\n return;\n }\n case \"close\":\n for (const cb of closeCbs) cb();\n return;\n }\n }\n\n window.addEventListener(\"message\", handleMessage);\n pingReady();\n const readyTimer = setInterval(pingReady, READY_RETRY_MS);\n const timeoutTimer = setTimeout(() => {\n if (settled) return;\n settled = true;\n clearInterval(readyTimer);\n window.removeEventListener(\"message\", handleMessage);\n reject(new Error(\"t3ams handshake timed out\"));\n }, timeoutMs);\n\n return promise;\n}\n","import { connectT3ams, isInT3ams, type T3amsGameSession } from \"./client.js\";\nimport type {\n GameStateUpdate,\n T3amsGameMeta,\n T3amsJson,\n T3amsPlayer,\n T3amsSessionInfo,\n T3amsTurn,\n} from \"./protocol.js\";\n\nexport interface T3amsConfig {\n readonly name: string;\n readonly description?: string;\n readonly iconUrl?: string;\n /** Minimum players needed before the game can start; optional, display-only. */\n readonly minPlayers?: number;\n /** Preferred join cap; optional, display-only. */\n readonly maxPlayers?: number;\n readonly timeoutMs?: number;\n}\n\nexport type T3amsStatus =\n \"connecting\" | \"ready\" | \"error\" | \"closed\" | \"skipped\";\n\nexport interface T3amsSnapshot {\n readonly status: T3amsStatus;\n readonly error: string | null;\n readonly session: T3amsSessionInfo | null;\n readonly player: T3amsPlayer | null;\n readonly meta: T3amsGameMeta | null;\n readonly roster: readonly T3amsPlayer[];\n readonly state: GameStateUpdate;\n readonly turn: T3amsTurn | null;\n}\n\nlet session: T3amsGameSession | null = null;\nlet config: T3amsConfig | null = null;\nlet started = false;\nconst listeners = new Set<() => void>();\n\nlet snapshot: T3amsSnapshot = {\n status: isInT3ams() ? \"connecting\" : \"skipped\",\n error: null,\n session: null,\n player: null,\n meta: null,\n roster: [],\n state: \"lobby\",\n turn: null,\n};\n\nfunction update(patch: Partial<T3amsSnapshot>): void {\n snapshot = { ...snapshot, ...patch };\n for (const listener of listeners) listener();\n}\n\nexport function configureT3ams(configuration: T3amsConfig): void {\n config = configuration;\n}\n\nexport function ensureT3amsConnection(): void {\n if (!isInT3ams() || started) return;\n started = true;\n connectT3ams(config?.timeoutMs ?? 3_000)\n .then((connected) => {\n session = connected;\n const fallbackMeta: T3amsGameMeta | null =\n config === null\n ? null\n : {\n id: \"\",\n name: config.name,\n ...(config.description !== undefined\n ? { description: config.description }\n : {}),\n ...(config.iconUrl !== undefined\n ? { iconUrl: config.iconUrl }\n : {}),\n ...(config.minPlayers !== undefined\n ? { minPlayers: config.minPlayers }\n : {}),\n ...(config.maxPlayers !== undefined\n ? { maxPlayers: config.maxPlayers }\n : {}),\n };\n const initialStatus = connected.session.initialStatus;\n const state: GameStateUpdate =\n initialStatus === \"playing\" || initialStatus === \"finished\"\n ? initialStatus\n : \"lobby\";\n update({\n status: \"ready\",\n error: null,\n session: connected.session,\n player: connected.player,\n meta: connected.meta ?? fallbackMeta,\n roster: connected.roster,\n state,\n });\n connected.onRoster((roster) => update({ roster }));\n connected.onClose(() => update({ status: \"closed\" }));\n })\n .catch(() => {\n // No hello within the probe window: no t3ams host answered — run standalone.\n update({ status: \"skipped\", error: null });\n });\n}\n\nexport function retryT3amsConnection(): void {\n started = false;\n session = null;\n update({\n status: isInT3ams() ? \"connecting\" : \"skipped\",\n error: null,\n session: null,\n player: null,\n meta: null,\n roster: [],\n turn: null,\n });\n ensureT3amsConnection();\n}\n\nexport function getT3amsSession(): T3amsGameSession | null {\n return session;\n}\n\nexport function subscribeT3ams(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\nexport function getT3amsSnapshot(): T3amsSnapshot {\n return snapshot;\n}\n\nexport function t3amsSetState(status: GameStateUpdate, detail?: string): void {\n if (session === null) return;\n session.setState(status, detail);\n update({ state: status });\n}\n\nexport function t3amsSetTurn(turn: T3amsTurn | null): void {\n if (session === null) return;\n session.setTurn(turn);\n update({ turn });\n}\n\nexport function t3amsSend(data: T3amsJson): void {\n if (session === null) return;\n session.send(data);\n}\n\nexport function t3amsReportResult(summary: string): void {\n if (session === null) return;\n session.reportResult(summary);\n}\n\nexport function t3amsExit(): void {\n if (session === null) return;\n session.exit();\n}\n","export interface DelegatedChildSessionRequest {\n readonly requestId: string;\n readonly gameId: string;\n readonly gameOrigin: string;\n readonly port: MessagePort;\n}\n\nexport interface DelegatedChildSessionInfo {\n readonly sessionId: string;\n readonly gameId: string;\n readonly gameOrigin: string;\n}\n\nexport interface GameCapabilityBridgeOptions {\n readonly iframe: HTMLIFrameElement;\n readonly gameId: string;\n readonly gameOrigin: string;\n readonly openDelegatedChildSession: (\n request: DelegatedChildSessionRequest,\n ) => Promise<DelegatedChildSessionInfo>;\n readonly onUnavailable: (\n reason: \"unsupported\" | \"rejected\" | \"timeout\",\n ) => void;\n readonly onReady: (session: DelegatedChildSessionInfo) => void;\n}\n\nexport interface GameCapabilityBridge {\n start(): Promise<void>;\n close(): void;\n dispose(): void;\n}\n\nexport interface DelegatedHostPortOptions {\n readonly parentOrigin?: string;\n readonly timeoutMs?: number;\n}\n\ninterface DelegatedSessionPortMessage {\n readonly t3amsHost: 1;\n readonly type: \"delegated-session-port\";\n readonly protocolVersion: 1;\n readonly sessionId: string;\n readonly gameId: string;\n readonly port?: MessagePort;\n}\n\ninterface DelegatedSessionUnavailableMessage {\n readonly t3amsHost: 1;\n readonly type: \"delegated-session-unavailable\";\n readonly protocolVersion: 1;\n readonly reason: \"unsupported\" | \"rejected\" | \"timeout\";\n}\n\nfunction randomId(): string {\n if (globalThis.crypto?.randomUUID != null)\n return globalThis.crypto.randomUUID();\n const bytes = new Uint8Array(16);\n if (globalThis.crypto?.getRandomValues != null)\n globalThis.crypto.getRandomValues(bytes);\n else\n for (let index = 0; index < bytes.length; index += 1)\n bytes[index] = Math.floor(Math.random() * 256);\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\n \"\",\n );\n}\n\nfunction resolvedOrigin(value: string): string | null {\n try {\n const url = new URL(value);\n return url.origin === value ? value : null;\n } catch {\n return null;\n }\n}\n/**\n * Resolve once the game document announces it is loaded and listening at\n * `gameOrigin`, so the port is transferred to a settled origin (a transfer to a\n * still-navigating iframe is silently dropped by the browser). Resolves `false`\n * on timeout.\n */\nfunction waitForGameReady(\n iframe: HTMLIFrameElement,\n gameOrigin: string,\n timeoutMs: number,\n): Promise<boolean> {\n const { promise, resolve } = Promise.withResolvers<boolean>();\n if (typeof window === \"undefined\") {\n resolve(false);\n return promise;\n }\n let settled = false;\n const finish = (value: boolean): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n window.removeEventListener(\"message\", onMessage);\n resolve(value);\n };\n const onMessage = (event: MessageEvent): void => {\n if (event.source !== iframe.contentWindow || event.origin !== gameOrigin)\n return;\n const data = event.data;\n if (\n typeof data === \"object\" &&\n data !== null &&\n \"t3amsHost\" in data &&\n (data as { t3amsHost: unknown }).t3amsHost === 1 &&\n \"type\" in data &&\n (data as { type: unknown }).type === \"delegated-session-ready\"\n )\n finish(true);\n };\n const timer = setTimeout(() => finish(false), timeoutMs);\n window.addEventListener(\"message\", onMessage);\n return promise;\n}\n\nfunction isDelegatedSessionPortMessage(\n value: unknown,\n gameId: string,\n): value is DelegatedSessionPortMessage {\n if (typeof value !== \"object\" || value === null) return false;\n const message = value as Partial<DelegatedSessionPortMessage>;\n return (\n message.t3amsHost === 1 &&\n message.type === \"delegated-session-port\" &&\n message.protocolVersion === 1 &&\n message.gameId === gameId &&\n typeof message.sessionId === \"string\" &&\n typeof MessagePort !== \"undefined\" &&\n message.port instanceof MessagePort\n );\n}\n\nfunction isDelegatedSessionUnavailableMessage(\n value: unknown,\n): value is DelegatedSessionUnavailableMessage {\n if (typeof value !== \"object\" || value === null) return false;\n const message = value as Partial<DelegatedSessionUnavailableMessage>;\n return (\n message.t3amsHost === 1 &&\n message.type === \"delegated-session-unavailable\" &&\n message.protocolVersion === 1 &&\n (message.reason === \"unsupported\" ||\n message.reason === \"rejected\" ||\n message.reason === \"timeout\")\n );\n}\n\nfunction findParentOrigin(): string | null {\n if (typeof document !== \"undefined\" && document.referrer !== \"\") {\n try {\n return new URL(document.referrer).origin;\n } catch {\n // Fall through to ancestorOrigins.\n }\n }\n const ancestors = window.location.ancestorOrigins;\n return ancestors != null && ancestors.length > 0\n ? (ancestors[0] ?? null)\n : null;\n}\n\n/**\n * Wait for the one host capability port attached to this game document.\n * The port is accepted only from the immediate parent and pinned origin.\n */\nexport function waitForDelegatedHostPort(\n gameId: string,\n options: DelegatedHostPortOptions = {},\n): Promise<MessagePort> {\n const { promise, resolve, reject } = Promise.withResolvers<MessagePort>();\n if (typeof window === \"undefined\" || window.parent === window) {\n reject(new Error(\"Delegated host port is unavailable outside an iframe\"));\n return promise;\n }\n const parentOrigin = options.parentOrigin ?? findParentOrigin();\n if (parentOrigin == null) {\n reject(new Error(\"Unable to resolve the delegated host origin\"));\n return promise;\n }\n let settled = false;\n const timeout = window.setTimeout(\n () => finish(new Error(\"Timed out waiting for delegated host port\")),\n options.timeoutMs ?? 5_000,\n );\n const finish = (error?: Error, port?: MessagePort): void => {\n if (settled) {\n port?.close();\n return;\n }\n settled = true;\n window.clearTimeout(timeout);\n window.removeEventListener(\"message\", onMessage);\n if (error != null) reject(error);\n else resolve(port as MessagePort);\n };\n const onMessage = (event: MessageEvent): void => {\n if (event.source !== window.parent || event.origin !== parentOrigin) return;\n if (isDelegatedSessionUnavailableMessage(event.data)) {\n finish(new Error(`Delegated host unavailable: ${event.data.reason}`));\n return;\n }\n if (!isDelegatedSessionPortMessage(event.data, gameId)) return;\n finish(undefined, event.data.port);\n };\n window.addEventListener(\"message\", onMessage);\n return promise;\n}\nexport function createGameCapabilityBridge(\n options: GameCapabilityBridgeOptions,\n): GameCapabilityBridge {\n let disposed = false;\n let closed = false;\n let portToHost: MessagePort | null = null;\n let portToGame: MessagePort | null = null;\n let session: DelegatedChildSessionInfo | null = null;\n\n const closePorts = (): void => {\n portToHost?.close();\n portToGame?.close();\n portToHost = null;\n portToGame = null;\n };\n const notifyUnavailable = (\n reason: \"unsupported\" | \"rejected\" | \"timeout\",\n ): void => {\n try {\n options.iframe.contentWindow?.postMessage(\n {\n t3amsHost: 1,\n type: \"delegated-session-unavailable\",\n protocolVersion: 1,\n reason,\n },\n options.gameOrigin,\n );\n } catch {\n // A navigating iframe is already unavailable.\n }\n options.onUnavailable(reason);\n };\n\n return {\n async start(): Promise<void> {\n if (disposed || closed || session != null) return;\n const target = options.iframe.contentWindow;\n const origin = resolvedOrigin(options.gameOrigin);\n if (target == null || origin == null) {\n notifyUnavailable(\"unsupported\");\n return;\n }\n const channel = new MessageChannel();\n portToHost = channel.port1;\n portToGame = channel.port2;\n const requestId = `t3ams-cap-v1:${options.gameId}:${randomId()}`;\n try {\n const [gameReady, accepted] = await Promise.all([\n waitForGameReady(options.iframe, origin, 10_000),\n options.openDelegatedChildSession({\n requestId,\n gameId: options.gameId,\n gameOrigin: origin,\n port: channel.port1,\n }),\n ]);\n session = accepted;\n const liveTarget = options.iframe.contentWindow;\n if (disposed || closed || liveTarget == null) {\n closePorts();\n return;\n }\n if (!gameReady) {\n closePorts();\n notifyUnavailable(\"timeout\");\n return;\n }\n liveTarget.postMessage(\n {\n t3amsHost: 1,\n type: \"delegated-session-port\",\n protocolVersion: 1,\n sessionId: session.sessionId,\n gameId: session.gameId,\n port: channel.port2,\n },\n origin,\n [channel.port2],\n );\n portToGame = null;\n options.onReady(session);\n } catch (error) {\n closePorts();\n notifyUnavailable(\n error instanceof Error && error.message.includes(\"Timed out\")\n ? \"timeout\"\n : \"rejected\",\n );\n }\n },\n close(): void {\n if (closed) return;\n closed = true;\n closePorts();\n session = null;\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n closed = true;\n closePorts();\n session = null;\n },\n };\n}\n\n","import {\n createDefaultLogger,\n createHostApi,\n createTransport,\n type HostApi,\n type Provider,\n type Transport,\n} from \"@novasamatech/host-api\";\n\nimport { isInT3ams } from \"./client.js\";\n\nconst T3AMS_HOST_PROTOCOL_VERSION = 1 as const;\n\ntype DelegatedSessionPortMessage = {\n readonly t3amsHost: 1;\n readonly type: \"delegated-session-port\";\n readonly protocolVersion: 1;\n readonly sessionId: string;\n readonly gameId: string;\n readonly port: MessagePort;\n};\n\ntype DelegatedSessionUnavailableMessage = {\n readonly t3amsHost: 1;\n readonly type: \"delegated-session-unavailable\";\n readonly protocolVersion: 1;\n readonly reason: \"unsupported\" | \"rejected\" | \"timeout\";\n};\n\nfunction isDelegatedBootstrapMessage(\n data: unknown,\n): data is DelegatedSessionPortMessage | DelegatedSessionUnavailableMessage {\n if (typeof data !== \"object\" || data === null) return false;\n const message = data as Record<string, unknown>;\n if (\n message[\"t3amsHost\"] !== T3AMS_HOST_PROTOCOL_VERSION ||\n message[\"protocolVersion\"] !== T3AMS_HOST_PROTOCOL_VERSION\n ) {\n return false;\n }\n if (message[\"type\"] === \"delegated-session-port\") {\n return (\n typeof message[\"sessionId\"] === \"string\" &&\n typeof message[\"gameId\"] === \"string\" &&\n typeof MessagePort !== \"undefined\" &&\n message[\"port\"] instanceof MessagePort\n );\n }\n return (\n message[\"type\"] === \"delegated-session-unavailable\" &&\n (message[\"reason\"] === \"unsupported\" ||\n message[\"reason\"] === \"rejected\" ||\n message[\"reason\"] === \"timeout\")\n );\n}\n\nfunction detach(view: Uint8Array): Uint8Array {\n const copy = new Uint8Array(view.byteLength);\n copy.set(view);\n return copy;\n}\n\nfunction createDelegatedProvider(): Provider {\n let pinnedOrigin: string | null = (() => {\n if (typeof document !== \"undefined\" && document.referrer !== \"\") {\n try {\n return new URL(document.referrer).origin;\n } catch {\n return null;\n }\n }\n const ancestors = window.location.ancestorOrigins;\n return ancestors != null && ancestors.length > 0\n ? (ancestors[0] ?? null)\n : null;\n })();\n const subscribers = new Set<(message: Uint8Array) => void>();\n let port: MessagePort | null = null;\n let closed = false;\n let readyTimer: ReturnType<typeof setInterval> | null = null;\n\n const closeSession = (): void => {\n closed = true;\n port?.close();\n port = null;\n };\n\n const handleBootstrap = (event: MessageEvent): void => {\n if (event.source !== window.parent) return;\n if (pinnedOrigin === null) pinnedOrigin = event.origin;\n else if (event.origin !== pinnedOrigin) return;\n if (!isDelegatedBootstrapMessage(event.data)) return;\n\n if (event.data.type === \"delegated-session-unavailable\") {\n closeSession();\n return;\n }\n // A fresh port from the already-pinned parent supersedes any prior one:\n // t3ams re-posts on overlay re-mount (e.g. React StrictMode's double-invoke),\n // and the latest port is the live end. Fail-closed here would strand the\n // handshake on a dead first port. Origin is still pinned, so this is safe.\n if (port !== null) {\n port.onmessage = null;\n port.close();\n }\n closed = false;\n port = event.data.port;\n if (readyTimer !== null) {\n clearInterval(readyTimer);\n readyTimer = null;\n }\n port.onmessage = (portEvent: MessageEvent): void => {\n if (!(portEvent.data instanceof Uint8Array)) return;\n const detached = detach(portEvent.data);\n for (const subscriber of subscribers) subscriber(detached);\n };\n };\n\n window.addEventListener(\"message\", handleBootstrap);\n // Announce that this game document is loaded and listening, so t3ams posts\n // the capability port only after the iframe reaches its real origin — posting\n // before the origin settles is silently dropped by the browser. Retry until a\n // port binds, since the parent's bridge may start after this announcement.\n const announceReady = (): void => {\n if (port !== null || typeof window === \"undefined\") return;\n window.parent.postMessage(\n { t3amsHost: 1, type: \"delegated-session-ready\", protocolVersion: 1 },\n pinnedOrigin ?? \"*\",\n );\n };\n if (typeof window !== \"undefined\" && isInT3ams()) {\n announceReady();\n readyTimer = setInterval(announceReady, 250);\n }\n\n return {\n logger: createDefaultLogger(\"t3ams-delegated\"),\n isCorrectEnvironment: () => isInT3ams(),\n postMessage(message) {\n if (closed || !port) return;\n const detached = detach(message);\n port.postMessage(detached, [detached.buffer]);\n },\n subscribe(callback) {\n subscribers.add(callback);\n return () => subscribers.delete(callback);\n },\n dispose() {\n subscribers.clear();\n window.removeEventListener(\"message\", handleBootstrap);\n if (readyTimer !== null) {\n clearInterval(readyTimer);\n readyTimer = null;\n }\n closeSession();\n },\n };\n}\n\nconst eagerDelegatedProvider =\n typeof window !== \"undefined\" && isInT3ams()\n ? createDelegatedProvider()\n : undefined;\n\nlet delegatedTransport: Transport | undefined;\nlet delegatedHostApi: HostApi | undefined;\n\n/** Returns the delegated host transport while running inside a t3ams game. */\nexport function getT3amsHostTransport(): Transport | undefined {\n if (typeof window === \"undefined\" || !isInT3ams()) return undefined;\n delegatedTransport ??= createTransport(\n eagerDelegatedProvider ?? createDelegatedProvider(),\n );\n return delegatedTransport;\n}\n\n/** Returns the delegated host API while running inside a t3ams game. */\nexport function getT3amsHostApi(): HostApi | undefined {\n const transport = getT3amsHostTransport();\n if (transport === undefined) return undefined;\n delegatedHostApi ??= createHostApi(transport);\n return delegatedHostApi;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@t3ams/sdk",
3
+ "version": "0.1.4",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js"
9
+ }
10
+ },
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/paritytech/t3ams-games-sdk",
22
+ "directory": "packages/typescript/sdk"
23
+ },
24
+ "scripts": {
25
+ "build": "tsdown",
26
+ "test": "vitest run",
27
+ "typecheck": "tsc --noEmit"
28
+ },
29
+ "dependencies": {
30
+ "@novasamatech/host-api": "^0.8.5"
31
+ },
32
+ "devDependencies": {
33
+ "tsdown": "^0.20.1",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.0.18"
36
+ }
37
+ }