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/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,7 +25,7 @@ server/
|
|
|
47
25
|
npm i api-ape
|
|
48
26
|
```
|
|
49
27
|
|
|
50
|
-
###
|
|
28
|
+
### Import
|
|
51
29
|
|
|
52
30
|
```js
|
|
53
31
|
// CommonJS
|
|
@@ -77,20 +55,6 @@ ape(server, {
|
|
|
77
55
|
server.listen(3000)
|
|
78
56
|
```
|
|
79
57
|
|
|
80
|
-
### Dual-Purpose `ape` Function
|
|
81
|
-
|
|
82
|
-
The `ape` function intelligently detects how it's being used:
|
|
83
|
-
|
|
84
|
-
```js
|
|
85
|
-
// Called with HTTP server → Server setup
|
|
86
|
-
ape(server, { where: 'api' })
|
|
87
|
-
|
|
88
|
-
// Called with data → API call to /ape endpoint
|
|
89
|
-
ape({ someData: 'value' }) // Calls /ape on connected server
|
|
90
|
-
|
|
91
|
-
// Or via the proxy
|
|
92
|
-
api.ape({ someData: 'value' }) // Same as above
|
|
93
|
-
```
|
|
94
58
|
|
|
95
59
|
## Server-to-Server Connection
|
|
96
60
|
|
|
@@ -104,7 +68,7 @@ const { ape } = require('api-ape')
|
|
|
104
68
|
ape(server, { where: 'api' })
|
|
105
69
|
|
|
106
70
|
// Connect to another api-ape server
|
|
107
|
-
api.connect('ws://other-server:3000/api/ape
|
|
71
|
+
api.connect('other-server', 3000) // → ws://other-server:3000/api/ape
|
|
108
72
|
|
|
109
73
|
// Now use it exactly like browser code!
|
|
110
74
|
const result = await api.hello('World')
|
|
@@ -128,6 +92,8 @@ This enables server-side microservice patterns while keeping the familiar api-ap
|
|
|
128
92
|
| `where` | `string` | Directory containing controller files |
|
|
129
93
|
| `onConnect` | `function` | Connection lifecycle hook |
|
|
130
94
|
| `fileTransferOptions` | `object` | Binary transfer settings (see below) |
|
|
95
|
+
| `authFramework` | `object` | Authentication framework instance (see below) |
|
|
96
|
+
| `authMiddleware` | `object` | Authorization middleware instance (see below) |
|
|
131
97
|
|
|
132
98
|
### File Transfer Options
|
|
133
99
|
|
|
@@ -147,13 +113,16 @@ ape(app, {
|
|
|
147
113
|
|----------|-------------|
|
|
148
114
|
| `this.broadcast(type, data)` | Send to ALL connected clients |
|
|
149
115
|
| `this.broadcastOthers(type, data)` | Send to all EXCEPT the caller |
|
|
150
|
-
| `this.
|
|
151
|
-
| `this.getClients()` | Get array of connected clientIds |
|
|
116
|
+
| `this.publish(channel, data)` | Send to all subscribers of a channel |
|
|
152
117
|
| `this.clientId` | Unique ID of the calling client (generated by api-ape) |
|
|
153
118
|
| `this.sessionId` | Session ID from cookie (set by outer framework, may be `null`) |
|
|
154
119
|
| `this.req` | Original HTTP request |
|
|
155
120
|
| `this.socket` | WebSocket instance |
|
|
156
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) |
|
|
157
126
|
|
|
158
127
|
### Connection Lifecycle Hooks
|
|
159
128
|
|
|
@@ -193,6 +162,197 @@ api/
|
|
|
193
162
|
Remove one of these files to fix this conflict.
|
|
194
163
|
```
|
|
195
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
|
+
|
|
196
356
|
## File Transfers
|
|
197
357
|
|
|
198
358
|
Controllers can return `Buffer` data directly. The framework handles conversion:
|
|
@@ -276,8 +436,8 @@ Long-lived HTTP streaming connection for receiving server messages.
|
|
|
276
436
|
Send messages to server when using HTTP streaming transport.
|
|
277
437
|
|
|
278
438
|
- **Session**: Cookie-based (`apeClientId`)
|
|
279
|
-
- **Body**:
|
|
280
|
-
- **Response**:
|
|
439
|
+
- **Body**: JSS-encoded message
|
|
440
|
+
- **Response**: JSS-encoded result
|
|
281
441
|
|
|
282
442
|
### How It Works
|
|
283
443
|
|
|
@@ -415,14 +575,14 @@ Join the distributed mesh.
|
|
|
415
575
|
```js
|
|
416
576
|
ape.joinVia(redis);
|
|
417
577
|
ape.joinVia(redis, {
|
|
418
|
-
namespace: 'myapp', // Key/table prefix (default: '
|
|
578
|
+
namespace: 'myapp', // Key/table prefix (default: 'apes')
|
|
419
579
|
serverId: 'srv-west-1' // Custom server ID (default: auto-generated)
|
|
420
580
|
});
|
|
421
581
|
```
|
|
422
582
|
|
|
423
583
|
| Option | Type | Default | Description |
|
|
424
584
|
|--------|------|---------|-------------|
|
|
425
|
-
| `namespace` | `string` | `'
|
|
585
|
+
| `namespace` | `string` | `'apes'` | Prefix for all keys/tables |
|
|
426
586
|
| `serverId` | `string` | Auto-generated | Unique ID for this server instance |
|
|
427
587
|
|
|
428
588
|
#### `ape.leaveCluster()`
|
|
@@ -439,11 +599,11 @@ Forest creates its own database objects with your namespace prefix:
|
|
|
439
599
|
|
|
440
600
|
| Backend | Created Objects |
|
|
441
601
|
|---------|----------------|
|
|
442
|
-
| **Redis** | `
|
|
443
|
-
| **MongoDB** | Database: `
|
|
444
|
-
| **PostgreSQL** | Tables: `
|
|
445
|
-
| **Supabase** | Table: `
|
|
446
|
-
| **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/*` |
|
|
447
607
|
|
|
448
608
|
### Custom Adapters
|
|
449
609
|
|
|
@@ -582,3 +742,48 @@ See detailed adapter implementations in [`server/adapters/`](adapters/):
|
|
|
582
742
|
| `supabase.js` | Supabase Realtime adapter |
|
|
583
743
|
| `firebase.js` | Firebase RTDB adapter |
|
|
584
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>()`
|