@soulcraft/sdk 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/client/index.d.ts +5 -38
  2. package/dist/client/index.d.ts.map +1 -1
  3. package/dist/client/index.js +5 -47
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/client/namespace-proxy.d.ts +3 -4
  6. package/dist/client/namespace-proxy.d.ts.map +1 -1
  7. package/dist/client/namespace-proxy.js +3 -4
  8. package/dist/client/namespace-proxy.js.map +1 -1
  9. package/dist/index.d.ts +6 -6
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/modules/hall/browser.d.ts +83 -27
  14. package/dist/modules/hall/browser.d.ts.map +1 -1
  15. package/dist/modules/hall/browser.js +238 -49
  16. package/dist/modules/hall/browser.js.map +1 -1
  17. package/dist/modules/hall/media.d.ts +164 -0
  18. package/dist/modules/hall/media.d.ts.map +1 -0
  19. package/dist/modules/hall/media.js +182 -0
  20. package/dist/modules/hall/media.js.map +1 -0
  21. package/dist/modules/hall/server.d.ts +83 -6
  22. package/dist/modules/hall/server.d.ts.map +1 -1
  23. package/dist/modules/hall/server.js +206 -9
  24. package/dist/modules/hall/server.js.map +1 -1
  25. package/dist/modules/hall/types.d.ts +548 -25
  26. package/dist/modules/hall/types.d.ts.map +1 -1
  27. package/dist/modules/hall/types.js +12 -7
  28. package/dist/modules/hall/types.js.map +1 -1
  29. package/dist/server/hall-handlers.d.ts +40 -12
  30. package/dist/server/hall-handlers.d.ts.map +1 -1
  31. package/dist/server/hall-handlers.js +40 -12
  32. package/dist/server/hall-handlers.js.map +1 -1
  33. package/dist/server/handlers/chat/engine.d.ts.map +1 -1
  34. package/dist/server/handlers/chat/engine.js +5 -1
  35. package/dist/server/handlers/chat/engine.js.map +1 -1
  36. package/dist/server/handlers/chat/types.d.ts +17 -2
  37. package/dist/server/handlers/chat/types.d.ts.map +1 -1
  38. package/dist/server/hono-router.d.ts +2 -9
  39. package/dist/server/hono-router.d.ts.map +1 -1
  40. package/dist/server/hono-router.js +2 -46
  41. package/dist/server/hono-router.js.map +1 -1
  42. package/dist/server/index.d.ts +4 -19
  43. package/dist/server/index.d.ts.map +1 -1
  44. package/dist/server/index.js +10 -29
  45. package/dist/server/index.js.map +1 -1
  46. package/dist/types.d.ts +2 -41
  47. package/dist/types.d.ts.map +1 -1
  48. package/docs/ADR-005-hall-integration.md +449 -0
  49. package/package.json +1 -1
  50. package/dist/client/create-client-sdk.d.ts +0 -113
  51. package/dist/client/create-client-sdk.d.ts.map +0 -1
  52. package/dist/client/create-client-sdk.js +0 -169
  53. package/dist/client/create-client-sdk.js.map +0 -1
  54. package/dist/modules/app-context/index.d.ts +0 -214
  55. package/dist/modules/app-context/index.d.ts.map +0 -1
  56. package/dist/modules/app-context/index.js +0 -569
  57. package/dist/modules/app-context/index.js.map +0 -1
  58. package/dist/modules/billing/firestore-provider.d.ts +0 -60
  59. package/dist/modules/billing/firestore-provider.d.ts.map +0 -1
  60. package/dist/modules/billing/firestore-provider.js +0 -315
  61. package/dist/modules/billing/firestore-provider.js.map +0 -1
  62. package/dist/modules/brainy/proxy.d.ts +0 -48
  63. package/dist/modules/brainy/proxy.d.ts.map +0 -1
  64. package/dist/modules/brainy/proxy.js +0 -95
  65. package/dist/modules/brainy/proxy.js.map +0 -1
  66. package/dist/server/create-sdk.d.ts +0 -74
  67. package/dist/server/create-sdk.d.ts.map +0 -1
  68. package/dist/server/create-sdk.js +0 -104
  69. package/dist/server/create-sdk.js.map +0 -1
  70. package/dist/server/from-license.d.ts +0 -252
  71. package/dist/server/from-license.d.ts.map +0 -1
  72. package/dist/server/from-license.js +0 -349
  73. package/dist/server/from-license.js.map +0 -1
  74. package/dist/server/handlers.d.ts +0 -312
  75. package/dist/server/handlers.d.ts.map +0 -1
  76. package/dist/server/handlers.js +0 -376
  77. package/dist/server/handlers.js.map +0 -1
  78. package/dist/server/postmessage-handler.d.ts +0 -152
  79. package/dist/server/postmessage-handler.d.ts.map +0 -1
  80. package/dist/server/postmessage-handler.js +0 -138
  81. package/dist/server/postmessage-handler.js.map +0 -1
  82. package/dist/transports/http.d.ts +0 -86
  83. package/dist/transports/http.d.ts.map +0 -1
  84. package/dist/transports/http.js +0 -137
  85. package/dist/transports/http.js.map +0 -1
  86. package/dist/transports/postmessage.d.ts +0 -159
  87. package/dist/transports/postmessage.d.ts.map +0 -1
  88. package/dist/transports/postmessage.js +0 -207
  89. package/dist/transports/postmessage.js.map +0 -1
  90. package/dist/transports/workshop.d.ts +0 -173
  91. package/dist/transports/workshop.d.ts.map +0 -1
  92. package/dist/transports/workshop.js +0 -307
  93. package/dist/transports/workshop.js.map +0 -1
@@ -2,20 +2,25 @@
2
2
  * @module modules/hall/types
3
3
  * @description Types for the sdk.hall namespace — the Soulcraft real-time communication layer.
4
4
  *
5
- * Hall is a standalone Rust server at `hall.soulcraft.com`. It handles WebRTC SFU
6
- * (video/audio/data channels), in-process Whisper ASR transcription, BERT concept
7
- * matching, RFC 6464 speaker detection, and per-track MKV recording.
5
+ * Hall is a standalone Rust server at `hall.soulcraft.com`. It provides:
6
+ * - **WebRTC SFU** — video/audio/data channels with RFC 6464 speaker detection
7
+ * - **Whisper ASR** in-process transcription with BERT concept matching
8
+ * - **Pub/Sub** — product-scoped topic routing with presence tracking and replay buffers
9
+ * - **Media Pipeline** — upload, ffmpeg transcoding, thumbnails, async notifications
10
+ * - **Broadcast** — three-tier auto-scaling: participant (SFU) → WHEP → LL-HLS
11
+ * - **Recording** — per-track MKV + optional composite MP4 with webhook notifications
8
12
  *
9
13
  * The `sdk.hall` namespace has two faces depending on context:
10
14
  *
11
15
  * **Server mode** (`@soulcraft/sdk/server`) — the product backend (Academy, Workshop, Venue)
12
- * connects to Hall with a shared secret, creates rooms, and mints short-lived session tokens.
13
- * Use `createHallModule(options)` from `@soulcraft/sdk/server`.
16
+ * connects to Hall with a shared secret, creates rooms, manages pub/sub topics, uploads
17
+ * media, and mints session tokens. Use `createHallModule(options)` from `@soulcraft/sdk/server`.
14
18
  *
15
19
  * **Client mode** (`@soulcraft/sdk/client`) — a browser kit app joins a room using a session
16
- * token issued by the product backend. Use `HallRoomClient` from `@soulcraft/sdk/client`.
20
+ * token, or connects to pub/sub using a pubsub token. Use `joinHallRoom()` or
21
+ * `joinHallPubsub()` from `@soulcraft/sdk/client`.
17
22
  *
18
- * Products never pass their shared secret to the browser. The token is the only browser credential.
23
+ * Products never pass their shared secret to the browser. Tokens are the only browser credentials.
19
24
  */
20
25
  /**
21
26
  * Options for the server-side Hall connection (product backend → Hall server).
@@ -54,7 +59,28 @@ export interface ConceptInput {
54
59
  /** Kit-defined subtype, if any. */
55
60
  subtype?: string;
56
61
  }
57
- /** Options for creating a room. All fields are optional with safe defaults. */
62
+ /**
63
+ * Options for creating a room. All fields are optional with safe defaults.
64
+ *
65
+ * @example Standard room
66
+ * ```typescript
67
+ * await hall.createRoom('cohort-123', {
68
+ * enableTranscription: true,
69
+ * concepts: await loadConcepts(cohortId),
70
+ * })
71
+ * ```
72
+ *
73
+ * @example Broadcast room (webinar/lecture)
74
+ * ```typescript
75
+ * await hall.createRoom('lecture-456', {
76
+ * maxParticipants: 5,
77
+ * allowBroadcast: true,
78
+ * enableRecording: true,
79
+ * recordingComposite: true,
80
+ * recordingWebhookUrl: 'https://academy.soulcraft.com/api/recordings/complete',
81
+ * })
82
+ * ```
83
+ */
58
84
  export interface RoomOptions {
59
85
  /** Maximum peers before the SFU stops accepting new connections (default: 30). */
60
86
  maxPeers?: number;
@@ -72,6 +98,30 @@ export interface RoomOptions {
72
98
  * Pass an empty array to disable concept matching while keeping transcription.
73
99
  */
74
100
  concepts?: ConceptInput[];
101
+ /**
102
+ * Maximum bidirectional participants in broadcast rooms (default: 30).
103
+ * Overflow joiners are assigned as viewers (WHEP or LL-HLS).
104
+ * Only meaningful when `allowBroadcast` is true.
105
+ */
106
+ maxParticipants?: number;
107
+ /**
108
+ * Enable auto-scaling broadcast. When true, overflow joiners become viewers
109
+ * using WHEP (sub-second latency) or LL-HLS (2–3s, unlimited scale).
110
+ * Default: false.
111
+ */
112
+ allowBroadcast?: boolean;
113
+ /**
114
+ * Produce a composite MP4 recording (mixed audio + active speaker video)
115
+ * in addition to individual per-track MKV files.
116
+ * Default: false.
117
+ */
118
+ recordingComposite?: boolean;
119
+ /**
120
+ * Webhook URL for recording completion notification.
121
+ * Hall POSTs `{ sessionId, manifest }` JSON when recording finishes.
122
+ * Retried with exponential backoff (3 attempts).
123
+ */
124
+ recordingWebhookUrl?: string;
75
125
  }
76
126
  /**
77
127
  * Manifest emitted when recording stops.
@@ -89,6 +139,16 @@ export interface RecordingManifest {
89
139
  audioTracks: string[];
90
140
  /** Absolute paths to per-participant video track MKV files. */
91
141
  videoTracks: string[];
142
+ /**
143
+ * Path to the composite MP4 (mixed audio + active speaker video).
144
+ * Only present when `RoomOptions.recordingComposite` was true.
145
+ */
146
+ compositePath?: string;
147
+ /**
148
+ * Cloud storage URLs (if cloud upload was configured in `hall.toml`).
149
+ * Populated after upload completes (GCS, S3, etc.).
150
+ */
151
+ cloudUrls?: string[];
92
152
  }
93
153
  /** A Whisper ASR transcript segment from a peer in the room. */
94
154
  export interface TranscriptEvent {
@@ -146,6 +206,179 @@ export interface PeerLeftEvent {
146
206
  roomId: string;
147
207
  peerId: string;
148
208
  }
209
+ /** A single replayed message from a topic's replay buffer. */
210
+ export interface ReplayEntry {
211
+ /** Sender identifier (product name or peer ID). */
212
+ senderId: string;
213
+ /** The message payload. */
214
+ payload: unknown;
215
+ /** Unix timestamp in milliseconds when the message was originally sent. */
216
+ timestampMs: number;
217
+ }
218
+ /** Confirmation of a pub/sub topic subscription. */
219
+ export interface TopicSubscribedEvent {
220
+ /** Topic that was subscribed to. */
221
+ topic: string;
222
+ /** Current number of subscribers on this topic. */
223
+ subscriberCount: number;
224
+ /** Replay buffer entries (most recent messages, if any). */
225
+ replay: ReplayEntry[];
226
+ }
227
+ /** Confirmation of a pub/sub topic unsubscription. */
228
+ export interface TopicUnsubscribedEvent {
229
+ /** Topic that was unsubscribed from. */
230
+ topic: string;
231
+ }
232
+ /** A message received on a subscribed pub/sub topic. */
233
+ export interface TopicMessageEvent {
234
+ /** Topic the message was published to. */
235
+ topic: string;
236
+ /** Sender identifier (product name or peer ID). */
237
+ senderId: string;
238
+ /** The message payload. */
239
+ payload: unknown;
240
+ /** Unix timestamp in milliseconds. */
241
+ timestampMs: number;
242
+ }
243
+ /** A subscriber joined or left a pub/sub topic. */
244
+ export interface PresenceUpdateEvent {
245
+ /** Topic the presence event occurred on. */
246
+ topic: string;
247
+ /** Peer who joined or left. */
248
+ peerId: string;
249
+ /** Whether the peer joined or left. */
250
+ action: 'joined' | 'left';
251
+ /** Presence metadata (only present on join). */
252
+ metadata?: Record<string, unknown>;
253
+ /** Current subscriber count after this event. */
254
+ subscriberCount: number;
255
+ }
256
+ /** A single subscriber in a presence snapshot. */
257
+ export interface PresenceEntry {
258
+ /** Subscriber identifier. */
259
+ peerId: string;
260
+ /** Optional presence metadata set at subscribe time. */
261
+ metadata?: Record<string, unknown>;
262
+ }
263
+ /** Full snapshot of all current subscribers on a topic. */
264
+ export interface PresenceSnapshotEvent {
265
+ /** Topic queried. */
266
+ topic: string;
267
+ /** All current subscribers with their metadata. */
268
+ subscribers: PresenceEntry[];
269
+ }
270
+ /** Metadata for a processed media file. */
271
+ export interface MediaInfo {
272
+ /** Unique media identifier. */
273
+ mediaId: string;
274
+ /** Product that uploaded this media. */
275
+ productName: string;
276
+ /** Original filename from the upload. */
277
+ originalFilename: string;
278
+ /** MIME type of the original file. */
279
+ mimeType: string;
280
+ /** File size in bytes. */
281
+ size: number;
282
+ /** Duration in seconds (audio/video only). */
283
+ duration?: number;
284
+ /** Width × height in pixels (image/video only). */
285
+ dimensions?: [number, number];
286
+ /** Whether the file was transcoded from its original format. */
287
+ transcoded: boolean;
288
+ /** Processing status. */
289
+ status: 'processing' | 'ready' | 'error';
290
+ /** ISO 8601 timestamp when the media was uploaded. */
291
+ createdAt: string;
292
+ }
293
+ /** Notification that a media file has been processed and is ready. */
294
+ export interface MediaReadyEvent {
295
+ /** Unique media identifier. */
296
+ mediaId: string;
297
+ /** MIME type of the original file. */
298
+ mimeType: string;
299
+ /** File size in bytes. */
300
+ size: number;
301
+ /** Duration in seconds (audio/video only). */
302
+ duration?: number;
303
+ /** Width × height in pixels (image/video only). */
304
+ dimensions?: [number, number];
305
+ }
306
+ /** Notification that media processing failed. */
307
+ export interface MediaErrorEvent {
308
+ /** Media identifier that failed. */
309
+ mediaId: string;
310
+ /** Human-readable error description. */
311
+ error: string;
312
+ }
313
+ /**
314
+ * Supported transcode target formats for media uploads.
315
+ * Sent as the `transcode` field in the upload form.
316
+ */
317
+ export type TranscodeTarget = 'audio/mp3' | 'video/mp4' | 'image/webp';
318
+ /**
319
+ * Role assigned to a peer in a broadcast room.
320
+ * - `participant` — full bidirectional WebRTC (can publish and receive media)
321
+ * - `viewer` — receive-only (WHEP or LL-HLS, cannot publish)
322
+ */
323
+ export type HallPeerRole = 'participant' | 'viewer';
324
+ /** A viewer was promoted to full participant. */
325
+ export interface PeerPromotedEvent {
326
+ roomId: string;
327
+ peerId: string;
328
+ }
329
+ /** A participant was demoted to viewer. */
330
+ export interface PeerDemotedEvent {
331
+ roomId: string;
332
+ peerId: string;
333
+ }
334
+ /** Periodic viewer count update for broadcast rooms (every ~5s). */
335
+ export interface ViewerCountEvent {
336
+ roomId: string;
337
+ /** Number of full bidirectional participants. */
338
+ participants: number;
339
+ /** Number of WHEP (sub-second latency) viewers. */
340
+ whepViewers: number;
341
+ /** Number of LL-HLS viewers. */
342
+ hlsViewers: number;
343
+ }
344
+ /**
345
+ * Screen share simulcast mode.
346
+ * - `static` — prioritize resolution (presentations, documents)
347
+ * - `motion` — prioritize framerate (demos, video playback)
348
+ */
349
+ export type ScreenShareMode = 'static' | 'motion';
350
+ /** Periodic thumbnail of an active screen share. */
351
+ export interface ScreenShareThumbnailEvent {
352
+ roomId: string;
353
+ peerId: string;
354
+ /** URL to the latest thumbnail image. */
355
+ thumbnailUrl: string;
356
+ }
357
+ /** A single chat message in room history. */
358
+ export interface ChatMessage {
359
+ /** Peer who sent the message. */
360
+ peerId: string;
361
+ /** Message text. */
362
+ text: string;
363
+ /** Unix timestamp in milliseconds. */
364
+ timestampMs: number;
365
+ }
366
+ /** Response to a `getChatHistory` request. */
367
+ export interface ChatHistoryEvent {
368
+ roomId: string;
369
+ /** Chat messages in chronological order. */
370
+ messages: ChatMessage[];
371
+ }
372
+ /**
373
+ * Sent to a browser client when their role changes (promoted or demoted).
374
+ * The SDK handles transport upgrade/downgrade transparently.
375
+ */
376
+ export interface RoleChangedEvent {
377
+ /** New role: `"participant"` or `"viewer"`. */
378
+ role: HallPeerRole;
379
+ /** Whether the peer can now publish media tracks. */
380
+ canPublish: boolean;
381
+ }
149
382
  /**
150
383
  * Messages sent from the product backend to the Hall server over the product WebSocket.
151
384
  * Encoded as msgpack with `t` (type tag) and `d` (data) fields — matching Rust serde
@@ -179,6 +412,7 @@ export type HallClientMessage = {
179
412
  roomId: string;
180
413
  peerId: string;
181
414
  ttlSecs?: number | undefined;
415
+ role?: HallPeerRole | undefined;
182
416
  };
183
417
  } | {
184
418
  t: 'startRecording';
@@ -197,6 +431,60 @@ export type HallClientMessage = {
197
431
  channel: string;
198
432
  payload: Uint8Array;
199
433
  };
434
+ } | {
435
+ t: 'subscribeTopic';
436
+ d: {
437
+ topic: string;
438
+ metadata?: Record<string, unknown> | undefined;
439
+ };
440
+ } | {
441
+ t: 'unsubscribeTopic';
442
+ d: {
443
+ topic: string;
444
+ };
445
+ } | {
446
+ t: 'broadcastTopic';
447
+ d: {
448
+ topic: string;
449
+ payload: unknown;
450
+ };
451
+ } | {
452
+ t: 'getPresence';
453
+ d: {
454
+ topic: string;
455
+ };
456
+ } | {
457
+ t: 'createPubsubToken';
458
+ d: {
459
+ peerId: string;
460
+ allowedTopics?: string[] | undefined;
461
+ ttlSecs?: number | undefined;
462
+ };
463
+ } | {
464
+ t: 'promotePeer';
465
+ d: {
466
+ roomId: string;
467
+ peerId: string;
468
+ };
469
+ } | {
470
+ t: 'demotePeer';
471
+ d: {
472
+ roomId: string;
473
+ peerId: string;
474
+ };
475
+ } | {
476
+ t: 'setScreenShareMode';
477
+ d: {
478
+ roomId: string;
479
+ peerId: string;
480
+ mode: ScreenShareMode;
481
+ };
482
+ } | {
483
+ t: 'getChatHistory';
484
+ d: {
485
+ roomId: string;
486
+ lastN?: number | undefined;
487
+ };
200
488
  };
201
489
  /**
202
490
  * Messages sent from the Hall server to the product backend.
@@ -276,6 +564,48 @@ export type HallServerMessage = {
276
564
  } | {
277
565
  t: 'peerLeft';
278
566
  d: PeerLeftEvent;
567
+ } | {
568
+ t: 'topicSubscribed';
569
+ d: TopicSubscribedEvent;
570
+ } | {
571
+ t: 'topicUnsubscribed';
572
+ d: TopicUnsubscribedEvent;
573
+ } | {
574
+ t: 'topicMessage';
575
+ d: TopicMessageEvent;
576
+ } | {
577
+ t: 'presenceUpdate';
578
+ d: PresenceUpdateEvent;
579
+ } | {
580
+ t: 'presenceSnapshot';
581
+ d: PresenceSnapshotEvent;
582
+ } | {
583
+ t: 'pubsubToken';
584
+ d: {
585
+ token: string;
586
+ expiresAt: number;
587
+ };
588
+ } | {
589
+ t: 'mediaReady';
590
+ d: MediaReadyEvent;
591
+ } | {
592
+ t: 'mediaError';
593
+ d: MediaErrorEvent;
594
+ } | {
595
+ t: 'peerPromoted';
596
+ d: PeerPromotedEvent;
597
+ } | {
598
+ t: 'peerDemoted';
599
+ d: PeerDemotedEvent;
600
+ } | {
601
+ t: 'viewerCount';
602
+ d: ViewerCountEvent;
603
+ } | {
604
+ t: 'screenShareThumbnail';
605
+ d: ScreenShareThumbnailEvent;
606
+ } | {
607
+ t: 'chatHistory';
608
+ d: ChatHistoryEvent;
279
609
  };
280
610
  /** Event map for server-side {@link HallRoom} listeners. */
281
611
  export interface HallRoomEvents {
@@ -286,6 +616,13 @@ export interface HallRoomEvents {
286
616
  peerJoined: PeerJoinedEvent;
287
617
  peerLeft: PeerLeftEvent;
288
618
  recordingManifest: RecordingManifest;
619
+ peerPromoted: PeerPromotedEvent;
620
+ peerDemoted: PeerDemotedEvent;
621
+ viewerCount: ViewerCountEvent;
622
+ screenShareThumbnail: ScreenShareThumbnailEvent;
623
+ chatHistory: ChatHistoryEvent;
624
+ mediaReady: MediaReadyEvent;
625
+ mediaError: MediaErrorEvent;
289
626
  closed: {
290
627
  roomId: string;
291
628
  };
@@ -293,9 +630,9 @@ export interface HallRoomEvents {
293
630
  /**
294
631
  * The `sdk.hall` namespace on `SoulcraftSDK` in **server mode**.
295
632
  *
296
- * Products call these methods in request handlers to create rooms and issue session
297
- * tokens that browser clients use to join. Rooms are managed by the Hall server;
298
- * the product only holds a control-plane connection over WebSocket.
633
+ * Products call these methods in request handlers to create rooms, manage pub/sub
634
+ * topics, upload media, and issue session tokens. Rooms and topics are managed by
635
+ * the Hall server; the product only holds a control-plane connection over WebSocket.
299
636
  *
300
637
  * Obtain via `createHallModule(options)` from `@soulcraft/sdk/server`.
301
638
  *
@@ -330,11 +667,27 @@ export interface HallModule {
330
667
  * @throws {Error} If auth fails or the connection cannot be established.
331
668
  */
332
669
  connect(): Promise<void>;
670
+ /**
671
+ * Registers a listener for unexpected disconnects (not triggered by `close()`).
672
+ *
673
+ * @param listener - Called with a human-readable disconnect reason.
674
+ */
675
+ onDisconnect(listener: (reason: string) => void): void;
676
+ /**
677
+ * Registers a listener for successful reconnections.
678
+ * On reconnect, rooms from before the disconnect are gone — re-create them.
679
+ */
680
+ onReconnect(listener: () => void): void;
681
+ /**
682
+ * Gracefully closes the Hall connection and releases resources.
683
+ * Called automatically by `sdk.shutdown()` in server mode.
684
+ */
685
+ close(): Promise<void>;
333
686
  /**
334
687
  * Creates a new room. Errors if a room with the same ID already exists.
335
688
  *
336
689
  * @param roomId - Unique room identifier (e.g. Brainy session entity ID).
337
- * @param options - Room options: peer limit, transcription, recording, concept list.
690
+ * @param options - Room options: peer limit, transcription, recording, broadcast, concept list.
338
691
  * @returns The HallRoom handle — register event listeners on it immediately.
339
692
  */
340
693
  createRoom(roomId: string, options?: RoomOptions): Promise<HallRoom>;
@@ -357,15 +710,16 @@ export interface HallModule {
357
710
  */
358
711
  getRoom(roomId: string): HallRoom | undefined;
359
712
  /**
360
- * Mints a short-lived session token for a browser client.
713
+ * Mints a short-lived session token for a browser client to join a room.
361
714
  * Pass `{ token, hallUrl }` to the browser — never the product secret.
362
715
  *
363
716
  * @param roomId - The room the browser will join.
364
717
  * @param peerId - The peer's unique identifier (e.g. authenticated user ID).
365
718
  * @param ttlSecs - Token lifetime in seconds (default: 300).
719
+ * @param role - Optional role hint: `"participant"`, `"viewer"`, or undefined (auto).
366
720
  * @returns `{ token, expiresAt }`.
367
721
  */
368
- createSessionToken(roomId: string, peerId: string, ttlSecs?: number): Promise<{
722
+ createSessionToken(roomId: string, peerId: string, ttlSecs?: number, role?: HallPeerRole): Promise<{
369
723
  token: string;
370
724
  expiresAt: number;
371
725
  }>;
@@ -384,21 +738,91 @@ export interface HallModule {
384
738
  */
385
739
  stopRecording(roomId: string): Promise<void>;
386
740
  /**
387
- * Registers a listener for unexpected disconnects (not triggered by `close()`).
741
+ * Subscribe to a pub/sub topic. Topics are product-scoped no cross-product visibility.
742
+ * The returned event fires on the connection-level `onTopicSubscribed` listener.
388
743
  *
389
- * @param listener - Called with a human-readable disconnect reason.
744
+ * @param topic - Topic name to subscribe to.
745
+ * @param metadata - Optional presence metadata visible to other subscribers.
390
746
  */
391
- onDisconnect(listener: (reason: string) => void): void;
747
+ subscribeTopic(topic: string, metadata?: Record<string, unknown>): void;
392
748
  /**
393
- * Registers a listener for successful reconnections.
394
- * On reconnect, rooms from before the disconnect are gone — re-create them.
749
+ * Unsubscribe from a pub/sub topic.
750
+ *
751
+ * @param topic - Topic name to unsubscribe from.
395
752
  */
396
- onReconnect(listener: () => void): void;
753
+ unsubscribeTopic(topic: string): void;
397
754
  /**
398
- * Gracefully closes the Hall connection and releases resources.
399
- * Called automatically by `sdk.shutdown()` in server mode.
755
+ * Broadcast a message to all subscribers of a pub/sub topic.
756
+ *
757
+ * @param topic - Topic to broadcast to.
758
+ * @param payload - Arbitrary JSON payload delivered to all subscribers.
400
759
  */
401
- close(): Promise<void>;
760
+ broadcastTopic(topic: string, payload: unknown): void;
761
+ /**
762
+ * Request a snapshot of all current subscribers on a topic.
763
+ * The result fires on the connection-level `onPresenceSnapshot` listener.
764
+ *
765
+ * @param topic - Topic to query presence for.
766
+ */
767
+ getPresence(topic: string): void;
768
+ /**
769
+ * Register a listener for pub/sub events on the product connection.
770
+ *
771
+ * @param event - The pub/sub event name.
772
+ * @param listener - Callback invoked with the typed event payload.
773
+ */
774
+ onPubsub<K extends keyof HallPubsubEvents>(event: K, listener: (data: HallPubsubEvents[K]) => void): void;
775
+ /**
776
+ * Issue a browser pub/sub token. The browser uses this to connect to
777
+ * `wss://hall.soulcraft.com/ws/pubsub/{token}` for client-side pub/sub.
778
+ *
779
+ * @param peerId - Peer ID the token is issued for.
780
+ * @param allowedTopics - Optional topic whitelist. If omitted, grants access to all topics.
781
+ * @param ttlSecs - Token lifetime in seconds (default: 300).
782
+ * @returns `{ token, expiresAt }`.
783
+ */
784
+ createPubsubToken(peerId: string, allowedTopics?: string[], ttlSecs?: number): Promise<{
785
+ token: string;
786
+ expiresAt: number;
787
+ }>;
788
+ /**
789
+ * Promote a viewer to a full bidirectional participant in a broadcast room.
790
+ *
791
+ * @param roomId - Room containing the peer.
792
+ * @param peerId - Peer ID to promote.
793
+ */
794
+ promotePeer(roomId: string, peerId: string): void;
795
+ /**
796
+ * Demote a participant to a receive-only viewer in a broadcast room.
797
+ *
798
+ * @param roomId - Room containing the peer.
799
+ * @param peerId - Peer ID to demote.
800
+ */
801
+ demotePeer(roomId: string, peerId: string): void;
802
+ /**
803
+ * Set the screen share simulcast strategy for a peer.
804
+ *
805
+ * @param roomId - Room containing the screensharing peer.
806
+ * @param peerId - Peer sharing their screen.
807
+ * @param mode - `"static"` (prioritize resolution) or `"motion"` (prioritize framerate).
808
+ */
809
+ setScreenShareMode(roomId: string, peerId: string, mode: ScreenShareMode): void;
810
+ /**
811
+ * Request historical chat messages for a room.
812
+ * Chat is also bridged to pub/sub topic `room:{roomId}:chat` with replay buffer.
813
+ *
814
+ * @param roomId - Room to get chat history for.
815
+ * @param lastN - Maximum number of recent messages to return (default: all stored).
816
+ */
817
+ getChatHistory(roomId: string, lastN?: number): void;
818
+ }
819
+ /** Event map for pub/sub events on the server-side Hall connection. */
820
+ export interface HallPubsubEvents {
821
+ topicSubscribed: TopicSubscribedEvent;
822
+ topicUnsubscribed: TopicUnsubscribedEvent;
823
+ topicMessage: TopicMessageEvent;
824
+ presenceUpdate: PresenceUpdateEvent;
825
+ presenceSnapshot: PresenceSnapshotEvent;
402
826
  }
403
827
  /**
404
828
  * A handle for a live Hall room, returned by `sdk.hall.createRoom()`.
@@ -408,10 +832,14 @@ export interface HallModule {
408
832
  *
409
833
  * @example
410
834
  * ```typescript
411
- * const room = await hall.createRoom('cohort-123', { enableTranscription: true })
835
+ * const room = await hall.createRoom('cohort-123', {
836
+ * enableTranscription: true,
837
+ * allowBroadcast: true,
838
+ * })
412
839
  * room.on('transcript', (t) => console.log(`${t.peerId}: ${t.text}`))
413
840
  * room.on('conceptMention', (c) => brain.relate({ from: c.peerId, to: c.nodeId }))
414
841
  * room.on('peerJoined', (p) => updatePresence(p.peerId, 'joined'))
842
+ * room.on('viewerCount', (v) => updateDashboard(v.participants, v.whepViewers + v.hlsViewers))
415
843
  * ```
416
844
  */
417
845
  export interface HallRoom {
@@ -462,6 +890,8 @@ export interface HallRoomHandleEvents {
462
890
  peerId: string;
463
891
  track: MediaStreamTrack;
464
892
  };
893
+ /** Role changed (promoted or demoted in a broadcast room). */
894
+ roleChanged: RoleChangedEvent;
465
895
  /** The Hall server closed the room. */
466
896
  closed: {
467
897
  roomId: string;
@@ -478,6 +908,9 @@ export interface HallRoomHandleEvents {
478
908
  * (transcript, conceptMention, relationProposed) alongside media track events
479
909
  * so kit apps have a single event surface for the full session experience.
480
910
  *
911
+ * In broadcast rooms, the `roleChanged` event fires when the peer is promoted
912
+ * or demoted. The SDK handles transport upgrade/downgrade transparently.
913
+ *
481
914
  * @example
482
915
  * ```typescript
483
916
  * const room = await joinHallRoom({ token, hallUrl: 'wss://hall.soulcraft.com' })
@@ -489,7 +922,7 @@ export interface HallRoomHandleEvents {
489
922
  * room.on('transcript', ({ peerId, text, isFinal }) => {
490
923
  * if (isFinal) appendTranscript(peerId, text)
491
924
  * })
492
- * room.on('conceptMention', ({ nodeId, confidence }) => pulseGraphNode(nodeId, confidence))
925
+ * room.on('roleChanged', ({ role, canPublish }) => updateUI(role, canPublish))
493
926
  * ```
494
927
  */
495
928
  export interface HallRoomHandle {
@@ -497,6 +930,10 @@ export interface HallRoomHandle {
497
930
  readonly roomId: string;
498
931
  /** The peer ID for this connection (decoded from the session token). */
499
932
  readonly peerId: string;
933
+ /** The assigned role in the room. Updated when `roleChanged` fires. */
934
+ readonly role: HallPeerRole;
935
+ /** Whether this peer can publish media tracks. Updated when `roleChanged` fires. */
936
+ readonly canPublish: boolean;
500
937
  /**
501
938
  * Register a listener for a room event.
502
939
  *
@@ -524,4 +961,90 @@ export interface HallRoomHandle {
524
961
  */
525
962
  close(): void;
526
963
  }
964
+ /** Events emitted by a `HallPubsubHandle` in the browser. */
965
+ export interface HallPubsubHandleEvents {
966
+ /** Topic subscription confirmed. */
967
+ topicSubscribed: TopicSubscribedEvent;
968
+ /** Topic unsubscription confirmed. */
969
+ topicUnsubscribed: TopicUnsubscribedEvent;
970
+ /** Message received on a subscribed topic. */
971
+ topicMessage: TopicMessageEvent;
972
+ /** A subscriber joined or left a topic. */
973
+ presenceUpdate: PresenceUpdateEvent;
974
+ /** Full presence snapshot for a topic. */
975
+ presenceSnapshot: PresenceSnapshotEvent;
976
+ /** A connection or protocol error occurred. */
977
+ error: {
978
+ code: string;
979
+ message: string;
980
+ };
981
+ /** The pub/sub connection was closed. */
982
+ closed: void;
983
+ }
984
+ /**
985
+ * An active pub/sub connection to Hall, returned by `joinHallPubsub()`.
986
+ *
987
+ * Wraps a WebSocket connection to `wss://hall.soulcraft.com/ws/pubsub/{token}`.
988
+ * Uses msgpack wire format, same as the product WebSocket.
989
+ *
990
+ * @example
991
+ * ```typescript
992
+ * import { joinHallPubsub } from '@soulcraft/sdk/client'
993
+ *
994
+ * const pubsub = await joinHallPubsub({ token, hallUrl: 'wss://hall.soulcraft.com' })
995
+ * pubsub.subscribe('room:abc:chat', { username: 'Alice' })
996
+ * pubsub.on('topicMessage', ({ topic, senderId, payload }) => showMessage(senderId, payload))
997
+ * pubsub.on('presenceUpdate', ({ peerId, action }) => updatePresence(peerId, action))
998
+ *
999
+ * pubsub.broadcast('room:abc:chat', { text: 'Hello!' })
1000
+ * // On leave:
1001
+ * pubsub.close()
1002
+ * ```
1003
+ */
1004
+ export interface HallPubsubHandle {
1005
+ /** The peer ID for this connection (decoded from the pubsub token). */
1006
+ readonly peerId: string;
1007
+ /**
1008
+ * Subscribe to a pub/sub topic.
1009
+ *
1010
+ * @param topic - Topic name to subscribe to.
1011
+ * @param metadata - Optional presence metadata visible to other subscribers.
1012
+ */
1013
+ subscribe(topic: string, metadata?: Record<string, unknown>): void;
1014
+ /**
1015
+ * Unsubscribe from a pub/sub topic.
1016
+ *
1017
+ * @param topic - Topic name to unsubscribe from.
1018
+ */
1019
+ unsubscribe(topic: string): void;
1020
+ /**
1021
+ * Broadcast a message to all subscribers of a topic.
1022
+ *
1023
+ * @param topic - Topic to broadcast to.
1024
+ * @param payload - Arbitrary JSON payload delivered to all subscribers.
1025
+ */
1026
+ broadcast(topic: string, payload: unknown): void;
1027
+ /**
1028
+ * Request a presence snapshot for a topic.
1029
+ *
1030
+ * @param topic - Topic to query presence for.
1031
+ */
1032
+ getPresence(topic: string): void;
1033
+ /**
1034
+ * Register a listener for a pub/sub event.
1035
+ *
1036
+ * @param event - The event name.
1037
+ * @param listener - Callback invoked with the typed event payload.
1038
+ */
1039
+ on<K extends keyof HallPubsubHandleEvents>(event: K, listener: (data: HallPubsubHandleEvents[K]) => void): this;
1040
+ /**
1041
+ * Remove a previously registered listener.
1042
+ *
1043
+ * @param event - The event name.
1044
+ * @param listener - The exact function reference passed to `on`.
1045
+ */
1046
+ off<K extends keyof HallPubsubHandleEvents>(event: K, listener: (data: HallPubsubHandleEvents[K]) => void): this;
1047
+ /** Disconnect the pub/sub WebSocket. */
1048
+ close(): void;
1049
+ }
527
1050
  //# sourceMappingURL=types.d.ts.map