@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.
- package/dist/client/index.d.ts +62 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +60 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/ai/index.d.ts +55 -0
- package/dist/modules/ai/index.d.ts.map +1 -0
- package/dist/modules/ai/index.js +263 -0
- package/dist/modules/ai/index.js.map +1 -0
- package/dist/modules/ai/types.d.ts +216 -0
- package/dist/modules/ai/types.d.ts.map +1 -0
- package/dist/modules/ai/types.js +30 -0
- package/dist/modules/ai/types.js.map +1 -0
- package/dist/modules/auth/backchannel.d.ts +85 -0
- package/dist/modules/auth/backchannel.d.ts.map +1 -0
- package/dist/modules/auth/backchannel.js +168 -0
- package/dist/modules/auth/backchannel.js.map +1 -0
- package/dist/modules/auth/config.d.ts +122 -0
- package/dist/modules/auth/config.d.ts.map +1 -0
- package/dist/modules/auth/config.js +158 -0
- package/dist/modules/auth/config.js.map +1 -0
- package/dist/modules/auth/middleware.d.ts +146 -0
- package/dist/modules/auth/middleware.d.ts.map +1 -0
- package/dist/modules/auth/middleware.js +204 -0
- package/dist/modules/auth/middleware.js.map +1 -0
- package/dist/modules/auth/types.d.ts +162 -0
- package/dist/modules/auth/types.d.ts.map +1 -0
- package/dist/modules/auth/types.js +14 -0
- package/dist/modules/auth/types.js.map +1 -0
- package/dist/modules/billing/types.d.ts +7 -0
- package/dist/modules/billing/types.d.ts.map +1 -0
- package/dist/modules/billing/types.js +7 -0
- package/dist/modules/billing/types.js.map +1 -0
- package/dist/modules/brainy/auth.d.ts +104 -0
- package/dist/modules/brainy/auth.d.ts.map +1 -0
- package/dist/modules/brainy/auth.js +144 -0
- package/dist/modules/brainy/auth.js.map +1 -0
- package/dist/modules/brainy/errors.d.ts +118 -0
- package/dist/modules/brainy/errors.d.ts.map +1 -0
- package/dist/modules/brainy/errors.js +142 -0
- package/dist/modules/brainy/errors.js.map +1 -0
- package/dist/modules/brainy/events.d.ts +63 -0
- package/dist/modules/brainy/events.d.ts.map +1 -0
- package/dist/modules/brainy/events.js +14 -0
- package/dist/modules/brainy/events.js.map +1 -0
- package/dist/modules/brainy/proxy.d.ts +48 -0
- package/dist/modules/brainy/proxy.d.ts.map +1 -0
- package/dist/modules/brainy/proxy.js +95 -0
- package/dist/modules/brainy/proxy.js.map +1 -0
- package/dist/modules/brainy/types.d.ts +83 -0
- package/dist/modules/brainy/types.d.ts.map +1 -0
- package/dist/modules/brainy/types.js +21 -0
- package/dist/modules/brainy/types.js.map +1 -0
- package/dist/modules/events/index.d.ts +41 -0
- package/dist/modules/events/index.d.ts.map +1 -0
- package/dist/modules/events/index.js +53 -0
- package/dist/modules/events/index.js.map +1 -0
- package/dist/modules/events/types.d.ts +129 -0
- package/dist/modules/events/types.d.ts.map +1 -0
- package/dist/modules/events/types.js +32 -0
- package/dist/modules/events/types.js.map +1 -0
- package/dist/modules/formats/types.d.ts +7 -0
- package/dist/modules/formats/types.d.ts.map +1 -0
- package/dist/modules/formats/types.js +7 -0
- package/dist/modules/formats/types.js.map +1 -0
- package/dist/modules/hall/types.d.ts +56 -0
- package/dist/modules/hall/types.d.ts.map +1 -0
- package/dist/modules/hall/types.js +16 -0
- package/dist/modules/hall/types.js.map +1 -0
- package/dist/modules/kits/types.d.ts +7 -0
- package/dist/modules/kits/types.d.ts.map +1 -0
- package/dist/modules/kits/types.js +7 -0
- package/dist/modules/kits/types.js.map +1 -0
- package/dist/modules/license/types.d.ts +7 -0
- package/dist/modules/license/types.d.ts.map +1 -0
- package/dist/modules/license/types.js +7 -0
- package/dist/modules/license/types.js.map +1 -0
- package/dist/modules/notifications/types.d.ts +7 -0
- package/dist/modules/notifications/types.d.ts.map +1 -0
- package/dist/modules/notifications/types.js +7 -0
- package/dist/modules/notifications/types.js.map +1 -0
- package/dist/modules/skills/index.d.ts +60 -0
- package/dist/modules/skills/index.d.ts.map +1 -0
- package/dist/modules/skills/index.js +253 -0
- package/dist/modules/skills/index.js.map +1 -0
- package/dist/modules/skills/types.d.ts +127 -0
- package/dist/modules/skills/types.d.ts.map +1 -0
- package/dist/modules/skills/types.js +23 -0
- package/dist/modules/skills/types.js.map +1 -0
- package/dist/modules/versions/types.d.ts +31 -0
- package/dist/modules/versions/types.d.ts.map +1 -0
- package/dist/modules/versions/types.js +9 -0
- package/dist/modules/versions/types.js.map +1 -0
- package/dist/modules/vfs/types.d.ts +26 -0
- package/dist/modules/vfs/types.d.ts.map +1 -0
- package/dist/modules/vfs/types.js +11 -0
- package/dist/modules/vfs/types.js.map +1 -0
- package/dist/server/create-sdk.d.ts +70 -0
- package/dist/server/create-sdk.d.ts.map +1 -0
- package/dist/server/create-sdk.js +125 -0
- package/dist/server/create-sdk.js.map +1 -0
- package/dist/server/hall-handlers.d.ts +195 -0
- package/dist/server/hall-handlers.d.ts.map +1 -0
- package/dist/server/hall-handlers.js +239 -0
- package/dist/server/hall-handlers.js.map +1 -0
- package/dist/server/handlers.d.ts +216 -0
- package/dist/server/handlers.d.ts.map +1 -0
- package/dist/server/handlers.js +214 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/index.d.ts +52 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +50 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/instance-pool.d.ts +299 -0
- package/dist/server/instance-pool.d.ts.map +1 -0
- package/dist/server/instance-pool.js +359 -0
- package/dist/server/instance-pool.js.map +1 -0
- package/dist/transports/http.d.ts +86 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +134 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/local.d.ts +76 -0
- package/dist/transports/local.d.ts.map +1 -0
- package/dist/transports/local.js +101 -0
- package/dist/transports/local.js.map +1 -0
- package/dist/transports/sse.d.ts +99 -0
- package/dist/transports/sse.d.ts.map +1 -0
- package/dist/transports/sse.js +192 -0
- package/dist/transports/sse.js.map +1 -0
- package/dist/transports/transport.d.ts +68 -0
- package/dist/transports/transport.d.ts.map +1 -0
- package/dist/transports/transport.js +14 -0
- package/dist/transports/transport.js.map +1 -0
- package/dist/transports/ws.d.ts +135 -0
- package/dist/transports/ws.d.ts.map +1 -0
- package/dist/transports/ws.js +331 -0
- package/dist/transports/ws.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/docs/ADR-001-sdk-design.md +282 -0
- package/docs/IMPLEMENTATION-PLAN.md +708 -0
- package/docs/USAGE.md +646 -0
- package/docs/kit-sdk-guide.md +474 -0
- 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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"}
|