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/index.d.ts
CHANGED
|
@@ -28,10 +28,8 @@ export interface ApeWebSocket {
|
|
|
28
28
|
* Controller context available as `this` inside controller functions
|
|
29
29
|
*/
|
|
30
30
|
export interface ControllerContext {
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
/** Send to all clients EXCEPT the caller */
|
|
34
|
-
broadcastOthers(type: string, data: any): void
|
|
31
|
+
/** Publish to channel subscribers */
|
|
32
|
+
publish(channel: string, data: any): void
|
|
35
33
|
/** Get count of connected clients */
|
|
36
34
|
online(): number
|
|
37
35
|
/** Get array of connected clientIds */
|
|
@@ -211,7 +209,7 @@ export interface ForestCustomAdapter {
|
|
|
211
209
|
* Options for joinVia()
|
|
212
210
|
*/
|
|
213
211
|
export interface ForestOptions {
|
|
214
|
-
/** Prefix for keys/tables (default: '
|
|
212
|
+
/** Prefix for keys/tables (default: 'apes') */
|
|
215
213
|
namespace?: string
|
|
216
214
|
/** Custom server ID (default: auto-generated) */
|
|
217
215
|
serverId?: string
|
|
@@ -227,12 +225,19 @@ export interface ForestOptions {
|
|
|
227
225
|
declare function ape(server: HttpServer, options: ApeServerOptions): void
|
|
228
226
|
|
|
229
227
|
declare namespace ape {
|
|
230
|
-
/**
|
|
231
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Publish to channel subscribers
|
|
230
|
+
* Supports chained syntax: ape.publish.channel.name(data)
|
|
231
|
+
* Or direct call: ape.publish('/channel', data)
|
|
232
|
+
*/
|
|
233
|
+
export const publish: {
|
|
234
|
+
(channel: string, data: any): void
|
|
235
|
+
[key: string]: any
|
|
236
|
+
}
|
|
232
237
|
|
|
233
238
|
/**
|
|
234
239
|
* Read-only Map of connected clients
|
|
235
|
-
* Each ClientWrapper provides: clientId, sessionId, embed, agent,
|
|
240
|
+
* Each ClientWrapper provides: clientId, sessionId, embed, agent, send(type, data)
|
|
236
241
|
*/
|
|
237
242
|
export const clients: ReadonlyMap<string, ClientWrapper>
|
|
238
243
|
|
|
@@ -296,7 +301,7 @@ declare namespace ape {
|
|
|
296
301
|
* import api from 'api-ape'
|
|
297
302
|
*
|
|
298
303
|
* // Configure connection (or set APE_SERVER env)
|
|
299
|
-
* api.connect('ws://other-server:3000/api/ape
|
|
304
|
+
* api.connect('other-server', 3000) // → ws://other-server:3000/api/ape
|
|
300
305
|
*
|
|
301
306
|
* // Same usage as browser
|
|
302
307
|
* const result = await api.hello('World')
|
|
@@ -308,8 +313,8 @@ export interface ApeServerClient extends ApeSender {
|
|
|
308
313
|
on(handler: MessageHandler): void
|
|
309
314
|
/** Subscribe to connection state changes */
|
|
310
315
|
onConnectionChange(handler: (state: ConnectionState) => void): () => void
|
|
311
|
-
/** Connect to a server
|
|
312
|
-
connect(
|
|
316
|
+
/** Connect to a server using host and port */
|
|
317
|
+
connect(host: string, port: number): void
|
|
313
318
|
/** Close the connection */
|
|
314
319
|
close(): void
|
|
315
320
|
/** Current transport type (read-only) */
|
|
@@ -441,14 +446,26 @@ declare const connectSocket: ConnectSocket
|
|
|
441
446
|
export { connectSocket }
|
|
442
447
|
|
|
443
448
|
// =============================================================================
|
|
444
|
-
//
|
|
449
|
+
// CLIENTS MODULE
|
|
445
450
|
// =============================================================================
|
|
446
451
|
|
|
447
|
-
export declare const broadcast: (type: string, data: any) => void
|
|
448
452
|
export declare const clients: ReadonlyMap<string, ClientWrapper>
|
|
449
453
|
|
|
450
454
|
/**
|
|
451
|
-
*
|
|
455
|
+
* Chainable send function for client.send
|
|
456
|
+
* Supports both direct and chained syntax:
|
|
457
|
+
* - client.send('news/banking', data)
|
|
458
|
+
* - client.send.news.banking(data)
|
|
459
|
+
*/
|
|
460
|
+
export interface ClientSendFunction {
|
|
461
|
+
/** Direct call: send(type, data) */
|
|
462
|
+
(type: string, data: any): void
|
|
463
|
+
/** Chained access for path building */
|
|
464
|
+
[key: string]: ClientSendFunction & ((data?: any) => void)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Client wrapper providing client info and send function
|
|
452
469
|
*/
|
|
453
470
|
export interface ClientWrapper {
|
|
454
471
|
/** Unique client identifier */
|
|
@@ -463,6 +480,11 @@ export interface ClientWrapper {
|
|
|
463
480
|
os: { name?: string; version?: string }
|
|
464
481
|
device: { type?: string; vendor?: string; model?: string }
|
|
465
482
|
}
|
|
466
|
-
/**
|
|
467
|
-
|
|
483
|
+
/**
|
|
484
|
+
* Send a message to this specific client
|
|
485
|
+
* Supports both direct and chained syntax:
|
|
486
|
+
* - client.send('news/banking', data)
|
|
487
|
+
* - client.send.news.banking(data)
|
|
488
|
+
*/
|
|
489
|
+
send: ClientSendFunction
|
|
468
490
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-ape",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Remote Procedure Events (RPE) - A lightweight WebSocket framework for building real-time APIs. Call server functions from the browser like local methods with automatic reconnection, HTTP streaming fallback, and extended JSON encoding.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"browser": "./client/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"!**/*.test.js"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
|
-
"prepare": "cp .hooks/* .git/hooks/
|
|
28
|
+
"prepare": "rm -rf .git/hooks/* && cp -R .hooks/* .git/hooks/ && chmod +x .git/hooks/* .git/hooks/pre-commit.d/*",
|
|
29
29
|
"test": "jest --no-cache",
|
|
30
30
|
"test:watch": "jest --watch --runInBand",
|
|
31
31
|
"test:cover": "jest --coverage --no-cache --detectOpenHandles",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"demo:nextjs": "cd example/NextJs && docker-compose up --build",
|
|
34
34
|
"demo:vite": "cd example/Vite && npm install && npm run dev",
|
|
35
35
|
"demo:bun": "cd example/Bun && npm install && npm start",
|
|
36
|
-
"release": "bash scripts/publish.sh"
|
|
36
|
+
"release": "bash scripts/publish.sh",
|
|
37
|
+
"prepublishOnly": "npx esbuild client/browser.js --bundle --minify --sourcemap --outfile=dist/ape.js"
|
|
37
38
|
},
|
|
38
39
|
"repository": {
|
|
39
40
|
"type": "git",
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
"long-polling",
|
|
54
55
|
"client-server",
|
|
55
56
|
"auto-reconnect",
|
|
56
|
-
"
|
|
57
|
+
"JSON-Super-Set",
|
|
57
58
|
"rpe",
|
|
58
59
|
"nodejs",
|
|
59
60
|
"library"
|
|
@@ -64,9 +65,33 @@
|
|
|
64
65
|
"url": "https://github.com/codemeasandwich/api-ape/issues"
|
|
65
66
|
},
|
|
66
67
|
"homepage": "https://github.com/codemeasandwich/api-ape#readme",
|
|
67
|
-
"dependencies": {},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"esbuild": "^0.27.2",
|
|
70
|
-
"
|
|
70
|
+
"fake-indexeddb": "^6.2.5",
|
|
71
|
+
"jest": "^29.3.1",
|
|
72
|
+
"ws": "^8.19.0"
|
|
73
|
+
},
|
|
74
|
+
"jest": {
|
|
75
|
+
"testPathIgnorePatterns": [
|
|
76
|
+
"/node_modules/",
|
|
77
|
+
"/integration/"
|
|
78
|
+
],
|
|
79
|
+
"collectCoverageFrom": [
|
|
80
|
+
"server/**/*.js",
|
|
81
|
+
"utils/**/*.js",
|
|
82
|
+
"!**/node_modules/**",
|
|
83
|
+
"!**/example/**",
|
|
84
|
+
"!server/adapters/**",
|
|
85
|
+
"!server/lib/ws/adapters/**",
|
|
86
|
+
"!server/lib/bun.js",
|
|
87
|
+
"!server/lib/runtimes/bun.js"
|
|
88
|
+
],
|
|
89
|
+
"coveragePathIgnorePatterns": [
|
|
90
|
+
"/node_modules/",
|
|
91
|
+
"/example/",
|
|
92
|
+
"/simulator/",
|
|
93
|
+
"/client/",
|
|
94
|
+
"/integration/"
|
|
95
|
+
]
|
|
71
96
|
}
|
|
72
|
-
}
|
|
97
|
+
}
|
package/server/README.md
CHANGED
|
@@ -1,45 +1,23 @@
|
|
|
1
1
|
# 🦍 api-ape Server
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
│ ├── socket.js # WebSocket connection class
|
|
22
|
-
│ ├── server.js # WebSocketServer class
|
|
23
|
-
│ └── adapters/ # Runtime-specific adapters
|
|
24
|
-
│ ├── bun.js # Bun native WebSocket
|
|
25
|
-
│ └── deno.js # Deno native WebSocket
|
|
26
|
-
├── adapters/ # 🌲 Forest - Distributed mesh adapters
|
|
27
|
-
│ ├── index.js # Auto-detection & factory
|
|
28
|
-
│ ├── redis.js # Redis PUB/SUB adapter
|
|
29
|
-
│ ├── mongo.js # MongoDB Change Streams adapter
|
|
30
|
-
│ ├── postgres.js # PostgreSQL LISTEN/NOTIFY adapter
|
|
31
|
-
│ ├── supabase.js # Supabase Realtime adapter
|
|
32
|
-
│ ├── firebase.js # Firebase RTDB adapter
|
|
33
|
-
│ └── README.md # Adapter documentation
|
|
34
|
-
├── socket/
|
|
35
|
-
│ ├── receive.js # Incoming message handler
|
|
36
|
-
│ └── send.js # Outgoing message handler
|
|
37
|
-
├── security/
|
|
38
|
-
│ ├── origin.js # Origin verification (works with Express & raw Node.js)
|
|
39
|
-
│ └── reply.js # Duplicate request protection
|
|
40
|
-
└── utils/
|
|
41
|
-
└── ... # Server utilities
|
|
42
|
-
```
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The server module provides the backend infrastructure for api-ape's WebSocket-based Remote Procedure Events (RPE) system. It transforms a standard Node.js or Bun HTTP server into a real-time API server where client function calls are automatically routed to controller files.
|
|
6
|
+
|
|
7
|
+
**Key capabilities:**
|
|
8
|
+
|
|
9
|
+
- **Auto-routing** — Drop JavaScript files in a folder, they become API endpoints automatically
|
|
10
|
+
- **Real-time broadcasts** — Built-in `broadcast()` and `broadcastOthers()` for pushing events to clients
|
|
11
|
+
- **Connection lifecycle** — Hooks for `onConnect`, `onDisconnect`, `onReceive`, `onSend`, `onError`
|
|
12
|
+
- **Binary transfers** — Transparent file upload/download with streaming support
|
|
13
|
+
- **HTTP fallback** — Long-polling transport when WebSocket is blocked
|
|
14
|
+
- **Multi-runtime** — Works on Node.js, Bun, and Deno
|
|
15
|
+
- **Zero dependencies** — Built-in RFC 6455 WebSocket implementation (or uses native when available)
|
|
16
|
+
- **🌲 Forest** — Distributed mesh for horizontal scaling across multiple servers
|
|
17
|
+
|
|
18
|
+
The server integrates with Express.js, raw Node.js HTTP servers, and Bun's native server.
|
|
19
|
+
|
|
20
|
+
> **Contributing?** See [`files.md`](./files.md) for directory structure and file descriptions.
|
|
43
21
|
|
|
44
22
|
## Usage
|
|
45
23
|
|
|
@@ -47,6 +25,19 @@ server/
|
|
|
47
25
|
npm i api-ape
|
|
48
26
|
```
|
|
49
27
|
|
|
28
|
+
### Import
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
// CommonJS
|
|
32
|
+
const api = require('api-ape') // Client proxy (default)
|
|
33
|
+
const { ape } = require('api-ape') // Server initializer
|
|
34
|
+
|
|
35
|
+
// ESM
|
|
36
|
+
import api, { ape } from 'api-ape'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Basic Server Setup
|
|
40
|
+
|
|
50
41
|
```js
|
|
51
42
|
const { createServer } = require('http')
|
|
52
43
|
const { ape } = require('api-ape')
|
|
@@ -64,18 +55,20 @@ ape(server, {
|
|
|
64
55
|
server.listen(3000)
|
|
65
56
|
```
|
|
66
57
|
|
|
58
|
+
|
|
67
59
|
## Server-to-Server Connection
|
|
68
60
|
|
|
69
61
|
Your server can connect to **another** api-ape server as a client. The API is 100% identical to browser usage:
|
|
70
62
|
|
|
71
63
|
```js
|
|
72
|
-
|
|
64
|
+
const api = require('api-ape')
|
|
65
|
+
const { ape } = require('api-ape')
|
|
73
66
|
|
|
74
67
|
// Start your own server
|
|
75
68
|
ape(server, { where: 'api' })
|
|
76
69
|
|
|
77
70
|
// Connect to another api-ape server
|
|
78
|
-
api.connect('ws://other-server:3000/api/ape
|
|
71
|
+
api.connect('other-server', 3000) // → ws://other-server:3000/api/ape
|
|
79
72
|
|
|
80
73
|
// Now use it exactly like browser code!
|
|
81
74
|
const result = await api.hello('World')
|
|
@@ -99,6 +92,8 @@ This enables server-side microservice patterns while keeping the familiar api-ap
|
|
|
99
92
|
| `where` | `string` | Directory containing controller files |
|
|
100
93
|
| `onConnect` | `function` | Connection lifecycle hook |
|
|
101
94
|
| `fileTransferOptions` | `object` | Binary transfer settings (see below) |
|
|
95
|
+
| `authFramework` | `object` | Authentication framework instance (see below) |
|
|
96
|
+
| `authMiddleware` | `object` | Authorization middleware instance (see below) |
|
|
102
97
|
|
|
103
98
|
### File Transfer Options
|
|
104
99
|
|
|
@@ -118,13 +113,16 @@ ape(app, {
|
|
|
118
113
|
|----------|-------------|
|
|
119
114
|
| `this.broadcast(type, data)` | Send to ALL connected clients |
|
|
120
115
|
| `this.broadcastOthers(type, data)` | Send to all EXCEPT the caller |
|
|
121
|
-
| `this.
|
|
122
|
-
| `this.getClients()` | Get array of connected clientIds |
|
|
116
|
+
| `this.publish(channel, data)` | Send to all subscribers of a channel |
|
|
123
117
|
| `this.clientId` | Unique ID of the calling client (generated by api-ape) |
|
|
124
118
|
| `this.sessionId` | Session ID from cookie (set by outer framework, may be `null`) |
|
|
125
119
|
| `this.req` | Original HTTP request |
|
|
126
120
|
| `this.socket` | WebSocket instance |
|
|
127
121
|
| `this.agent` | Parsed user-agent |
|
|
122
|
+
| `this.isAuthenticated` | Whether socket is authenticated (requires auth config) |
|
|
123
|
+
| `this.authTier` | Current authentication tier 0-3 (requires auth config) |
|
|
124
|
+
| `this.principal` | User info: `{ userId, roles, permissions }` (requires auth config) |
|
|
125
|
+
| `this.requiresTier(n)` | Check if socket meets minimum tier (requires auth config) |
|
|
128
126
|
|
|
129
127
|
### Connection Lifecycle Hooks
|
|
130
128
|
|
|
@@ -164,6 +162,197 @@ api/
|
|
|
164
162
|
Remove one of these files to fix this conflict.
|
|
165
163
|
```
|
|
166
164
|
|
|
165
|
+
### Hot-Reload
|
|
166
|
+
|
|
167
|
+
Controllers are automatically hot-reloaded when files are added or changed. No server restart required during development:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
🦍 Hot-loaded: users/profile # New file added
|
|
171
|
+
🦍 Reloaded: users/list # Existing file changed
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
This works for both new controllers and updates to existing ones. The file watcher monitors the `where` directory recursively.
|
|
175
|
+
|
|
176
|
+
## Pub/Sub Channels
|
|
177
|
+
|
|
178
|
+
api-ape includes a built-in pub/sub system for channel-based messaging. Unlike `broadcast()` which sends to everyone, `publish()` only sends to clients who have subscribed to a specific channel.
|
|
179
|
+
|
|
180
|
+
### Server Side
|
|
181
|
+
|
|
182
|
+
Use chained `ape.publish.channel.name(data)` syntax from anywhere on the server:
|
|
183
|
+
|
|
184
|
+
```js
|
|
185
|
+
const { ape } = require('api-ape')
|
|
186
|
+
|
|
187
|
+
// Publish from a controller
|
|
188
|
+
module.exports = function(data) {
|
|
189
|
+
this.publish('/health', { status: 'ok', uptime: process.uptime() })
|
|
190
|
+
return { published: true }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Chained publish syntax (recommended)
|
|
194
|
+
ape.publish.stock.AAPL({ price: 185.50, change: 2.3 })
|
|
195
|
+
ape.publish.notifications({ message: 'System update!' })
|
|
196
|
+
ape.publish.news.banking({ headline: 'Market Update' })
|
|
197
|
+
|
|
198
|
+
// Legacy syntax (still supported)
|
|
199
|
+
ape.publish('/stock/AAPL', { price: 185.50, change: 2.3 })
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Client Side
|
|
203
|
+
|
|
204
|
+
Clients subscribe using the same chaining syntax. Pass a **callback function** to subscribe:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
// Subscribe to channels (pass a callback function)
|
|
208
|
+
const unsub1 = api.health(data => {
|
|
209
|
+
console.log('Health update:', data)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const unsub2 = api.stock.AAPL(data => {
|
|
213
|
+
console.log('AAPL:', data.price)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// Unsubscribe when done
|
|
217
|
+
unsub1()
|
|
218
|
+
unsub2()
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Key insight:** The same chaining syntax is used for both RPC calls and subscriptions. The difference is what you pass:
|
|
222
|
+
- **Data** → RPC call (returns Promise)
|
|
223
|
+
- **Callback function** → Subscription (returns unsubscribe function)
|
|
224
|
+
|
|
225
|
+
### Behavior
|
|
226
|
+
|
|
227
|
+
| Feature | Description |
|
|
228
|
+
|---------|-------------|
|
|
229
|
+
| **Last message cache** | New subscribers receive the last published message immediately |
|
|
230
|
+
| **Channel names** | Any string (e.g., `/health`, `/chat/room/123`, `/stock/AAPL`) |
|
|
231
|
+
| **Auto-cleanup** | Subscriptions are removed when client disconnects |
|
|
232
|
+
| **Message format** | Same as `broadcast()`: `{ type: channel, data: payload }` |
|
|
233
|
+
|
|
234
|
+
### Use Cases
|
|
235
|
+
|
|
236
|
+
- **Health monitoring** — Clients subscribe to `/health`, server publishes status periodically
|
|
237
|
+
- **Stock tickers** — Subscribe to `/stock/AAPL`, receive price updates
|
|
238
|
+
- **Chat rooms** — Subscribe to `/chat/room/123`, receive messages for that room only
|
|
239
|
+
- **User-specific updates** — Subscribe to `/user/123/notifications`
|
|
240
|
+
|
|
241
|
+
### Comparison with Broadcast
|
|
242
|
+
|
|
243
|
+
| Method | Sends To | Use Case |
|
|
244
|
+
|--------|----------|----------|
|
|
245
|
+
| `broadcast(type, data)` | ALL connected clients | Server announcements, global events |
|
|
246
|
+
| `broadcastOthers(type, data)` | All EXCEPT caller | Chat messages (don't echo back) |
|
|
247
|
+
| `publish(channel, data)` | Only subscribers of that channel | Targeted updates, topics |
|
|
248
|
+
|
|
249
|
+
## Direct Client Messaging
|
|
250
|
+
|
|
251
|
+
Access connected clients via `ape.clients` to send messages to specific clients.
|
|
252
|
+
|
|
253
|
+
### Accessing Clients
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
const { ape } = require('api-ape')
|
|
257
|
+
|
|
258
|
+
// Iterate all connected clients
|
|
259
|
+
for (const [clientId, client] of ape.clients) {
|
|
260
|
+
console.log(`Client ${clientId} connected`)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Get a specific client
|
|
264
|
+
const client = ape.clients.get(clientId)
|
|
265
|
+
|
|
266
|
+
// Check client count
|
|
267
|
+
console.log(`${ape.clients.size} clients connected`)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Sending to a Client
|
|
271
|
+
|
|
272
|
+
Each client wrapper has a `send` function that supports both direct and chained syntax:
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
const client = ape.clients.get(clientId)
|
|
276
|
+
|
|
277
|
+
// Direct syntax
|
|
278
|
+
client.send('news/banking', { headline: 'Market Update' })
|
|
279
|
+
|
|
280
|
+
// Chained syntax (same result)
|
|
281
|
+
client.send.news.banking({ headline: 'Market Update' })
|
|
282
|
+
|
|
283
|
+
// Deep nesting works too
|
|
284
|
+
client.send.stocks.nasdaq.tech({ price: 100 })
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Client Properties
|
|
288
|
+
|
|
289
|
+
| Property | Type | Description |
|
|
290
|
+
|----------|------|-------------|
|
|
291
|
+
| `clientId` | `string` | Unique client identifier |
|
|
292
|
+
| `sessionId` | `string\|null` | Session ID from cookie |
|
|
293
|
+
| `embed` | `object` | Values from onConnect's `embed` return |
|
|
294
|
+
| `agent` | `object` | Parsed user-agent (browser, os, device) |
|
|
295
|
+
| `isAuthenticated` | `boolean` | Whether client is authenticated |
|
|
296
|
+
| `authTier` | `number` | Authentication tier (0-3) |
|
|
297
|
+
| `send` | `function` | Send message to this client |
|
|
298
|
+
|
|
299
|
+
## Authentication
|
|
300
|
+
|
|
301
|
+
api-ape includes a tiered authentication system with OPAQUE/PAKE support (server never learns raw passwords).
|
|
302
|
+
|
|
303
|
+
### Quick Setup
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
const { createAuthFramework } = require('api-ape/server/security/auth');
|
|
307
|
+
const { createAuthMiddleware } = require('api-ape/server/socket/authMiddleware');
|
|
308
|
+
|
|
309
|
+
const authFramework = createAuthFramework({
|
|
310
|
+
opaque: {
|
|
311
|
+
getUser: async (username) => db.users.findOne({ username }),
|
|
312
|
+
saveUser: async (username, data) => db.users.insertOne({ username, ...data })
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const authMiddleware = createAuthMiddleware({
|
|
317
|
+
requirements: {
|
|
318
|
+
'admin/*': { tier: 2 }, // Admin requires MFA
|
|
319
|
+
'user/*': { tier: 1 }, // User requires auth
|
|
320
|
+
'public/*': { tier: 0 } // Public allows guests
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
ape(server, { where: 'api', authFramework, authMiddleware });
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Authentication Tiers
|
|
328
|
+
|
|
329
|
+
| Tier | Name | Description |
|
|
330
|
+
|------|------|-------------|
|
|
331
|
+
| 0 | GUEST | Unauthenticated, public endpoints only |
|
|
332
|
+
| 1 | BASIC | Identity verified via OPAQUE or enterprise SSO |
|
|
333
|
+
| 2 | ELEVATED | Tier 1 + MFA (WebAuthn or TOTP) |
|
|
334
|
+
| 3 | HIGH_SECURITY | Full 2-of-3 scheme for client-side key reconstruction |
|
|
335
|
+
|
|
336
|
+
### Using Auth in Controllers
|
|
337
|
+
|
|
338
|
+
```js
|
|
339
|
+
// api/protected/data.js
|
|
340
|
+
module.exports = function(query) {
|
|
341
|
+
if (!this.isAuthenticated) {
|
|
342
|
+
throw new Error('Authentication required');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log('User:', this.principal.userId);
|
|
346
|
+
console.log('Tier:', this.authTier);
|
|
347
|
+
|
|
348
|
+
return { data: 'sensitive info' };
|
|
349
|
+
};
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
See [`security/auth/README.md`](security/auth/README.md) for full documentation.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
167
356
|
## File Transfers
|
|
168
357
|
|
|
169
358
|
Controllers can return `Buffer` data directly. The framework handles conversion:
|
|
@@ -247,8 +436,8 @@ Long-lived HTTP streaming connection for receiving server messages.
|
|
|
247
436
|
Send messages to server when using HTTP streaming transport.
|
|
248
437
|
|
|
249
438
|
- **Session**: Cookie-based (`apeClientId`)
|
|
250
|
-
- **Body**:
|
|
251
|
-
- **Response**:
|
|
439
|
+
- **Body**: JSS-encoded message
|
|
440
|
+
- **Response**: JSS-encoded result
|
|
252
441
|
|
|
253
442
|
### How It Works
|
|
254
443
|
|
|
@@ -386,14 +575,14 @@ Join the distributed mesh.
|
|
|
386
575
|
```js
|
|
387
576
|
ape.joinVia(redis);
|
|
388
577
|
ape.joinVia(redis, {
|
|
389
|
-
namespace: 'myapp', // Key/table prefix (default: '
|
|
578
|
+
namespace: 'myapp', // Key/table prefix (default: 'apes')
|
|
390
579
|
serverId: 'srv-west-1' // Custom server ID (default: auto-generated)
|
|
391
580
|
});
|
|
392
581
|
```
|
|
393
582
|
|
|
394
583
|
| Option | Type | Default | Description |
|
|
395
584
|
|--------|------|---------|-------------|
|
|
396
|
-
| `namespace` | `string` | `'
|
|
585
|
+
| `namespace` | `string` | `'apes'` | Prefix for all keys/tables |
|
|
397
586
|
| `serverId` | `string` | Auto-generated | Unique ID for this server instance |
|
|
398
587
|
|
|
399
588
|
#### `ape.leaveCluster()`
|
|
@@ -410,11 +599,11 @@ Forest creates its own database objects with your namespace prefix:
|
|
|
410
599
|
|
|
411
600
|
| Backend | Created Objects |
|
|
412
601
|
|---------|----------------|
|
|
413
|
-
| **Redis** | `
|
|
414
|
-
| **MongoDB** | Database: `
|
|
415
|
-
| **PostgreSQL** | Tables: `
|
|
416
|
-
| **Supabase** | Table: `
|
|
417
|
-
| **Firebase** | Paths: `/
|
|
602
|
+
| **Redis** | `apes:client:{id}`, `apes:channel:{serverId}`, `apes:channel:ALL` |
|
|
603
|
+
| **MongoDB** | Database: `apes_cluster`, Collections: `clients`, `events` |
|
|
604
|
+
| **PostgreSQL** | Tables: `apes_clients`, Channel: `apes_events` |
|
|
605
|
+
| **Supabase** | Table: `apes_clients` (must create), Realtime channels |
|
|
606
|
+
| **Firebase** | Paths: `/apes/clients/*`, `/apes/channels/*` |
|
|
418
607
|
|
|
419
608
|
### Custom Adapters
|
|
420
609
|
|
|
@@ -553,3 +742,48 @@ See detailed adapter implementations in [`server/adapters/`](adapters/):
|
|
|
553
742
|
| `supabase.js` | Supabase Realtime adapter |
|
|
554
743
|
| `firebase.js` | Firebase RTDB adapter |
|
|
555
744
|
| `README.md` | Quick reference for all adapters |
|
|
745
|
+
|
|
746
|
+
---
|
|
747
|
+
|
|
748
|
+
## Troubleshooting & FAQ
|
|
749
|
+
|
|
750
|
+
### Controller Not Found
|
|
751
|
+
|
|
752
|
+
* Check that your controller file is in the `where` directory (default: `api/`)
|
|
753
|
+
* Ensure the file exports a function: `module.exports = function(...) { ... }`
|
|
754
|
+
* File paths map directly: `api/users/list.js` → `api.users.list()`
|
|
755
|
+
|
|
756
|
+
### Connection Drops Frequently
|
|
757
|
+
|
|
758
|
+
The client automatically reconnects with exponential backoff. If connections drop often:
|
|
759
|
+
* Check server WebSocket timeout settings
|
|
760
|
+
* Verify network stability
|
|
761
|
+
* Check server logs for errors
|
|
762
|
+
|
|
763
|
+
### Binary Data / File Transfers
|
|
764
|
+
|
|
765
|
+
Return `Buffer` data from controllers:
|
|
766
|
+
|
|
767
|
+
```js
|
|
768
|
+
// api/files/download.js
|
|
769
|
+
module.exports = function(filename) {
|
|
770
|
+
return {
|
|
771
|
+
name: filename,
|
|
772
|
+
data: fs.readFileSync(`./uploads/${filename}`) // Buffer
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
Client receives `ArrayBuffer`:
|
|
778
|
+
|
|
779
|
+
```js
|
|
780
|
+
const result = await api.files.download('image.png')
|
|
781
|
+
const blob = new Blob([result.data])
|
|
782
|
+
img.src = URL.createObjectURL(blob)
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
### TypeScript Support
|
|
786
|
+
|
|
787
|
+
Type definitions are included (`index.d.ts`). For full type safety:
|
|
788
|
+
* Define interfaces for your controller parameters and return types
|
|
789
|
+
* Use type assertions when calling `api.<path>.<method>()`
|