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

|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
### Client (`api`)
|
|
72
59
|
|
|
60
|
+
**Browser:**
|
|
73
61
|
```html
|
|
74
62
|
<script src="/api/ape.js"></script>
|
|
75
63
|
<script>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
98
|
-
api.
|
|
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
|
|
114
|
-
* **Real-time broadcasts** — Built-in `broadcast()` and `broadcastOthers()` methods
|
|
115
|
-
* **
|
|
116
|
-
* **
|
|
117
|
-
* **
|
|
118
|
-
* **
|
|
119
|
-
* **
|
|
120
|
-
*
|
|
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
|
-
##
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
**
|
|
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
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
130
|
+
### Tests
|
|
503
131
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
|
647
|
-
|
|
648
|
-
**License:** MIT
|
|
140
|
+
## License
|
|
649
141
|
|
|
650
|
-
**
|
|
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**
|