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