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/index.js CHANGED
@@ -1,91 +1,342 @@
1
1
  /**
2
- * api-ape server entry point
3
- *
4
- * V3 Usage:
5
- * const api = require('api-ape') // Get client proxy (default)
6
- * const { ape } = require('api-ape') // Get server/API function
7
- *
8
- * // ESM
9
- * import api, { ape } from 'api-ape'
10
- *
11
- * Server Setup:
12
- * ape(server, { where: 'api' }) // First arg is HTTP server setup
13
- *
14
- * API Call:
15
- * api.ape({ data: 'foo' }) // Calls /ape endpoint
16
- * // or equivalently:
17
- * ape({ data: 'foo' }) // Also calls /ape (detects it's not a server)
18
- *
19
- * The ape function intelligently detects:
20
- * - HTTP server (has .listen/.on) → Server setup mode
21
- * - Anything else API call to /ape
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('./lib/main')
25
- const { broadcast, clients } = require('./lib/broadcast')
26
- const api = require('./client')
27
- const { _queueOrSend } = require('./client')
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
- // Attach broadcast utilities to the serverApe function
30
- serverApe.broadcast = broadcast
31
- serverApe.clients = clients
125
+ /**
126
+ * Chained publish proxy for fluent syntax
127
+ * @type {Proxy}
128
+ * @private
129
+ */
130
+ const publishProxy = createPublishProxy();
32
131
 
33
132
  /**
34
- * Check if value looks like an HTTP server
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
- return val && typeof val === 'object' && (
38
- typeof val.listen === 'function' ||
39
- typeof val.on === 'function' ||
40
- typeof val.address === 'function'
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
- * - Called with HTTP server → Setup server
47
- * - Called with anything else API call to /ape
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
- if (isHttpServer(firstArg)) {
51
- // Server setup mode
52
- return serverApe(firstArg, ...rest)
53
- }
54
- // API call mode - directly call the internal queueOrSend
55
- return _queueOrSend('/ape', firstArg)
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
- // Copy properties from serverApe to ape
59
- ape.broadcast = broadcast
60
- ape.clients = clients
61
-
62
- // Store original serverApe for direct access if needed
63
- ape._serverApe = serverApe
64
-
65
- // Define ape on the proxy's target so it can be destructured
66
- // The proxy handler checks Reflect.has first, so this will be found
67
- Object.defineProperty(api, 'ape', {
68
- value: ape,
69
- writable: false,
70
- enumerable: true,
71
- configurable: false
72
- })
73
-
74
- // Default export: the proxy (so const api = require('api-ape') works)
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
+ };