api-ape 1.1.0 → 2.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.
Files changed (41) hide show
  1. package/README.md +114 -11
  2. package/client/browser.js +19 -1
  3. package/client/connectSocket.js +556 -368
  4. package/client/transports/streaming.js +253 -0
  5. package/dist/ape.js +651 -301
  6. package/example/Bun/README.md +74 -0
  7. package/example/Bun/api/message.ts +11 -0
  8. package/example/Bun/index.html +76 -0
  9. package/example/Bun/package.json +9 -0
  10. package/example/Bun/server.ts +59 -0
  11. package/example/Bun/styles.css +128 -0
  12. package/example/ExpressJs/README.md +5 -7
  13. package/example/ExpressJs/backend.js +23 -21
  14. package/example/NextJs/ape/client.js +3 -3
  15. package/example/NextJs/ape/onConnect.js +5 -5
  16. package/example/NextJs/package-lock.json +1353 -60
  17. package/example/NextJs/package.json +0 -1
  18. package/example/NextJs/pages/index.tsx +21 -10
  19. package/example/NextJs/server.js +7 -11
  20. package/example/README.md +51 -0
  21. package/example/Vite/README.md +68 -0
  22. package/example/Vite/ape/client.ts +66 -0
  23. package/example/Vite/ape/onConnect.ts +52 -0
  24. package/example/Vite/api/message.ts +57 -0
  25. package/example/Vite/index.html +16 -0
  26. package/example/Vite/package.json +19 -0
  27. package/example/Vite/server.ts +62 -0
  28. package/example/Vite/src/App.vue +170 -0
  29. package/example/Vite/src/components/Info.vue +352 -0
  30. package/example/Vite/src/main.ts +5 -0
  31. package/example/Vite/src/style.css +200 -0
  32. package/example/Vite/src/vite-env.d.ts +7 -0
  33. package/example/Vite/vite.config.ts +20 -0
  34. package/index.d.ts +40 -3
  35. package/package.json +26 -11
  36. package/server/README.md +54 -9
  37. package/server/index.js +10 -2
  38. package/server/lib/longPolling.js +221 -0
  39. package/server/lib/main.js +172 -60
  40. package/server/security/origin.js +16 -4
  41. package/server/utils/deepRequire.js +25 -10
package/README.md CHANGED
@@ -24,18 +24,30 @@ yarn add api-ape
24
24
 
25
25
  ## Quick Start
26
26
 
27
- ### Server (Express.js)
27
+ ### Server (Node.js)
28
28
 
29
29
  ```js
30
- const express = require('express')
30
+ const { createServer } = require('http')
31
31
  const ape = require('api-ape')
32
32
 
33
- const app = express()
33
+ const server = createServer()
34
34
 
35
35
  // Wire up api-ape - loads controllers from ./api folder
36
- ape(app, { where: 'api' })
36
+ ape(server, { where: 'api' })
37
37
 
38
- app.listen(3000)
38
+ server.listen(3000)
39
+ ```
40
+
41
+ **With Express:**
42
+ ```js
43
+ const express = require('express')
44
+ const ape = require('api-ape')
45
+
46
+ const app = express()
47
+ const server = app.listen(3000) // Get the HTTP server
48
+
49
+ // Pass the HTTP server (not the Express app)
50
+ ape(server, { where: 'api' })
39
51
  ```
40
52
 
41
53
  ### Create a Controller
@@ -77,6 +89,7 @@ Include the bundled client and start calling:
77
89
  * **Real-time broadcasts** — Built-in `broadcast()` and `broadcastOthers()` methods for pushing to clients
78
90
  * **Promise-based calls** — Chainable paths like `ape.users.list()` map to `api/users/list.js`
79
91
  * **Automatic reconnection** — Client auto-reconnects on disconnect with exponential backoff
92
+ * **HTTP streaming fallback** — Automatically falls back to long polling when WebSockets are blocked
80
93
  * **JJS Encoding** — Extended JSON supporting Date, RegExp, Error, Set, Map, undefined, and circular refs
81
94
  * **Connection lifecycle hooks** — Customize behavior on connect, receive, send, error, and disconnect
82
95
 
@@ -86,12 +99,13 @@ Include the bundled client and start calling:
86
99
 
87
100
  ### Server
88
101
 
89
- #### `ape(app, options)`
102
+ #### `ape(server, options)`
90
103
 
91
- Initialize api-ape on an Express app.
104
+ Initialize api-ape on a Node.js HTTP/HTTPS server.
92
105
 
93
106
  | Option | Type | Description |
94
107
  |--------|------|-------------|
108
+ | `server` | `http.Server` | Node.js HTTP or HTTPS server instance |
95
109
  | `where` | `string` | Directory containing controller files (default: `'api'`) |
96
110
  | `onConnent` | `function` | Connection lifecycle hook (see [Connection Lifecycle](#connection-lifecycle)) |
97
111
 
@@ -139,6 +153,53 @@ ape.on('notification', ({ data, err, type }) => {
139
153
  })
140
154
  ```
141
155
 
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
+
182
+ Get the currently active transport type.
183
+
184
+ ```js
185
+ const transport = ape.getTransport()
186
+ console.log(transport) // 'websocket' | 'polling' | null
187
+ ```
188
+
189
+ #### `ape.onConnectionChange(handler)`
190
+
191
+ Listen for connection state changes.
192
+
193
+ ```js
194
+ const unsubscribe = ape.onConnectionChange((state) => {
195
+ console.log('Connection state:', state)
196
+ // 'disconnected' | 'connecting' | 'connected'
197
+ })
198
+
199
+ // Later: stop listening
200
+ unsubscribe()
201
+ ```
202
+
142
203
  ---
143
204
 
144
205
  ## Configuration
@@ -146,7 +207,7 @@ ape.on('notification', ({ data, err, type }) => {
146
207
  ### Default Options
147
208
 
148
209
  ```js
149
- ape(app, {
210
+ ape(server, {
150
211
  where: 'api', // Controller directory
151
212
  onConnent: undefined // Lifecycle hook (optional)
152
213
  })
@@ -157,7 +218,7 @@ ape(app, {
157
218
  Customize behavior per connection:
158
219
 
159
220
  ```js
160
- ape(app, {
221
+ ape(server, {
161
222
  where: 'api',
162
223
  onConnent(socket, req, hostId) {
163
224
  return {
@@ -282,6 +343,48 @@ This is automatic — send a Date, receive a Date. No configuration needed.
282
343
 
283
344
  ---
284
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
+
285
388
  ## Examples & Demos
286
389
 
287
390
  The repository contains working examples:
@@ -328,7 +431,7 @@ docker-compose up --build
328
431
 
329
432
  ### CORS Errors in Browser
330
433
 
331
- Ensure your Express server allows WebSocket connections from your origin. api-ape uses `express-ws` which handles CORS automatically, but verify your Express CORS middleware allows WebSocket upgrade requests.
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.
332
435
 
333
436
  ### Controller Not Found
334
437
 
@@ -460,7 +563,7 @@ api-ape/
460
563
  │ └── connectSocket.js # WebSocket client with auto-reconnect
461
564
  ├── server/
462
565
  │ ├── lib/
463
- │ │ ├── main.js # Express integration
566
+ │ │ ├── main.js # HTTP server integration
464
567
  │ │ ├── loader.js # Auto-loads controller files
465
568
  │ │ ├── broadcast.js # Client tracking & broadcast
466
569
  │ │ └── wiring.js # WebSocket handler setup
package/client/browser.js CHANGED
@@ -4,7 +4,7 @@ import connectSocket from './connectSocket.js'
4
4
  const port = window.location.port || (window.location.protocol === 'https:' ? 443 : 80)
5
5
  connectSocket.configure({ port: parseInt(port, 10) })
6
6
 
7
- const { sender, setOnReciver } = connectSocket()
7
+ const { sender, setOnReciver, onConnectionChange, getTransport } = connectSocket()
8
8
  connectSocket.autoReconnect()
9
9
 
10
10
  // Global API - use defineProperty to bypass Proxy interception
@@ -15,3 +15,21 @@ Object.defineProperty(window.ape, 'on', {
15
15
  enumerable: false,
16
16
  configurable: false
17
17
  })
18
+ Object.defineProperty(window.ape, 'onConnectionChange', {
19
+ value: onConnectionChange,
20
+ writable: false,
21
+ enumerable: false,
22
+ configurable: false
23
+ })
24
+ Object.defineProperty(window.ape, 'configure', {
25
+ value: connectSocket.configure,
26
+ writable: false,
27
+ enumerable: false,
28
+ configurable: false
29
+ })
30
+ Object.defineProperty(window.ape, 'getTransport', {
31
+ value: getTransport,
32
+ writable: false,
33
+ enumerable: false,
34
+ configurable: false
35
+ })