api-ape 3.0.1 → 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 +58 -570
  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 -202
  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 +32 -7
  44. package/server/README.md +287 -53
  45. package/server/adapters/README.md +28 -19
  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 +332 -27
  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 -221
  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 -225
  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 -308
  186. package/server/lib/broadcast.js +0 -146
package/README.md CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/api-ape.svg)](https://www.npmjs.com/package/api-ape)
4
4
  [![GitHub issues](https://img.shields.io/github/issues/codemeasandwich/api-ape)](https://github.com/codemeasandwich/api-ape/issues)
5
- [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](#zero-dependencies)
5
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](server/README.md#zero-dependency-websocket)
6
6
  [![Dependabot](https://img.shields.io/dependabot/codemeasandwich/api-ape)](https://github.com/codemeasandwich/api-ape/security/dependabot)
7
- [![CSRF protected](https://img.shields.io/badge/CSRF%20🚷-protected-green.svg)](#csrf-protection)
7
+ [![CSRF protected](https://img.shields.io/badge/CSRF%20🚷-protected-green.svg)](client/README.md#security)
8
8
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/api-ape)](https://bundlephobia.com/package/api-ape)
9
- [![JJS Encoding](https://img.shields.io/badge/encoding-JJS-blue.svg)](#jjs-encoding)
9
+ [![JSS Encoding](https://img.shields.io/badge/encoding-JSS-blue.svg)](server/README.md)
10
10
  [![license](https://img.shields.io/npm/l/api-ape.svg)](https://github.com/codemeasandwich/api-ape/blob/main/LICENSE)
11
11
 
12
+ ![api-ape logo](assets/apiApeLogo.jpg)
13
+
12
14
  **Remote Procedure Events (RPE)** — A lightweight WebSocket framework for building real-time APIs. Call server functions from the browser like local methods. Get real-time broadcasts with zero setup.
13
15
 
14
16
  ---
@@ -17,10 +19,6 @@
17
19
 
18
20
  ```bash
19
21
  npm install api-ape
20
- # or
21
- pnpm add api-ape
22
- # or
23
- yarn add api-ape
24
22
  ```
25
23
 
26
24
  **Requirements:** Node.js 14+ (for server), modern browsers (for client)
@@ -29,33 +27,18 @@ yarn add api-ape
29
27
 
30
28
  ## Quick Start
31
29
 
32
- ### Server (Node.js)
30
+ ### Server (`ape`)
33
31
 
34
32
  ```js
35
33
  const { createServer } = require('http')
36
34
  const { ape } = require('api-ape')
37
35
 
38
36
  const server = createServer()
39
-
40
- // Wire up api-ape - loads controllers from ./api folder
41
- ape(server, { where: 'api' })
42
-
37
+ ape(server, { where: 'api' }) // Load controllers from ./api folder
43
38
  server.listen(3000)
44
39
  ```
45
40
 
46
- **With Express:**
47
- ```js
48
- const express = require('express')
49
- const { ape } = require('api-ape')
50
-
51
- const app = express()
52
- const server = app.listen(3000) // Get the HTTP server
53
-
54
- // Pass the HTTP server (not the Express app)
55
- ape(server, { where: 'api' })
56
- ```
57
-
58
- ### Create a Controller
41
+ ### Controllers
59
42
 
60
43
  Drop a file in your `api/` folder — it automatically becomes an endpoint:
61
44
 
@@ -64,593 +47,98 @@ Drop a file in your `api/` folder — it automatically becomes an endpoint:
64
47
  module.exports = function(name) {
65
48
  return `Hello, ${name}!`
66
49
  }
67
- ```
68
50
 
69
- ### Client (Browser)
51
+ // api/message.js
52
+ module.exports = function(text) {
53
+ this.broadcastOthers('message', { text, from: this.clientId })
54
+ return { sent: true }
55
+ }
56
+ ```
70
57
 
71
- Include the bundled client and start calling:
58
+ ### Client (`api`)
72
59
 
60
+ **Browser:**
73
61
  ```html
74
62
  <script src="/api/ape.js"></script>
75
63
  <script>
76
- // Call server functions like local methods
77
- const result = await api.hello('World')
78
- console.log(result) // "Hello, World!"
79
-
80
- // Listen for broadcasts
81
- api.on('message', ({ data }) => {
82
- console.log('New message:', data)
83
- })
64
+ const result = await api.hello('World') // "Hello, World!"
65
+
66
+ // Subscribe to messages (pass a callback)
67
+ const unsub = api.message(data => console.log(data))
84
68
  </script>
85
69
  ```
86
70
 
87
- ### Client (React, Vue, etc.)
88
-
89
- With bundlers, use the unified import — no async setup needed:
90
-
71
+ **Bundlers (React, Vue, etc.):**
91
72
  ```js
92
73
  import api from 'api-ape'
93
74
 
94
- // Just use it! Calls are buffered until connected.
95
75
  const result = await api.hello('World')
96
76
 
97
- // Listen for broadcasts
98
- api.on('message', ({ data }) => console.log(data))
99
-
100
- // Track connection state
101
- api.onConnectionChange((state) => {
102
- console.log('Connection:', state)
103
- // 'offline' | 'walled' | 'disconnected' | 'connecting' | 'connected'
104
- })
77
+ // Subscribe (pass callback) vs RPC call (pass data)
78
+ const unsub = api.message(data => console.log(data)) // Subscribe
79
+ await api.message({ text: 'Hello' }) // RPC call
105
80
  ```
106
81
 
107
- **That's it!** Your server function is now callable from the browser.
108
-
109
82
  ---
110
83
 
111
84
  ## Key Concepts
112
85
 
113
- * **Auto-wiring** — Drop JS files in a folder, they become API endpoints automatically
114
- * **Real-time broadcasts** — Built-in `broadcast()` and `broadcastOthers()` methods for pushing to clients
115
- * **Promise-based calls** — Chainable paths like `api.users.list()` map to `api/users/list.js`
116
- * **Automatic reconnection** — Client auto-reconnects on disconnect with exponential backoff
117
- * **HTTP streaming fallback** — Automatically falls back to long polling when WebSockets are blocked
118
- * **JJS Encoding** — Extended JSON supporting Date, RegExp, Error, Set, Map, undefined, and circular refs
119
- * **Connection lifecycle hooks** — Customize behavior on connect, receive, send, error, and disconnect
120
- * **🌲 Forest** — Distributed mesh for horizontal scaling across multiple servers
121
-
122
- ---
123
-
124
- ## 🌲 Forest: Distributed Mesh
125
-
126
- **Forest** enables horizontal scaling by coordinating multiple api-ape servers through a shared database. Messages are routed directly to the server hosting the destination client — no broadcast spam.
127
-
128
- ```js
129
- import { createClient } from 'redis';
130
- const redis = createClient();
131
- await redis.connect();
132
-
133
- // Join the mesh — that's it!
134
- ape.joinVia(redis);
135
- ```
136
-
137
- ### Supported Backends
138
-
139
- | Backend | Push Mechanism | Best For |
140
- |---------|---------------|----------|
141
- | **Redis** | PUB/SUB | Most deployments |
142
- | **MongoDB** | Change Streams | Mongo-native stacks |
143
- | **PostgreSQL** | LISTEN/NOTIFY | SQL shops |
144
- | **Supabase** | Realtime | Supabase users |
145
- | **Firebase** | Native push | Serverless/edge |
146
-
147
- ### How It Works
148
-
149
- ```
150
- ┌─────────────┐ ┌─────────────┐
151
- │ Server A │ │ Server B │
152
- │ client-1 │ │ client-2 │
153
- └──────┬──────┘ └──────▲──────┘
154
- │ │
155
- │ 1. sendTo("client-2") │
156
- │ → lookup: client-2 → srv-B │
157
- │ │
158
- │ 2. channels.push("srv-B", msg) │
159
- └──────────┬───────────────────────┘
160
-
161
- ┌──────▼──────┐
162
- │ Database │
163
- │ (message │
164
- │ bus) │
165
- └─────────────┘
166
- ```
167
-
168
- APE creates its own namespaced keys/tables (`ape:*` or `ape_*`). No schema conflicts with your data.
169
-
170
- 👉 **[See detailed Forest documentation](server/README.md#forest-distributed-mesh)**
171
-
172
- ---
173
-
174
- ## API Reference
175
-
176
- ### Server
177
-
178
- #### `ape(server, options)`
179
-
180
- Initialize api-ape on a Node.js HTTP/HTTPS server.
181
-
182
- | Option | Type | Description |
183
- |--------|------|-------------|
184
- | `server` | `http.Server` | Node.js HTTP or HTTPS server instance |
185
- | `where` | `string` | Directory containing controller files (default: `'api'`) |
186
- | `onConnect` | `function` | Connection lifecycle hook (see [Connection Lifecycle](#connection-lifecycle)) |
187
-
188
- #### Controller Context (`this`)
189
-
190
- Inside controller functions, `this` provides:
191
-
192
- | Property | Description |
193
- |----------|-------------|
194
- | `this.broadcast(type, data)` | Send to **ALL** connected clients |
195
- | `this.broadcastOthers(type, data)` | Send to all **EXCEPT** the caller |
196
- | `this.clientId` | Unique ID of the calling client (generated by api-ape) |
197
- | `this.sessionId` | Session ID from cookie (set by outer framework, may be `null`) |
198
- | `this.req` | Original HTTP request |
199
- | `this.socket` | WebSocket instance |
200
- | `this.agent` | Parsed user-agent (browser, OS, device) |
201
-
202
- #### `ape.clients`
203
-
204
- A read-only Map of connected clients. Each client provides:
205
-
206
- | Property | Description |
207
- |----------|-------------|
208
- | `clientId` | Unique client identifier |
209
- | `sessionId` | Session ID from cookie (may be `null`) |
210
- | `embed` | Embedded values from onConnect |
211
- | `agent` | Parsed user-agent (browser, OS, device) |
212
- | `sendTo(type, data)` | Send a message to this specific client |
213
-
214
- ### Client
215
-
216
- #### `api.<path>.<method>(...args)`
217
-
218
- Call a server function. Returns a Promise.
219
-
220
- ```js
221
- // Calls api/users/list.js
222
- const users = await api.users.list()
223
-
224
- // Calls api/users/create.js with data
225
- const user = await api.users.create({ name: 'Alice' })
226
-
227
- // Nested paths work too
228
- // api.admin.users -> api/admin/users.js
229
- // api.admin.users.delete -> api/admin/users/delete.js
230
- await api.admin.users.delete(userId)
231
- ```
232
-
233
- #### `api.on(type, handler)`
234
-
235
- Listen for server broadcasts.
236
-
237
- ```js
238
- api.on('notification', ({ data, err, type }) => {
239
- console.log('Received:', data)
240
- })
241
- ```
242
-
243
- #### `api.transport`
244
-
245
- Get the currently active transport type. This is a read-only property.
246
-
247
- ```js
248
- console.log(api.transport) // 'websocket' | 'polling' | null
249
- ```
250
-
251
- #### `api.onConnectionChange(handler)`
252
-
253
- Listen for connection state changes.
254
-
255
- ```js
256
- const unsubscribe = api.onConnectionChange((state) => {
257
- console.log('Connection state:', state)
258
- })
259
-
260
- // Later: stop listening
261
- unsubscribe()
262
- ```
263
-
264
- **Connection States:**
265
-
266
- | State | Description |
267
- |-------|-------------|
268
- | `offline` | Browser reports no network (`navigator.onLine = false`) |
269
- | `walled` | Captive portal detected (can't reach server) |
270
- | `disconnected` | Had connection, lost it |
271
- | `connecting` | Actively connecting to server |
272
- | `connected` | Connected and ready |
273
-
274
- ---
275
-
276
- ## Configuration
277
-
278
- ### Default Options
279
-
280
- ```js
281
- ape(server, {
282
- where: 'api' // Controller directory
283
- })
284
- ```
285
-
286
- ### Connection Lifecycle Hook
287
-
288
- Customize behavior per connection:
289
-
290
- ```js
291
- ape(server, {
292
- where: 'api',
293
- onConnect(socket, req, clientId) {
294
- return {
295
- // Embed values into `this` for all controllers
296
- embed: {
297
- userId: req.session?.userId,
298
- id: String(clientId)
299
- },
300
-
301
- // Before/after hooks
302
- onReceive: (queryId, data, type) => {
303
- console.log(`→ ${type}`)
304
- return (err, result) => console.log(`← ${type}`, err || result)
305
- },
306
-
307
- onSend: (data, type) => {
308
- console.log(`⇐ ${type}`)
309
- return (err, result) => console.log(`Sent: ${type}`)
310
- },
311
-
312
- onError: (errStr) => console.error(errStr),
313
- onDisconnect: () => console.log('Client left')
314
- }
315
- }
316
- })
317
- ```
86
+ * **[Auto-wiring](server/README.md#auto-routing)** — Drop JS files in a folder, they become API endpoints
87
+ * **[Real-time broadcasts](server/README.md#controller-context-this)** — Built-in `broadcast()` and `broadcastOthers()` methods
88
+ * **[Pub/Sub channels](server/README.md#pubsub-channels)** — Clients subscribe to channels, server publishes updates
89
+ * **[Promise-based calls](client/README.md#usage)** — `api.users.list()` maps to `api/users/list.js`
90
+ * **[Automatic reconnection](client/README.md#features)** — Client reconnects with exponential backoff
91
+ * **[HTTP fallback](server/README.md#http-streaming-endpoints)** — Falls back to long polling when WebSockets are blocked
92
+ * **[JSS Encoding](server/README.md#troubleshooting--faq)** — Supports Date, RegExp, Error, Set, Map over the wire
93
+ * **[🌲 Forest](server/README.md#-forest-distributed-mesh)** — Distributed mesh for horizontal scaling
94
+ * **[🔐 Authentication](server/README.md#authentication)** — OPAQUE/PAKE auth with tiered access control
318
95
 
319
96
  ---
320
97
 
321
- ## Common Recipes
322
-
323
- ### Broadcast to Other Clients
324
-
325
- ```js
326
- // api/message.js
327
- module.exports = function(data) {
328
- // Broadcast to all OTHER connected clients (not the sender)
329
- this.broadcastOthers('message', data)
330
- return { success: true }
331
- }
332
- ```
333
-
334
- ### Broadcast to All Clients
335
-
336
- ```js
337
- // api/announcement.js
338
- module.exports = function(announcement) {
339
- // Broadcast to ALL connected clients including sender
340
- this.broadcast('announcement', announcement)
341
- return { sent: true }
342
- }
343
- ```
344
-
345
- ### Using ape.clients
346
-
347
- ```js
348
- const { ape } = require('api-ape')
98
+ ## Examples
349
99
 
350
- // Get online count
351
- console.log('Online:', ape.clients.size)
100
+ * **[`example/ExpressJs/`](example/ExpressJs/)** Simple real-time chat
101
+ * **[`example/NextJs/`](example/NextJs/)** — Production chat with React & Docker
102
+ * **[`example/Vite/`](example/Vite/)** — Vite + Vue
103
+ * **[`example/Bun/`](example/Bun/)** — Bun runtime
352
104
 
353
- // Get all client IDs
354
- const clientIds = Array.from(ape.clients.keys())
355
-
356
- // Send to a specific client
357
- const client = ape.clients.get(someClientId)
358
- if (client) {
359
- client.sendTo('notification', { message: 'Hello!' })
360
- }
361
-
362
- // Iterate all clients
363
- ape.clients.forEach((client, clientId) => {
364
- console.log(`Client ${clientId}:`, client.sessionId, client.agent.browser.name)
365
- })
366
- ```
367
-
368
- ### Access Request Data
369
-
370
- ```js
371
- // api/profile.js
372
- module.exports = function() {
373
- // Access original HTTP request
374
- const userId = this.req.session?.userId
375
- const userAgent = this.agent.browser.name
376
-
377
- return { userId, userAgent }
378
- }
379
- ```
380
-
381
- ### Error Handling
382
-
383
- ```js
384
- // api/data.js
385
- module.exports = async function(id) {
386
- try {
387
- const data = await fetchData(id)
388
- return data
389
- } catch (err) {
390
- // Errors are automatically sent to client
391
- throw new Error(`Failed to fetch: ${err.message}`)
392
- }
393
- }
394
- ```
395
-
396
- ### Client-Side Error Handling
397
-
398
- ```js
399
- try {
400
- const result = await api.data.get(id)
401
- console.log(result)
402
- } catch (err) {
403
- console.error('Server error:', err)
404
- }
405
- ```
406
-
407
- ---
408
-
409
- ## Examples & Demos
410
-
411
- The repository contains working examples:
412
-
413
- * **[`example/ExpressJs/`](example/ExpressJs/)** — Simple real-time chat app
414
- - Minimal setup with Express.js
415
- - Broadcast messages to other clients
416
- - Message history
417
-
418
- * **[`example/NextJs/`](example/NextJs/)** — Production-ready chat application
419
- - Custom Next.js server integration
420
- - React hooks integration
421
- - User presence tracking
422
- - Docker support
423
- - Connection lifecycle hooks
424
-
425
- * **[`example/Vite/`](example/Vite/)** — Vite + Vue example
426
- * **[`example/Bun/`](example/Bun/)** — Bun runtime example
427
-
428
- ### Run an Example
429
-
430
- **ExpressJs:**
431
105
  ```bash
432
- cd example/ExpressJs
433
- npm install
434
- npm start
435
- # Open http://localhost:3000
436
- ```
437
-
438
- **NextJs:**
439
- ```bash
440
- cd example/NextJs
441
- npm install
442
- npm run dev
443
- # Open http://localhost:3000
444
- ```
445
-
446
- Or with Docker:
447
- ```bash
448
- cd example/NextJs
449
- docker-compose up --build
106
+ cd example/ExpressJs && npm install && npm start
450
107
  ```
451
108
 
452
109
  ---
453
110
 
454
- ## Tests & CI
455
-
456
- ```bash
457
- npm test # Run test suite
458
- npm run test:watch # Watch mode
459
- npm run test:cover # Coverage report
460
- ```
111
+ ## Documentation
461
112
 
462
- **Test Commands:**
463
- - `npm test` — Run all tests
464
- - `npm run test:watch` Watch mode for development
465
- - `npm run test:cover` Generate coverage report
466
-
467
- **Supported:** Node.js 14+, modern browsers (Chrome, Firefox, Safari, Edge)
113
+ | Docs | Description |
114
+ |------|-------------|
115
+ | **[Server README](server/README.md)** | API reference, lifecycle hooks, auto-routing, file transfers, 🌲 Forest |
116
+ | **[Client README](client/README.md)** | Client usage, connection states, file transfers, security |
117
+ | **[Auth README](server/security/auth/README.md)** | OPAQUE authentication, tiered access, authorization |
118
+ | **[Adapters README](server/adapters/README.md)** | Database adapters for Forest |
468
119
 
469
120
  ---
470
121
 
471
122
  ## Contributing
472
123
 
473
- Contributions welcome! Here's how to help:
474
-
475
- 1. **Fork the repository**
476
- 2. **Create a branch:** `git checkout -b feature/your-feature-name`
477
- 3. **Make your changes** and add tests
478
- 4. **Run tests:** `npm test`
479
- 5. **Commit:** Follow conventional commit messages
480
- 6. **Push and open a PR** with a clear description
481
-
482
- **Guidelines:**
483
- * Add tests for new features
484
- * Keep code style consistent
485
- * Update documentation if needed
486
- * Ensure all tests pass
487
-
488
- ---
489
-
490
- ## Releases / Changelog
491
-
492
- Versioning follows [Semantic Versioning](https://semver.org/).
493
-
494
- **Current version:** See `package.json` or npm registry
495
-
496
- **Release notes:** Check [GitHub releases](https://github.com/codemeasandwich/api-ape/releases) for detailed changelog.
497
-
498
- ---
499
-
500
- ## Zero Dependencies
124
+ 1. Fork the repository
125
+ 2. Create a branch: `git checkout -b feature/your-feature`
126
+ 3. Make changes and add tests
127
+ 4. Run tests: `npm test`
128
+ 5. Push and open a PR
501
129
 
502
- api-ape has **zero runtime dependencies**. The WebSocket implementation is built-in:
130
+ ### Tests
503
131
 
504
- - **Node.js 24+**: Uses native `node:ws` module
505
- - **Bun / Deno**: Uses framework-provided WebSocket support
506
- - **Earlier Node.js**: Uses built-in RFC 6455 compliant WebSocket server
507
-
508
- No `npm install` surprises, no dependency audits, no supply chain concerns.
509
-
510
- ---
511
-
512
- ## Security
513
-
514
- **Reporting vulnerabilities:** Please report security issues via [GitHub Security Advisories](https://github.com/codemeasandwich/api-ape/security/advisories) or email the maintainer.
515
-
516
- ### CSRF Protection
517
-
518
- api-ape includes built-in **Cross-Site Request Forgery (CSRF)** protection via Origin validation:
519
-
520
- - **Origin Header Check** — Every WebSocket connection validates the `Origin` header against the `Host` header
521
- - **Automatic Rejection** — Connections from mismatched origins are destroyed immediately
522
- - **No Configuration Needed** — Protection is enabled by default
523
-
524
- This prevents malicious sites from making requests to your api-ape server while impersonating logged-in users.
525
-
526
- ### Security Considerations
527
-
528
- * Validate all input in controllers
529
- * Use authentication/authorization in `onConnect` hooks
530
- * Sanitize data before broadcasting
531
- * Keep dependencies up to date
532
-
533
- ---
534
-
535
- ## Project Structure
536
-
537
- ```
538
- api-ape/
539
- ├── client/
540
- │ ├── index.js # Unified client entry point (auto-buffered)
541
- │ ├── browser.js # Browser entry point (window.ape)
542
- │ ├── connectSocket.js # WebSocket client with auto-reconnect
543
- │ └── transports/
544
- │ └── streaming.js # HTTP streaming fallback transport
545
- ├── server/
546
- │ ├── lib/
547
- │ │ ├── main.js # HTTP server integration
548
- │ │ ├── loader.js # Auto-loads controller files
549
- │ │ ├── broadcast.js # Client tracking & broadcast
550
- │ │ ├── wiring.js # WebSocket handler setup
551
- │ │ ├── fileTransfer.js # Binary file transfer via HTTP
552
- │ │ ├── longPolling.js # HTTP streaming fallback handler
553
- │ │ ├── bun.js # Bun runtime support
554
- │ │ ├── wsProvider.js # WebSocket provider abstraction
555
- │ │ └── ws/ # Native WebSocket implementation
556
- │ ├── socket/
557
- │ │ ├── open.js # Connection handler
558
- │ │ ├── receive.js # Incoming message handler
559
- │ │ └── send.js # Outgoing message handler
560
- │ ├── security/
561
- │ │ ├── reply.js # Duplicate request protection
562
- │ │ ├── origin.js # Origin validation
563
- │ │ └── extractRootDomain.js # Domain extraction utility
564
- │ └── utils/
565
- │ ├── deepRequire.js # Deep module loader
566
- │ ├── genId.js # ID generation
567
- │ └── parseUserAgent.js # Browser/OS/device parser
568
- ├── utils/
569
- │ ├── jss.js # JSON SuperSet encoder/decoder
570
- │ └── messageHash.js # Request deduplication hashing
571
- └── example/
572
- ├── ExpressJs/ # Minimal chat app example
573
- ├── NextJs/ # Next.js production example
574
- ├── Vite/ # Vite/Vue example
575
- └── Bun/ # Bun runtime example
576
- ```
577
-
578
- ---
579
-
580
- ## Troubleshooting & FAQ
581
-
582
- ### CORS Errors in Browser
583
-
584
- Ensure your server allows WebSocket connections from your origin. api-ape uses the `ws` library which handles WebSocket upgrades on the HTTP server level.
585
-
586
- ### Controller Not Found
587
-
588
- * Check that your controller file is in the `where` directory (default: `api/`)
589
- * Ensure the file exports a function: `module.exports = function(...) { ... }`
590
- * File paths map directly: `api/users/list.js` → `api.users.list()`
591
-
592
- ### Connection Drops Frequently
593
-
594
- The client automatically reconnects with exponential backoff. If connections drop often:
595
- * Check server WebSocket timeout settings
596
- * Verify network stability
597
- * Check server logs for errors
598
-
599
- ### Binary Data / File Transfers
600
-
601
- api-ape supports transparent binary file transfers. Simply return `Buffer` data from controllers:
602
-
603
- ```js
604
- // api/files/download.js
605
- module.exports = function(filename) {
606
- return {
607
- name: filename,
608
- data: fs.readFileSync(`./uploads/${filename}`) // Buffer
609
- }
610
- }
611
- ```
612
-
613
- The client receives `ArrayBuffer` automatically:
614
-
615
- ```js
616
- const result = await api.files.download('image.png')
617
- console.log(result.data) // ArrayBuffer
618
-
619
- // Display as image
620
- const blob = new Blob([result.data])
621
- img.src = URL.createObjectURL(blob)
622
- ```
623
-
624
- **Uploads work the same way:**
625
-
626
- ```js
627
- // Client
628
- const arrayBuffer = await file.arrayBuffer()
629
- await api.files.upload({ name: file.name, data: arrayBuffer })
630
-
631
- // Server (api/files/upload.js)
632
- module.exports = function({ name, data }) {
633
- fs.writeFileSync(`./uploads/${name}`, data) // data is Buffer
634
- return { success: true }
635
- }
132
+ ```bash
133
+ npm test # Run tests
134
+ npm run test:watch # Watch mode
135
+ npm run test:cover # Coverage
636
136
  ```
637
137
 
638
- ### TypeScript Support
639
-
640
- Type definitions are included (`index.d.ts`). For full type safety, you may need to:
641
- * Define interfaces for your controller parameters and return types
642
- * Use type assertions when calling `api.<path>.<method>()`
643
-
644
138
  ---
645
139
 
646
- ## License & Authors
647
-
648
- **License:** MIT
140
+ ## License
649
141
 
650
- **Author:** [Brian Shannon](https://www.linkedin.com/in/brianshann/)
651
-
652
- **Repository:** [github.com/codemeasandwich/api-ape](https://github.com/codemeasandwich/api-ape)
653
-
654
- ---
142
+ **MIT** [Brian Shannon](https://www.linkedin.com/in/brianshann/)
655
143
 
656
144
  **Made with 🦍 by the api-ape community**