api-ape 2.0.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 (89) hide show
  1. package/README.md +203 -124
  2. package/client/README.md +37 -30
  3. package/client/browser.js +10 -8
  4. package/client/connectSocket.js +662 -381
  5. package/client/index.js +171 -0
  6. package/client/transports/streaming.js +240 -0
  7. package/dist/ape.js +2 -699
  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 +71 -18
  12. package/package.json +50 -15
  13. package/server/README.md +99 -13
  14. package/server/lib/broadcast.js +25 -8
  15. package/server/lib/bun.js +122 -0
  16. package/server/lib/longPolling.js +226 -0
  17. package/server/lib/main.js +381 -38
  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/security/origin.js +16 -4
  27. package/server/socket/receive.js +14 -1
  28. package/server/socket/send.js +6 -6
  29. package/server/utils/deepRequire.js +25 -10
  30. package/server/utils/parseUserAgent.js +286 -0
  31. package/example/Bun/README.md +0 -74
  32. package/example/Bun/api/message.ts +0 -11
  33. package/example/Bun/index.html +0 -76
  34. package/example/Bun/package.json +0 -9
  35. package/example/Bun/server.ts +0 -59
  36. package/example/Bun/styles.css +0 -128
  37. package/example/ExpressJs/README.md +0 -95
  38. package/example/ExpressJs/api/message.js +0 -11
  39. package/example/ExpressJs/backend.js +0 -39
  40. package/example/ExpressJs/index.html +0 -88
  41. package/example/ExpressJs/package-lock.json +0 -834
  42. package/example/ExpressJs/package.json +0 -10
  43. package/example/ExpressJs/styles.css +0 -128
  44. package/example/NextJs/.dockerignore +0 -29
  45. package/example/NextJs/Dockerfile +0 -52
  46. package/example/NextJs/Dockerfile.dev +0 -27
  47. package/example/NextJs/README.md +0 -113
  48. package/example/NextJs/ape/client.js +0 -66
  49. package/example/NextJs/ape/embed.js +0 -12
  50. package/example/NextJs/ape/index.js +0 -23
  51. package/example/NextJs/ape/logic/chat.js +0 -62
  52. package/example/NextJs/ape/onConnect.js +0 -69
  53. package/example/NextJs/ape/onDisconnect.js +0 -13
  54. package/example/NextJs/ape/onError.js +0 -9
  55. package/example/NextJs/ape/onReceive.js +0 -15
  56. package/example/NextJs/ape/onSend.js +0 -15
  57. package/example/NextJs/api/message.js +0 -44
  58. package/example/NextJs/docker-compose.yml +0 -22
  59. package/example/NextJs/next-env.d.ts +0 -5
  60. package/example/NextJs/next.config.js +0 -8
  61. package/example/NextJs/package-lock.json +0 -6400
  62. package/example/NextJs/package.json +0 -24
  63. package/example/NextJs/pages/Info.tsx +0 -153
  64. package/example/NextJs/pages/_app.tsx +0 -6
  65. package/example/NextJs/pages/index.tsx +0 -275
  66. package/example/NextJs/public/favicon.ico +0 -0
  67. package/example/NextJs/public/vercel.svg +0 -4
  68. package/example/NextJs/server.js +0 -36
  69. package/example/NextJs/styles/Chat.module.css +0 -448
  70. package/example/NextJs/styles/Home.module.css +0 -129
  71. package/example/NextJs/styles/globals.css +0 -26
  72. package/example/NextJs/tsconfig.json +0 -20
  73. package/example/README.md +0 -117
  74. package/example/Vite/README.md +0 -68
  75. package/example/Vite/ape/client.ts +0 -66
  76. package/example/Vite/ape/onConnect.ts +0 -52
  77. package/example/Vite/api/message.ts +0 -57
  78. package/example/Vite/index.html +0 -16
  79. package/example/Vite/package.json +0 -19
  80. package/example/Vite/server.ts +0 -62
  81. package/example/Vite/src/App.vue +0 -170
  82. package/example/Vite/src/components/Info.vue +0 -352
  83. package/example/Vite/src/main.ts +0 -5
  84. package/example/Vite/src/style.css +0 -200
  85. package/example/Vite/src/vite-env.d.ts +0 -7
  86. package/example/Vite/vite.config.ts +0 -20
  87. package/todo.md +0 -85
  88. package/utils/jss.test.js +0 -261
  89. 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,8 +111,9 @@ 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
116
+ * **HTTP streaming fallback** — Automatically falls back to long polling when WebSockets are blocked
92
117
  * **JJS Encoding** — Extended JSON supporting Date, RegExp, Error, Set, Map, undefined, and circular refs
93
118
  * **Connection lifecycle hooks** — Customize behavior on connect, receive, send, error, and disconnect
94
119
 
@@ -117,41 +142,74 @@ Inside controller functions, `this` provides:
117
142
  | `this.broadcast(type, data)` | Send to **ALL** connected clients |
118
143
  | `this.broadcastOthers(type, data)` | Send to all **EXCEPT** the caller |
119
144
  | `this.online()` | Get count of connected clients |
120
- | `this.getClients()` | Get array of connected hostIds |
121
- | `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`) |
122
148
  | `this.req` | Original HTTP request |
123
149
  | `this.socket` | WebSocket instance |
124
150
  | `this.agent` | Parsed user-agent (browser, OS, device) |
125
151
 
126
152
  ### Client
127
153
 
128
- #### `ape.<path>.<method>(...args)`
154
+ #### `api.<path>.<method>(...args)`
129
155
 
130
156
  Call a server function. Returns a Promise.
131
157
 
132
158
  ```js
133
159
  // Calls api/users/list.js
134
- const users = await ape.users.list()
160
+ const users = await api.users.list()
135
161
 
136
162
  // Calls api/users/create.js with data
137
- const user = await ape.users.create({ name: 'Alice' })
163
+ const user = await api.users.create({ name: 'Alice' })
138
164
 
139
165
  // Nested paths work too
140
- // ape.admin.users -> api/admin/users.js
141
- // ape.admin.users.delete -> api/admin/users/delete.js
142
- 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)
143
169
  ```
144
170
 
145
- #### `ape.on(type, handler)`
171
+ #### `api.on(type, handler)`
146
172
 
147
173
  Listen for server broadcasts.
148
174
 
149
175
  ```js
150
- ape.on('notification', ({ data, err, type }) => {
176
+ api.on('notification', ({ data, err, type }) => {
151
177
  console.log('Received:', data)
152
178
  })
153
179
  ```
154
180
 
181
+ #### `api.getTransport()`
182
+
183
+ Get the currently active transport type.
184
+
185
+ ```js
186
+ const transport = api.getTransport()
187
+ console.log(transport) // 'websocket' | 'polling' | null
188
+ ```
189
+
190
+ #### `api.onConnectionChange(handler)`
191
+
192
+ Listen for connection state changes.
193
+
194
+ ```js
195
+ const unsubscribe = api.onConnectionChange((state) => {
196
+ console.log('Connection state:', state)
197
+ })
198
+
199
+ // Later: stop listening
200
+ unsubscribe()
201
+ ```
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
+
155
213
  ---
156
214
 
157
215
  ## Configuration
@@ -160,8 +218,7 @@ ape.on('notification', ({ data, err, type }) => {
160
218
 
161
219
  ```js
162
220
  ape(server, {
163
- where: 'api', // Controller directory
164
- onConnent: undefined // Lifecycle hook (optional)
221
+ where: 'api' // Controller directory
165
222
  })
166
223
  ```
167
224
 
@@ -172,12 +229,12 @@ Customize behavior per connection:
172
229
  ```js
173
230
  ape(server, {
174
231
  where: 'api',
175
- onConnent(socket, req, hostId) {
232
+ onConnent(socket, req, clientId) {
176
233
  return {
177
234
  // Embed values into `this` for all controllers
178
235
  embed: {
179
236
  userId: req.session?.userId,
180
- clientId: String(hostId)
237
+ id: String(clientId)
181
238
  },
182
239
 
183
240
  // Before/after hooks
@@ -268,7 +325,7 @@ module.exports = async function(id) {
268
325
 
269
326
  ```js
270
327
  try {
271
- const result = await ape.data.get(id)
328
+ const result = await api.data.get(id)
272
329
  console.log(result)
273
330
  } catch (err) {
274
331
  console.error('Server error:', err)
@@ -277,40 +334,25 @@ try {
277
334
 
278
335
  ---
279
336
 
280
- ## JJS Encoding
281
-
282
- api-ape uses **JJS (JSON SuperSet)** encoding, which extends JSON to support:
283
-
284
- | Type | Supported |
285
- |------|-----------|
286
- | `Date` | ✅ Preserved as Date objects |
287
- | `RegExp` | ✅ Preserved as RegExp |
288
- | `Error` | ✅ Preserved with name, message, stack |
289
- | `undefined` | ✅ Preserved (not converted to null) |
290
- | `Set` | ✅ Preserved as Set |
291
- | `Map` | ✅ Preserved as Map |
292
- | Circular refs | ✅ Handled via pointers |
293
-
294
- This is automatic — send a Date, receive a Date. No configuration needed.
295
-
296
- ---
297
-
298
337
  ## Examples & Demos
299
338
 
300
339
  The repository contains working examples:
301
340
 
302
- * **`example/ExpressJs/`** — Simple real-time chat app
341
+ * **[`example/ExpressJs/`](example/ExpressJs/)** — Simple real-time chat app
303
342
  - Minimal setup with Express.js
304
343
  - Broadcast messages to other clients
305
344
  - Message history
306
345
 
307
- * **`example/NextJs/`** — Production-ready chat application
346
+ * **[`example/NextJs/`](example/NextJs/)** — Production-ready chat application
308
347
  - Custom Next.js server integration
309
348
  - React hooks integration
310
349
  - User presence tracking
311
350
  - Docker support
312
351
  - Connection lifecycle hooks
313
352
 
353
+ * **[`example/Vite/`](example/Vite/)** — Vite + Vue example
354
+ * **[`example/Bun/`](example/Bun/)** — Bun runtime example
355
+
314
356
  ### Run an Example
315
357
 
316
358
  **ExpressJs:**
@@ -337,74 +379,6 @@ docker-compose up --build
337
379
 
338
380
  ---
339
381
 
340
- ## Troubleshooting & FAQ
341
-
342
- ### CORS Errors in Browser
343
-
344
- Ensure your server allows WebSocket connections from your origin. api-ape uses the `ws` library which handles WebSocket upgrades on the HTTP server level.
345
-
346
- ### Controller Not Found
347
-
348
- * Check that your controller file is in the `where` directory (default: `api/`)
349
- * Ensure the file exports a function: `module.exports = function(...) { ... }`
350
- * File paths map directly: `api/users/list.js` → `ape.users.list()`
351
-
352
- ### Connection Drops Frequently
353
-
354
- The client automatically reconnects with exponential backoff. If connections drop often:
355
- * Check server WebSocket timeout settings
356
- * Verify network stability
357
- * Check server logs for errors
358
-
359
- ### Binary Data / File Transfers
360
-
361
- api-ape supports transparent binary file transfers. Simply return `Buffer` data from controllers:
362
-
363
- ```js
364
- // api/files/download.js
365
- module.exports = function(filename) {
366
- return {
367
- name: filename,
368
- data: fs.readFileSync(`./uploads/${filename}`) // Buffer
369
- }
370
- }
371
- ```
372
-
373
- The client receives `ArrayBuffer` automatically:
374
-
375
- ```js
376
- const result = await ape.files.download('image.png')
377
- console.log(result.data) // ArrayBuffer
378
-
379
- // Display as image
380
- const blob = new Blob([result.data])
381
- img.src = URL.createObjectURL(blob)
382
- ```
383
-
384
- **Uploads work the same way:**
385
-
386
- ```js
387
- // Client
388
- const arrayBuffer = await file.arrayBuffer()
389
- await ape.files.upload({ name: file.name, data: arrayBuffer })
390
-
391
- // Server (api/files/upload.js)
392
- module.exports = function({ name, data }) {
393
- fs.writeFileSync(`./uploads/${name}`, data) // data is Buffer
394
- return { success: true }
395
- }
396
- ```
397
-
398
- Binary data is transferred via temporary HTTP endpoints (`/api/ape/data/:hash`) with session verification and auto-cleanup.
399
-
400
- ### TypeScript Support
401
-
402
- Type definitions are included (`index.d.ts`). For full type safety, you may need to:
403
- * Define interfaces for your controller parameters and return types
404
- * Use type assertions when calling `ape.<path>.<method>()`
405
-
406
- ---
407
-
408
382
  ## Tests & CI
409
383
 
410
384
  ```bash
@@ -417,7 +391,6 @@ npm run test:cover # Coverage report
417
391
  - `npm test` — Run all tests
418
392
  - `npm run test:watch` — Watch mode for development
419
393
  - `npm run test:cover` — Generate coverage report
420
- - `npm run test:update` — Update snapshots
421
394
 
422
395
  **Supported:** Node.js 14+, modern browsers (Chrome, Firefox, Safari, Edge)
423
396
 
@@ -452,11 +425,34 @@ Versioning follows [Semantic Versioning](https://semver.org/).
452
425
 
453
426
  ---
454
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
+
455
440
  ## Security
456
441
 
457
442
  **Reporting vulnerabilities:** Please report security issues via [GitHub Security Advisories](https://github.com/codemeasandwich/api-ape/security/advisories) or email the maintainer.
458
443
 
459
- **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
+
460
456
  * Validate all input in controllers
461
457
  * Use authentication/authorization in `onConnent` hooks
462
458
  * Sanitize data before broadcasting
@@ -469,34 +465,117 @@ Versioning follows [Semantic Versioning](https://semver.org/).
469
465
  ```
470
466
  api-ape/
471
467
  ├── client/
472
- │ ├── browser.js # Browser entry point (window.ape)
473
- └── 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
474
473
  ├── server/
475
474
  │ ├── lib/
476
- │ │ ├── main.js # HTTP server integration
477
- │ │ ├── loader.js # Auto-loads controller files
478
- │ │ ├── broadcast.js # Client tracking & broadcast
479
- │ │ └── 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
480
484
  │ ├── socket/
481
- │ │ ├── receive.js # Incoming message handler
482
- │ │ └── send.js # Outgoing message handler
483
- │ └── security/
484
- └── 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
485
496
  ├── utils/
486
- │ ├── jss.js # JSON SuperSet encoder/decoder
487
- │ └── messageHash.js # Request deduplication
497
+ │ ├── jss.js # JSON SuperSet encoder/decoder
498
+ │ └── messageHash.js # Request deduplication hashing
488
499
  └── example/
489
- ├── ExpressJs/ # Chat app example
490
- └── 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)
491
550
  ```
492
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
+
493
572
  ---
494
573
 
495
574
  ## License & Authors
496
575
 
497
576
  **License:** MIT
498
577
 
499
- **Author:** [Brian Shannon](https://github.com/codemeasandwich)
578
+ **Author:** [Brian Shannon](https://www.linkedin.com/in/brianshann/)
500
579
 
501
580
  **Repository:** [github.com/codemeasandwich/api-ape](https://github.com/codemeasandwich/api-ape)
502
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.
package/client/browser.js CHANGED
@@ -1,23 +1,25 @@
1
1
  import connectSocket from './connectSocket.js'
2
2
 
3
- // Auto-configure for current page
4
- const port = window.location.port || (window.location.protocol === 'https:' ? 443 : 80)
5
- connectSocket.configure({ port: parseInt(port, 10) })
6
-
7
- const { sender, setOnReciver, onConnectionChange } = connectSocket()
3
+ const { sender, setOnReciver, onConnectionChange, getTransport } = connectSocket()
8
4
  connectSocket.autoReconnect()
9
5
 
10
6
  // Global API - use defineProperty to bypass Proxy interception
11
- window.ape = sender
12
- Object.defineProperty(window.ape, 'on', {
7
+ window.api = sender
8
+ Object.defineProperty(window.api, 'on', {
13
9
  value: setOnReciver,
14
10
  writable: false,
15
11
  enumerable: false,
16
12
  configurable: false
17
13
  })
18
- Object.defineProperty(window.ape, 'onConnectionChange', {
14
+ Object.defineProperty(window.api, 'onConnectionChange', {
19
15
  value: onConnectionChange,
20
16
  writable: false,
21
17
  enumerable: false,
22
18
  configurable: false
23
19
  })
20
+ Object.defineProperty(window.api, 'getTransport', {
21
+ value: getTransport,
22
+ writable: false,
23
+ enumerable: false,
24
+ configurable: false
25
+ })