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
@@ -1,233 +1,188 @@
1
- const { addClient, removeClient, broadcast, clients, updateClientEmbed } = require('./broadcast')
2
- const makeid = require('../utils/genId')
3
- const jss = require('../../utils/jss')
4
- const parseUserAgent = require('../utils/parseUserAgent')
5
-
6
- // Active streaming connections: clientId -> { res, messageQueue, heartbeatTimer }
7
- const streamClients = new Map()
8
-
9
- // Pending message handlers for POST requests: queryId -> { resolve, reject, timer }
10
- const pendingRequests = new Map()
11
-
12
1
  /**
13
- * Set apeClientId cookie if not present
2
+ * @fileoverview Long Polling Handler - HTTP Fallback for WebSocket
3
+ *
4
+ * This module provides HTTP long-polling as a fallback transport when WebSocket
5
+ * connections are not available. Long polling is essential for:
6
+ *
7
+ * - Clients behind restrictive firewalls that block WebSocket
8
+ * - Networks that don't support WebSocket protocol
9
+ * - Legacy browser support
10
+ * - Debugging and testing scenarios
11
+ *
12
+ * How Long Polling Works:
13
+ * 1. Client makes a GET request that is held open (streaming response)
14
+ * 2. Server sends events to client by writing to the response stream
15
+ * 3. Client sends messages via POST requests
16
+ * 4. Heartbeats keep the connection alive
17
+ * 5. Connection is recycled periodically to prevent timeouts
18
+ *
19
+ * This module coordinates the GET and POST handlers and maintains
20
+ * the client state map shared between them.
21
+ *
22
+ * @module server/lib/longPolling
23
+ * @see {@link module:server/lib/longPolling/getHandler} - GET handler for streaming responses
24
+ * @see {@link module:server/lib/longPolling/postHandler} - POST handler for client messages
25
+ * @see {@link module:server/lib/wiring} - WebSocket wiring (primary transport)
26
+ *
27
+ * @example
28
+ * // Create long polling handlers for api-ape server
29
+ * const { createLongPollingHandler } = require('./longPolling')
30
+ *
31
+ * const { handleStreamGet, handleStreamPost } = createLongPollingHandler(
32
+ * controllers,
33
+ * onConnect,
34
+ * fileTransfer
35
+ * )
36
+ *
37
+ * // Use in HTTP server
38
+ * server.on('request', (req, res) => {
39
+ * if (req.url === '/api/ape/poll' && req.method === 'GET') {
40
+ * handleStreamGet(req, res)
41
+ * } else if (req.url === '/api/ape/poll' && req.method === 'POST') {
42
+ * handleStreamPost(req, res, controllers)
43
+ * }
44
+ * })
14
45
  */
15
- function ensureClientId(req, res) {
16
- const cookies = req.headers.cookie || ''
17
- const match = cookies.match(/(?:^|;\s*)apeClientId=([^;]*)/)
18
46
 
19
- if (match) {
20
- return match[1]
21
- }
22
-
23
- // Generate new clientId and set cookie
24
- const clientId = makeid(20)
25
- res.setHeader('Set-Cookie', `apeClientId=${clientId}; Path=/; HttpOnly; SameSite=Strict`)
26
- return clientId
27
- }
47
+ const {
48
+ createGetHandler,
49
+ ensureClientId,
50
+ } = require("./longPolling/getHandler");
51
+ const { createPostHandler, getClientId } = require("./longPolling/postHandler");
28
52
 
29
53
  /**
30
- * Get clientId from cookie
54
+ * Map of active long-polling client connections.
55
+ *
56
+ * Keyed by client ID (from cookie), values contain:
57
+ * - res: HTTP response stream
58
+ * - messageQueue: Pending messages
59
+ * - heartbeatTimer: Interval timer for keepalives
60
+ * - isActive: Whether the connection is still active
61
+ * - embed: Custom data attached during onConnect
62
+ * - onDisconnect: Cleanup callback
63
+ *
64
+ * @private
65
+ * @type {Map<string, Object>}
31
66
  */
32
- function getClientId(req) {
33
- const cookies = req.headers.cookie || ''
34
- const match = cookies.match(/(?:^|;\s*)apeClientId=([^;]*)/)
35
- return match ? match[1] : null
36
- }
67
+ const streamClients = new Map();
37
68
 
38
69
  /**
39
- * Send JSON response helper
70
+ * @typedef {Object} LongPollingHandlers
71
+ * Object containing the HTTP handlers for long polling.
72
+ *
73
+ * @property {Function} handleStreamGet - GET request handler for streaming responses.
74
+ * Creates a long-lived HTTP response that streams events to the client.
75
+ * @property {Function} handleStreamPost - POST request handler for client messages.
76
+ * Processes messages sent by clients and routes to controllers.
40
77
  */
41
- function sendJson(res, statusCode, data) {
42
- res.writeHead(statusCode, { 'Content-Type': 'application/json' })
43
- res.end(JSON.stringify(data))
44
- }
45
78
 
46
79
  /**
47
- * Create long polling handler
80
+ * Creates the long polling HTTP handlers.
81
+ *
82
+ * This function sets up the GET and POST handlers that work together
83
+ * to provide bidirectional communication over HTTP:
84
+ *
85
+ * **GET Handler (Streaming Receive)**:
86
+ * - Client opens a long-lived HTTP connection
87
+ * - Server streams JSON events to the response
88
+ * - Heartbeats sent every 20 seconds to keep connection alive
89
+ * - Connection recycled after 25 seconds (client reconnects)
90
+ *
91
+ * **POST Handler (Send Messages)**:
92
+ * - Client sends JSON messages via POST
93
+ * - Messages are routed to appropriate controllers
94
+ * - Response contains the controller's return value
95
+ *
96
+ * Both handlers share the `streamClients` Map to coordinate state.
97
+ *
98
+ * @function createLongPollingHandler
99
+ * @param {Object<string, Function>} controllers - Map of controller functions keyed by endpoint
100
+ * @param {Function} [onConnect] - Optional callback when a client connects.
101
+ * Receives (socket, req, send) and can return { onDisconnect, embed }.
102
+ * @param {import('./fileTransfer').FileTransferManager} fileTransfer - File transfer manager
103
+ * @param {Object} [options] - Optional configuration options
104
+ * @param {number} [options.heartbeatInterval=20000] - Interval in ms for heartbeat pings
105
+ * @param {number} [options.recycleTimeout=25000] - Timeout in ms before recycling connection
106
+ * @returns {LongPollingHandlers} Object with handleStreamGet and handleStreamPost
107
+ *
108
+ * @example
109
+ * // Basic setup
110
+ * const handlers = createLongPollingHandler(controllers, null, fileTransfer)
111
+ *
112
+ * // In request handler
113
+ * if (pathname === '/api/ape/poll') {
114
+ * if (req.method === 'GET') {
115
+ * handlers.handleStreamGet(req, res)
116
+ * } else if (req.method === 'POST') {
117
+ * handlers.handleStreamPost(req, res, controllers)
118
+ * }
119
+ * }
120
+ *
121
+ * @example
122
+ * // With connection callback
123
+ * const handlers = createLongPollingHandler(
124
+ * controllers,
125
+ * async (socket, req, send) => {
126
+ * // Authenticate user
127
+ * const user = await authenticate(req)
128
+ *
129
+ * // Send welcome message
130
+ * send('welcome', { userId: user.id })
131
+ *
132
+ * return {
133
+ * embed: { userId: user.id },
134
+ * onDisconnect: () => {
135
+ * console.log(`User ${user.id} disconnected`)
136
+ * }
137
+ * }
138
+ * },
139
+ * fileTransfer
140
+ * )
48
141
  */
49
- function createLongPollingHandler(controllers, onConnect, fileTransfer) {
50
-
51
- /**
52
- * Handle GET /api/ape/poll - Streaming receive
53
- * Keeps connection open and writes JSON messages as they arrive
54
- */
55
- function handleStreamGet(req, res) {
56
- const clientId = ensureClientId(req, res)
57
-
58
- // Set up streaming response headers
59
- res.writeHead(200, {
60
- 'Content-Type': 'application/json',
61
- 'Cache-Control': 'no-cache',
62
- 'Connection': 'keep-alive',
63
- 'X-Accel-Buffering': 'no' // Disable nginx buffering
64
- })
65
-
66
- // Create message queue for this client
67
- const clientState = {
68
- res,
69
- messageQueue: [],
70
- heartbeatTimer: null,
71
- isActive: true
72
- }
73
-
74
- // Send function for this streaming client
75
- const send = (type, data, err) => {
76
- if (!clientState.isActive) return
77
-
78
- const message = jss.stringify({ type, data, err: err || undefined })
79
- try {
80
- res.write(message)
81
- } catch (e) {
82
- cleanup()
83
- }
84
- }
85
- send.toString = () => clientId
86
-
87
- // Clean up on close
88
- const cleanup = () => {
89
- if (!clientState.isActive) return
90
- clientState.isActive = false
91
-
92
- if (clientState.heartbeatTimer) {
93
- clearInterval(clientState.heartbeatTimer)
94
- }
95
-
96
- streamClients.delete(clientId)
97
- removeClient({ clientId })
98
-
99
- // Notify disconnect handler if registered
100
- if (clientState.onDisconnect) {
101
- clientState.onDisconnect()
102
- }
103
- }
104
-
105
- req.on('close', cleanup)
106
- req.on('error', cleanup)
107
- res.on('error', cleanup)
108
-
109
- // Heartbeat to keep connection alive (every 20s)
110
- clientState.heartbeatTimer = setInterval(() => {
111
- if (!clientState.isActive) return
112
- try {
113
- // Send heartbeat as empty comment (client ignores)
114
- res.write('{"type":"__heartbeat__"}')
115
- } catch (e) {
116
- cleanup()
117
- }
118
- }, 20000)
119
-
120
- // Extract sessionId from cookies
121
- const sessionIdMatch = (req.headers.cookie || '').match(/(?:^|;\s*)sessionId=([^;]*)/)
122
- const sessionId = sessionIdMatch ? sessionIdMatch[1] : null
123
-
124
- // Parse user agent
125
- const agent = parseUserAgent(req.headers['user-agent'])
126
-
127
- // Register client for broadcasts with full metadata
128
- addClient({ clientId, sessionId, agent, send, embed: null })
129
- streamClients.set(clientId, clientState)
130
-
131
- // Call onConnect hook if provided
132
- if (onConnect) {
133
- Promise.resolve(onConnect(null, req, send))
134
- .then(handlers => {
135
- if (handlers) {
136
- if (handlers.onDisconnect) {
137
- clientState.onDisconnect = handlers.onDisconnect
138
- }
139
- if (handlers.embed) {
140
- clientState.embed = handlers.embed
141
- updateClientEmbed(clientId, handlers.embed)
142
- }
143
- }
144
- })
145
- .catch(err => {
146
- console.error('onConnect error:', err)
147
- })
148
- }
149
-
150
- // Close after 25 seconds (before typical proxy timeout)
151
- // Client will immediately reconnect
152
- setTimeout(() => {
153
- cleanup()
154
- try {
155
- res.end()
156
- } catch (e) { }
157
- }, 25000)
158
- }
159
-
160
- /**
161
- * Handle POST /api/ape/poll - Send messages
162
- * Process message through controllers, return response
163
- */
164
- function handleStreamPost(req, res, controllers) {
165
- const clientId = getClientId(req)
166
-
167
- if (!clientId) {
168
- return sendJson(res, 401, { error: 'Missing session. GET /api/ape/poll first.' })
169
- }
170
-
171
- // Collect body
172
- const chunks = []
173
- req.on('data', chunk => chunks.push(chunk))
174
- req.on('end', async () => {
175
- try {
176
- const body = Buffer.concat(chunks).toString('utf8')
177
- const { type: rawType, data, createdAt } = jss.parse(body)
178
-
179
- // Normalize type
180
- const type = rawType.replace(/^\//, '').toLowerCase()
181
-
182
- // Find controller
183
- const controller = controllers[type]
184
- if (!controller) {
185
- return sendJson(res, 404, { error: `Controller "${type}" not found` })
186
- }
187
-
188
- // Get client state for embed values
189
- const clientState = streamClients.get(clientId)
190
- const embedValues = clientState?.embed || {}
191
-
192
- // Extract sessionId from cookies (set by outer framework)
193
- const sessionIdMatch = (req.headers.cookie || '').match(/(?:^|;\s*)sessionId=([^;]*)/)
194
- const sessionId = sessionIdMatch ? sessionIdMatch[1] : null
195
-
196
- // Build controller context
197
- const context = {
198
- ...embedValues,
199
- clientId,
200
- sessionId, // Session ID from cookie (set by outer framework)
201
- req,
202
- broadcast: (t, d) => broadcast(t, d),
203
- broadcastOthers: (t, d) => broadcast(t, d, clientId),
204
- clients
205
- }
206
-
207
- // Execute controller
208
- const result = await controller.call(context, data)
209
-
210
- // Send response
211
- const responsePayload = { data: result }
212
- res.writeHead(200, { 'Content-Type': 'application/json' })
213
- res.end(jss.stringify(responsePayload))
214
-
215
- } catch (err) {
216
- const errorMessage = err.message || String(err)
217
- sendJson(res, 500, { error: errorMessage })
218
- }
219
- })
220
-
221
- req.on('error', (err) => {
222
- sendJson(res, 500, { error: err.message })
223
- })
224
- }
225
-
226
- return {
227
- handleStreamGet,
228
- handleStreamPost,
229
- getStreamClients: () => streamClients
230
- }
142
+ function createLongPollingHandler(controllers, onConnect, fileTransfer, options = {}) {
143
+ /**
144
+ * GET handler for streaming responses.
145
+ * Creates a long-lived HTTP response that streams events to the client.
146
+ * @type {Function}
147
+ */
148
+ const handleStreamGet = createGetHandler(streamClients, onConnect, options);
149
+
150
+ /**
151
+ * POST handler for client messages.
152
+ * Processes incoming messages and routes them to controllers.
153
+ * @type {Function}
154
+ */
155
+ const handleStreamPost = createPostHandler(streamClients);
156
+
157
+ return {
158
+ handleStreamGet,
159
+ handleStreamPost,
160
+ };
231
161
  }
232
162
 
233
- module.exports = { createLongPollingHandler, getClientId, ensureClientId }
163
+ module.exports = {
164
+ /**
165
+ * Create long polling handlers for HTTP fallback transport.
166
+ * @function
167
+ */
168
+ createLongPollingHandler,
169
+
170
+ /**
171
+ * Extract client ID from request cookies (POST handler utility).
172
+ * Returns null if no client ID cookie is present.
173
+ * @function
174
+ * @param {http.IncomingMessage} req - The HTTP request
175
+ * @returns {string|null} The client ID or null
176
+ */
177
+ getClientId,
178
+
179
+ /**
180
+ * Get or create a client ID from request/response (GET handler utility).
181
+ * Sets a cookie if no client ID exists.
182
+ * @function
183
+ * @param {http.IncomingMessage} req - The HTTP request
184
+ * @param {http.ServerResponse} res - The HTTP response
185
+ * @returns {string} The client ID
186
+ */
187
+ ensureClientId,
188
+ };