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
package/server/index.js
CHANGED
|
@@ -1,91 +1,342 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* api-ape
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Server Setup
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
2
|
+
* @fileoverview api-ape Server Entry Point
|
|
3
|
+
*
|
|
4
|
+
* This is the main entry point for the api-ape framework. It provides a unified
|
|
5
|
+
* interface for both server setup and client API calls, with intelligent detection
|
|
6
|
+
* of the intended usage mode.
|
|
7
|
+
*
|
|
8
|
+
* ## Dual-Purpose Design
|
|
9
|
+
*
|
|
10
|
+
* The `ape` function serves two purposes:
|
|
11
|
+
* 1. **Server Setup**: When called with an HTTP server, it initializes WebSocket handling
|
|
12
|
+
* 2. **API Call**: When called with data, it makes an API call to the `/ape` endpoint
|
|
13
|
+
*
|
|
14
|
+
* This design allows the same import to be used for both server-side setup
|
|
15
|
+
* and making API calls from Node.js code.
|
|
16
|
+
*
|
|
17
|
+
* ## Usage Patterns
|
|
18
|
+
*
|
|
19
|
+
* ### CommonJS
|
|
20
|
+
* ```javascript
|
|
21
|
+
* const api = require('api-ape') // Get client proxy (default export)
|
|
22
|
+
* const { ape } = require('api-ape') // Get server/API function
|
|
23
|
+
*
|
|
24
|
+
* // Server setup (first arg is HTTP server)
|
|
25
|
+
* ape(httpServer, { where: 'api' })
|
|
26
|
+
*
|
|
27
|
+
* // API call (first arg is data)
|
|
28
|
+
* api.users({ action: 'list' })
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* ### ES Modules
|
|
32
|
+
* ```javascript
|
|
33
|
+
* import api, { ape } from 'api-ape'
|
|
34
|
+
*
|
|
35
|
+
* // Same usage as CommonJS
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* ## Server Detection
|
|
39
|
+
*
|
|
40
|
+
* The `ape` function detects HTTP servers by checking for:
|
|
41
|
+
* - `.listen()` method (http.Server)
|
|
42
|
+
* - `.on()` method (EventEmitter)
|
|
43
|
+
* - `.address()` method (bound server)
|
|
44
|
+
*
|
|
45
|
+
* If none of these are present, the call is treated as an API request.
|
|
46
|
+
*
|
|
47
|
+
* ## Exports
|
|
48
|
+
*
|
|
49
|
+
* | Export | Type | Description |
|
|
50
|
+
* |-------------|----------|------------------------------------------|
|
|
51
|
+
* | `default` | Proxy | Client API proxy for making calls |
|
|
52
|
+
* | `ape` | Function | Server setup or API call to /ape |
|
|
53
|
+
* | `api` | Proxy | Same as default export |
|
|
54
|
+
* | `broadcast` | Function | Send message to all connected clients |
|
|
55
|
+
* | `publish` | Function | Send message to channel subscribers |
|
|
56
|
+
* | `clients` | Map | Read-only map of connected clients |
|
|
57
|
+
*
|
|
58
|
+
* @module server/index
|
|
59
|
+
* @see {@link module:server/lib/main} for server initialization
|
|
60
|
+
* @see {@link module:server/lib/broadcast} for broadcast functionality
|
|
61
|
+
* @see {@link module:server/client} for Node.js client API
|
|
62
|
+
*
|
|
63
|
+
* @example <caption>Basic Server Setup</caption>
|
|
64
|
+
* const http = require('http')
|
|
65
|
+
* const { ape } = require('api-ape')
|
|
66
|
+
*
|
|
67
|
+
* const server = http.createServer((req, res) => {
|
|
68
|
+
* res.end('Hello World')
|
|
69
|
+
* })
|
|
70
|
+
*
|
|
71
|
+
* // Initialize api-ape with your API directory
|
|
72
|
+
* ape(server, { where: 'api' })
|
|
73
|
+
*
|
|
74
|
+
* server.listen(3000, () => {
|
|
75
|
+
* console.log('Server running on port 3000')
|
|
76
|
+
* })
|
|
77
|
+
*
|
|
78
|
+
* @example <caption>Express Integration</caption>
|
|
79
|
+
* const express = require('express')
|
|
80
|
+
* const { ape } = require('api-ape')
|
|
81
|
+
*
|
|
82
|
+
* const app = express()
|
|
83
|
+
* const server = app.listen(3000)
|
|
84
|
+
*
|
|
85
|
+
* ape(server, {
|
|
86
|
+
* where: 'api',
|
|
87
|
+
* onConnect: (socket, req, send) => {
|
|
88
|
+
* console.log('Client connected')
|
|
89
|
+
* return {
|
|
90
|
+
* embed: { userId: getUserId(req) },
|
|
91
|
+
* onDisconnect: () => console.log('Client disconnected')
|
|
92
|
+
* }
|
|
93
|
+
* }
|
|
94
|
+
* })
|
|
95
|
+
*
|
|
96
|
+
* @example <caption>Broadcasting to Clients</caption>
|
|
97
|
+
* const { ape, broadcast, clients } = require('api-ape')
|
|
98
|
+
*
|
|
99
|
+
* // Broadcast to all connected clients
|
|
100
|
+
* broadcast('notification', { message: 'Server update!' })
|
|
101
|
+
*
|
|
102
|
+
* // Broadcast to all except sender
|
|
103
|
+
* broadcast('chat', { text: 'Hello' }, excludeClientId)
|
|
104
|
+
*
|
|
105
|
+
* // Check connected clients
|
|
106
|
+
* console.log(`${clients.size} clients connected`)
|
|
107
|
+
*
|
|
108
|
+
* @example <caption>Making API Calls from Node.js</caption>
|
|
109
|
+
* const api = require('api-ape')
|
|
110
|
+
*
|
|
111
|
+
* // Configure connection (if not on same server)
|
|
112
|
+
* api.connect('localhost', 3000)
|
|
113
|
+
*
|
|
114
|
+
* // Make API calls
|
|
115
|
+
* const users = await api.users.list()
|
|
116
|
+
* const result = await api.chat({ message: 'Hello!' })
|
|
22
117
|
*/
|
|
23
118
|
|
|
24
|
-
const serverApe = require(
|
|
25
|
-
const {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
119
|
+
const serverApe = require("./lib/main");
|
|
120
|
+
const { clients, publish } = require("./lib/broadcast");
|
|
121
|
+
const createPublishProxy = require("./lib/broadcast/publishProxy");
|
|
122
|
+
const api = require("./client");
|
|
123
|
+
const { _queueOrSend } = require("./client");
|
|
28
124
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Chained publish proxy for fluent syntax
|
|
127
|
+
* @type {Proxy}
|
|
128
|
+
* @private
|
|
129
|
+
*/
|
|
130
|
+
const publishProxy = createPublishProxy();
|
|
32
131
|
|
|
33
132
|
/**
|
|
34
|
-
*
|
|
133
|
+
* Attach pub/sub utilities to the serverApe function for convenience access
|
|
134
|
+
*
|
|
135
|
+
* This allows users to access pub/sub functionality directly from the
|
|
136
|
+
* server setup function: `ape.publish.channel(data)`
|
|
137
|
+
*
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
serverApe.clients = clients;
|
|
141
|
+
serverApe.publish = publishProxy;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if a value looks like an HTTP server instance
|
|
145
|
+
*
|
|
146
|
+
* Detects HTTP servers by checking for common server methods.
|
|
147
|
+
* This heuristic works for:
|
|
148
|
+
* - Node.js http.Server
|
|
149
|
+
* - Express app.listen() result
|
|
150
|
+
* - Koa server
|
|
151
|
+
* - Fastify server
|
|
152
|
+
* - Bun.serve() result
|
|
153
|
+
*
|
|
154
|
+
* @param {any} val - Value to check
|
|
155
|
+
* @returns {boolean} True if the value appears to be an HTTP server
|
|
156
|
+
* @private
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* isHttpServer(http.createServer()) // true
|
|
160
|
+
* isHttpServer(app.listen(3000)) // true (Express)
|
|
161
|
+
* isHttpServer({ data: 'payload' }) // false
|
|
162
|
+
* isHttpServer(null) // false
|
|
35
163
|
*/
|
|
36
164
|
function isHttpServer(val) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
165
|
+
return (
|
|
166
|
+
val &&
|
|
167
|
+
typeof val === "object" &&
|
|
168
|
+
(typeof val.listen === "function" ||
|
|
169
|
+
typeof val.on === "function" ||
|
|
170
|
+
typeof val.address === "function")
|
|
171
|
+
);
|
|
42
172
|
}
|
|
43
173
|
|
|
44
174
|
/**
|
|
45
|
-
* Dual-purpose ape function
|
|
46
|
-
*
|
|
47
|
-
*
|
|
175
|
+
* Dual-purpose ape function for server setup or API calls
|
|
176
|
+
*
|
|
177
|
+
* This function intelligently detects its intended use:
|
|
178
|
+
*
|
|
179
|
+
* ## Server Setup Mode
|
|
180
|
+
* When the first argument is an HTTP server, initializes api-ape:
|
|
181
|
+
* - Sets up WebSocket handling on the server
|
|
182
|
+
* - Loads controllers from the specified directory
|
|
183
|
+
* - Configures connection lifecycle callbacks
|
|
184
|
+
* - Enables file transfer handling
|
|
185
|
+
*
|
|
186
|
+
* ## API Call Mode
|
|
187
|
+
* When the first argument is not a server, makes an API call:
|
|
188
|
+
* - Sends the data to the `/ape` endpoint
|
|
189
|
+
* - Returns a Promise that resolves with the response
|
|
190
|
+
*
|
|
191
|
+
* @param {http.Server|Object|any} firstArg - HTTP server for setup, or data for API call
|
|
192
|
+
* @param {...any} rest - Additional arguments (options for server setup)
|
|
193
|
+
* @returns {Object|Promise<any>} Server info object (setup mode) or response Promise (API mode)
|
|
194
|
+
*
|
|
195
|
+
* @example <caption>Server Setup</caption>
|
|
196
|
+
* const server = http.createServer()
|
|
197
|
+
*
|
|
198
|
+
* ape(server, {
|
|
199
|
+
* where: 'api', // Directory containing API controllers
|
|
200
|
+
* onConnect: (socket, req, send) => ({
|
|
201
|
+
* embed: { userId: '123' }, // Values available in all controllers
|
|
202
|
+
* onReceive: (queryId, data, type) => { },
|
|
203
|
+
* onSend: (data, type) => { },
|
|
204
|
+
* onError: (errString) => { },
|
|
205
|
+
* onDisconnect: () => { }
|
|
206
|
+
* }),
|
|
207
|
+
* fileTransferOptions: {
|
|
208
|
+
* startTimeout: 60000, // Timeout before upload starts
|
|
209
|
+
* completeTimeout: 60000 // Timeout for upload completion
|
|
210
|
+
* }
|
|
211
|
+
* })
|
|
212
|
+
*
|
|
213
|
+
* @example <caption>API Call</caption>
|
|
214
|
+
* // Calls the /ape endpoint with the provided data
|
|
215
|
+
* const result = await ape({ action: 'ping' })
|
|
48
216
|
*/
|
|
49
217
|
function ape(firstArg, ...rest) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
218
|
+
if (isHttpServer(firstArg)) {
|
|
219
|
+
// Server setup mode
|
|
220
|
+
return serverApe(firstArg, ...rest);
|
|
221
|
+
}
|
|
222
|
+
// API call mode - directly call the internal queueOrSend
|
|
223
|
+
return _queueOrSend("/ape", firstArg);
|
|
56
224
|
}
|
|
57
225
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
module.exports = api
|
|
76
|
-
|
|
77
|
-
// Also export named exports for ESM compatibility
|
|
78
|
-
module.exports.ape = ape
|
|
79
|
-
module.exports.api = api
|
|
80
|
-
module.exports.broadcast = broadcast
|
|
81
|
-
module.exports.clients = clients
|
|
82
|
-
module.exports.default = api
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Publish a message to all subscribers of a channel
|
|
228
|
+
*
|
|
229
|
+
* Supports both chained syntax and direct function call:
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* // Chained syntax (v2)
|
|
233
|
+
* ape.publish.news.banking({ headline: 'Market Update' })
|
|
234
|
+
* ape.publish.health({ status: 'ok', uptime: process.uptime() })
|
|
235
|
+
*
|
|
236
|
+
* // Direct call (legacy, still supported)
|
|
237
|
+
* ape.publish('/health', { status: 'ok', uptime: process.uptime() })
|
|
238
|
+
*
|
|
239
|
+
* // Clients subscribed to '/health' will receive:
|
|
240
|
+
* // { type: '/health', data: { status: 'ok', uptime: 12345 } }
|
|
241
|
+
*/
|
|
242
|
+
ape.publish = publishProxy;
|
|
87
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Read-only Map of connected clients
|
|
246
|
+
*
|
|
247
|
+
* Provides access to all currently connected clients. Each client entry
|
|
248
|
+
* includes methods for sending messages and accessing client metadata.
|
|
249
|
+
*
|
|
250
|
+
* The Map is read-only - attempts to modify it will throw an error.
|
|
251
|
+
* Client connections are managed internally by api-ape.
|
|
252
|
+
*
|
|
253
|
+
* @type {Map<string, ClientWrapper>}
|
|
254
|
+
*
|
|
255
|
+
* @property {number} size - Number of connected clients
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* // Check number of connected clients
|
|
259
|
+
* console.log(`${ape.clients.size} clients online`)
|
|
260
|
+
*
|
|
261
|
+
* // Iterate over clients
|
|
262
|
+
* ape.clients.forEach((client, clientId) => {
|
|
263
|
+
* console.log(`Client ${clientId}: ${client.agent.browser?.name}`)
|
|
264
|
+
* })
|
|
265
|
+
*
|
|
266
|
+
* // Send to specific client
|
|
267
|
+
* const client = ape.clients.get(clientId)
|
|
268
|
+
* if (client) {
|
|
269
|
+
* client.send('notification', { message: 'Hello!' })
|
|
270
|
+
* }
|
|
271
|
+
*
|
|
272
|
+
* // Access client properties
|
|
273
|
+
* for (const client of ape.clients.values()) {
|
|
274
|
+
* console.log({
|
|
275
|
+
* clientId: client.clientId,
|
|
276
|
+
* sessionId: client.sessionId,
|
|
277
|
+
* embed: client.embed,
|
|
278
|
+
* agent: client.agent
|
|
279
|
+
* })
|
|
280
|
+
* }
|
|
281
|
+
*/
|
|
282
|
+
ape.clients = clients;
|
|
88
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Store reference to original serverApe for direct access if needed
|
|
286
|
+
*
|
|
287
|
+
* This is primarily for internal use or advanced scenarios where
|
|
288
|
+
* direct access to the server initialization function is required.
|
|
289
|
+
*
|
|
290
|
+
* @type {Function}
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
293
|
+
ape._serverApe = serverApe;
|
|
89
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Define ape on the proxy's target so it can be destructured
|
|
297
|
+
*
|
|
298
|
+
* The proxy handler checks Reflect.has first, so this property
|
|
299
|
+
* will be found when destructuring: `const { ape } = require('api-ape')`
|
|
300
|
+
*
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
Object.defineProperty(api, "ape", {
|
|
304
|
+
value: ape,
|
|
305
|
+
writable: false,
|
|
306
|
+
enumerable: true,
|
|
307
|
+
configurable: false,
|
|
308
|
+
});
|
|
90
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Default export: the client API proxy
|
|
312
|
+
*
|
|
313
|
+
* This allows the common pattern: `const api = require('api-ape')`
|
|
314
|
+
* The proxy intercepts property access to build API endpoint paths.
|
|
315
|
+
*
|
|
316
|
+
* @type {Proxy}
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* const api = require('api-ape')
|
|
320
|
+
*
|
|
321
|
+
* // These are equivalent:
|
|
322
|
+
* api.users({ action: 'list' }) // Calls /users
|
|
323
|
+
* api.users.profile({ id: 1 }) // Calls /users/profile
|
|
324
|
+
* api.chat('/room1', { msg: 'Hi' }) // Calls /chat/room1
|
|
325
|
+
*/
|
|
326
|
+
module.exports = api;
|
|
91
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Named exports for ES Module compatibility and destructuring
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* // CommonJS destructuring
|
|
333
|
+
* const { ape, clients } = require('api-ape')
|
|
334
|
+
*
|
|
335
|
+
* // ES Modules
|
|
336
|
+
* import api, { ape, clients } from 'api-ape'
|
|
337
|
+
*/
|
|
338
|
+
module.exports.ape = ape;
|
|
339
|
+
module.exports.api = api;
|
|
340
|
+
module.exports.publish = publishProxy;
|
|
341
|
+
module.exports.clients = clients;
|
|
342
|
+
module.exports.default = api;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Server Lib Module
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The lib module is the core implementation of api-ape's server functionality. It orchestrates everything needed to transform a standard HTTP server into a real-time WebSocket API server with automatic controller routing, client management, and message handling.
|
|
6
|
+
|
|
7
|
+
**Key capabilities:**
|
|
8
|
+
|
|
9
|
+
- **Server initialization** — Detect runtime (Node.js, Bun, Deno) and configure appropriate handlers
|
|
10
|
+
- **Controller loading** — Recursively load JavaScript files from a folder and map them to API endpoints
|
|
11
|
+
- **WebSocket management** — Handle connections, message routing, and client lifecycle
|
|
12
|
+
- **Client tracking** — Maintain registry of connected clients with broadcast and pub/sub capabilities
|
|
13
|
+
- **HTTP fallback** — Provide long-polling transport when WebSocket is unavailable
|
|
14
|
+
- **File transfers** — Manage binary upload/download with streaming support
|
|
15
|
+
- **Runtime abstraction** — Unified API across Node.js, Bun, and Deno
|
|
16
|
+
|
|
17
|
+
This module is the engine that powers the "drop a file, get an endpoint" developer experience.
|
|
18
|
+
|
|
19
|
+
> **Contributing?** See [`files.md`](./files.md) for directory structure and file descriptions.
|
|
20
|
+
|
|
21
|
+
## See Also
|
|
22
|
+
|
|
23
|
+
- [`../README.md`](../README.md) — Server overview and API reference
|
|
24
|
+
- [`runtimes/README.md`](./runtimes/README.md) — Runtime-specific integrations
|
|
25
|
+
- [`ws/README.md`](./ws/README.md) — WebSocket polyfill documentation
|
|
26
|
+
- [`longPolling/README.md`](./longPolling/README.md) — HTTP fallback handlers
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Client Tracking for api-ape Server
|
|
3
|
+
*
|
|
4
|
+
* Manages connected WebSocket clients with a read-only proxy for external access.
|
|
5
|
+
*
|
|
6
|
+
* @module server/lib/broadcast/clients
|
|
7
|
+
* @see {@link module:server/lib/broadcast} for the main broadcast module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const createSendProxy = require("./sendProxy");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal Map of connected clients
|
|
14
|
+
* @type {Map<string, ClientWrapper>}
|
|
15
|
+
* @private
|
|
16
|
+
*/
|
|
17
|
+
const _clients = new Map();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a ClientWrapper that exposes client info and send function
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} clientInfo - Raw client info from wiring/longPolling
|
|
23
|
+
* @returns {Object} Public client interface
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
function createClientWrapper(clientInfo) {
|
|
27
|
+
return {
|
|
28
|
+
get clientId() {
|
|
29
|
+
return clientInfo.clientId;
|
|
30
|
+
},
|
|
31
|
+
get sessionId() {
|
|
32
|
+
return clientInfo.sessionId || null;
|
|
33
|
+
},
|
|
34
|
+
get embed() {
|
|
35
|
+
return clientInfo.embed || {};
|
|
36
|
+
},
|
|
37
|
+
get agent() {
|
|
38
|
+
return clientInfo.agent || {};
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* Get current auth state for this client
|
|
42
|
+
* @returns {Object|null} Auth state or null if not tracked
|
|
43
|
+
*/
|
|
44
|
+
get authState() {
|
|
45
|
+
if (clientInfo.socketAuth) {
|
|
46
|
+
return clientInfo.socketAuth.getState();
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Check if client is authenticated
|
|
52
|
+
* @returns {boolean} Whether client is authenticated
|
|
53
|
+
*/
|
|
54
|
+
get isAuthenticated() {
|
|
55
|
+
if (clientInfo.socketAuth) {
|
|
56
|
+
return clientInfo.socketAuth.isAuthenticated();
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
},
|
|
60
|
+
/**
|
|
61
|
+
* Get auth tier for this client
|
|
62
|
+
* @returns {number} Auth tier (0-3)
|
|
63
|
+
*/
|
|
64
|
+
get authTier() {
|
|
65
|
+
if (clientInfo.socketAuth) {
|
|
66
|
+
return clientInfo.socketAuth.getTier();
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Send a message to this client
|
|
72
|
+
*
|
|
73
|
+
* Supports both direct and chained syntax:
|
|
74
|
+
* - client.send('news/banking', data)
|
|
75
|
+
* - client.send.news.banking(data)
|
|
76
|
+
*
|
|
77
|
+
* @type {Function & Proxy}
|
|
78
|
+
*/
|
|
79
|
+
send: createSendProxy((type, data) => {
|
|
80
|
+
if (clientInfo.send) {
|
|
81
|
+
try {
|
|
82
|
+
clientInfo.send(false, type, data, false);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
/* istanbul ignore next */
|
|
85
|
+
console.error(
|
|
86
|
+
`📢 send failed for ${clientInfo.clientId}:`,
|
|
87
|
+
e.message,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Read-only proxy for the clients Map
|
|
97
|
+
*
|
|
98
|
+
* Allows read operations but blocks modifications.
|
|
99
|
+
* @type {Map<string, ClientWrapper>}
|
|
100
|
+
*/
|
|
101
|
+
const clients = new Proxy(_clients, {
|
|
102
|
+
/**
|
|
103
|
+
* Proxy get handler - intercepts property access
|
|
104
|
+
* @param {Map} target - The underlying clients Map
|
|
105
|
+
* @param {string|symbol} prop - Property being accessed
|
|
106
|
+
* @returns {any} The property value or bound method
|
|
107
|
+
*/
|
|
108
|
+
get(target, prop) {
|
|
109
|
+
if (prop === "set" || prop === "delete" || prop === "clear") {
|
|
110
|
+
return () => {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`ape.clients.${prop}() is not allowed. Clients are managed internally by api-ape.`,
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (prop === "size") {
|
|
117
|
+
return target.size;
|
|
118
|
+
}
|
|
119
|
+
const value = target[prop];
|
|
120
|
+
if (typeof value === "function") {
|
|
121
|
+
return value.bind(target);
|
|
122
|
+
}
|
|
123
|
+
/* istanbul ignore next */
|
|
124
|
+
return value;
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Add a client to the connected clients map
|
|
130
|
+
*
|
|
131
|
+
* @param {Object} clientInfo - Client information object
|
|
132
|
+
* @param {Function} [onAdd] - Optional callback after adding
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
135
|
+
function addClient(clientInfo, onAdd) {
|
|
136
|
+
const wrapper = createClientWrapper(clientInfo);
|
|
137
|
+
_clients.set(clientInfo.clientId, wrapper);
|
|
138
|
+
wrapper._raw = clientInfo;
|
|
139
|
+
console.log(
|
|
140
|
+
`🟢 Client added: ${clientInfo.clientId} (total: ${_clients.size})`,
|
|
141
|
+
);
|
|
142
|
+
if (onAdd) onAdd(clientInfo.clientId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Remove a client from the connected clients map
|
|
147
|
+
*
|
|
148
|
+
* @param {string|Object} clientIdOrInfo - Client ID or info object with clientId
|
|
149
|
+
* @param {Function} [onRemove] - Optional cleanup callback (receives clientId)
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
function removeClient(clientIdOrInfo, onRemove) {
|
|
153
|
+
const clientId =
|
|
154
|
+
typeof clientIdOrInfo === "string"
|
|
155
|
+
? clientIdOrInfo
|
|
156
|
+
: clientIdOrInfo.clientId;
|
|
157
|
+
|
|
158
|
+
if (_clients.has(clientId)) {
|
|
159
|
+
_clients.delete(clientId);
|
|
160
|
+
if (onRemove) onRemove(clientId);
|
|
161
|
+
console.log(`🔴 Client removed: ${clientId} (total: ${_clients.size})`);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(
|
|
164
|
+
`⚠️ Client not found for removal: ${clientId} (total: ${_clients.size})`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Update a client's embed values after onConnect resolves
|
|
171
|
+
*
|
|
172
|
+
* @param {string} clientId - The client's unique identifier
|
|
173
|
+
* @param {Object} embed - The embed values from onConnect
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
function updateClientEmbed(clientId, embed) {
|
|
177
|
+
const wrapper = _clients.get(clientId);
|
|
178
|
+
if (wrapper && wrapper._raw) {
|
|
179
|
+
wrapper._raw.embed = embed;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Update a client's send function after it's ready
|
|
185
|
+
*
|
|
186
|
+
* @param {string} clientId - The client's unique identifier
|
|
187
|
+
* @param {Function} send - The send function for this client
|
|
188
|
+
* @private
|
|
189
|
+
*/
|
|
190
|
+
function updateClientSend(clientId, send) {
|
|
191
|
+
const wrapper = _clients.get(clientId);
|
|
192
|
+
if (wrapper && wrapper._raw) {
|
|
193
|
+
wrapper._raw.send = send;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Update a client's auth state manager
|
|
199
|
+
*
|
|
200
|
+
* @param {string} clientId - The client's unique identifier
|
|
201
|
+
* @param {Object} socketAuth - Socket auth manager instance
|
|
202
|
+
* @private
|
|
203
|
+
*/
|
|
204
|
+
function updateClientAuth(clientId, socketAuth) {
|
|
205
|
+
const wrapper = _clients.get(clientId);
|
|
206
|
+
if (wrapper && wrapper._raw) {
|
|
207
|
+
wrapper._raw.socketAuth = socketAuth;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = {
|
|
212
|
+
clients,
|
|
213
|
+
_clients,
|
|
214
|
+
addClient,
|
|
215
|
+
removeClient,
|
|
216
|
+
updateClientEmbed,
|
|
217
|
+
updateClientSend,
|
|
218
|
+
updateClientAuth,
|
|
219
|
+
};
|