api-ape 3.0.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -572
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -203
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +31 -6
- package/server/README.md +272 -67
- package/server/adapters/README.md +23 -14
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +322 -71
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -219
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -224
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -311
- package/server/lib/broadcast.js +0 -146
package/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,34 +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
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
### Client (`api`)
|
|
73
59
|
|
|
60
|
+
**Browser:**
|
|
74
61
|
```html
|
|
75
62
|
<script src="/api/ape.js"></script>
|
|
76
63
|
<script>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
99
|
-
api.
|
|
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
|
|
115
|
-
* **Real-time broadcasts** — Built-in `broadcast()` and `broadcastOthers()` methods
|
|
116
|
-
* **
|
|
117
|
-
* **
|
|
118
|
-
* **
|
|
119
|
-
* **
|
|
120
|
-
* **
|
|
121
|
-
*
|
|
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
|
|
98
|
+
## Examples
|
|
411
99
|
|
|
412
|
-
|
|
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/
|
|
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
|
-
##
|
|
111
|
+
## Documentation
|
|
456
112
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
###
|
|
130
|
+
### Tests
|
|
588
131
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
648
|
-
|
|
649
|
-
**License:** MIT
|
|
140
|
+
## License
|
|
650
141
|
|
|
651
|
-
**
|
|
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**
|