api-ape 2.1.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +184 -195
  2. package/client/README.md +37 -30
  3. package/client/browser.js +4 -14
  4. package/client/connectSocket.js +167 -42
  5. package/client/index.js +171 -0
  6. package/client/transports/streaming.js +3 -16
  7. package/dist/ape.js +2 -1049
  8. package/dist/ape.js.map +7 -0
  9. package/dist/api-ape.min.js +2 -0
  10. package/dist/api-ape.min.js.map +7 -0
  11. package/index.d.ts +67 -23
  12. package/package.json +27 -8
  13. package/server/README.md +52 -11
  14. package/server/lib/broadcast.js +25 -8
  15. package/server/lib/bun.js +122 -0
  16. package/server/lib/longPolling.js +28 -23
  17. package/server/lib/main.js +372 -46
  18. package/server/lib/wiring.js +19 -12
  19. package/server/lib/ws/adapters/bun.js +225 -0
  20. package/server/lib/ws/adapters/deno.js +186 -0
  21. package/server/lib/ws/frames.js +217 -0
  22. package/server/lib/ws/index.js +15 -0
  23. package/server/lib/ws/server.js +109 -0
  24. package/server/lib/ws/socket.js +222 -0
  25. package/server/lib/wsProvider.js +135 -0
  26. package/server/socket/receive.js +14 -1
  27. package/server/socket/send.js +6 -6
  28. package/server/utils/parseUserAgent.js +286 -0
  29. package/example/Bun/README.md +0 -74
  30. package/example/Bun/api/message.ts +0 -11
  31. package/example/Bun/index.html +0 -76
  32. package/example/Bun/package.json +0 -9
  33. package/example/Bun/server.ts +0 -59
  34. package/example/Bun/styles.css +0 -128
  35. package/example/ExpressJs/README.md +0 -95
  36. package/example/ExpressJs/api/message.js +0 -11
  37. package/example/ExpressJs/backend.js +0 -39
  38. package/example/ExpressJs/index.html +0 -88
  39. package/example/ExpressJs/package-lock.json +0 -834
  40. package/example/ExpressJs/package.json +0 -10
  41. package/example/ExpressJs/styles.css +0 -128
  42. package/example/NextJs/.dockerignore +0 -29
  43. package/example/NextJs/Dockerfile +0 -52
  44. package/example/NextJs/Dockerfile.dev +0 -27
  45. package/example/NextJs/README.md +0 -113
  46. package/example/NextJs/ape/client.js +0 -66
  47. package/example/NextJs/ape/embed.js +0 -12
  48. package/example/NextJs/ape/index.js +0 -23
  49. package/example/NextJs/ape/logic/chat.js +0 -62
  50. package/example/NextJs/ape/onConnect.js +0 -69
  51. package/example/NextJs/ape/onDisconnect.js +0 -13
  52. package/example/NextJs/ape/onError.js +0 -9
  53. package/example/NextJs/ape/onReceive.js +0 -15
  54. package/example/NextJs/ape/onSend.js +0 -15
  55. package/example/NextJs/api/message.js +0 -44
  56. package/example/NextJs/docker-compose.yml +0 -22
  57. package/example/NextJs/next-env.d.ts +0 -5
  58. package/example/NextJs/next.config.js +0 -8
  59. package/example/NextJs/package-lock.json +0 -6400
  60. package/example/NextJs/package.json +0 -24
  61. package/example/NextJs/pages/Info.tsx +0 -153
  62. package/example/NextJs/pages/_app.tsx +0 -6
  63. package/example/NextJs/pages/index.tsx +0 -275
  64. package/example/NextJs/public/favicon.ico +0 -0
  65. package/example/NextJs/public/vercel.svg +0 -4
  66. package/example/NextJs/server.js +0 -36
  67. package/example/NextJs/styles/Chat.module.css +0 -448
  68. package/example/NextJs/styles/Home.module.css +0 -129
  69. package/example/NextJs/styles/globals.css +0 -26
  70. package/example/NextJs/tsconfig.json +0 -20
  71. package/example/README.md +0 -117
  72. package/example/Vite/README.md +0 -68
  73. package/example/Vite/ape/client.ts +0 -66
  74. package/example/Vite/ape/onConnect.ts +0 -52
  75. package/example/Vite/api/message.ts +0 -57
  76. package/example/Vite/index.html +0 -16
  77. package/example/Vite/package.json +0 -19
  78. package/example/Vite/server.ts +0 -62
  79. package/example/Vite/src/App.vue +0 -170
  80. package/example/Vite/src/components/Info.vue +0 -352
  81. package/example/Vite/src/main.ts +0 -5
  82. package/example/Vite/src/style.css +0 -200
  83. package/example/Vite/src/vite-env.d.ts +0 -7
  84. package/example/Vite/vite.config.ts +0 -20
  85. package/todo.md +0 -85
  86. package/utils/jss.test.js +0 -261
  87. package/utils/messageHash.test.js +0 -56
package/package.json CHANGED
@@ -1,17 +1,39 @@
1
1
  {
2
2
  "name": "api-ape",
3
- "version": "2.1.0",
3
+ "version": "2.2.2",
4
4
  "description": "Remote Procedure Events (RPE) - A lightweight WebSocket framework for building real-time APIs. Call server functions from the browser like local methods with automatic reconnection, HTTP streaming fallback, and extended JSON encoding.",
5
5
  "main": "index.js",
6
+ "browser": "./client/index.js",
6
7
  "types": "index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "browser": "./client/index.js",
11
+ "default": "./index.js"
12
+ },
13
+ "./client": "./client/index.js",
14
+ "./client/*": "./client/*",
15
+ "./server": "./server/index.js",
16
+ "./server/*": "./server/*"
17
+ },
18
+ "files": [
19
+ "dist/",
20
+ "client/",
21
+ "server/",
22
+ "utils/",
23
+ "index.js",
24
+ "index.d.ts",
25
+ "!**/*.test.js"
26
+ ],
7
27
  "scripts": {
28
+ "prepare": "cp .hooks/* .git/hooks/ 2>/dev/null && chmod +x .git/hooks/* || true",
8
29
  "test": "jest --no-cache",
9
30
  "test:watch": "jest --watch --runInBand",
10
31
  "test:cover": "jest --coverage --no-cache --detectOpenHandles",
11
32
  "demo:express": "cd example/ExpressJs && npm install && npm start",
12
33
  "demo:nextjs": "cd example/NextJs && docker-compose up --build",
13
34
  "demo:vite": "cd example/Vite && npm install && npm run dev",
14
- "demo:bun": "cd example/Bun && npm install && npm start"
35
+ "demo:bun": "cd example/Bun && npm install && npm start",
36
+ "publish": "bash scripts/publish.sh"
15
37
  },
16
38
  "repository": {
17
39
  "type": "git",
@@ -20,7 +42,7 @@
20
42
  "keywords": [
21
43
  "websocket",
22
44
  "realtime",
23
- "rpc",
45
+ "CSRF",
24
46
  "api",
25
47
  "remote-procedure-call",
26
48
  "real-time",
@@ -34,7 +56,7 @@
34
56
  "jjs",
35
57
  "rpe",
36
58
  "nodejs",
37
- "framework"
59
+ "library"
38
60
  ],
39
61
  "author": "brian shannon",
40
62
  "license": "MIT",
@@ -42,10 +64,7 @@
42
64
  "url": "https://github.com/codemeasandwich/api-ape/issues"
43
65
  },
44
66
  "homepage": "https://github.com/codemeasandwich/api-ape#readme",
45
- "dependencies": {
46
- "ua-parser-js": "^1.0.37",
47
- "ws": "^8.14.0"
48
- },
67
+ "dependencies": {},
49
68
  "devDependencies": {
50
69
  "esbuild": "^0.27.2",
51
70
  "jest": "^29.3.1"
package/server/README.md CHANGED
@@ -13,7 +13,16 @@ server/
13
13
  │ ├── broadcast.js # Client tracking & broadcast utilities
14
14
  │ ├── fileTransfer.js # Binary file transfer manager
15
15
  │ ├── longPolling.js # HTTP streaming fallback handler
16
- └── wiring.js # WebSocket handler setup
16
+ ├── wiring.js # WebSocket handler setup
17
+ │ ├── wsProvider.js # Runtime detection (Node 24+ native / polyfill)
18
+ │ └── ws/ # RFC 6455 WebSocket polyfill (zero dependencies)
19
+ │ ├── index.js # Module entry point
20
+ │ ├── frames.js # Frame encoding/decoding
21
+ │ ├── socket.js # WebSocket connection class
22
+ │ ├── server.js # WebSocketServer class
23
+ │ └── adapters/ # Runtime-specific adapters
24
+ │ ├── bun.js # Bun native WebSocket
25
+ │ └── deno.js # Deno native WebSocket
17
26
  ├── socket/
18
27
  │ ├── receive.js # Incoming message handler
19
28
  │ └── send.js # Outgoing message handler
@@ -76,8 +85,9 @@ ape(app, {
76
85
  | `this.broadcast(type, data)` | Send to ALL connected clients |
77
86
  | `this.broadcastOthers(type, data)` | Send to all EXCEPT the caller |
78
87
  | `this.online()` | Get count of connected clients |
79
- | `this.getClients()` | Get array of connected hostIds |
80
- | `this.hostId` | Unique ID of the calling client |
88
+ | `this.getClients()` | Get array of connected clientIds |
89
+ | `this.clientId` | Unique ID of the calling client (generated by api-ape) |
90
+ | `this.sessionId` | Session ID from cookie (set by outer framework, may be `null`) |
81
91
  | `this.req` | Original HTTP request |
82
92
  | `this.socket` | WebSocket instance |
83
93
  | `this.agent` | Parsed user-agent |
@@ -102,15 +112,15 @@ Drop JS files in your `where` directory:
102
112
 
103
113
  ```
104
114
  api/
105
- ├── hello.js → ape.hello(data)
106
- ├── users.js → ape.users(data)
115
+ ├── hello.js → api.hello(data)
116
+ ├── users.js → api.users(data)
107
117
  ├── posts/
108
- │ ├── index.js → ape.posts(data) # index.js maps to parent folder
109
- │ ├── list.js → ape.posts.list(data)
110
- │ └── create.js → ape.posts.create(data)
118
+ │ ├── index.js → api.posts(data) # index.js maps to parent folder
119
+ │ ├── list.js → api.posts.list(data)
120
+ │ └── create.js → api.posts.create(data)
111
121
  ```
112
122
 
113
- **Note**: Both `api/users.js` and `api/users/index.js` map to the same endpoint `ape.users(data)`. Use `index.js` when you want to group related files in a folder.
123
+ **Note**: Both `api/users.js` and `api/users/index.js` map to the same endpoint `api.users(data)`. Use `index.js` when you want to group related files in a folder.
114
124
 
115
125
  **⚠️ Duplicate Detection**: If both files exist, api-ape will throw an error on startup:
116
126
  ```
@@ -159,7 +169,7 @@ api-ape automatically provides HTTP streaming endpoints as a fallback when WebSo
159
169
 
160
170
  Long-lived HTTP streaming connection for receiving server messages.
161
171
 
162
- - **Session**: Cookie-based (`apeHostId`)
172
+ - **Session**: Cookie-based (`apeClientId`)
163
173
  - **Response**: Streaming JSON messages
164
174
  - **Heartbeat**: Every 20 seconds
165
175
  - **Auto-reconnect**: Client reconnects after 25 seconds
@@ -168,7 +178,7 @@ Long-lived HTTP streaming connection for receiving server messages.
168
178
 
169
179
  Send messages to server when using HTTP streaming transport.
170
180
 
171
- - **Session**: Cookie-based (`apeHostId`)
181
+ - **Session**: Cookie-based (`apeClientId`)
172
182
  - **Body**: JJS-encoded message
173
183
  - **Response**: JJS-encoded result
174
184
 
@@ -180,3 +190,34 @@ Send messages to server when using HTTP streaming transport.
180
190
  4. Automatically upgrades back to WebSocket when available
181
191
 
182
192
  The fallback is **completely transparent** to your controllers - they work identically with both transports.
193
+
194
+ ---
195
+
196
+ ## Zero-Dependency WebSocket
197
+
198
+ api-ape includes its own RFC 6455 WebSocket implementation with **zero npm dependencies**.
199
+
200
+ ### Runtime Detection
201
+
202
+ The server automatically detects and uses the best available WebSocket implementation:
203
+
204
+ 1. **Deno**: Uses native `Deno.upgradeWebSocket()` API
205
+ 2. **Bun**: Uses native `Bun.serve()` WebSocket handlers
206
+ 3. **Node.js 24+** (stable): Uses native `node:ws` module
207
+ 4. **Earlier Node.js**: Uses built-in RFC 6455 polyfill
208
+
209
+ ```javascript
210
+ // Automatic - no configuration needed
211
+ ape(server, { where: 'api' })
212
+ ```
213
+
214
+ ### Polyfill Features
215
+
216
+ The built-in polyfill implements:
217
+
218
+ - Full RFC 6455 handshake (SHA-1 + GUID)
219
+ - Text and binary frames
220
+ - Frame fragmentation
221
+ - Ping/pong heartbeats
222
+ - Proper close handshake
223
+ - Masking (client→server)
@@ -11,31 +11,48 @@ const connectedClients = new Set()
11
11
  */
12
12
  function addClient(clientInfo) {
13
13
  connectedClients.add(clientInfo)
14
+ console.log(`🟢 Client added: ${clientInfo.clientId} (total: ${connectedClients.size})`)
14
15
  }
15
16
 
16
17
  /**
17
18
  * Remove a client from the connected set
19
+ * Accepts either the client object or { clientId } for lookup
18
20
  */
19
21
  function removeClient(clientInfo) {
20
- connectedClients.delete(clientInfo)
22
+ const sizeBefore = connectedClients.size
23
+ // If exact reference found, delete it
24
+ if (connectedClients.has(clientInfo)) {
25
+ connectedClients.delete(clientInfo)
26
+ console.log(`🔴 Client removed (ref): ${clientInfo.clientId} (total: ${connectedClients.size})`)
27
+ return
28
+ }
29
+ // Otherwise search by clientId (needed for long polling cleanup)
30
+ for (const client of connectedClients) {
31
+ if (client.clientId === clientInfo.clientId) {
32
+ connectedClients.delete(client)
33
+ console.log(`🔴 Client removed (lookup): ${clientInfo.clientId} (total: ${connectedClients.size})`)
34
+ return
35
+ }
36
+ }
37
+ console.log(`⚠️ Client not found for removal: ${clientInfo.clientId} (total: ${connectedClients.size})`)
21
38
  }
22
39
 
23
40
  /**
24
41
  * Broadcast to all connected clients
25
42
  * @param {string} type - Message type
26
43
  * @param {any} data - Data to send
27
- * @param {string} [excludeHostId] - Optional hostId to exclude (e.g., sender)
44
+ * @param {string} [excludeClientId] - Optional clientId to exclude (e.g., sender)
28
45
  */
29
- function broadcast(type, data, excludeHostId) {
30
- console.log(`📢 Broadcasting "${type}" to ${connectedClients.size} clients`, excludeHostId ? `(excluding ${excludeHostId})` : '')
46
+ function broadcast(type, data, excludeClientId) {
47
+ console.log(`📢 Broadcasting "${type}" to ${connectedClients.size} clients`, excludeClientId ? `(excluding ${excludeClientId})` : '')
31
48
  connectedClients.forEach(client => {
32
- if (excludeHostId && client.hostId === excludeHostId) {
49
+ if (excludeClientId && client.clientId === excludeClientId) {
33
50
  return // Skip excluded client
34
51
  }
35
52
  try {
36
53
  client.send(false, type, data, false)
37
54
  } catch (e) {
38
- console.error(`📢 Broadcast failed to ${client.hostId}:`, e.message)
55
+ console.error(`📢 Broadcast failed to ${client.clientId}:`, e.message)
39
56
  }
40
57
  })
41
58
  }
@@ -48,10 +65,10 @@ function online() {
48
65
  }
49
66
 
50
67
  /**
51
- * Get all connected client hostIds
68
+ * Get all connected client clientIds
52
69
  */
53
70
  function getClients() {
54
- return Array.from(connectedClients).map(c => c.hostId)
71
+ return Array.from(connectedClients).map(c => c.clientId)
55
72
  }
56
73
 
57
74
  module.exports = {
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Bun-specific api-ape integration
3
+ * Returns handlers for use with Bun.serve()
4
+ *
5
+ * Usage:
6
+ * ```ts
7
+ * import { apeBun } from 'api-ape/bun'
8
+ *
9
+ * const ape = apeBun({ where: 'api', onConnent: ... })
10
+ *
11
+ * Bun.serve({
12
+ * port: 3000,
13
+ * fetch: ape.fetch,
14
+ * websocket: ape.websocket
15
+ * })
16
+ * ```
17
+ */
18
+
19
+ const loader = require('./loader')
20
+ const wiring = require('./wiring')
21
+ const path = require('path')
22
+ const fs = require('fs')
23
+ const { getFileTransferManager } = require('./fileTransfer')
24
+ const { BunWebSocket, BunWebSocketServer } = require('./ws/adapters/bun')
25
+
26
+ /**
27
+ * Create api-ape handlers for Bun.serve()
28
+ * @param {{ where: string, onConnent?: Function, fileTransferOptions?: Object }} options
29
+ */
30
+ function apeBun({ where, onConnent, fileTransferOptions }) {
31
+ const controllers = loader(where)
32
+ const fileTransfer = getFileTransferManager(fileTransferOptions)
33
+ const wss = new BunWebSocketServer({ noServer: true })
34
+
35
+ const wsPath = `/${where}/ape`
36
+ const wiringHandler = wiring(controllers, onConnent, fileTransfer)
37
+
38
+ // Handle connections
39
+ wss.on('connection', wiringHandler)
40
+
41
+ /**
42
+ * Bun fetch handler - handles HTTP requests and WebSocket upgrades
43
+ */
44
+ function fetch(req, server) {
45
+ const url = new URL(req.url)
46
+ const pathname = url.pathname
47
+
48
+ // WebSocket upgrade
49
+ if (pathname === wsPath) {
50
+ const upgrade = req.headers.get('upgrade')
51
+ if (upgrade?.toLowerCase() === 'websocket') {
52
+ // Use Bun's native upgrade
53
+ const success = server.upgrade(req, {
54
+ data: { req }
55
+ })
56
+ if (success) {
57
+ return undefined // Bun handles the response
58
+ }
59
+ return new Response('WebSocket upgrade failed', { status: 500 })
60
+ }
61
+ }
62
+
63
+ // Serve client bundle
64
+ if (pathname === `/${where}/ape.js`) {
65
+ try {
66
+ const filePath = path.join(__dirname, '../../dist/ape.js')
67
+ const data = fs.readFileSync(filePath)
68
+ return new Response(data, {
69
+ headers: { 'Content-Type': 'application/javascript' }
70
+ })
71
+ } catch {
72
+ return new Response('Client bundle not found', { status: 500 })
73
+ }
74
+ }
75
+
76
+ // Not an api-ape route
77
+ return null
78
+ }
79
+
80
+ /**
81
+ * Bun websocket handlers
82
+ */
83
+ const websocket = {
84
+ open(ws) {
85
+ const wrapper = new BunWebSocket(ws)
86
+ wss._clients.set(ws, wrapper)
87
+
88
+ const { req } = ws.data || {}
89
+ wiringHandler(wrapper, req)
90
+ },
91
+
92
+ message(ws, message) {
93
+ const wrapper = wss._clients.get(ws)
94
+ if (wrapper) {
95
+ wrapper._onMessage(message)
96
+ }
97
+ },
98
+
99
+ close(ws, code, reason) {
100
+ const wrapper = wss._clients.get(ws)
101
+ if (wrapper) {
102
+ wrapper._onClose(code, reason)
103
+ wss._clients.delete(ws)
104
+ }
105
+ },
106
+
107
+ error(ws, error) {
108
+ const wrapper = wss._clients.get(ws)
109
+ if (wrapper) {
110
+ wrapper._onError(error)
111
+ }
112
+ }
113
+ }
114
+
115
+ return {
116
+ fetch,
117
+ websocket,
118
+ wss
119
+ }
120
+ }
121
+
122
+ module.exports = { apeBun }
@@ -2,35 +2,35 @@ const { addClient, removeClient, broadcast } = require('./broadcast')
2
2
  const makeid = require('../utils/genId')
3
3
  const jss = require('../../utils/jss')
4
4
 
5
- // Active streaming connections: hostId -> { res, messageQueue, heartbeatTimer }
5
+ // Active streaming connections: clientId -> { res, messageQueue, heartbeatTimer }
6
6
  const streamClients = new Map()
7
7
 
8
8
  // Pending message handlers for POST requests: queryId -> { resolve, reject, timer }
9
9
  const pendingRequests = new Map()
10
10
 
11
11
  /**
12
- * Set apeHostId cookie if not present
12
+ * Set apeClientId cookie if not present
13
13
  */
14
- function ensureHostId(req, res) {
14
+ function ensureClientId(req, res) {
15
15
  const cookies = req.headers.cookie || ''
16
- const match = cookies.match(/(?:^|;\s*)apeHostId=([^;]*)/)
16
+ const match = cookies.match(/(?:^|;\s*)apeClientId=([^;]*)/)
17
17
 
18
18
  if (match) {
19
19
  return match[1]
20
20
  }
21
21
 
22
- // Generate new hostId and set cookie
23
- const hostId = makeid(20)
24
- res.setHeader('Set-Cookie', `apeHostId=${hostId}; Path=/; HttpOnly; SameSite=Strict`)
25
- return hostId
22
+ // Generate new clientId and set cookie
23
+ const clientId = makeid(20)
24
+ res.setHeader('Set-Cookie', `apeClientId=${clientId}; Path=/; HttpOnly; SameSite=Strict`)
25
+ return clientId
26
26
  }
27
27
 
28
28
  /**
29
- * Get hostId from cookie
29
+ * Get clientId from cookie
30
30
  */
31
- function getHostId(req) {
31
+ function getClientId(req) {
32
32
  const cookies = req.headers.cookie || ''
33
- const match = cookies.match(/(?:^|;\s*)apeHostId=([^;]*)/)
33
+ const match = cookies.match(/(?:^|;\s*)apeClientId=([^;]*)/)
34
34
  return match ? match[1] : null
35
35
  }
36
36
 
@@ -52,7 +52,7 @@ function createLongPollingHandler(controllers, onConnent, fileTransfer) {
52
52
  * Keeps connection open and writes JSON messages as they arrive
53
53
  */
54
54
  function handleStreamGet(req, res) {
55
- const hostId = ensureHostId(req, res)
55
+ const clientId = ensureClientId(req, res)
56
56
 
57
57
  // Set up streaming response headers
58
58
  res.writeHead(200, {
@@ -81,7 +81,7 @@ function createLongPollingHandler(controllers, onConnent, fileTransfer) {
81
81
  cleanup()
82
82
  }
83
83
  }
84
- send.toString = () => hostId
84
+ send.toString = () => clientId
85
85
 
86
86
  // Clean up on close
87
87
  const cleanup = () => {
@@ -92,8 +92,8 @@ function createLongPollingHandler(controllers, onConnent, fileTransfer) {
92
92
  clearInterval(clientState.heartbeatTimer)
93
93
  }
94
94
 
95
- streamClients.delete(hostId)
96
- removeClient({ hostId })
95
+ streamClients.delete(clientId)
96
+ removeClient({ clientId })
97
97
 
98
98
  // Notify disconnect handler if registered
99
99
  if (clientState.onDisconnect) {
@@ -117,9 +117,9 @@ function createLongPollingHandler(controllers, onConnent, fileTransfer) {
117
117
  }, 20000)
118
118
 
119
119
  // Register client for broadcasts
120
- const clientInfo = { hostId, send }
120
+ const clientInfo = { clientId, send }
121
121
  addClient(clientInfo)
122
- streamClients.set(hostId, clientState)
122
+ streamClients.set(clientId, clientState)
123
123
 
124
124
  // Call onConnent hook if provided
125
125
  if (onConnent) {
@@ -154,9 +154,9 @@ function createLongPollingHandler(controllers, onConnent, fileTransfer) {
154
154
  * Process message through controllers, return response
155
155
  */
156
156
  function handleStreamPost(req, res, controllers) {
157
- const hostId = getHostId(req)
157
+ const clientId = getClientId(req)
158
158
 
159
- if (!hostId) {
159
+ if (!clientId) {
160
160
  return sendJson(res, 401, { error: 'Missing session. GET /api/ape/poll first.' })
161
161
  }
162
162
 
@@ -178,16 +178,21 @@ function createLongPollingHandler(controllers, onConnent, fileTransfer) {
178
178
  }
179
179
 
180
180
  // Get client state for embed values
181
- const clientState = streamClients.get(hostId)
181
+ const clientState = streamClients.get(clientId)
182
182
  const embedValues = clientState?.embed || {}
183
183
 
184
+ // Extract sessionId from cookies (set by outer framework)
185
+ const sessionIdMatch = (req.headers.cookie || '').match(/(?:^|;\s*)sessionId=([^;]*)/)
186
+ const sessionId = sessionIdMatch ? sessionIdMatch[1] : null
187
+
184
188
  // Build controller context
185
189
  const context = {
186
190
  ...embedValues,
187
- hostId,
191
+ clientId,
192
+ sessionId, // Session ID from cookie (set by outer framework)
188
193
  req,
189
194
  broadcast: (t, d) => broadcast(t, d),
190
- broadcastOthers: (t, d) => broadcast(t, d, hostId),
195
+ broadcastOthers: (t, d) => broadcast(t, d, clientId),
191
196
  online: () => streamClients.size,
192
197
  getClients: () => Array.from(streamClients.keys())
193
198
  }
@@ -218,4 +223,4 @@ function createLongPollingHandler(controllers, onConnent, fileTransfer) {
218
223
  }
219
224
  }
220
225
 
221
- module.exports = { createLongPollingHandler, getHostId, ensureHostId }
226
+ module.exports = { createLongPollingHandler, getClientId, ensureClientId }