api-ape 2.1.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +184 -195
  2. package/client/README.md +37 -30
  3. package/client/browser.js +4 -14
  4. package/client/connectSocket.js +167 -42
  5. package/client/index.js +171 -0
  6. package/client/transports/streaming.js +3 -16
  7. package/dist/ape.js +2 -1049
  8. package/dist/ape.js.map +7 -0
  9. package/dist/api-ape.min.js +2 -0
  10. package/dist/api-ape.min.js.map +7 -0
  11. package/index.d.ts +67 -23
  12. package/package.json +27 -8
  13. package/server/README.md +52 -11
  14. package/server/lib/broadcast.js +25 -8
  15. package/server/lib/bun.js +122 -0
  16. package/server/lib/longPolling.js +28 -23
  17. package/server/lib/main.js +372 -46
  18. package/server/lib/wiring.js +19 -12
  19. package/server/lib/ws/adapters/bun.js +225 -0
  20. package/server/lib/ws/adapters/deno.js +186 -0
  21. package/server/lib/ws/frames.js +217 -0
  22. package/server/lib/ws/index.js +15 -0
  23. package/server/lib/ws/server.js +109 -0
  24. package/server/lib/ws/socket.js +222 -0
  25. package/server/lib/wsProvider.js +135 -0
  26. package/server/socket/receive.js +14 -1
  27. package/server/socket/send.js +6 -6
  28. package/server/utils/parseUserAgent.js +286 -0
  29. package/example/Bun/README.md +0 -74
  30. package/example/Bun/api/message.ts +0 -11
  31. package/example/Bun/index.html +0 -76
  32. package/example/Bun/package.json +0 -9
  33. package/example/Bun/server.ts +0 -59
  34. package/example/Bun/styles.css +0 -128
  35. package/example/ExpressJs/README.md +0 -95
  36. package/example/ExpressJs/api/message.js +0 -11
  37. package/example/ExpressJs/backend.js +0 -39
  38. package/example/ExpressJs/index.html +0 -88
  39. package/example/ExpressJs/package-lock.json +0 -834
  40. package/example/ExpressJs/package.json +0 -10
  41. package/example/ExpressJs/styles.css +0 -128
  42. package/example/NextJs/.dockerignore +0 -29
  43. package/example/NextJs/Dockerfile +0 -52
  44. package/example/NextJs/Dockerfile.dev +0 -27
  45. package/example/NextJs/README.md +0 -113
  46. package/example/NextJs/ape/client.js +0 -66
  47. package/example/NextJs/ape/embed.js +0 -12
  48. package/example/NextJs/ape/index.js +0 -23
  49. package/example/NextJs/ape/logic/chat.js +0 -62
  50. package/example/NextJs/ape/onConnect.js +0 -69
  51. package/example/NextJs/ape/onDisconnect.js +0 -13
  52. package/example/NextJs/ape/onError.js +0 -9
  53. package/example/NextJs/ape/onReceive.js +0 -15
  54. package/example/NextJs/ape/onSend.js +0 -15
  55. package/example/NextJs/api/message.js +0 -44
  56. package/example/NextJs/docker-compose.yml +0 -22
  57. package/example/NextJs/next-env.d.ts +0 -5
  58. package/example/NextJs/next.config.js +0 -8
  59. package/example/NextJs/package-lock.json +0 -6400
  60. package/example/NextJs/package.json +0 -24
  61. package/example/NextJs/pages/Info.tsx +0 -153
  62. package/example/NextJs/pages/_app.tsx +0 -6
  63. package/example/NextJs/pages/index.tsx +0 -275
  64. package/example/NextJs/public/favicon.ico +0 -0
  65. package/example/NextJs/public/vercel.svg +0 -4
  66. package/example/NextJs/server.js +0 -36
  67. package/example/NextJs/styles/Chat.module.css +0 -448
  68. package/example/NextJs/styles/Home.module.css +0 -129
  69. package/example/NextJs/styles/globals.css +0 -26
  70. package/example/NextJs/tsconfig.json +0 -20
  71. package/example/README.md +0 -117
  72. package/example/Vite/README.md +0 -68
  73. package/example/Vite/ape/client.ts +0 -66
  74. package/example/Vite/ape/onConnect.ts +0 -52
  75. package/example/Vite/api/message.ts +0 -57
  76. package/example/Vite/index.html +0 -16
  77. package/example/Vite/package.json +0 -19
  78. package/example/Vite/server.ts +0 -62
  79. package/example/Vite/src/App.vue +0 -170
  80. package/example/Vite/src/components/Info.vue +0 -352
  81. package/example/Vite/src/main.ts +0 -5
  82. package/example/Vite/src/style.css +0 -200
  83. package/example/Vite/src/vite-env.d.ts +0 -7
  84. package/example/Vite/vite.config.ts +0 -20
  85. package/todo.md +0 -85
  86. package/utils/jss.test.js +0 -261
  87. package/utils/messageHash.test.js +0 -56
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # 🦍 api-ape
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/api-ape.svg)](https://www.npmjs.com/package/api-ape)
4
- [![license](https://img.shields.io/npm/l/api-ape.svg)](https://github.com/codemeasandwich/api-ape/blob/main/LICENSE)
5
4
  [![GitHub issues](https://img.shields.io/github/issues/codemeasandwich/api-ape)](https://github.com/codemeasandwich/api-ape/issues)
5
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](#zero-dependencies)
6
+ [![CSRF protected](https://img.shields.io/badge/CSRF%20🚷-protected-green.svg)](#csrf-protection)
7
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/api-ape)](https://bundlephobia.com/package/api-ape)
8
+ [![JJS Encoding](https://img.shields.io/badge/encoding-JJS-blue.svg)](#jjs-encoding)
9
+ [![license](https://img.shields.io/npm/l/api-ape.svg)](https://github.com/codemeasandwich/api-ape/blob/main/LICENSE)
6
10
 
7
11
  **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.
8
12
 
@@ -69,16 +73,36 @@ Include the bundled client and start calling:
69
73
  <script src="/api/ape.js"></script>
70
74
  <script>
71
75
  // Call server functions like local methods
72
- const result = await ape.hello('World')
76
+ const result = await api.hello('World')
73
77
  console.log(result) // "Hello, World!"
74
78
 
75
79
  // Listen for broadcasts
76
- ape.on('message', ({ data }) => {
80
+ api.on('message', ({ data }) => {
77
81
  console.log('New message:', data)
78
82
  })
79
83
  </script>
80
84
  ```
81
85
 
86
+ ### Client (React, Vue, etc.)
87
+
88
+ With bundlers, use the unified import — no async setup needed:
89
+
90
+ ```js
91
+ import api from 'api-ape'
92
+
93
+ // Just use it! Calls are buffered until connected.
94
+ const result = await api.hello('World')
95
+
96
+ // Listen for broadcasts
97
+ api.on('message', ({ data }) => console.log(data))
98
+
99
+ // Track connection state
100
+ api.onConnectionChange((state) => {
101
+ console.log('Connection:', state)
102
+ // 'offline' | 'walled' | 'disconnected' | 'connecting' | 'connected'
103
+ })
104
+ ```
105
+
82
106
  **That's it!** Your server function is now callable from the browser.
83
107
 
84
108
  ---
@@ -87,7 +111,7 @@ Include the bundled client and start calling:
87
111
 
88
112
  * **Auto-wiring** — Drop JS files in a folder, they become API endpoints automatically
89
113
  * **Real-time broadcasts** — Built-in `broadcast()` and `broadcastOthers()` methods for pushing to clients
90
- * **Promise-based calls** — Chainable paths like `ape.users.list()` map to `api/users/list.js`
114
+ * **Promise-based calls** — Chainable paths like `api.users.list()` map to `api/users/list.js`
91
115
  * **Automatic reconnection** — Client auto-reconnects on disconnect with exponential backoff
92
116
  * **HTTP streaming fallback** — Automatically falls back to long polling when WebSockets are blocked
93
117
  * **JJS Encoding** — Extended JSON supporting Date, RegExp, Error, Set, Map, undefined, and circular refs
@@ -118,88 +142,74 @@ Inside controller functions, `this` provides:
118
142
  | `this.broadcast(type, data)` | Send to **ALL** connected clients |
119
143
  | `this.broadcastOthers(type, data)` | Send to all **EXCEPT** the caller |
120
144
  | `this.online()` | Get count of connected clients |
121
- | `this.getClients()` | Get array of connected hostIds |
122
- | `this.hostId` | Unique ID of the calling client |
145
+ | `this.getClients()` | Get array of connected clientIds |
146
+ | `this.clientId` | Unique ID of the calling client (generated by api-ape) |
147
+ | `this.sessionId` | Session ID from cookie (set by outer framework, may be `null`) |
123
148
  | `this.req` | Original HTTP request |
124
149
  | `this.socket` | WebSocket instance |
125
150
  | `this.agent` | Parsed user-agent (browser, OS, device) |
126
151
 
127
152
  ### Client
128
153
 
129
- #### `ape.<path>.<method>(...args)`
154
+ #### `api.<path>.<method>(...args)`
130
155
 
131
156
  Call a server function. Returns a Promise.
132
157
 
133
158
  ```js
134
159
  // Calls api/users/list.js
135
- const users = await ape.users.list()
160
+ const users = await api.users.list()
136
161
 
137
162
  // Calls api/users/create.js with data
138
- const user = await ape.users.create({ name: 'Alice' })
163
+ const user = await api.users.create({ name: 'Alice' })
139
164
 
140
165
  // Nested paths work too
141
- // ape.admin.users -> api/admin/users.js
142
- // ape.admin.users.delete -> api/admin/users/delete.js
143
- await ape.admin.users.delete(userId)
166
+ // api.admin.users -> api/admin/users.js
167
+ // api.admin.users.delete -> api/admin/users/delete.js
168
+ await api.admin.users.delete(userId)
144
169
  ```
145
170
 
146
- #### `ape.on(type, handler)`
171
+ #### `api.on(type, handler)`
147
172
 
148
173
  Listen for server broadcasts.
149
174
 
150
175
  ```js
151
- ape.on('notification', ({ data, err, type }) => {
176
+ api.on('notification', ({ data, err, type }) => {
152
177
  console.log('Received:', data)
153
178
  })
154
179
  ```
155
180
 
156
- #### `ape.configure(options)`
157
-
158
- Configure client connection options.
159
-
160
- ```js
161
- // Configure transport mode
162
- ape.configure({ transport: 'auto' }) // Auto-detect (default)
163
- ape.configure({ transport: 'websocket' }) // Force WebSocket only
164
- ape.configure({ transport: 'polling' }) // Force HTTP streaming
165
-
166
- // Configure connection details
167
- ape.configure({
168
- port: 9010,
169
- host: 'example.com',
170
- transport: 'auto'
171
- })
172
- ```
173
-
174
- | Option | Type | Description |
175
- |--------|------|-------------|
176
- | `port` | `number` | Server port (default: auto-detect) |
177
- | `host` | `string` | Server hostname (default: auto-detect) |
178
- | `transport` | `string` | Transport mode: `'auto'`, `'websocket'`, or `'polling'` |
179
-
180
- #### `ape.getTransport()`
181
+ #### `api.getTransport()`
181
182
 
182
183
  Get the currently active transport type.
183
184
 
184
185
  ```js
185
- const transport = ape.getTransport()
186
+ const transport = api.getTransport()
186
187
  console.log(transport) // 'websocket' | 'polling' | null
187
188
  ```
188
189
 
189
- #### `ape.onConnectionChange(handler)`
190
+ #### `api.onConnectionChange(handler)`
190
191
 
191
192
  Listen for connection state changes.
192
193
 
193
194
  ```js
194
- const unsubscribe = ape.onConnectionChange((state) => {
195
+ const unsubscribe = api.onConnectionChange((state) => {
195
196
  console.log('Connection state:', state)
196
- // 'disconnected' | 'connecting' | 'connected'
197
197
  })
198
198
 
199
199
  // Later: stop listening
200
200
  unsubscribe()
201
201
  ```
202
202
 
203
+ **Connection States:**
204
+
205
+ | State | Description |
206
+ |-------|-------------|
207
+ | `offline` | Browser reports no network (`navigator.onLine = false`) |
208
+ | `walled` | Captive portal detected (can't reach server) |
209
+ | `disconnected` | Had connection, lost it |
210
+ | `connecting` | Actively connecting to server |
211
+ | `connected` | Connected and ready |
212
+
203
213
  ---
204
214
 
205
215
  ## Configuration
@@ -208,8 +218,7 @@ unsubscribe()
208
218
 
209
219
  ```js
210
220
  ape(server, {
211
- where: 'api', // Controller directory
212
- onConnent: undefined // Lifecycle hook (optional)
221
+ where: 'api' // Controller directory
213
222
  })
214
223
  ```
215
224
 
@@ -220,12 +229,12 @@ Customize behavior per connection:
220
229
  ```js
221
230
  ape(server, {
222
231
  where: 'api',
223
- onConnent(socket, req, hostId) {
232
+ onConnent(socket, req, clientId) {
224
233
  return {
225
234
  // Embed values into `this` for all controllers
226
235
  embed: {
227
236
  userId: req.session?.userId,
228
- clientId: String(hostId)
237
+ id: String(clientId)
229
238
  },
230
239
 
231
240
  // Before/after hooks
@@ -316,7 +325,7 @@ module.exports = async function(id) {
316
325
 
317
326
  ```js
318
327
  try {
319
- const result = await ape.data.get(id)
328
+ const result = await api.data.get(id)
320
329
  console.log(result)
321
330
  } catch (err) {
322
331
  console.error('Server error:', err)
@@ -325,82 +334,25 @@ try {
325
334
 
326
335
  ---
327
336
 
328
- ## JJS Encoding
329
-
330
- api-ape uses **JJS (JSON SuperSet)** encoding, which extends JSON to support:
331
-
332
- | Type | Supported |
333
- |------|-----------|
334
- | `Date` | ✅ Preserved as Date objects |
335
- | `RegExp` | ✅ Preserved as RegExp |
336
- | `Error` | ✅ Preserved with name, message, stack |
337
- | `undefined` | ✅ Preserved (not converted to null) |
338
- | `Set` | ✅ Preserved as Set |
339
- | `Map` | ✅ Preserved as Map |
340
- | Circular refs | ✅ Handled via pointers |
341
-
342
- This is automatic — send a Date, receive a Date. No configuration needed.
343
-
344
- ---
345
-
346
- ## HTTP Streaming Fallback
347
-
348
- api-ape automatically falls back to HTTP streaming when WebSockets are unavailable or blocked by firewalls/proxies.
349
-
350
- ### How It Works
351
-
352
- 1. **WebSocket First**: Client attempts WebSocket connection (4 second timeout)
353
- 2. **Auto-Fallback**: On failure, switches to HTTP streaming transport
354
- 3. **Background Retry**: Keeps attempting WebSocket reconnection every 30 seconds
355
- 4. **Auto-Upgrade**: Switches back to WebSocket when available
356
-
357
- ### Streaming Transport
358
-
359
- When in polling mode:
360
- - **GET `/api/ape/poll`**: Long-lived connection, server streams JSON messages
361
- - **POST `/api/ape/poll`**: Client sends messages
362
- - Cookie-based session (`apeHostId`) for authentication
363
- - Heartbeat every 20 seconds to prevent proxy timeout
364
-
365
- ### Force Transport Mode
366
-
367
- ```js
368
- // Force HTTP streaming (useful for testing)
369
- ape.configure({ transport: 'polling' })
370
-
371
- // Force WebSocket only (fail if unavailable)
372
- ape.configure({ transport: 'websocket' })
373
-
374
- // Auto-detect (default)
375
- ape.configure({ transport: 'auto' })
376
- ```
377
-
378
- ### Check Active Transport
379
-
380
- ```js
381
- if (ape.getTransport() === 'polling') {
382
- console.log('Using HTTP streaming fallback')
383
- }
384
- ```
385
-
386
- ---
387
-
388
337
  ## Examples & Demos
389
338
 
390
339
  The repository contains working examples:
391
340
 
392
- * **`example/ExpressJs/`** — Simple real-time chat app
341
+ * **[`example/ExpressJs/`](example/ExpressJs/)** — Simple real-time chat app
393
342
  - Minimal setup with Express.js
394
343
  - Broadcast messages to other clients
395
344
  - Message history
396
345
 
397
- * **`example/NextJs/`** — Production-ready chat application
346
+ * **[`example/NextJs/`](example/NextJs/)** — Production-ready chat application
398
347
  - Custom Next.js server integration
399
348
  - React hooks integration
400
349
  - User presence tracking
401
350
  - Docker support
402
351
  - Connection lifecycle hooks
403
352
 
353
+ * **[`example/Vite/`](example/Vite/)** — Vite + Vue example
354
+ * **[`example/Bun/`](example/Bun/)** — Bun runtime example
355
+
404
356
  ### Run an Example
405
357
 
406
358
  **ExpressJs:**
@@ -427,74 +379,6 @@ docker-compose up --build
427
379
 
428
380
  ---
429
381
 
430
- ## Troubleshooting & FAQ
431
-
432
- ### CORS Errors in Browser
433
-
434
- Ensure your server allows WebSocket connections from your origin. api-ape uses the `ws` library which handles WebSocket upgrades on the HTTP server level.
435
-
436
- ### Controller Not Found
437
-
438
- * Check that your controller file is in the `where` directory (default: `api/`)
439
- * Ensure the file exports a function: `module.exports = function(...) { ... }`
440
- * File paths map directly: `api/users/list.js` → `ape.users.list()`
441
-
442
- ### Connection Drops Frequently
443
-
444
- The client automatically reconnects with exponential backoff. If connections drop often:
445
- * Check server WebSocket timeout settings
446
- * Verify network stability
447
- * Check server logs for errors
448
-
449
- ### Binary Data / File Transfers
450
-
451
- api-ape supports transparent binary file transfers. Simply return `Buffer` data from controllers:
452
-
453
- ```js
454
- // api/files/download.js
455
- module.exports = function(filename) {
456
- return {
457
- name: filename,
458
- data: fs.readFileSync(`./uploads/${filename}`) // Buffer
459
- }
460
- }
461
- ```
462
-
463
- The client receives `ArrayBuffer` automatically:
464
-
465
- ```js
466
- const result = await ape.files.download('image.png')
467
- console.log(result.data) // ArrayBuffer
468
-
469
- // Display as image
470
- const blob = new Blob([result.data])
471
- img.src = URL.createObjectURL(blob)
472
- ```
473
-
474
- **Uploads work the same way:**
475
-
476
- ```js
477
- // Client
478
- const arrayBuffer = await file.arrayBuffer()
479
- await ape.files.upload({ name: file.name, data: arrayBuffer })
480
-
481
- // Server (api/files/upload.js)
482
- module.exports = function({ name, data }) {
483
- fs.writeFileSync(`./uploads/${name}`, data) // data is Buffer
484
- return { success: true }
485
- }
486
- ```
487
-
488
- Binary data is transferred via temporary HTTP endpoints (`/api/ape/data/:hash`) with session verification and auto-cleanup.
489
-
490
- ### TypeScript Support
491
-
492
- Type definitions are included (`index.d.ts`). For full type safety, you may need to:
493
- * Define interfaces for your controller parameters and return types
494
- * Use type assertions when calling `ape.<path>.<method>()`
495
-
496
- ---
497
-
498
382
  ## Tests & CI
499
383
 
500
384
  ```bash
@@ -507,7 +391,6 @@ npm run test:cover # Coverage report
507
391
  - `npm test` — Run all tests
508
392
  - `npm run test:watch` — Watch mode for development
509
393
  - `npm run test:cover` — Generate coverage report
510
- - `npm run test:update` — Update snapshots
511
394
 
512
395
  **Supported:** Node.js 14+, modern browsers (Chrome, Firefox, Safari, Edge)
513
396
 
@@ -542,11 +425,34 @@ Versioning follows [Semantic Versioning](https://semver.org/).
542
425
 
543
426
  ---
544
427
 
428
+ ## Zero Dependencies
429
+
430
+ api-ape has **zero runtime dependencies**. The WebSocket implementation is built-in:
431
+
432
+ - **Node.js 24+**: Uses native `node:ws` module
433
+ - **Bun / Deno**: Uses framework-provided WebSocket support
434
+ - **Earlier Node.js**: Uses built-in RFC 6455 compliant WebSocket server
435
+
436
+ No `npm install` surprises, no dependency audits, no supply chain concerns.
437
+
438
+ ---
439
+
545
440
  ## Security
546
441
 
547
442
  **Reporting vulnerabilities:** Please report security issues via [GitHub Security Advisories](https://github.com/codemeasandwich/api-ape/security/advisories) or email the maintainer.
548
443
 
549
- **Security considerations:**
444
+ ### CSRF Protection
445
+
446
+ api-ape includes built-in **Cross-Site Request Forgery (CSRF)** protection via Origin validation:
447
+
448
+ - **Origin Header Check** — Every WebSocket connection validates the `Origin` header against the `Host` header
449
+ - **Automatic Rejection** — Connections from mismatched origins are destroyed immediately
450
+ - **No Configuration Needed** — Protection is enabled by default
451
+
452
+ This prevents malicious sites from making requests to your api-ape server while impersonating logged-in users.
453
+
454
+ ### Security Considerations
455
+
550
456
  * Validate all input in controllers
551
457
  * Use authentication/authorization in `onConnent` hooks
552
458
  * Sanitize data before broadcasting
@@ -559,34 +465,117 @@ Versioning follows [Semantic Versioning](https://semver.org/).
559
465
  ```
560
466
  api-ape/
561
467
  ├── client/
562
- │ ├── browser.js # Browser entry point (window.ape)
563
- └── connectSocket.js # WebSocket client with auto-reconnect
468
+ │ ├── index.js # Unified client entry point (auto-buffered)
469
+ ├── browser.js # Browser entry point (window.ape)
470
+ │ ├── connectSocket.js # WebSocket client with auto-reconnect
471
+ │ └── transports/
472
+ │ └── streaming.js # HTTP streaming fallback transport
564
473
  ├── server/
565
474
  │ ├── lib/
566
- │ │ ├── main.js # HTTP server integration
567
- │ │ ├── loader.js # Auto-loads controller files
568
- │ │ ├── broadcast.js # Client tracking & broadcast
569
- │ │ └── wiring.js # WebSocket handler setup
475
+ │ │ ├── main.js # HTTP server integration
476
+ │ │ ├── loader.js # Auto-loads controller files
477
+ │ │ ├── broadcast.js # Client tracking & broadcast
478
+ │ │ ├── wiring.js # WebSocket handler setup
479
+ │ │ ├── fileTransfer.js # Binary file transfer via HTTP
480
+ │ │ ├── longPolling.js # HTTP streaming fallback handler
481
+ │ │ ├── bun.js # Bun runtime support
482
+ │ │ ├── wsProvider.js # WebSocket provider abstraction
483
+ │ │ └── ws/ # Native WebSocket implementation
570
484
  │ ├── socket/
571
- │ │ ├── receive.js # Incoming message handler
572
- │ │ └── send.js # Outgoing message handler
573
- │ └── security/
574
- └── reply.js # Duplicate request protection
485
+ │ │ ├── open.js # Connection handler
486
+ │ │ ├── receive.js # Incoming message handler
487
+ └── send.js # Outgoing message handler
488
+ ├── security/
489
+ │ │ ├── reply.js # Duplicate request protection
490
+ │ │ ├── origin.js # Origin validation
491
+ │ │ └── extractRootDomain.js # Domain extraction utility
492
+ │ └── utils/
493
+ │ ├── deepRequire.js # Deep module loader
494
+ │ ├── genId.js # ID generation
495
+ │ └── parseUserAgent.js # Browser/OS/device parser
575
496
  ├── utils/
576
- │ ├── jss.js # JSON SuperSet encoder/decoder
577
- │ └── messageHash.js # Request deduplication
497
+ │ ├── jss.js # JSON SuperSet encoder/decoder
498
+ │ └── messageHash.js # Request deduplication hashing
578
499
  └── example/
579
- ├── ExpressJs/ # Chat app example
580
- └── NextJs/ # Next.js integration
500
+ ├── ExpressJs/ # Minimal chat app example
501
+ ├── NextJs/ # Next.js production example
502
+ ├── Vite/ # Vite/Vue example
503
+ └── Bun/ # Bun runtime example
504
+ ```
505
+
506
+ ---
507
+
508
+ ## Troubleshooting & FAQ
509
+
510
+ ### CORS Errors in Browser
511
+
512
+ Ensure your server allows WebSocket connections from your origin. api-ape uses the `ws` library which handles WebSocket upgrades on the HTTP server level.
513
+
514
+ ### Controller Not Found
515
+
516
+ * Check that your controller file is in the `where` directory (default: `api/`)
517
+ * Ensure the file exports a function: `module.exports = function(...) { ... }`
518
+ * File paths map directly: `api/users/list.js` → `api.users.list()`
519
+
520
+ ### Connection Drops Frequently
521
+
522
+ The client automatically reconnects with exponential backoff. If connections drop often:
523
+ * Check server WebSocket timeout settings
524
+ * Verify network stability
525
+ * Check server logs for errors
526
+
527
+ ### Binary Data / File Transfers
528
+
529
+ api-ape supports transparent binary file transfers. Simply return `Buffer` data from controllers:
530
+
531
+ ```js
532
+ // api/files/download.js
533
+ module.exports = function(filename) {
534
+ return {
535
+ name: filename,
536
+ data: fs.readFileSync(`./uploads/${filename}`) // Buffer
537
+ }
538
+ }
539
+ ```
540
+
541
+ The client receives `ArrayBuffer` automatically:
542
+
543
+ ```js
544
+ const result = await api.files.download('image.png')
545
+ console.log(result.data) // ArrayBuffer
546
+
547
+ // Display as image
548
+ const blob = new Blob([result.data])
549
+ img.src = URL.createObjectURL(blob)
581
550
  ```
582
551
 
552
+ **Uploads work the same way:**
553
+
554
+ ```js
555
+ // Client
556
+ const arrayBuffer = await file.arrayBuffer()
557
+ await api.files.upload({ name: file.name, data: arrayBuffer })
558
+
559
+ // Server (api/files/upload.js)
560
+ module.exports = function({ name, data }) {
561
+ fs.writeFileSync(`./uploads/${name}`, data) // data is Buffer
562
+ return { success: true }
563
+ }
564
+ ```
565
+
566
+ ### TypeScript Support
567
+
568
+ Type definitions are included (`index.d.ts`). For full type safety, you may need to:
569
+ * Define interfaces for your controller parameters and return types
570
+ * Use type assertions when calling `api.<path>.<method>()`
571
+
583
572
  ---
584
573
 
585
574
  ## License & Authors
586
575
 
587
576
  **License:** MIT
588
577
 
589
- **Author:** [Brian Shannon](https://github.com/codemeasandwich)
578
+ **Author:** [Brian Shannon](https://www.linkedin.com/in/brianshann/)
590
579
 
591
580
  **Repository:** [github.com/codemeasandwich/api-ape](https://github.com/codemeasandwich/api-ape)
592
581
 
package/client/README.md CHANGED
@@ -6,7 +6,8 @@ WebSocket client library with auto-reconnection and proxy-based API calls.
6
6
 
7
7
  | File | Description |
8
8
  |------|-------------|
9
- | `browser.js` | Browser entry point - exposes `window.ape` |
9
+ | `index.js` | **Unified entry** auto-initializing client with call buffering |
10
+ | `browser.js` | Browser entry point — exposes `window.ape` |
10
11
  | `connectSocket.js` | WebSocket client with auto-reconnect, queuing, and JJS encoding |
11
12
 
12
13
  ## Usage
@@ -17,36 +18,44 @@ WebSocket client library with auto-reconnection and proxy-based API calls.
17
18
  <script src="/api/ape.js"></script>
18
19
  <script>
19
20
  // Call server functions
20
- ape.hello('World').then(result => console.log(result))
21
+ api.hello('World').then(result => console.log(result))
21
22
 
22
23
  // Listen for broadcasts
23
- ape.on('message', ({ data }) => console.log(data))
24
+ api.on('message', ({ data }) => console.log(data))
24
25
  </script>
25
26
  ```
26
27
 
27
- ### ES Module Import
28
+ ### ES Module Import (React, Vue, etc.)
28
29
 
29
30
  ```bash
30
31
  npm i api-ape
31
32
  ```
32
33
 
33
34
  ```js
34
- import ape from 'api-ape'
35
+ import api from 'api-ape'
35
36
 
36
- // Configure
37
- ape.configure({ port: 3000 })
38
-
39
- // Connect and enable auto-reconnect
40
- const { sender, setOnReciver } = ape()
41
- ape.autoReconnect()
42
-
43
- // Use sender as API
44
- sender.users.list().then(users => ...)
37
+ // Just use it! Calls are buffered until connected.
38
+ api.users.list().then(users => console.log(users))
45
39
 
46
40
  // Listen for broadcasts
47
- setOnReciver('newUser', ({ data }) => ...)
41
+ api.on('message', ({ data }) => console.log(data))
42
+
43
+ // Track connection state
44
+ api.onConnectionChange((state) => {
45
+ console.log('Connection:', state)
46
+ // 'offline' | 'walled' | 'disconnected' | 'connecting' | 'connected'
47
+ })
48
48
  ```
49
49
 
50
+ **Connection States:**
51
+ - `offline` — Browser reports no network
52
+ - `walled` — Captive portal detected (WiFi without real internet)
53
+ - `disconnected` — Had connection, lost it
54
+ - `connecting` — Actively connecting
55
+ - `connected` — Ready to use
56
+
57
+ **No async setup needed!** The client auto-initializes and buffers calls until connected.
58
+
50
59
  ## Features
51
60
 
52
61
  - **Proxy-based API** — `ape.path.method(data)` converts to WebSocket calls
@@ -55,19 +64,6 @@ setOnReciver('newUser', ({ data }) => ...)
55
64
  - **JJS encoding** — Supports Date, RegExp, Error, Set, Map, undefined over the wire
56
65
  - **Request timeout** — Configurable timeout (default: 10s)
57
66
 
58
- ## Configuration
59
-
60
- ```js
61
- ape.configure({
62
- port: 3000, // WebSocket port
63
- host: 'api.example.com' // WebSocket host
64
- })
65
- ```
66
-
67
- Default port detection:
68
- - Local (`localhost`, `127.0.0.1`): `9010`
69
- - Remote: Uses current page port or `443`/`80`
70
-
71
67
  ## File Transfers
72
68
 
73
69
  Binary data is automatically handled. The client fetches binary resources and uploads binary data transparently.
@@ -76,7 +72,7 @@ Binary data is automatically handled. The client fetches binary resources and up
76
72
 
77
73
  ```js
78
74
  // Server returns Buffer, client receives ArrayBuffer
79
- const result = await ape.files.download('image.png')
75
+ const result = await api.files.download('image.png')
80
76
  console.log(result.data) // ArrayBuffer
81
77
 
82
78
  // Display as image
@@ -91,7 +87,7 @@ const file = input.files[0]
91
87
  const arrayBuffer = await file.arrayBuffer()
92
88
 
93
89
  // Binary data is uploaded automatically
94
- await ape.files.upload({
90
+ await api.files.upload({
95
91
  name: file.name,
96
92
  data: arrayBuffer // Sent via HTTP PUT
97
93
  })
@@ -99,3 +95,14 @@ await ape.files.upload({
99
95
 
100
96
  Binary transfers use `/api/ape/data/:hash` endpoints with session verification.
101
97
 
98
+ ## Security
99
+
100
+ ### CSRF Protection
101
+
102
+ api-ape includes built-in **Cross-Site Request Forgery (CSRF)** protection:
103
+
104
+ - **Origin Validation** — WebSocket connections validate Origin header against Host
105
+ - **Automatic Rejection** — Mismatched origins are rejected immediately
106
+ - **Session Verification** — Binary transfers verify session cookies
107
+
108
+ No configuration needed — protection is enabled by default.