@soulcraft/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/client/index.d.ts +62 -0
  2. package/dist/client/index.d.ts.map +1 -0
  3. package/dist/client/index.js +60 -0
  4. package/dist/client/index.js.map +1 -0
  5. package/dist/index.d.ts +33 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +17 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/modules/ai/index.d.ts +55 -0
  10. package/dist/modules/ai/index.d.ts.map +1 -0
  11. package/dist/modules/ai/index.js +263 -0
  12. package/dist/modules/ai/index.js.map +1 -0
  13. package/dist/modules/ai/types.d.ts +216 -0
  14. package/dist/modules/ai/types.d.ts.map +1 -0
  15. package/dist/modules/ai/types.js +30 -0
  16. package/dist/modules/ai/types.js.map +1 -0
  17. package/dist/modules/auth/backchannel.d.ts +85 -0
  18. package/dist/modules/auth/backchannel.d.ts.map +1 -0
  19. package/dist/modules/auth/backchannel.js +168 -0
  20. package/dist/modules/auth/backchannel.js.map +1 -0
  21. package/dist/modules/auth/config.d.ts +122 -0
  22. package/dist/modules/auth/config.d.ts.map +1 -0
  23. package/dist/modules/auth/config.js +158 -0
  24. package/dist/modules/auth/config.js.map +1 -0
  25. package/dist/modules/auth/middleware.d.ts +146 -0
  26. package/dist/modules/auth/middleware.d.ts.map +1 -0
  27. package/dist/modules/auth/middleware.js +204 -0
  28. package/dist/modules/auth/middleware.js.map +1 -0
  29. package/dist/modules/auth/types.d.ts +162 -0
  30. package/dist/modules/auth/types.d.ts.map +1 -0
  31. package/dist/modules/auth/types.js +14 -0
  32. package/dist/modules/auth/types.js.map +1 -0
  33. package/dist/modules/billing/types.d.ts +7 -0
  34. package/dist/modules/billing/types.d.ts.map +1 -0
  35. package/dist/modules/billing/types.js +7 -0
  36. package/dist/modules/billing/types.js.map +1 -0
  37. package/dist/modules/brainy/auth.d.ts +104 -0
  38. package/dist/modules/brainy/auth.d.ts.map +1 -0
  39. package/dist/modules/brainy/auth.js +144 -0
  40. package/dist/modules/brainy/auth.js.map +1 -0
  41. package/dist/modules/brainy/errors.d.ts +118 -0
  42. package/dist/modules/brainy/errors.d.ts.map +1 -0
  43. package/dist/modules/brainy/errors.js +142 -0
  44. package/dist/modules/brainy/errors.js.map +1 -0
  45. package/dist/modules/brainy/events.d.ts +63 -0
  46. package/dist/modules/brainy/events.d.ts.map +1 -0
  47. package/dist/modules/brainy/events.js +14 -0
  48. package/dist/modules/brainy/events.js.map +1 -0
  49. package/dist/modules/brainy/proxy.d.ts +48 -0
  50. package/dist/modules/brainy/proxy.d.ts.map +1 -0
  51. package/dist/modules/brainy/proxy.js +95 -0
  52. package/dist/modules/brainy/proxy.js.map +1 -0
  53. package/dist/modules/brainy/types.d.ts +83 -0
  54. package/dist/modules/brainy/types.d.ts.map +1 -0
  55. package/dist/modules/brainy/types.js +21 -0
  56. package/dist/modules/brainy/types.js.map +1 -0
  57. package/dist/modules/events/index.d.ts +41 -0
  58. package/dist/modules/events/index.d.ts.map +1 -0
  59. package/dist/modules/events/index.js +53 -0
  60. package/dist/modules/events/index.js.map +1 -0
  61. package/dist/modules/events/types.d.ts +129 -0
  62. package/dist/modules/events/types.d.ts.map +1 -0
  63. package/dist/modules/events/types.js +32 -0
  64. package/dist/modules/events/types.js.map +1 -0
  65. package/dist/modules/formats/types.d.ts +7 -0
  66. package/dist/modules/formats/types.d.ts.map +1 -0
  67. package/dist/modules/formats/types.js +7 -0
  68. package/dist/modules/formats/types.js.map +1 -0
  69. package/dist/modules/hall/types.d.ts +56 -0
  70. package/dist/modules/hall/types.d.ts.map +1 -0
  71. package/dist/modules/hall/types.js +16 -0
  72. package/dist/modules/hall/types.js.map +1 -0
  73. package/dist/modules/kits/types.d.ts +7 -0
  74. package/dist/modules/kits/types.d.ts.map +1 -0
  75. package/dist/modules/kits/types.js +7 -0
  76. package/dist/modules/kits/types.js.map +1 -0
  77. package/dist/modules/license/types.d.ts +7 -0
  78. package/dist/modules/license/types.d.ts.map +1 -0
  79. package/dist/modules/license/types.js +7 -0
  80. package/dist/modules/license/types.js.map +1 -0
  81. package/dist/modules/notifications/types.d.ts +7 -0
  82. package/dist/modules/notifications/types.d.ts.map +1 -0
  83. package/dist/modules/notifications/types.js +7 -0
  84. package/dist/modules/notifications/types.js.map +1 -0
  85. package/dist/modules/skills/index.d.ts +60 -0
  86. package/dist/modules/skills/index.d.ts.map +1 -0
  87. package/dist/modules/skills/index.js +253 -0
  88. package/dist/modules/skills/index.js.map +1 -0
  89. package/dist/modules/skills/types.d.ts +127 -0
  90. package/dist/modules/skills/types.d.ts.map +1 -0
  91. package/dist/modules/skills/types.js +23 -0
  92. package/dist/modules/skills/types.js.map +1 -0
  93. package/dist/modules/versions/types.d.ts +31 -0
  94. package/dist/modules/versions/types.d.ts.map +1 -0
  95. package/dist/modules/versions/types.js +9 -0
  96. package/dist/modules/versions/types.js.map +1 -0
  97. package/dist/modules/vfs/types.d.ts +26 -0
  98. package/dist/modules/vfs/types.d.ts.map +1 -0
  99. package/dist/modules/vfs/types.js +11 -0
  100. package/dist/modules/vfs/types.js.map +1 -0
  101. package/dist/server/create-sdk.d.ts +70 -0
  102. package/dist/server/create-sdk.d.ts.map +1 -0
  103. package/dist/server/create-sdk.js +125 -0
  104. package/dist/server/create-sdk.js.map +1 -0
  105. package/dist/server/hall-handlers.d.ts +195 -0
  106. package/dist/server/hall-handlers.d.ts.map +1 -0
  107. package/dist/server/hall-handlers.js +239 -0
  108. package/dist/server/hall-handlers.js.map +1 -0
  109. package/dist/server/handlers.d.ts +216 -0
  110. package/dist/server/handlers.d.ts.map +1 -0
  111. package/dist/server/handlers.js +214 -0
  112. package/dist/server/handlers.js.map +1 -0
  113. package/dist/server/index.d.ts +52 -0
  114. package/dist/server/index.d.ts.map +1 -0
  115. package/dist/server/index.js +50 -0
  116. package/dist/server/index.js.map +1 -0
  117. package/dist/server/instance-pool.d.ts +299 -0
  118. package/dist/server/instance-pool.d.ts.map +1 -0
  119. package/dist/server/instance-pool.js +359 -0
  120. package/dist/server/instance-pool.js.map +1 -0
  121. package/dist/transports/http.d.ts +86 -0
  122. package/dist/transports/http.d.ts.map +1 -0
  123. package/dist/transports/http.js +134 -0
  124. package/dist/transports/http.js.map +1 -0
  125. package/dist/transports/local.d.ts +76 -0
  126. package/dist/transports/local.d.ts.map +1 -0
  127. package/dist/transports/local.js +101 -0
  128. package/dist/transports/local.js.map +1 -0
  129. package/dist/transports/sse.d.ts +99 -0
  130. package/dist/transports/sse.d.ts.map +1 -0
  131. package/dist/transports/sse.js +192 -0
  132. package/dist/transports/sse.js.map +1 -0
  133. package/dist/transports/transport.d.ts +68 -0
  134. package/dist/transports/transport.d.ts.map +1 -0
  135. package/dist/transports/transport.js +14 -0
  136. package/dist/transports/transport.js.map +1 -0
  137. package/dist/transports/ws.d.ts +135 -0
  138. package/dist/transports/ws.d.ts.map +1 -0
  139. package/dist/transports/ws.js +331 -0
  140. package/dist/transports/ws.js.map +1 -0
  141. package/dist/types.d.ts +152 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +8 -0
  144. package/dist/types.js.map +1 -0
  145. package/docs/ADR-001-sdk-design.md +282 -0
  146. package/docs/IMPLEMENTATION-PLAN.md +708 -0
  147. package/docs/USAGE.md +646 -0
  148. package/docs/kit-sdk-guide.md +474 -0
  149. package/package.json +61 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @module server/create-sdk
3
+ * @description `createSDK` factory for server-mode @soulcraft/sdk.
4
+ *
5
+ * Assembles a complete `SoulcraftSDK` instance from a live Brainy instance.
6
+ * In server mode the SDK uses the {@link LocalTransport} — zero overhead, no
7
+ * serialization, in-process method dispatch directly into the Brainy instance.
8
+ *
9
+ * The returned SDK object is scoped to a single Brainy instance. In a typical
10
+ * server request handler the caller resolves the correct Brainy instance from a
11
+ * {@link BrainyInstancePool} and then calls `createSDK({ brain })`. A new SDK
12
+ * object per request is cheap — it's a thin wrapper, not a connection.
13
+ *
14
+ * @example Workshop request handler
15
+ * ```typescript
16
+ * import { BrainyInstancePool, createSDK } from '@soulcraft/sdk/server'
17
+ *
18
+ * const pool = new BrainyInstancePool({ storage: 'filesystem', dataPath: './data', strategy: 'per-user' })
19
+ *
20
+ * app.get('/api/inventory', requireAuth, async (c) => {
21
+ * const user = c.get('user')!
22
+ * const brain = await pool.forUser(user.emailHash, 'main')
23
+ * const sdk = createSDK({ brain })
24
+ *
25
+ * const items = await sdk.brainy.find({ query: 'inventory items', type: 'Product' })
26
+ * const readme = await sdk.vfs.readFile('/projects/my-project/README.md')
27
+ * return c.json({ items })
28
+ * })
29
+ * ```
30
+ */
31
+ import { LocalTransport } from '../transports/local.js';
32
+ import { createBrainyProxy } from '../modules/brainy/proxy.js';
33
+ import { createEventsModule } from '../modules/events/index.js';
34
+ import { createAiModule } from '../modules/ai/index.js';
35
+ import { createSkillsModule } from '../modules/skills/index.js';
36
+ import { createCapabilityToken, verifyCapabilityToken } from '../modules/brainy/auth.js';
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+ // Factory
39
+ // ─────────────────────────────────────────────────────────────────────────────
40
+ /**
41
+ * Create a server-mode `SoulcraftSDK` wrapping a live Brainy instance.
42
+ *
43
+ * All `sdk.brainy.*` and `sdk.vfs.*` calls are dispatched in-process with zero
44
+ * serialization overhead via {@link LocalTransport}.
45
+ *
46
+ * `sdk.ai.*` calls go directly to the Anthropic API (requires `ANTHROPIC_API_KEY`).
47
+ * `sdk.skills.*` reads from the Brainy VFS and falls back to `@soulcraft/kits`.
48
+ * `sdk.events` is a local EventEmitter — events do not cross process boundaries.
49
+ *
50
+ * @param options - SDK creation options.
51
+ * @returns A fully assembled `SoulcraftSDK` instance.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const brain = await pool.forUser(user.emailHash, workspaceId)
56
+ * const sdk = createSDK({ brain })
57
+ *
58
+ * const results = await sdk.brainy.find({ query: 'candle inventory' })
59
+ * const readme = await sdk.vfs.readFile('/projects/README.md')
60
+ * await sdk.events.emit('kit:session-completed', { userId, sessionId, kitId })
61
+ * ```
62
+ */
63
+ export function createSDK(options) {
64
+ const { brain } = options;
65
+ const transport = new LocalTransport(brain);
66
+ const brainyProxy = createBrainyProxy(transport);
67
+ // VFS and versions are sub-APIs of Brainy, accessed via the same proxy.
68
+ // The proxy handles dot-separated paths: sdk.vfs.readFile → 'vfs.readFile'
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ const vfs = brainyProxy.vfs;
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ const versions = brainyProxy.versions;
73
+ const events = createEventsModule();
74
+ const ai = createAiModule();
75
+ const skills = createSkillsModule(brain);
76
+ const auth = {
77
+ verifyToken: (token, secret) => verifyCapabilityToken(token, secret),
78
+ createToken: (opts) => createCapabilityToken(opts),
79
+ };
80
+ return {
81
+ brainy: brainyProxy,
82
+ vfs,
83
+ versions,
84
+ auth,
85
+ events,
86
+ ai,
87
+ skills,
88
+ // Stubs for unimplemented modules — will throw if called
89
+ license: _unimplementedModule('license'),
90
+ kits: _unimplementedModule('kits'),
91
+ formats: _unimplementedModule('formats'),
92
+ billing: _unimplementedModule('billing'),
93
+ notifications: _unimplementedModule('notifications'),
94
+ async shutdown() {
95
+ await brain.flush();
96
+ },
97
+ async flush(scopeKey) {
98
+ // Local transport — the caller owns the brain, so we flush directly.
99
+ // The scopeKey is informational only; the brain is already resolved.
100
+ void scopeKey;
101
+ await brain.flush();
102
+ },
103
+ };
104
+ }
105
+ // ─────────────────────────────────────────────────────────────────────────────
106
+ // Internal helpers
107
+ // ─────────────────────────────────────────────────────────────────────────────
108
+ /**
109
+ * Creates a Proxy that throws a descriptive error for any property access
110
+ * on an unimplemented SDK module. This makes missing modules fail fast and
111
+ * informatively rather than silently returning undefined.
112
+ *
113
+ * @param moduleName - The module name shown in the error message.
114
+ * @returns A Proxy that throws on any property access.
115
+ */
116
+ function _unimplementedModule(moduleName) {
117
+ return new Proxy({}, {
118
+ get(_target, prop) {
119
+ throw new Error(`sdk.${moduleName}.${String(prop)} is not yet implemented. ` +
120
+ `The '${moduleName}' module is planned for a future SDK release. ` +
121
+ `See docs/ADR-001-sdk-design.md for the implementation roadmap.`);
122
+ },
123
+ });
124
+ }
125
+ //# sourceMappingURL=create-sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-sdk.js","sourceRoot":"","sources":["../../src/server/create-sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAA;AAsBxF,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,SAAS,CAAC,OAAyB;IACjD,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAA;IAEzB,MAAM,SAAS,GAAG,IAAI,cAAc,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,WAAW,GAAG,iBAAiB,CAAC,SAAS,CAAoB,CAAA;IAEnE,wEAAwE;IACxE,2EAA2E;IAC3E,8DAA8D;IAC9D,MAAM,GAAG,GAAI,WAAmB,CAAC,GAAgB,CAAA;IACjD,8DAA8D;IAC9D,MAAM,QAAQ,GAAI,WAAmB,CAAC,QAA0B,CAAA;IAEhE,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,MAAM,EAAE,GAAG,cAAc,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IAExC,MAAM,IAAI,GAAe;QACvB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC;QACpE,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC;KACnD,CAAA;IAED,OAAO;QACL,MAAM,EAAE,WAAW;QACnB,GAAG;QACH,QAAQ;QACR,IAAI;QACJ,MAAM;QACN,EAAE;QACF,MAAM;QAEN,yDAAyD;QACzD,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC;QACxC,IAAI,EAAE,oBAAoB,CAAC,MAAM,CAAC;QAClC,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC;QACxC,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC;QACxC,aAAa,EAAE,oBAAoB,CAAC,eAAe,CAAC;QAEpD,KAAK,CAAC,QAAQ;YACZ,MAAM,KAAK,CAAC,KAAK,EAAE,CAAA;QACrB,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,QAAgB;YAC1B,qEAAqE;YACrE,qEAAqE;YACrE,KAAK,QAAQ,CAAA;YACb,MAAM,KAAK,CAAC,KAAK,EAAE,CAAA;QACrB,CAAC;KACF,CAAA;AACH,CAAC;AAED,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF;;;;;;;GAOG;AACH,SAAS,oBAAoB,CAAC,UAAkB;IAC9C,OAAO,IAAI,KAAK,CAAC,EAA6B,EAAE;QAC9C,GAAG,CAAC,OAAO,EAAE,IAAqB;YAChC,MAAM,IAAI,KAAK,CACb,OAAO,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,2BAA2B;gBAC5D,QAAQ,UAAU,gDAAgD;gBAClE,gEAAgE,CACjE,CAAA;QACH,CAAC;KACF,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * @module server/hall-handlers
3
+ * @description Factory functions for mounting Hall WebRTC signaling over WebSocket.
4
+ *
5
+ * Hall uses a lightweight JSON signaling protocol over WebSocket:
6
+ *
7
+ * Client → Server:
8
+ * { type: 'offer', sdp: string } — SDP offer (first message from peer)
9
+ * { type: 'ice', candidate: string } — ICE candidate trickle
10
+ * { type: 'data', channel: string, data: number[] } — data channel message
11
+ * { type: 'leave' } — graceful peer disconnect
12
+ *
13
+ * Server → Client:
14
+ * { type: 'answer', sdp: string } — SDP answer
15
+ * { type: 'ice', candidate: string } — ICE candidate from SFU
16
+ * { type: 'speaker_changed', speakerId: string }
17
+ * { type: 'concept_mention', ...ConceptEvent }
18
+ * { type: 'relation_proposed', ...RelationProposedEvent }
19
+ * { type: 'transcript', peerId, text, isFinal, sessionMs }
20
+ * { type: 'attention', ...AttentionEvent }
21
+ * { type: 'track_added', peerId, trackId, kind }
22
+ * { type: 'track_removed', peerId, trackId }
23
+ * { type: 'error', message: string }
24
+ *
25
+ * Products mount a single WebSocket route (e.g. `GET /api/hall/rooms/:roomId/signal`)
26
+ * and delegate to the handler returned by createHallRoomHandler().
27
+ *
28
+ * @example Hono mounting
29
+ * ```typescript
30
+ * import { createHallRoomHandler } from '@soulcraft/sdk/server'
31
+ *
32
+ * const hallHandler = createHallRoomHandler({
33
+ * server: hallServer,
34
+ * authenticate: async (req) => getSessionUser(req),
35
+ * resolvePeerId: (req, user) => (user as { id: string }).id,
36
+ * resolveRoomId: (req) => new URL(req.url).pathname.split('/').at(-2)!,
37
+ * })
38
+ *
39
+ * app.get('/api/hall/rooms/:roomId/signal', upgradeWebSocket((c) => {
40
+ * const session = await hallHandler.handleUpgrade(c.req.raw)
41
+ * if (!session) return { onClose: () => {} }
42
+ * return {
43
+ * onMessage: (event, ws) => hallHandler.handleMessage(session, event.data as string, (m) => ws.send(m)),
44
+ * onClose: () => hallHandler.handleClose(session),
45
+ * }
46
+ * }))
47
+ * ```
48
+ */
49
+ import type { HallServer, Room } from '@soulcraft/hall';
50
+ /**
51
+ * @description An active peer connection managed by the Hall signaling handler.
52
+ * Returned by handleUpgrade() and passed to every subsequent call.
53
+ */
54
+ export interface HallSession {
55
+ /** The authenticated peer ID (e.g. user ID). */
56
+ readonly peerId: string;
57
+ /** The room this peer is joining. */
58
+ readonly room: Room;
59
+ /** Whether the peer has completed the SDP offer/answer handshake. */
60
+ joined: boolean;
61
+ }
62
+ /**
63
+ * @description Configuration for createHallRoomHandler().
64
+ */
65
+ export interface HallRoomHandlerConfig {
66
+ /**
67
+ * The running HallServer instance to use for room management.
68
+ */
69
+ server: HallServer;
70
+ /**
71
+ * Authenticates an incoming WebSocket upgrade request.
72
+ * Return the authenticated user object (any shape), or null/undefined to reject.
73
+ * @param request - The HTTP upgrade request.
74
+ * @returns Authenticated user or null/undefined.
75
+ */
76
+ authenticate(request: Request): Promise<unknown> | unknown;
77
+ /**
78
+ * Derives the peer ID from the request and the authenticated user.
79
+ * @param request - The HTTP upgrade request.
80
+ * @param user - The result of authenticate().
81
+ * @returns The peer ID string (e.g. user.id, session.userId).
82
+ */
83
+ resolvePeerId(request: Request, user: unknown): string;
84
+ /**
85
+ * Derives the room ID from the request.
86
+ * @param request - The HTTP upgrade request.
87
+ * @returns The room ID string (e.g. from URL path parameter).
88
+ */
89
+ resolveRoomId(request: Request): string;
90
+ /**
91
+ * Optional room options applied when the room does not already exist.
92
+ * @param request - The HTTP upgrade request.
93
+ * @param user - The authenticated user.
94
+ * @returns RoomOptions or undefined to use defaults.
95
+ */
96
+ roomOptions?(request: Request, user: unknown): import('@soulcraft/hall').RoomOptions | undefined;
97
+ }
98
+ /**
99
+ * @description The object returned by createHallRoomHandler(). Products pass incoming
100
+ * WebSocket connections through this handler for complete Hall signaling management.
101
+ */
102
+ export interface HallRoomHandler {
103
+ /**
104
+ * Authenticates the upgrade request and resolves the room/peer. Call this when
105
+ * the WebSocket connection is established.
106
+ * @param request - The HTTP upgrade request.
107
+ * @returns A HallSession if authentication succeeded, or null to reject.
108
+ */
109
+ handleUpgrade(request: Request): Promise<HallSession | null>;
110
+ /**
111
+ * Processes an incoming signaling message from a peer.
112
+ * @param session - The HallSession returned by handleUpgrade().
113
+ * @param data - Raw JSON string from the WebSocket message event.
114
+ * @param send - Callback to send a JSON string back to this peer's client.
115
+ */
116
+ handleMessage(session: HallSession, data: string, send: (message: string) => void): Promise<void>;
117
+ /**
118
+ * Cleans up when a peer disconnects. Always call this on WebSocket close/error.
119
+ * @param session - The HallSession to clean up.
120
+ */
121
+ handleClose(session: HallSession): Promise<void>;
122
+ }
123
+ /**
124
+ * @description TURN credential options for generateTurnCredentials().
125
+ */
126
+ export interface TurnCredentialOptions {
127
+ /** The HMAC-SHA1 secret shared between this server and the TURN server. */
128
+ secret: string;
129
+ /** Peer/user identifier embedded in the credential username. */
130
+ peerId: string;
131
+ /** Credential TTL in seconds (default: 86400 — 24 hours). */
132
+ ttlSeconds?: number;
133
+ }
134
+ /**
135
+ * @description Time-limited TURN credentials for a peer, suitable for passing to
136
+ * RTCPeerConnection as iceServers configuration.
137
+ */
138
+ export interface TurnCredentials {
139
+ /** Username in the form `{expiryTimestamp}:{peerId}`. */
140
+ username: string;
141
+ /** HMAC-SHA1 password derived from the shared secret and username. */
142
+ password: string;
143
+ /** Unix timestamp (seconds) when these credentials expire. */
144
+ expiresAt: number;
145
+ }
146
+ /**
147
+ * @description Generates time-limited TURN credentials using the TURN REST API
148
+ * authentication mechanism (RFC draft-uberti-behave-turn-rest-00).
149
+ *
150
+ * The TURN server must be configured with the same shared secret. Credentials
151
+ * expire after ttlSeconds and cannot be reused after that time.
152
+ *
153
+ * @param options - Secret, peer ID, and optional TTL.
154
+ * @returns Time-limited TURN credentials ready for RTCPeerConnection.iceServers.
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * const creds = generateTurnCredentials({
159
+ * secret: process.env.TURN_SECRET!,
160
+ * peerId: user.id,
161
+ * ttlSeconds: 3600,
162
+ * })
163
+ * // Pass to client:
164
+ * return c.json({ turnUsername: creds.username, turnPassword: creds.password })
165
+ * ```
166
+ */
167
+ export declare function generateTurnCredentials(options: TurnCredentialOptions): TurnCredentials;
168
+ /**
169
+ * @description Factory that creates a framework-agnostic Hall WebRTC signaling
170
+ * handler. Products mount the returned handler at a WebSocket route and delegate
171
+ * upgrade, message, and close lifecycle events to it.
172
+ *
173
+ * The handler owns the full signaling lifecycle:
174
+ * - Authenticates the upgrade request
175
+ * - Resolves or creates the Room via HallServer
176
+ * - Forwards SDP offer → gets SDP answer from the NAPI SFU
177
+ * - Forwards ICE candidates in both directions
178
+ * - Wires Room events (transcript, concept_mention, relation_proposed, etc.) to the peer's send callback
179
+ * - Cleans up on disconnect
180
+ *
181
+ * @param config - Handler configuration.
182
+ * @returns A HallRoomHandler with handleUpgrade / handleMessage / handleClose.
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const hallHandler = createHallRoomHandler({
187
+ * server: hallServer,
188
+ * authenticate: (req) => verifySession(req.headers.get('cookie') ?? ''),
189
+ * resolvePeerId: (_req, user) => (user as { id: string }).id,
190
+ * resolveRoomId: (req) => new URL(req.url).pathname.split('/').at(-2)!,
191
+ * })
192
+ * ```
193
+ */
194
+ export declare function createHallRoomHandler(config: HallRoomHandlerConfig): HallRoomHandler;
195
+ //# sourceMappingURL=hall-handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hall-handlers.d.ts","sourceRoot":"","sources":["../../src/server/hall-handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,IAAI,EAIL,MAAM,iBAAiB,CAAA;AAOxB;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,UAAU,CAAA;IAElB;;;;;OAKG;IACH,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAE1D;;;;;OAKG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAAA;IAEtD;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAA;IAEvC;;;;;OAKG;IACH,WAAW,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,iBAAiB,EAAE,WAAW,GAAG,SAAS,CAAA;CACjG;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAE5D;;;;;OAKG;IACH,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjG;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAA;IACd,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IACd,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAA;IAChB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAA;CAClB;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,eAAe,CAQvF;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAqJpF"}
@@ -0,0 +1,239 @@
1
+ /**
2
+ * @module server/hall-handlers
3
+ * @description Factory functions for mounting Hall WebRTC signaling over WebSocket.
4
+ *
5
+ * Hall uses a lightweight JSON signaling protocol over WebSocket:
6
+ *
7
+ * Client → Server:
8
+ * { type: 'offer', sdp: string } — SDP offer (first message from peer)
9
+ * { type: 'ice', candidate: string } — ICE candidate trickle
10
+ * { type: 'data', channel: string, data: number[] } — data channel message
11
+ * { type: 'leave' } — graceful peer disconnect
12
+ *
13
+ * Server → Client:
14
+ * { type: 'answer', sdp: string } — SDP answer
15
+ * { type: 'ice', candidate: string } — ICE candidate from SFU
16
+ * { type: 'speaker_changed', speakerId: string }
17
+ * { type: 'concept_mention', ...ConceptEvent }
18
+ * { type: 'relation_proposed', ...RelationProposedEvent }
19
+ * { type: 'transcript', peerId, text, isFinal, sessionMs }
20
+ * { type: 'attention', ...AttentionEvent }
21
+ * { type: 'track_added', peerId, trackId, kind }
22
+ * { type: 'track_removed', peerId, trackId }
23
+ * { type: 'error', message: string }
24
+ *
25
+ * Products mount a single WebSocket route (e.g. `GET /api/hall/rooms/:roomId/signal`)
26
+ * and delegate to the handler returned by createHallRoomHandler().
27
+ *
28
+ * @example Hono mounting
29
+ * ```typescript
30
+ * import { createHallRoomHandler } from '@soulcraft/sdk/server'
31
+ *
32
+ * const hallHandler = createHallRoomHandler({
33
+ * server: hallServer,
34
+ * authenticate: async (req) => getSessionUser(req),
35
+ * resolvePeerId: (req, user) => (user as { id: string }).id,
36
+ * resolveRoomId: (req) => new URL(req.url).pathname.split('/').at(-2)!,
37
+ * })
38
+ *
39
+ * app.get('/api/hall/rooms/:roomId/signal', upgradeWebSocket((c) => {
40
+ * const session = await hallHandler.handleUpgrade(c.req.raw)
41
+ * if (!session) return { onClose: () => {} }
42
+ * return {
43
+ * onMessage: (event, ws) => hallHandler.handleMessage(session, event.data as string, (m) => ws.send(m)),
44
+ * onClose: () => hallHandler.handleClose(session),
45
+ * }
46
+ * }))
47
+ * ```
48
+ */
49
+ import { createHmac } from 'crypto';
50
+ // ─────────────────────────────────────────────────────────────────────────────
51
+ // generateTurnCredentials
52
+ // ─────────────────────────────────────────────────────────────────────────────
53
+ /**
54
+ * @description Generates time-limited TURN credentials using the TURN REST API
55
+ * authentication mechanism (RFC draft-uberti-behave-turn-rest-00).
56
+ *
57
+ * The TURN server must be configured with the same shared secret. Credentials
58
+ * expire after ttlSeconds and cannot be reused after that time.
59
+ *
60
+ * @param options - Secret, peer ID, and optional TTL.
61
+ * @returns Time-limited TURN credentials ready for RTCPeerConnection.iceServers.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const creds = generateTurnCredentials({
66
+ * secret: process.env.TURN_SECRET!,
67
+ * peerId: user.id,
68
+ * ttlSeconds: 3600,
69
+ * })
70
+ * // Pass to client:
71
+ * return c.json({ turnUsername: creds.username, turnPassword: creds.password })
72
+ * ```
73
+ */
74
+ export function generateTurnCredentials(options) {
75
+ const ttl = options.ttlSeconds ?? 86_400;
76
+ const expiresAt = Math.floor(Date.now() / 1000) + ttl;
77
+ const username = `${expiresAt}:${options.peerId}`;
78
+ const password = createHmac('sha1', options.secret)
79
+ .update(username)
80
+ .digest('base64');
81
+ return { username, password, expiresAt };
82
+ }
83
+ // ─────────────────────────────────────────────────────────────────────────────
84
+ // createHallRoomHandler
85
+ // ─────────────────────────────────────────────────────────────────────────────
86
+ /**
87
+ * @description Factory that creates a framework-agnostic Hall WebRTC signaling
88
+ * handler. Products mount the returned handler at a WebSocket route and delegate
89
+ * upgrade, message, and close lifecycle events to it.
90
+ *
91
+ * The handler owns the full signaling lifecycle:
92
+ * - Authenticates the upgrade request
93
+ * - Resolves or creates the Room via HallServer
94
+ * - Forwards SDP offer → gets SDP answer from the NAPI SFU
95
+ * - Forwards ICE candidates in both directions
96
+ * - Wires Room events (transcript, concept_mention, relation_proposed, etc.) to the peer's send callback
97
+ * - Cleans up on disconnect
98
+ *
99
+ * @param config - Handler configuration.
100
+ * @returns A HallRoomHandler with handleUpgrade / handleMessage / handleClose.
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const hallHandler = createHallRoomHandler({
105
+ * server: hallServer,
106
+ * authenticate: (req) => verifySession(req.headers.get('cookie') ?? ''),
107
+ * resolvePeerId: (_req, user) => (user as { id: string }).id,
108
+ * resolveRoomId: (req) => new URL(req.url).pathname.split('/').at(-2)!,
109
+ * })
110
+ * ```
111
+ */
112
+ export function createHallRoomHandler(config) {
113
+ // Keyed by peerId — stores the per-peer send callback so room events can push to it.
114
+ const peerSenders = new Map();
115
+ // Room-level event listeners are registered once per room and fanned out to all
116
+ // connected peers via peerSenders. We track which rooms have been wired to avoid
117
+ // double-binding when multiple peers join the same room.
118
+ const wiredRooms = new Set();
119
+ function wireRoomEvents(room) {
120
+ if (wiredRooms.has(room.roomId))
121
+ return;
122
+ wiredRooms.add(room.roomId);
123
+ room.on('speaker_changed', (speakerId) => {
124
+ broadcast(JSON.stringify({ type: 'speaker_changed', speakerId }));
125
+ });
126
+ room.on('concept_mention', (event) => {
127
+ const { type: _t, ...rest } = event;
128
+ broadcast(JSON.stringify({ type: 'concept_mention', ...rest }));
129
+ });
130
+ room.on('relation_proposed', (event) => {
131
+ // Broadcast relation proposals to all peers so the product's client-side
132
+ // instructor UI can queue them for review.
133
+ const { type: _t, ...rest } = event;
134
+ broadcast(JSON.stringify({ type: 'relation_proposed', ...rest }));
135
+ });
136
+ room.on('attention', (event) => {
137
+ // Attention events are self-reported by peers — broadcast to all and let
138
+ // the product filter by role at the application layer.
139
+ const { type: _t, ...rest } = event;
140
+ broadcast(JSON.stringify({ type: 'attention', ...rest }));
141
+ });
142
+ room.on('track_added', (peerId, trackId, kind) => {
143
+ broadcast(JSON.stringify({ type: 'track_added', peerId, trackId, kind }));
144
+ });
145
+ room.on('track_removed', (peerId, trackId) => {
146
+ broadcast(JSON.stringify({ type: 'track_removed', peerId, trackId }));
147
+ });
148
+ room.on('transcript', (peerId, text, isFinal, sessionMs) => {
149
+ broadcast(JSON.stringify({ type: 'transcript', peerId, text, isFinal, sessionMs }));
150
+ });
151
+ }
152
+ function broadcast(message) {
153
+ for (const send of peerSenders.values()) {
154
+ send(message);
155
+ }
156
+ }
157
+ return {
158
+ async handleUpgrade(request) {
159
+ const user = await config.authenticate(request);
160
+ if (!user)
161
+ return null;
162
+ const roomId = config.resolveRoomId(request);
163
+ const peerId = config.resolvePeerId(request, user);
164
+ const options = config.roomOptions?.(request, user) ?? {};
165
+ const existing = await config.server.getRoom(roomId);
166
+ const room = existing !== null ? existing : await config.server.createRoom(roomId, options);
167
+ wireRoomEvents(room);
168
+ return { peerId, room, joined: false };
169
+ },
170
+ async handleMessage(session, data, send) {
171
+ // Register this peer's send callback so room events reach them.
172
+ peerSenders.set(session.peerId, send);
173
+ let msg;
174
+ try {
175
+ msg = JSON.parse(data);
176
+ }
177
+ catch {
178
+ send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }));
179
+ return;
180
+ }
181
+ const { type } = msg;
182
+ if (type === 'offer') {
183
+ const sdpOffer = msg['sdp'];
184
+ if (typeof sdpOffer !== 'string') {
185
+ send(JSON.stringify({ type: 'error', message: 'Missing sdp in offer' }));
186
+ return;
187
+ }
188
+ try {
189
+ const sdpAnswer = await session.room.addPeer(session.peerId, sdpOffer);
190
+ session.joined = true;
191
+ send(JSON.stringify({ type: 'answer', sdp: sdpAnswer }));
192
+ }
193
+ catch (err) {
194
+ send(JSON.stringify({ type: 'error', message: String(err) }));
195
+ }
196
+ return;
197
+ }
198
+ if (type === 'ice') {
199
+ const candidate = msg['candidate'];
200
+ if (typeof candidate !== 'string') {
201
+ send(JSON.stringify({ type: 'error', message: 'Missing candidate in ice message' }));
202
+ return;
203
+ }
204
+ try {
205
+ await session.room.addIceCandidate(session.peerId, candidate);
206
+ }
207
+ catch (err) {
208
+ send(JSON.stringify({ type: 'error', message: String(err) }));
209
+ }
210
+ return;
211
+ }
212
+ if (type === 'data') {
213
+ const channel = msg['channel'];
214
+ const rawData = msg['data'];
215
+ if (typeof channel !== 'string' || !Array.isArray(rawData)) {
216
+ send(JSON.stringify({ type: 'error', message: 'Invalid data channel message' }));
217
+ return;
218
+ }
219
+ session.room.sendData(channel, Buffer.from(rawData));
220
+ return;
221
+ }
222
+ if (type === 'leave') {
223
+ await session.room.removePeer(session.peerId).catch(() => undefined);
224
+ peerSenders.delete(session.peerId);
225
+ session.joined = false;
226
+ return;
227
+ }
228
+ send(JSON.stringify({ type: 'error', message: `Unknown message type: ${String(type)}` }));
229
+ },
230
+ async handleClose(session) {
231
+ peerSenders.delete(session.peerId);
232
+ if (session.joined) {
233
+ await session.room.removePeer(session.peerId).catch(() => undefined);
234
+ session.joined = false;
235
+ }
236
+ },
237
+ };
238
+ }
239
+ //# sourceMappingURL=hall-handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hall-handlers.js","sourceRoot":"","sources":["../../src/server/hall-handlers.ts"],"names":[],"mappings":"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"}