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.
- package/README.md +59 -572
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -203
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +31 -6
- package/server/README.md +272 -67
- package/server/adapters/README.md +23 -14
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +322 -71
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -219
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -224
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -311
- 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
|
-
*
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 = {
|
|
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
|
+
};
|