@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.
- package/dist/client/index.d.ts +5 -38
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +5 -47
- package/dist/client/index.js.map +1 -1
- package/dist/client/namespace-proxy.d.ts +3 -4
- package/dist/client/namespace-proxy.d.ts.map +1 -1
- package/dist/client/namespace-proxy.js +3 -4
- package/dist/client/namespace-proxy.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/modules/hall/browser.d.ts +83 -27
- package/dist/modules/hall/browser.d.ts.map +1 -1
- package/dist/modules/hall/browser.js +238 -49
- package/dist/modules/hall/browser.js.map +1 -1
- package/dist/modules/hall/media.d.ts +164 -0
- package/dist/modules/hall/media.d.ts.map +1 -0
- package/dist/modules/hall/media.js +182 -0
- package/dist/modules/hall/media.js.map +1 -0
- package/dist/modules/hall/server.d.ts +83 -6
- package/dist/modules/hall/server.d.ts.map +1 -1
- package/dist/modules/hall/server.js +206 -9
- package/dist/modules/hall/server.js.map +1 -1
- package/dist/modules/hall/types.d.ts +548 -25
- package/dist/modules/hall/types.d.ts.map +1 -1
- package/dist/modules/hall/types.js +12 -7
- package/dist/modules/hall/types.js.map +1 -1
- package/dist/server/hall-handlers.d.ts +40 -12
- package/dist/server/hall-handlers.d.ts.map +1 -1
- package/dist/server/hall-handlers.js +40 -12
- package/dist/server/hall-handlers.js.map +1 -1
- package/dist/server/handlers/chat/engine.d.ts.map +1 -1
- package/dist/server/handlers/chat/engine.js +5 -1
- package/dist/server/handlers/chat/engine.js.map +1 -1
- package/dist/server/handlers/chat/types.d.ts +17 -2
- package/dist/server/handlers/chat/types.d.ts.map +1 -1
- package/dist/server/hono-router.d.ts +2 -9
- package/dist/server/hono-router.d.ts.map +1 -1
- package/dist/server/hono-router.js +2 -46
- package/dist/server/hono-router.js.map +1 -1
- package/dist/server/index.d.ts +4 -19
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +10 -29
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +2 -41
- package/dist/types.d.ts.map +1 -1
- package/docs/ADR-005-hall-integration.md +449 -0
- package/package.json +1 -1
- package/dist/client/create-client-sdk.d.ts +0 -113
- package/dist/client/create-client-sdk.d.ts.map +0 -1
- package/dist/client/create-client-sdk.js +0 -169
- package/dist/client/create-client-sdk.js.map +0 -1
- package/dist/modules/app-context/index.d.ts +0 -214
- package/dist/modules/app-context/index.d.ts.map +0 -1
- package/dist/modules/app-context/index.js +0 -569
- package/dist/modules/app-context/index.js.map +0 -1
- package/dist/modules/billing/firestore-provider.d.ts +0 -60
- package/dist/modules/billing/firestore-provider.d.ts.map +0 -1
- package/dist/modules/billing/firestore-provider.js +0 -315
- package/dist/modules/billing/firestore-provider.js.map +0 -1
- package/dist/modules/brainy/proxy.d.ts +0 -48
- package/dist/modules/brainy/proxy.d.ts.map +0 -1
- package/dist/modules/brainy/proxy.js +0 -95
- package/dist/modules/brainy/proxy.js.map +0 -1
- package/dist/server/create-sdk.d.ts +0 -74
- package/dist/server/create-sdk.d.ts.map +0 -1
- package/dist/server/create-sdk.js +0 -104
- package/dist/server/create-sdk.js.map +0 -1
- package/dist/server/from-license.d.ts +0 -252
- package/dist/server/from-license.d.ts.map +0 -1
- package/dist/server/from-license.js +0 -349
- package/dist/server/from-license.js.map +0 -1
- package/dist/server/handlers.d.ts +0 -312
- package/dist/server/handlers.d.ts.map +0 -1
- package/dist/server/handlers.js +0 -376
- package/dist/server/handlers.js.map +0 -1
- package/dist/server/postmessage-handler.d.ts +0 -152
- package/dist/server/postmessage-handler.d.ts.map +0 -1
- package/dist/server/postmessage-handler.js +0 -138
- package/dist/server/postmessage-handler.js.map +0 -1
- package/dist/transports/http.d.ts +0 -86
- package/dist/transports/http.d.ts.map +0 -1
- package/dist/transports/http.js +0 -137
- package/dist/transports/http.js.map +0 -1
- package/dist/transports/postmessage.d.ts +0 -159
- package/dist/transports/postmessage.d.ts.map +0 -1
- package/dist/transports/postmessage.js +0 -207
- package/dist/transports/postmessage.js.map +0 -1
- package/dist/transports/workshop.d.ts +0 -173
- package/dist/transports/workshop.d.ts.map +0 -1
- package/dist/transports/workshop.js +0 -307
- package/dist/transports/workshop.js.map +0 -1
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# ADR-005: Hall Integration — Real-Time Communication Layer
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-03-12
|
|
5
|
+
**SDK version:** 1.7.0
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Soulcraft needs a real-time communication layer for live sessions (Academy cohorts,
|
|
12
|
+
Workshop collaboration, Venue events). Hall is a standalone Rust server at
|
|
13
|
+
`hall.soulcraft.com` that provides WebRTC SFU, transcription, pub/sub, media processing,
|
|
14
|
+
and broadcast auto-scaling.
|
|
15
|
+
|
|
16
|
+
The SDK wraps Hall's wire protocol to give products a typed, ergonomic API for both
|
|
17
|
+
server backends and browser clients — without exposing raw WebSocket/msgpack details.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
### Server/Client Asymmetry
|
|
24
|
+
|
|
25
|
+
Hall uses a strict separation between control plane (server) and data plane (browser):
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
┌──────────────────┐ msgpack/WS ┌──────────────────┐
|
|
29
|
+
│ Product backend │ ◄──────────────────► │ Hall server │
|
|
30
|
+
│ (Hono/SvelteKit) │ control plane │ (Rust, SFU) │
|
|
31
|
+
└────────┬─────────┘ └────────┬─────────┘
|
|
32
|
+
│ │
|
|
33
|
+
│ POST /api/join │ WebRTC / WS
|
|
34
|
+
│ { token, hallUrl } │ data plane
|
|
35
|
+
│ │
|
|
36
|
+
▼ ▼
|
|
37
|
+
┌──────────────────┐ WebRTC + WS ┌──────────────────┐
|
|
38
|
+
│ Browser kit app │ ◄──────────────────► │ Hall SFU │
|
|
39
|
+
│ (joinHallRoom) │ audio/video/data │ │
|
|
40
|
+
└──────────────────┘ └──────────────────┘
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **Server** (`@soulcraft/sdk/server`): `createHallModule()` returns a `HallModule` that
|
|
44
|
+
manages rooms, issues tokens, controls recording, manages pub/sub topics, uploads media,
|
|
45
|
+
and promotes/demotes peers in broadcast rooms.
|
|
46
|
+
|
|
47
|
+
- **Browser** (`@soulcraft/sdk/client`): `joinHallRoom()` joins a WebRTC room using a
|
|
48
|
+
short-lived token. `joinHallPubsub()` connects to topic-based messaging.
|
|
49
|
+
|
|
50
|
+
Products never pass their shared secret to the browser. All browser credentials are
|
|
51
|
+
short-lived tokens issued by the product backend.
|
|
52
|
+
|
|
53
|
+
### SDK Files
|
|
54
|
+
|
|
55
|
+
| File | Role |
|
|
56
|
+
|------|------|
|
|
57
|
+
| `src/modules/hall/types.ts` | All TypeScript interfaces and wire protocol message types |
|
|
58
|
+
| `src/modules/hall/server.ts` | `HallClient` — product WebSocket connection to Hall |
|
|
59
|
+
| `src/modules/hall/browser.ts` | `joinHallRoom()`, `joinHallPubsub()` — browser clients |
|
|
60
|
+
| `src/modules/hall/media.ts` | `HallMediaClient` — HTTP client for media pipeline |
|
|
61
|
+
| `src/modules/hall/protocol.ts` | Generic msgpack encode/decode for Hall wire format |
|
|
62
|
+
| `src/server/hall-handlers.ts` | Re-export factories: `createHallModule`, `createHallMediaClient` |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Wire Protocol
|
|
67
|
+
|
|
68
|
+
Hall uses **msgpack** over WebSocket with a tag/content encoding:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
{ t: 'createRoom', d: { roomId: 'cohort-123', options: { ... } } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The `t` field is the message type (string), `d` is the typed payload. This matches Rust
|
|
75
|
+
serde's `#[serde(tag = "t", content = "d")]` attribute. All field names are camelCase
|
|
76
|
+
(Rust uses `#[serde(rename_all = "camelCase")]`).
|
|
77
|
+
|
|
78
|
+
The full message unions are defined as `HallClientMessage` (17 variants) and
|
|
79
|
+
`HallServerMessage` (28 variants) in `types.ts`. These are internal to the SDK — products
|
|
80
|
+
never construct raw protocol messages.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Feature Overview
|
|
85
|
+
|
|
86
|
+
### 1. WebRTC SFU — Small Group Communication
|
|
87
|
+
|
|
88
|
+
For groups up to 30 participants with full bidirectional audio/video.
|
|
89
|
+
|
|
90
|
+
**Server side:**
|
|
91
|
+
```typescript
|
|
92
|
+
const hall = createHallModule({
|
|
93
|
+
url: process.env.HALL_URL!,
|
|
94
|
+
productName: 'academy',
|
|
95
|
+
secret: process.env.HALL_ACADEMY_SECRET!,
|
|
96
|
+
})
|
|
97
|
+
await hall.connect()
|
|
98
|
+
|
|
99
|
+
const room = await hall.createRoom('cohort-123', {
|
|
100
|
+
enableTranscription: true,
|
|
101
|
+
concepts: await loadConcepts(cohortId),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
room.on('transcript', (t) => saveTranscript(t))
|
|
105
|
+
room.on('conceptMention', (c) => brain.relate({ from: c.peerId, to: c.nodeId }))
|
|
106
|
+
|
|
107
|
+
const { token } = await hall.createSessionToken('cohort-123', userId)
|
|
108
|
+
// Return { token, hallUrl } to the browser
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Browser side:**
|
|
112
|
+
```typescript
|
|
113
|
+
import { joinHallRoom } from '@soulcraft/sdk/client'
|
|
114
|
+
|
|
115
|
+
const room = await joinHallRoom({ token, hallUrl })
|
|
116
|
+
room.addStream(await navigator.mediaDevices.getUserMedia({ video: true, audio: true }))
|
|
117
|
+
|
|
118
|
+
room.on('trackAdded', ({ peerId, streams }) => {
|
|
119
|
+
videoElement.srcObject = streams[0]
|
|
120
|
+
})
|
|
121
|
+
room.on('transcript', ({ peerId, text, isFinal }) => {
|
|
122
|
+
if (isFinal) appendTranscript(peerId, text)
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**AI features** — Hall runs Whisper ASR in-process and feeds transcripts through a BERT
|
|
127
|
+
model for concept matching against workspace knowledge graphs. Events flow from Hall →
|
|
128
|
+
product backend → (optionally) browser:
|
|
129
|
+
|
|
130
|
+
| Event | Description |
|
|
131
|
+
|-------|-------------|
|
|
132
|
+
| `transcript` | Whisper ASR transcript segment (partial or final) |
|
|
133
|
+
| `conceptMention` | BERT matched a concept node from the workspace graph |
|
|
134
|
+
| `relationProposed` | Inferred subject-verb-object relation for review |
|
|
135
|
+
| `speakerChanged` | Active speaker changed (RFC 6464 audio level detection) |
|
|
136
|
+
|
|
137
|
+
### 2. Three-Tier Broadcast Auto-Scaling
|
|
138
|
+
|
|
139
|
+
For large audiences (webinars, lectures, live events). Hall automatically scales across
|
|
140
|
+
three tiers based on audience size:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
Tier 1: Participant (WebRTC SFU) — ≤30 peers, full bidirectional, ~50ms latency
|
|
144
|
+
Tier 2: WHEP Viewer (WebRTC recv) — sub-second latency, receive-only, ~200 peers
|
|
145
|
+
Tier 3: LL-HLS Viewer (HTTP stream) — 2–3s latency, unlimited scale
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Server setup:**
|
|
149
|
+
```typescript
|
|
150
|
+
const room = await hall.createRoom('lecture-456', {
|
|
151
|
+
maxParticipants: 5, // Only 5 can publish (speakers)
|
|
152
|
+
allowBroadcast: true, // Overflow joiners become viewers
|
|
153
|
+
enableRecording: true,
|
|
154
|
+
recordingComposite: true, // Mixed MP4 in addition to per-track MKV
|
|
155
|
+
recordingWebhookUrl: 'https://academy.soulcraft.com/api/recordings/complete',
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// Monitor audience
|
|
159
|
+
room.on('viewerCount', ({ participants, whepViewers, hlsViewers }) => {
|
|
160
|
+
console.log(`${participants} speakers, ${whepViewers + hlsViewers} watching`)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// Promote a viewer to speaker
|
|
164
|
+
hall.promotePeer('lecture-456', 'student-789')
|
|
165
|
+
|
|
166
|
+
// Demote back to viewer
|
|
167
|
+
hall.demotePeer('lecture-456', 'student-789')
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Browser — role awareness:**
|
|
171
|
+
```typescript
|
|
172
|
+
const room = await joinHallRoom({ token, hallUrl })
|
|
173
|
+
|
|
174
|
+
room.on('roleChanged', ({ role, canPublish }) => {
|
|
175
|
+
if (canPublish) {
|
|
176
|
+
// Promoted — start publishing media
|
|
177
|
+
room.addStream(await navigator.mediaDevices.getUserMedia({ audio: true }))
|
|
178
|
+
} else {
|
|
179
|
+
// Demoted — SDK handles transport downgrade automatically
|
|
180
|
+
hidePublishUI()
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
// Viewers can also connect via WHEP or LL-HLS directly:
|
|
185
|
+
import { getWhepUrl, getHlsUrl } from '@soulcraft/sdk/client'
|
|
186
|
+
|
|
187
|
+
const whepUrl = getWhepUrl('https://hall.soulcraft.com', 'lecture-456')
|
|
188
|
+
const hlsUrl = getHlsUrl('https://hall.soulcraft.com', 'lecture-456')
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Screen share simulcast:**
|
|
192
|
+
```typescript
|
|
193
|
+
// Server controls the encoding strategy per-peer
|
|
194
|
+
hall.setScreenShareMode('lecture-456', speakerPeerId, 'static') // Presentations
|
|
195
|
+
hall.setScreenShareMode('lecture-456', speakerPeerId, 'motion') // Live demos
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 3. Pub/Sub — Topic-Based Messaging with Presence
|
|
199
|
+
|
|
200
|
+
Product-scoped topic routing with presence tracking, replay buffers, and both server-side
|
|
201
|
+
and browser-side APIs.
|
|
202
|
+
|
|
203
|
+
**Server side (product backend):**
|
|
204
|
+
```typescript
|
|
205
|
+
// Subscribe and broadcast from the backend
|
|
206
|
+
hall.subscribeTopic('notifications', { service: 'academy' })
|
|
207
|
+
hall.broadcastTopic('notifications', { type: 'cohort-started', cohortId: '123' })
|
|
208
|
+
|
|
209
|
+
// Listen for pub/sub events
|
|
210
|
+
hall.onPubsub('topicMessage', ({ topic, senderId, payload }) => {
|
|
211
|
+
console.log(`[${topic}] ${senderId}: ${JSON.stringify(payload)}`)
|
|
212
|
+
})
|
|
213
|
+
hall.onPubsub('presenceUpdate', ({ topic, peerId, action }) => {
|
|
214
|
+
console.log(`${peerId} ${action} ${topic}`)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Issue a browser pub/sub token
|
|
218
|
+
const { token } = await hall.createPubsubToken(userId, ['chat:*', 'presence:*'])
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Browser side:**
|
|
222
|
+
```typescript
|
|
223
|
+
import { joinHallPubsub } from '@soulcraft/sdk/client'
|
|
224
|
+
|
|
225
|
+
const pubsub = await joinHallPubsub({ token, hallUrl: 'wss://hall.soulcraft.com' })
|
|
226
|
+
|
|
227
|
+
pubsub.subscribe('chat:cohort-123', { username: 'Alice' })
|
|
228
|
+
pubsub.on('topicSubscribed', ({ topic, replay }) => {
|
|
229
|
+
// replay contains recent messages from the buffer
|
|
230
|
+
replay.forEach(({ senderId, payload }) => showMessage(senderId, payload))
|
|
231
|
+
})
|
|
232
|
+
pubsub.on('topicMessage', ({ senderId, payload }) => showMessage(senderId, payload))
|
|
233
|
+
pubsub.on('presenceUpdate', ({ peerId, action }) => updatePresence(peerId, action))
|
|
234
|
+
|
|
235
|
+
pubsub.broadcast('chat:cohort-123', { text: 'Hello everyone!' })
|
|
236
|
+
|
|
237
|
+
// Cleanup
|
|
238
|
+
pubsub.close()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Key pub/sub design decisions:**
|
|
242
|
+
- Topics are product-scoped — `academy` cannot see `workshop` topics
|
|
243
|
+
- Replay buffers are per-topic, configurable in `hall.toml`
|
|
244
|
+
- Presence metadata is arbitrary JSON, set at subscribe time
|
|
245
|
+
- Chat in rooms is bridged to pub/sub topic `room:{roomId}:chat`
|
|
246
|
+
|
|
247
|
+
### 4. Media Pipeline — Upload, Transcode, Retrieve
|
|
248
|
+
|
|
249
|
+
HTTP-based media processing for audio, video, and images. Async notifications arrive
|
|
250
|
+
over the product WebSocket.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { createHallMediaClient } from '@soulcraft/sdk/server'
|
|
254
|
+
|
|
255
|
+
const media = createHallMediaClient({
|
|
256
|
+
baseUrl: 'https://hall.soulcraft.com',
|
|
257
|
+
productName: 'workshop',
|
|
258
|
+
secret: process.env.HALL_WORKSHOP_SECRET!,
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Upload with transcoding
|
|
262
|
+
const { mediaId } = await media.upload(file, { transcode: 'video/mp4' })
|
|
263
|
+
|
|
264
|
+
// Listen for completion (on any room — media events are product-scoped)
|
|
265
|
+
room.on('mediaReady', ({ mediaId, duration, dimensions }) => {
|
|
266
|
+
const streamUrl = media.getStreamUrl(mediaId)
|
|
267
|
+
const thumbUrl = media.getThumbnailUrl(mediaId)
|
|
268
|
+
})
|
|
269
|
+
room.on('mediaError', ({ mediaId, error }) => {
|
|
270
|
+
console.error(`Media ${mediaId} failed: ${error}`)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// Query media info
|
|
274
|
+
const info = await media.getInfo(mediaId)
|
|
275
|
+
|
|
276
|
+
// Delete media
|
|
277
|
+
await media.delete(mediaId)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Auth model for media:**
|
|
281
|
+
- Upload/delete/info use `Authorization: Hall <productName>:<secret>` (server-only)
|
|
282
|
+
- Stream/thumbnail are public `GET /media/{mediaId}/*` endpoints (no auth, browser-safe)
|
|
283
|
+
- WHEP/HLS endpoints are public (browser embeds the URL directly)
|
|
284
|
+
|
|
285
|
+
### 5. Recording
|
|
286
|
+
|
|
287
|
+
Per-track MKV recording with optional composite MP4. Recording can be started/stopped
|
|
288
|
+
programmatically or enabled at room creation.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// Start recording on demand
|
|
292
|
+
await hall.startRecording('cohort-123')
|
|
293
|
+
|
|
294
|
+
// ... later ...
|
|
295
|
+
await hall.stopRecording('cohort-123')
|
|
296
|
+
|
|
297
|
+
// The room emits a manifest with file paths
|
|
298
|
+
room.on('recordingManifest', (manifest) => {
|
|
299
|
+
console.log(`Tracks: ${manifest.audioTracks.length} audio, ${manifest.videoTracks.length} video`)
|
|
300
|
+
if (manifest.compositePath) console.log(`Composite: ${manifest.compositePath}`)
|
|
301
|
+
if (manifest.cloudUrls) console.log(`Cloud: ${manifest.cloudUrls.join(', ')}`)
|
|
302
|
+
// Upload to your own storage or use Hall's cloud upload (configured in hall.toml)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// Or configure recording at room creation
|
|
306
|
+
await hall.createRoom('lecture-456', {
|
|
307
|
+
enableRecording: true,
|
|
308
|
+
recordingComposite: true,
|
|
309
|
+
recordingWebhookUrl: 'https://academy.soulcraft.com/api/recordings/complete',
|
|
310
|
+
})
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Webhook:** When configured, Hall POSTs `{ sessionId, manifest }` JSON to the webhook URL
|
|
314
|
+
on recording completion, with exponential backoff retry (3 attempts).
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Integration Patterns by Product
|
|
319
|
+
|
|
320
|
+
### Academy — Cohort Sessions
|
|
321
|
+
|
|
322
|
+
Academy uses Hall for live cohort sessions with AI-powered concept tracking:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// Server: hooks.server.ts
|
|
326
|
+
const hall = createHallModule({ url, productName: 'academy', secret })
|
|
327
|
+
await hall.connect()
|
|
328
|
+
|
|
329
|
+
// API route: /api/cohort/:id/join
|
|
330
|
+
const room = await hall.createRoom(cohortId, {
|
|
331
|
+
enableTranscription: true,
|
|
332
|
+
concepts: await brain.find({ nounType: 'concept' }),
|
|
333
|
+
allowBroadcast: true,
|
|
334
|
+
maxParticipants: 10,
|
|
335
|
+
})
|
|
336
|
+
room.on('conceptMention', async (c) => {
|
|
337
|
+
await brain.relate({ from: c.peerId, verb: c.verbType, to: c.nodeId })
|
|
338
|
+
})
|
|
339
|
+
room.on('transcript', (t) => saveToLessonLog(cohortId, t))
|
|
340
|
+
const { token } = await hall.createSessionToken(cohortId, userId)
|
|
341
|
+
return { token, hallUrl: process.env.HALL_URL }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Workshop — Collaborative Editing Sessions
|
|
345
|
+
|
|
346
|
+
Workshop uses Hall for real-time collaboration alongside Y.js document editing:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
const room = await hall.createRoom(`workspace-${workspaceId}`, {
|
|
350
|
+
enableTranscription: false, // Text-only collaboration
|
|
351
|
+
})
|
|
352
|
+
// Use pub/sub for cursor positions, selection state, awareness
|
|
353
|
+
hall.subscribeTopic(`cursor:${workspaceId}`)
|
|
354
|
+
hall.onPubsub('topicMessage', ({ topic, payload }) => broadcastToYDoc(payload))
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Venue — Live Events and Broadcasts
|
|
358
|
+
|
|
359
|
+
Venue uses Hall for large-audience events with the full broadcast tier:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
const room = await hall.createRoom(`event-${eventId}`, {
|
|
363
|
+
maxParticipants: 3, // Panel of speakers
|
|
364
|
+
allowBroadcast: true, // Unlimited audience
|
|
365
|
+
enableRecording: true,
|
|
366
|
+
recordingComposite: true,
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
// Browser viewers use WHEP or LL-HLS
|
|
370
|
+
const whepUrl = getWhepUrl(hallUrl, `event-${eventId}`)
|
|
371
|
+
const hlsUrl = getHlsUrl(hallUrl, `event-${eventId}`)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Environment Variables
|
|
377
|
+
|
|
378
|
+
| Variable | Description |
|
|
379
|
+
|----------|-------------|
|
|
380
|
+
| `HALL_URL` | WebSocket URL of the Hall server (`wss://hall.soulcraft.com`) |
|
|
381
|
+
| `HALL_<PRODUCT>_SECRET` | Product-specific shared secret (e.g. `HALL_ACADEMY_SECRET`) |
|
|
382
|
+
|
|
383
|
+
Secrets are configured in `hall.toml` on the Hall server under `[auth.products.<name>]`.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Exports
|
|
388
|
+
|
|
389
|
+
### From `@soulcraft/sdk/server`
|
|
390
|
+
|
|
391
|
+
| Export | Kind | Description |
|
|
392
|
+
|--------|------|-------------|
|
|
393
|
+
| `createHallModule` | Function | Create a server-side `HallModule` connection |
|
|
394
|
+
| `createHallMediaClient` | Function | Create an HTTP media pipeline client |
|
|
395
|
+
| `generateTurnCredentials` | Function | Generate ephemeral TURN credentials |
|
|
396
|
+
| `HallClient` | Class | Low-level WebSocket client (advanced use) |
|
|
397
|
+
| `HallMediaClient` | Class | Low-level HTTP media client (advanced use) |
|
|
398
|
+
| `HallModule` | Interface | Full server-side Hall API |
|
|
399
|
+
| `HallRoom` | Interface | Active room handle with event listeners |
|
|
400
|
+
| `HallRoomEvents` | Interface | Event map for room listeners |
|
|
401
|
+
| `HallPubsubEvents` | Interface | Event map for pub/sub listeners |
|
|
402
|
+
|
|
403
|
+
### From `@soulcraft/sdk/client`
|
|
404
|
+
|
|
405
|
+
| Export | Kind | Description |
|
|
406
|
+
|--------|------|-------------|
|
|
407
|
+
| `joinHallRoom` | Function | Join a WebRTC room from the browser |
|
|
408
|
+
| `joinHallPubsub` | Function | Connect to pub/sub from the browser |
|
|
409
|
+
| `getWhepUrl` | Function | Get WHEP viewer URL for a broadcast room |
|
|
410
|
+
| `getHlsUrl` | Function | Get LL-HLS viewer URL for a broadcast room |
|
|
411
|
+
| `HallRoomHandle` | Interface | Browser room handle with events |
|
|
412
|
+
| `HallRoomHandleEvents` | Interface | Event map for browser room |
|
|
413
|
+
| `HallPubsubHandle` | Interface | Browser pub/sub handle with events |
|
|
414
|
+
| `HallPubsubHandleEvents` | Interface | Event map for browser pub/sub |
|
|
415
|
+
|
|
416
|
+
### From `@soulcraft/sdk` (shared types)
|
|
417
|
+
|
|
418
|
+
All Hall types are re-exported from the shared entry for use in any environment.
|
|
419
|
+
`ChatMessage` and `MediaInfo` are exported as `HallChatMessage` and `HallMediaInfo`
|
|
420
|
+
to avoid collisions with the SDK 2.0 namespace types.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Rejected Alternatives
|
|
425
|
+
|
|
426
|
+
### 1. Embedding WebRTC in the SDK server
|
|
427
|
+
|
|
428
|
+
**Rejected:** Products could run their own SFU inside the SDK. This would couple the SDK
|
|
429
|
+
to WebRTC dependencies (libwebrtc, TURN), increase binary size, and duplicate what Hall
|
|
430
|
+
already does as a dedicated Rust server. The SDK is a control-plane wrapper, not a
|
|
431
|
+
media server.
|
|
432
|
+
|
|
433
|
+
### 2. REST API for Hall control
|
|
434
|
+
|
|
435
|
+
**Rejected:** REST would add HTTP overhead and lose the event-push capability (transcripts,
|
|
436
|
+
concept mentions, media notifications). The persistent WebSocket connection gives
|
|
437
|
+
sub-millisecond event delivery and supports reconnect with state preservation.
|
|
438
|
+
|
|
439
|
+
### 3. Unified browser transport (WebRTC + pub/sub on same connection)
|
|
440
|
+
|
|
441
|
+
**Rejected:** WebRTC peer connections and pub/sub topics have different lifecycles, auth
|
|
442
|
+
scopes, and reconnect semantics. Separate connections (`joinHallRoom` vs `joinHallPubsub`)
|
|
443
|
+
keep each concern simple and independently testable.
|
|
444
|
+
|
|
445
|
+
### 4. GraphQL for media pipeline
|
|
446
|
+
|
|
447
|
+
**Rejected:** The media pipeline is simple CRUD + upload. REST with `Authorization` header
|
|
448
|
+
is the right fit. GraphQL subscriptions for transcode notifications would duplicate the
|
|
449
|
+
WebSocket event system that already exists.
|
package/package.json
CHANGED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module client/create-client-sdk
|
|
3
|
-
* @description `createClientSDK` factory for browser/client-mode @soulcraft/sdk.
|
|
4
|
-
*
|
|
5
|
-
* Assembles a `SoulcraftSDK` from a `ClientSDKOptions` specification. Selects and
|
|
6
|
-
* wires the appropriate transport (`http`, `ws`, or `sse`) and returns a full SDK
|
|
7
|
-
* object whose `sdk.brainy.*` API works identically to server mode.
|
|
8
|
-
*
|
|
9
|
-
* ## Transport selection
|
|
10
|
-
*
|
|
11
|
-
* | `transport` | Use case |
|
|
12
|
-
* |-------------|----------|
|
|
13
|
-
* | `'http'` | Stateless RPC — kit apps, simple clients. No push events. |
|
|
14
|
-
* | `'ws'` | Bidirectional RPC + real-time change push. Call `sdk.connect()` first. |
|
|
15
|
-
* | `'sse'` | Receive-only push events. Pair with `'http'` for outbound RPC. |
|
|
16
|
-
*
|
|
17
|
-
* ## Auth
|
|
18
|
-
*
|
|
19
|
-
* - `auth: 'cookie'` — sends session cookies (same-origin browser use). HTTP only.
|
|
20
|
-
* - `auth: { token }` — sends `Authorization: Bearer <token>`. HTTP and WS.
|
|
21
|
-
* - Omit `auth` for unauthenticated requests (public endpoints).
|
|
22
|
-
*
|
|
23
|
-
* @example HTTP transport (kit app, cookie auth)
|
|
24
|
-
* ```typescript
|
|
25
|
-
* import { createClientSDK } from '@soulcraft/sdk/client'
|
|
26
|
-
*
|
|
27
|
-
* const sdk = createClientSDK({
|
|
28
|
-
* mode: 'client',
|
|
29
|
-
* product: 'workshop',
|
|
30
|
-
* transport: 'http',
|
|
31
|
-
* baseUrl: 'https://workshop.soulcraft.com',
|
|
32
|
-
* auth: 'cookie',
|
|
33
|
-
* })
|
|
34
|
-
*
|
|
35
|
-
* const results = await sdk.brainy.find({ query: 'candle kits', limit: 10 })
|
|
36
|
-
* ```
|
|
37
|
-
*
|
|
38
|
-
* @example WebSocket transport (real-time change push)
|
|
39
|
-
* ```typescript
|
|
40
|
-
* import { createClientSDK } from '@soulcraft/sdk/client'
|
|
41
|
-
*
|
|
42
|
-
* const sdk = createClientSDK({
|
|
43
|
-
* mode: 'client',
|
|
44
|
-
* product: 'venue',
|
|
45
|
-
* transport: 'ws',
|
|
46
|
-
* baseUrl: 'wss://venue.soulcraft.com/api/brainy/ws',
|
|
47
|
-
* auth: { token: capabilityToken },
|
|
48
|
-
* })
|
|
49
|
-
*
|
|
50
|
-
* await sdk.connect!()
|
|
51
|
-
* sdk.brainy.onDataChange((event) => refreshUI(event))
|
|
52
|
-
* const items = await sdk.brainy.find({ query: 'inventory' })
|
|
53
|
-
* ```
|
|
54
|
-
*
|
|
55
|
-
* @example SSE transport (receive-only change events + HTTP for outbound)
|
|
56
|
-
* ```typescript
|
|
57
|
-
* import { createClientSDK } from '@soulcraft/sdk/client'
|
|
58
|
-
*
|
|
59
|
-
* const sdk = createClientSDK({
|
|
60
|
-
* mode: 'client',
|
|
61
|
-
* product: 'venue',
|
|
62
|
-
* transport: 'sse',
|
|
63
|
-
* baseUrl: 'https://venue.soulcraft.com',
|
|
64
|
-
* auth: 'cookie',
|
|
65
|
-
* })
|
|
66
|
-
*
|
|
67
|
-
* // sdk.brainy.onDataChange() fires on live server changes.
|
|
68
|
-
* // sdk.brainy.find() etc. are not available on SSE — wire an HttpTransport separately.
|
|
69
|
-
* sdk.brainy.onDataChange((event) => console.log('change:', event))
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
import type { SoulcraftSDK } from '../types.js';
|
|
73
|
-
/**
|
|
74
|
-
* Legacy client SDK options. Kept for backward compatibility with `createClientSDK()`.
|
|
75
|
-
* New code should use `createSoulcraftProxy()` with a namespace-aware transport instead.
|
|
76
|
-
*/
|
|
77
|
-
interface LegacyClientSDKOptions {
|
|
78
|
-
mode: 'client';
|
|
79
|
-
product: string;
|
|
80
|
-
transport: 'http' | 'ws' | 'sse';
|
|
81
|
-
baseUrl: string;
|
|
82
|
-
auth?: 'cookie' | {
|
|
83
|
-
token: string;
|
|
84
|
-
};
|
|
85
|
-
timeoutMs?: number;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Create a client-mode `SoulcraftSDK` from the given options.
|
|
89
|
-
*
|
|
90
|
-
* Selects the transport specified by `options.transport`, wires auth, and returns
|
|
91
|
-
* a `SoulcraftSDK` whose `sdk.brainy.*` surface is identical to server mode.
|
|
92
|
-
*
|
|
93
|
-
* For `'ws'` transport, call `await sdk.connect!()` before making any `sdk.brainy.*`
|
|
94
|
-
* calls to establish the WebSocket connection.
|
|
95
|
-
*
|
|
96
|
-
* @param options - Client SDK options including transport, baseUrl, and auth.
|
|
97
|
-
* @returns A `SoulcraftSDK` backed by the chosen transport.
|
|
98
|
-
*
|
|
99
|
-
* @example HTTP transport
|
|
100
|
-
* ```typescript
|
|
101
|
-
* const sdk = createClientSDK({
|
|
102
|
-
* mode: 'client',
|
|
103
|
-
* product: 'workshop',
|
|
104
|
-
* transport: 'http',
|
|
105
|
-
* baseUrl: 'https://workshop.soulcraft.com',
|
|
106
|
-
* auth: 'cookie',
|
|
107
|
-
* })
|
|
108
|
-
* const results = await sdk.brainy.find({ query: 'candle inventory' })
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
export declare function createClientSDK(options: LegacyClientSDKOptions): SoulcraftSDK;
|
|
112
|
-
export {};
|
|
113
|
-
//# sourceMappingURL=create-client-sdk.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"create-client-sdk.d.ts","sourceRoot":"","sources":["../../src/client/create-client-sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG/C;;;GAGG;AACH,UAAU,sBAAsB;IAC9B,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,CAAA;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACnC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAMD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,YAAY,CAwE7E"}
|