api-ape 3.0.2 → 4.1.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 (186) hide show
  1. package/README.md +59 -572
  2. package/client/README.md +73 -14
  3. package/client/auth/crypto/aead.js +214 -0
  4. package/client/auth/crypto/constants.js +32 -0
  5. package/client/auth/crypto/encoding.js +104 -0
  6. package/client/auth/crypto/files.md +27 -0
  7. package/client/auth/crypto/kdf.js +217 -0
  8. package/client/auth/crypto-utils.js +118 -0
  9. package/client/auth/files.md +52 -0
  10. package/client/auth/key-recovery.js +288 -0
  11. package/client/auth/recovery/constants.js +37 -0
  12. package/client/auth/recovery/files.md +23 -0
  13. package/client/auth/recovery/key-derivation.js +61 -0
  14. package/client/auth/recovery/sss-browser.js +189 -0
  15. package/client/auth/share-storage.js +205 -0
  16. package/client/auth/storage/constants.js +18 -0
  17. package/client/auth/storage/db.js +132 -0
  18. package/client/auth/storage/files.md +27 -0
  19. package/client/auth/storage/keys.js +173 -0
  20. package/client/auth/storage/shares.js +200 -0
  21. package/client/browser.js +190 -23
  22. package/client/connectSocket.js +418 -988
  23. package/client/connection/README.md +23 -0
  24. package/client/connection/fileDownload.js +256 -0
  25. package/client/connection/fileHandling.js +450 -0
  26. package/client/connection/fileUtils.js +346 -0
  27. package/client/connection/files.md +71 -0
  28. package/client/connection/messageHandler.js +105 -0
  29. package/client/connection/network.js +350 -0
  30. package/client/connection/proxy.js +233 -0
  31. package/client/connection/sender.js +333 -0
  32. package/client/connection/state.js +321 -0
  33. package/client/connection/subscriptions.js +151 -0
  34. package/client/files.md +53 -0
  35. package/client/index.js +298 -142
  36. package/client/transports/README.md +50 -0
  37. package/client/transports/files.md +41 -0
  38. package/client/transports/streamParser.js +195 -0
  39. package/client/transports/streaming.js +555 -203
  40. package/dist/ape.js +6 -1
  41. package/dist/ape.js.map +4 -4
  42. package/index.d.ts +38 -16
  43. package/package.json +31 -6
  44. package/server/README.md +272 -67
  45. package/server/adapters/README.md +23 -14
  46. package/server/adapters/files.md +68 -0
  47. package/server/adapters/firebase.js +543 -160
  48. package/server/adapters/index.js +362 -112
  49. package/server/adapters/mongo.js +530 -140
  50. package/server/adapters/postgres.js +534 -155
  51. package/server/adapters/redis.js +508 -143
  52. package/server/adapters/supabase.js +555 -186
  53. package/server/client/README.md +43 -0
  54. package/server/client/connection.js +586 -0
  55. package/server/client/files.md +40 -0
  56. package/server/client/index.js +342 -0
  57. package/server/files.md +54 -0
  58. package/server/index.js +322 -71
  59. package/server/lib/README.md +26 -0
  60. package/server/lib/broadcast/clients.js +219 -0
  61. package/server/lib/broadcast/files.md +58 -0
  62. package/server/lib/broadcast/index.js +57 -0
  63. package/server/lib/broadcast/publishProxy.js +110 -0
  64. package/server/lib/broadcast/pubsub.js +137 -0
  65. package/server/lib/broadcast/sendProxy.js +103 -0
  66. package/server/lib/bun.js +315 -99
  67. package/server/lib/fileTransfer/README.md +63 -0
  68. package/server/lib/fileTransfer/files.md +30 -0
  69. package/server/lib/fileTransfer/streaming.js +435 -0
  70. package/server/lib/fileTransfer.js +710 -326
  71. package/server/lib/files.md +111 -0
  72. package/server/lib/httpUtils.js +283 -0
  73. package/server/lib/loader.js +208 -7
  74. package/server/lib/longPolling/README.md +63 -0
  75. package/server/lib/longPolling/files.md +44 -0
  76. package/server/lib/longPolling/getHandler.js +365 -0
  77. package/server/lib/longPolling/postHandler.js +327 -0
  78. package/server/lib/longPolling.js +174 -219
  79. package/server/lib/main.js +369 -532
  80. package/server/lib/runtimes/README.md +42 -0
  81. package/server/lib/runtimes/bun.js +586 -0
  82. package/server/lib/runtimes/files.md +56 -0
  83. package/server/lib/runtimes/node.js +511 -0
  84. package/server/lib/wiring.js +539 -98
  85. package/server/lib/ws/README.md +35 -0
  86. package/server/lib/ws/adapters/README.md +54 -0
  87. package/server/lib/ws/adapters/bun.js +538 -170
  88. package/server/lib/ws/adapters/deno.js +623 -149
  89. package/server/lib/ws/adapters/files.md +42 -0
  90. package/server/lib/ws/files.md +74 -0
  91. package/server/lib/ws/frames.js +532 -154
  92. package/server/lib/ws/index.js +207 -10
  93. package/server/lib/ws/server.js +385 -92
  94. package/server/lib/ws/socket.js +549 -181
  95. package/server/lib/wsProvider.js +363 -89
  96. package/server/plugins/binary.js +282 -0
  97. package/server/security/README.md +92 -0
  98. package/server/security/auth/README.md +319 -0
  99. package/server/security/auth/adapters/files.md +95 -0
  100. package/server/security/auth/adapters/ldap/constants.js +37 -0
  101. package/server/security/auth/adapters/ldap/files.md +19 -0
  102. package/server/security/auth/adapters/ldap/helpers.js +111 -0
  103. package/server/security/auth/adapters/ldap.js +353 -0
  104. package/server/security/auth/adapters/oauth2/constants.js +41 -0
  105. package/server/security/auth/adapters/oauth2/files.md +19 -0
  106. package/server/security/auth/adapters/oauth2/helpers.js +123 -0
  107. package/server/security/auth/adapters/oauth2.js +273 -0
  108. package/server/security/auth/adapters/opaque-handlers.js +314 -0
  109. package/server/security/auth/adapters/opaque.js +205 -0
  110. package/server/security/auth/adapters/saml/constants.js +52 -0
  111. package/server/security/auth/adapters/saml/files.md +19 -0
  112. package/server/security/auth/adapters/saml/helpers.js +74 -0
  113. package/server/security/auth/adapters/saml.js +173 -0
  114. package/server/security/auth/adapters/totp.js +703 -0
  115. package/server/security/auth/adapters/webauthn.js +625 -0
  116. package/server/security/auth/files.md +61 -0
  117. package/server/security/auth/framework/constants.js +27 -0
  118. package/server/security/auth/framework/files.md +23 -0
  119. package/server/security/auth/framework/handlers.js +272 -0
  120. package/server/security/auth/framework/socket-auth.js +177 -0
  121. package/server/security/auth/handlers/auth-messages.js +143 -0
  122. package/server/security/auth/handlers/files.md +28 -0
  123. package/server/security/auth/index.js +290 -0
  124. package/server/security/auth/mfa/crypto/aead.js +148 -0
  125. package/server/security/auth/mfa/crypto/constants.js +35 -0
  126. package/server/security/auth/mfa/crypto/files.md +27 -0
  127. package/server/security/auth/mfa/crypto/kdf.js +120 -0
  128. package/server/security/auth/mfa/crypto/utils.js +68 -0
  129. package/server/security/auth/mfa/crypto-utils.js +80 -0
  130. package/server/security/auth/mfa/files.md +77 -0
  131. package/server/security/auth/mfa/ledger/constants.js +75 -0
  132. package/server/security/auth/mfa/ledger/errors.js +73 -0
  133. package/server/security/auth/mfa/ledger/files.md +23 -0
  134. package/server/security/auth/mfa/ledger/share-record.js +32 -0
  135. package/server/security/auth/mfa/ledger.js +255 -0
  136. package/server/security/auth/mfa/recovery/constants.js +67 -0
  137. package/server/security/auth/mfa/recovery/files.md +19 -0
  138. package/server/security/auth/mfa/recovery/handlers.js +216 -0
  139. package/server/security/auth/mfa/recovery.js +191 -0
  140. package/server/security/auth/mfa/sss/constants.js +21 -0
  141. package/server/security/auth/mfa/sss/files.md +23 -0
  142. package/server/security/auth/mfa/sss/gf256.js +103 -0
  143. package/server/security/auth/mfa/sss/serialization.js +82 -0
  144. package/server/security/auth/mfa/sss.js +161 -0
  145. package/server/security/auth/mfa/two-of-three/constants.js +58 -0
  146. package/server/security/auth/mfa/two-of-three/files.md +23 -0
  147. package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
  148. package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
  149. package/server/security/auth/mfa/two-of-three.js +136 -0
  150. package/server/security/auth/nonce-manager.js +89 -0
  151. package/server/security/auth/state-machine-mfa.js +269 -0
  152. package/server/security/auth/state-machine.js +257 -0
  153. package/server/security/extractRootDomain.js +144 -16
  154. package/server/security/files.md +51 -0
  155. package/server/security/origin.js +197 -15
  156. package/server/security/reply.js +274 -16
  157. package/server/socket/README.md +119 -0
  158. package/server/socket/authMiddleware.js +299 -0
  159. package/server/socket/files.md +86 -0
  160. package/server/socket/open.js +154 -8
  161. package/server/socket/pluginHooks.js +334 -0
  162. package/server/socket/receive.js +184 -224
  163. package/server/socket/receiveContext.js +117 -0
  164. package/server/socket/send.js +416 -78
  165. package/server/socket/tagUtils.js +402 -0
  166. package/server/utils/README.md +19 -0
  167. package/server/utils/deepRequire.js +255 -30
  168. package/server/utils/files.md +57 -0
  169. package/server/utils/genId.js +182 -20
  170. package/server/utils/parseUserAgent.js +313 -251
  171. package/server/utils/userAgent/README.md +65 -0
  172. package/server/utils/userAgent/files.md +46 -0
  173. package/server/utils/userAgent/patterns.js +545 -0
  174. package/utils/README.md +21 -0
  175. package/utils/files.md +66 -0
  176. package/utils/jss/README.md +21 -0
  177. package/utils/jss/decode.js +471 -0
  178. package/utils/jss/encode.js +312 -0
  179. package/utils/jss/files.md +68 -0
  180. package/utils/jss/plugins.js +210 -0
  181. package/utils/jss.js +219 -273
  182. package/utils/messageHash.js +238 -35
  183. package/dist/api-ape.min.js +0 -2
  184. package/dist/api-ape.min.js.map +0 -7
  185. package/server/client.js +0 -311
  186. package/server/lib/broadcast.js +0 -146
package/server/client.js DELETED
@@ -1,311 +0,0 @@
1
- /**
2
- * api-ape Node.js client
3
- *
4
- * Mirrors the browser client API exactly - same usage on server and browser.
5
- *
6
- * Usage (identical to browser):
7
- * import api from 'api-ape'
8
- *
9
- * api.message({ user: 'Bob', text: 'Hello!' })
10
- * api.on('message', (data) => console.log(data))
11
- * api.onConnectionChange((state) => console.log(state))
12
- *
13
- * Configuration:
14
- * Set APE_SERVER environment variable to the WebSocket URL:
15
- * APE_SERVER=ws://other-server:3000/api/ape node app.js
16
- *
17
- * Or call api.connect(url) before first use
18
- */
19
-
20
- const jss = require('../utils/jss')
21
- const { WebSocket: WsPolyfill } = require('./lib/ws')
22
-
23
- // Use native WebSocket if available (Node 22+), otherwise use polyfill
24
- const WebSocket = globalThis.WebSocket || WsPolyfill
25
-
26
- // Connection state enum
27
- const ConnectionState = {
28
- Disconnected: 'disconnected',
29
- Connecting: 'connecting',
30
- Connected: 'connected',
31
- Closing: 'closing'
32
- }
33
-
34
- // Shared state (mirrors browser client)
35
- let ws = null
36
- let connectionState = ConnectionState.Disconnected
37
- const connectionChangeListeners = []
38
- const waitingOn = {}
39
- const receiverArray = []
40
- const ofTypesOb = {}
41
- let queryCounter = 0
42
- let bufferedCalls = []
43
- let bufferedReceivers = []
44
- let ready = false
45
- let reconnectEnabled = true
46
- let reconnectTimer = null
47
- let serverUrl = process.env.APE_SERVER || null
48
-
49
- const joinKey = '/'
50
- const connectTimeout = 5000
51
- const totalRequestTimeout = 10000
52
-
53
- function notifyConnectionChange(newState) {
54
- if (connectionState !== newState) {
55
- connectionState = newState
56
- connectionChangeListeners.forEach(fn => fn(newState))
57
- }
58
- }
59
-
60
- function generateQueryId() {
61
- return `q${Date.now().toString(36)}_${(queryCounter++).toString(36)}`
62
- }
63
-
64
- function connect(url) {
65
- if (url) serverUrl = url
66
-
67
- if (!serverUrl) {
68
- console.warn('🦍 api-ape: No server URL configured. Set APE_SERVER env or call api.connect(url)')
69
- return
70
- }
71
-
72
- if (ws && ws.readyState !== WebSocket.CLOSED) {
73
- return
74
- }
75
-
76
- notifyConnectionChange(ConnectionState.Connecting)
77
-
78
- ws = new WebSocket(serverUrl)
79
-
80
- ws.onopen = () => {
81
- ready = true
82
- notifyConnectionChange(ConnectionState.Connected)
83
-
84
- // Flush buffered receivers
85
- bufferedReceivers.forEach(({ type, handler }) => {
86
- setOnReceiver(type, handler)
87
- })
88
- bufferedReceivers = []
89
-
90
- // Flush buffered calls
91
- bufferedCalls.forEach(({ type, data, resolve, reject, createdAt, timer }) => {
92
- clearTimeout(timer)
93
- send(type, data, createdAt).then(resolve).catch(reject)
94
- })
95
- bufferedCalls = []
96
- }
97
-
98
- ws.onmessage = (event) => {
99
- const msg = jss.parse(typeof event.data === 'string' ? event.data : event.data.toString())
100
- const { err, type, queryId, data } = msg
101
-
102
- // Response to a query
103
- if (queryId && waitingOn[queryId]) {
104
- waitingOn[queryId](err, data)
105
- delete waitingOn[queryId]
106
- return
107
- }
108
-
109
- // Broadcast message
110
- if (ofTypesOb[type]) {
111
- ofTypesOb[type].forEach(handler => handler({ err, type, data }))
112
- }
113
- receiverArray.forEach(handler => handler({ err, type, data }))
114
- }
115
-
116
- ws.onerror = (err) => {
117
- console.error('🦍 api-ape client error:', err.message || err)
118
- }
119
-
120
- ws.onclose = () => {
121
- ready = false
122
- ws = null
123
- notifyConnectionChange(ConnectionState.Disconnected)
124
-
125
- if (reconnectEnabled && serverUrl) {
126
- reconnectTimer = setTimeout(() => connect(), 1000)
127
- }
128
- }
129
- }
130
-
131
- function send(type, data, createdAt = Date.now()) {
132
- const queryId = generateQueryId()
133
-
134
- return new Promise((resolve, reject) => {
135
- const timer = setTimeout(() => {
136
- delete waitingOn[queryId]
137
- reject(new Error(`Request timeout: ${type}`))
138
- }, totalRequestTimeout)
139
-
140
- waitingOn[queryId] = (err, result) => {
141
- clearTimeout(timer)
142
- if (err) {
143
- reject(typeof err === 'string' ? new Error(err) : err)
144
- } else {
145
- resolve(result)
146
- }
147
- }
148
-
149
- const message = jss.stringify({ type, data, queryId, createdAt })
150
- ws.send(message)
151
- })
152
- }
153
-
154
- function queueOrSend(type, data) {
155
- if (ready && ws && ws.readyState === WebSocket.OPEN) {
156
- return send(type, data)
157
- }
158
-
159
- // Queue the message
160
- return new Promise((resolve, reject) => {
161
- const createdAt = Date.now()
162
- const timer = setTimeout(() => {
163
- const idx = bufferedCalls.findIndex(m => m.createdAt === createdAt)
164
- if (idx > -1) bufferedCalls.splice(idx, 1)
165
- reject(new Error(`Connection timeout: ${type}`))
166
- }, connectTimeout)
167
-
168
- bufferedCalls.push({ type, data, resolve, reject, createdAt, timer })
169
-
170
- // Ensure we're connecting
171
- if (connectionState === ConnectionState.Disconnected && serverUrl) {
172
- connect()
173
- }
174
- })
175
- }
176
-
177
- /**
178
- * Subscribe to broadcasts from the server (same as browser api.on)
179
- */
180
- function on(type, handler) {
181
- if (typeof type === 'function') {
182
- handler = type
183
- type = null
184
- }
185
-
186
- if (ready) {
187
- setOnReceiver(type, handler)
188
- } else {
189
- bufferedReceivers.push({ type, handler })
190
- if (serverUrl) connect()
191
- }
192
- }
193
-
194
- function setOnReceiver(type, handler) {
195
- if (type === null) {
196
- receiverArray.push(handler)
197
- } else {
198
- if (!ofTypesOb[type]) ofTypesOb[type] = []
199
- ofTypesOb[type].push(handler)
200
- }
201
- }
202
-
203
- /**
204
- * Subscribe to connection state changes (same as browser api.onConnectionChange)
205
- */
206
- function onConnectionChange(handler) {
207
- connectionChangeListeners.push(handler)
208
- handler(connectionState)
209
- return () => {
210
- const idx = connectionChangeListeners.indexOf(handler)
211
- if (idx > -1) connectionChangeListeners.splice(idx, 1)
212
- }
213
- }
214
-
215
- /**
216
- * Create the sender proxy (mirrors browser client exactly)
217
- */
218
- const handler = {
219
- get(target, prop) {
220
- // First check if property exists on target (set via defineProperty)
221
- // This allows named exports like 'ape' to be accessed directly
222
- if (Reflect.has(target, prop)) {
223
- return Reflect.get(target, prop)
224
- }
225
-
226
- // Reserved properties - same as browser
227
- if (prop === 'on') return on
228
- if (prop === 'onConnectionChange') return onConnectionChange
229
- if (prop === 'transport') return ready ? 'websocket' : null
230
- if (prop === 'connect') return connect
231
- if (prop === 'close') return close
232
- if (prop === 'then' || prop === 'catch') return undefined // Not a Promise
233
-
234
- // Return a function that either calls directly or buffers
235
- const wrapperFn = function (a, b) {
236
- let path = joinKey + prop, body
237
- // Two args: first is path segment (string), second is body
238
- // One arg: it's always the body (matches browser client behavior)
239
- if (arguments.length === 2 && typeof a === 'string') {
240
- path += a
241
- body = b
242
- } else {
243
- // Single arg or non-string first arg: treat first arg as body
244
- body = a
245
- }
246
- return queueOrSend(path, body)
247
- }
248
- return new Proxy(wrapperFn, handler)
249
- }
250
- }
251
-
252
- function close() {
253
- reconnectEnabled = false
254
- if (reconnectTimer) {
255
- clearTimeout(reconnectTimer)
256
- reconnectTimer = null
257
- }
258
- if (ws) {
259
- notifyConnectionChange(ConnectionState.Closing)
260
- ws.close()
261
- }
262
- }
263
-
264
- // Create the proxy (same interface as browser senderProxy)
265
- const api = new Proxy({}, handler)
266
-
267
- // Define properties on the proxy (same as browser)
268
- Object.defineProperty(api, 'on', {
269
- value: on,
270
- writable: false,
271
- enumerable: false,
272
- configurable: false
273
- })
274
-
275
- Object.defineProperty(api, 'onConnectionChange', {
276
- value: onConnectionChange,
277
- writable: false,
278
- enumerable: false,
279
- configurable: false
280
- })
281
-
282
- Object.defineProperty(api, 'connect', {
283
- value: connect,
284
- writable: false,
285
- enumerable: false,
286
- configurable: false
287
- })
288
-
289
- Object.defineProperty(api, 'close', {
290
- value: close,
291
- writable: false,
292
- enumerable: false,
293
- configurable: false
294
- })
295
-
296
- // NOTE: We do NOT auto-connect on module load, even if APE_SERVER is set.
297
- // Connection only happens when:
298
- // 1. api.connect(url) is called explicitly
299
- // 2. A method is called and APE_SERVER env is set (lazy connection)
300
-
301
- // Export the same interface as browser
302
- module.exports = api
303
- module.exports.default = api
304
- module.exports.on = on
305
- module.exports.onConnectionChange = onConnectionChange
306
- module.exports.connect = connect
307
- module.exports.close = close
308
- module.exports.ConnectionState = ConnectionState
309
- // Internal: expose queueOrSend for ape dual-purpose function
310
- module.exports._queueOrSend = queueOrSend
311
-
@@ -1,146 +0,0 @@
1
- /**
2
- * Client tracking and broadcast utilities for api-ape
3
- * Provides a Map of connected clients with sendTo functionality
4
- */
5
-
6
- // Internal Map of connected clients: clientId -> ClientWrapper
7
- const _clients = new Map()
8
-
9
- /**
10
- * Create a ClientWrapper that exposes client info and sendTo function
11
- * @param {object} clientInfo - Raw client info from wiring/longPolling
12
- */
13
- function createClientWrapper(clientInfo) {
14
- return {
15
- get clientId() { return clientInfo.clientId },
16
- get sessionId() { return clientInfo.sessionId || null },
17
- get embed() { return clientInfo.embed || {} },
18
- get agent() { return clientInfo.agent || {} },
19
- /**
20
- * Send a message to this specific client
21
- * @param {string} type - Message type
22
- * @param {any} data - Data to send
23
- */
24
- sendTo(type, data) {
25
- if (clientInfo.send) {
26
- try {
27
- clientInfo.send(false, type, data, false)
28
- } catch (e) {
29
- console.error(`📢 sendTo failed for ${clientInfo.clientId}:`, e.message)
30
- }
31
- }
32
- }
33
- }
34
- }
35
-
36
- /**
37
- * Read-only proxy for the clients Map
38
- * Allows: get, has, keys, values, entries, forEach, size, iteration
39
- * Prevents: set, delete, clear (throws error if attempted)
40
- */
41
- const clients = new Proxy(_clients, {
42
- get(target, prop) {
43
- // Prevent mutation methods
44
- if (prop === 'set' || prop === 'delete' || prop === 'clear') {
45
- return () => {
46
- throw new Error(`ape.clients.${prop}() is not allowed. Clients are managed internally by api-ape.`)
47
- }
48
- }
49
-
50
- // Allow size property
51
- if (prop === 'size') {
52
- return target.size
53
- }
54
-
55
- // Bind methods to target
56
- const value = target[prop]
57
- if (typeof value === 'function') {
58
- return value.bind(target)
59
- }
60
-
61
- return value
62
- }
63
- })
64
-
65
- /**
66
- * Add a client to the connected map (internal use only)
67
- * @param {object} clientInfo - { clientId, sessionId, agent, embed, send }
68
- */
69
- function addClient(clientInfo) {
70
- const wrapper = createClientWrapper(clientInfo)
71
- _clients.set(clientInfo.clientId, wrapper)
72
-
73
- // Store reference to raw info so we can update embed later if needed
74
- wrapper._raw = clientInfo
75
-
76
- console.log(`🟢 Client added: ${clientInfo.clientId} (total: ${_clients.size})`)
77
- }
78
-
79
- /**
80
- * Remove a client from the connected map (internal use only)
81
- * @param {string|object} clientIdOrInfo - clientId string or { clientId } object
82
- */
83
- function removeClient(clientIdOrInfo) {
84
- const clientId = typeof clientIdOrInfo === 'string'
85
- ? clientIdOrInfo
86
- : clientIdOrInfo.clientId
87
-
88
- if (_clients.has(clientId)) {
89
- _clients.delete(clientId)
90
- console.log(`🔴 Client removed: ${clientId} (total: ${_clients.size})`)
91
- } else {
92
- console.log(`⚠️ Client not found for removal: ${clientId} (total: ${_clients.size})`)
93
- }
94
- }
95
-
96
- /**
97
- * Update a client's embed values after onConnect resolves (internal use only)
98
- * @param {string} clientId
99
- * @param {object} embed
100
- */
101
- function updateClientEmbed(clientId, embed) {
102
- const wrapper = _clients.get(clientId)
103
- if (wrapper && wrapper._raw) {
104
- wrapper._raw.embed = embed
105
- }
106
- }
107
-
108
- /**
109
- * Update a client's send function after it's ready (internal use only)
110
- * @param {string} clientId
111
- * @param {function} send
112
- */
113
- function updateClientSend(clientId, send) {
114
- const wrapper = _clients.get(clientId)
115
- if (wrapper && wrapper._raw) {
116
- wrapper._raw.send = send
117
- }
118
- }
119
-
120
- /**
121
- * Broadcast to all connected clients
122
- * @param {string} type - Message type
123
- * @param {any} data - Data to send
124
- * @param {string} [excludeClientId] - Optional clientId to exclude (e.g., sender)
125
- */
126
- function broadcast(type, data, excludeClientId) {
127
- console.log(`📢 Broadcasting "${type}" to ${_clients.size} clients`, excludeClientId ? `(excluding ${excludeClientId})` : '')
128
- _clients.forEach((wrapper, clientId) => {
129
- if (excludeClientId && clientId === excludeClientId) {
130
- return // Skip excluded client
131
- }
132
- wrapper.sendTo(type, data)
133
- })
134
- }
135
-
136
- module.exports = {
137
- // Public: read-only clients Map
138
- clients,
139
- // Public: broadcast function
140
- broadcast,
141
- // Internal: client management (used by wiring.js and longPolling.js)
142
- addClient,
143
- removeClient,
144
- updateClientEmbed,
145
- updateClientSend
146
- }