api-ape 0.0.0 → 1.0.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 (64) hide show
  1. package/README.md +458 -0
  2. package/client/README.md +69 -0
  3. package/client/browser.js +17 -0
  4. package/client/connectSocket.js +260 -0
  5. package/dist/ape.js +454 -0
  6. package/example/ExpressJs/README.md +97 -0
  7. package/example/ExpressJs/api/message.js +11 -0
  8. package/example/ExpressJs/backend.js +37 -0
  9. package/example/ExpressJs/index.html +88 -0
  10. package/example/ExpressJs/package-lock.json +834 -0
  11. package/example/ExpressJs/package.json +10 -0
  12. package/example/ExpressJs/styles.css +128 -0
  13. package/example/NextJs/.dockerignore +29 -0
  14. package/example/NextJs/Dockerfile +52 -0
  15. package/example/NextJs/Dockerfile.dev +27 -0
  16. package/example/NextJs/README.md +113 -0
  17. package/example/NextJs/ape/client.js +66 -0
  18. package/example/NextJs/ape/embed.js +12 -0
  19. package/example/NextJs/ape/index.js +23 -0
  20. package/example/NextJs/ape/logic/chat.js +62 -0
  21. package/example/NextJs/ape/onConnect.js +69 -0
  22. package/example/NextJs/ape/onDisconnect.js +13 -0
  23. package/example/NextJs/ape/onError.js +9 -0
  24. package/example/NextJs/ape/onReceive.js +15 -0
  25. package/example/NextJs/ape/onSend.js +15 -0
  26. package/example/NextJs/api/message.js +44 -0
  27. package/example/NextJs/docker-compose.yml +22 -0
  28. package/example/NextJs/next-env.d.ts +5 -0
  29. package/example/NextJs/next.config.js +8 -0
  30. package/example/NextJs/package-lock.json +5107 -0
  31. package/example/NextJs/package.json +25 -0
  32. package/example/NextJs/pages/Info.tsx +153 -0
  33. package/example/NextJs/pages/_app.tsx +6 -0
  34. package/example/NextJs/pages/index.tsx +264 -0
  35. package/example/NextJs/public/favicon.ico +0 -0
  36. package/example/NextJs/public/vercel.svg +4 -0
  37. package/example/NextJs/server.js +40 -0
  38. package/example/NextJs/styles/Chat.module.css +448 -0
  39. package/example/NextJs/styles/Home.module.css +129 -0
  40. package/example/NextJs/styles/globals.css +26 -0
  41. package/example/NextJs/tsconfig.json +20 -0
  42. package/example/README.md +66 -0
  43. package/index.d.ts +179 -0
  44. package/index.js +11 -0
  45. package/package.json +11 -4
  46. package/server/README.md +93 -0
  47. package/server/index.js +6 -0
  48. package/server/lib/broadcast.js +63 -0
  49. package/server/lib/loader.js +10 -0
  50. package/server/lib/main.js +23 -0
  51. package/server/lib/wiring.js +94 -0
  52. package/server/security/extractRootDomain.js +21 -0
  53. package/server/security/origin.js +13 -0
  54. package/server/security/reply.js +21 -0
  55. package/server/socket/open.js +10 -0
  56. package/server/socket/receive.js +66 -0
  57. package/server/socket/send.js +55 -0
  58. package/server/utils/deepRequire.js +45 -0
  59. package/server/utils/genId.js +24 -0
  60. package/todo.md +85 -0
  61. package/utils/jss.js +273 -0
  62. package/utils/jss.test.js +261 -0
  63. package/utils/messageHash.js +43 -0
  64. package/utils/messageHash.test.js +56 -0
package/README.md ADDED
@@ -0,0 +1,458 @@
1
+ # 🦍 api-ape
2
+
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
+ [![GitHub issues](https://img.shields.io/github/issues/codemeasandwich/api-ape)](https://github.com/codemeasandwich/api-ape/issues)
6
+
7
+ **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
+
9
+ ---
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install api-ape
15
+ # or
16
+ pnpm add api-ape
17
+ # or
18
+ yarn add api-ape
19
+ ```
20
+
21
+ **Requirements:** Node.js 14+ (for server), modern browsers (for client)
22
+
23
+ ---
24
+
25
+ ## Quick Start
26
+
27
+ ### Server (Express.js)
28
+
29
+ ```js
30
+ const express = require('express')
31
+ const ape = require('api-ape')
32
+
33
+ const app = express()
34
+
35
+ // Wire up api-ape - loads controllers from ./api folder
36
+ ape(app, { where: 'api' })
37
+
38
+ app.listen(3000)
39
+ ```
40
+
41
+ ### Create a Controller
42
+
43
+ Drop a file in your `api/` folder — it automatically becomes an endpoint:
44
+
45
+ ```js
46
+ // api/hello.js
47
+ module.exports = function(name) {
48
+ return `Hello, ${name}!`
49
+ }
50
+ ```
51
+
52
+ ### Client (Browser)
53
+
54
+ Include the bundled client and start calling:
55
+
56
+ ```html
57
+ <script src="/api/ape.js"></script>
58
+ <script>
59
+ // Call server functions like local methods
60
+ const result = await ape.hello('World')
61
+ console.log(result) // "Hello, World!"
62
+
63
+ // Listen for broadcasts
64
+ ape.on('message', ({ data }) => {
65
+ console.log('New message:', data)
66
+ })
67
+ </script>
68
+ ```
69
+
70
+ **That's it!** Your server function is now callable from the browser.
71
+
72
+ ---
73
+
74
+ ## Key Concepts
75
+
76
+ * **Auto-wiring** — Drop JS files in a folder, they become API endpoints automatically
77
+ * **Real-time broadcasts** — Built-in `broadcast()` and `broadcastOthers()` methods for pushing to clients
78
+ * **Promise-based calls** — Chainable paths like `ape.users.list()` map to `api/users/list.js`
79
+ * **Automatic reconnection** — Client auto-reconnects on disconnect with exponential backoff
80
+ * **JJS Encoding** — Extended JSON supporting Date, RegExp, Error, Set, Map, undefined, and circular refs
81
+ * **Connection lifecycle hooks** — Customize behavior on connect, receive, send, error, and disconnect
82
+
83
+ ---
84
+
85
+ ## API Reference
86
+
87
+ ### Server
88
+
89
+ #### `ape(app, options)`
90
+
91
+ Initialize api-ape on an Express app.
92
+
93
+ | Option | Type | Description |
94
+ |--------|------|-------------|
95
+ | `where` | `string` | Directory containing controller files (default: `'api'`) |
96
+ | `onConnent` | `function` | Connection lifecycle hook (see [Connection Lifecycle](#connection-lifecycle)) |
97
+
98
+ #### Controller Context (`this`)
99
+
100
+ Inside controller functions, `this` provides:
101
+
102
+ | Property | Description |
103
+ |----------|-------------|
104
+ | `this.broadcast(type, data)` | Send to **ALL** connected clients |
105
+ | `this.broadcastOthers(type, data)` | Send to all **EXCEPT** the caller |
106
+ | `this.online()` | Get count of connected clients |
107
+ | `this.getClients()` | Get array of connected hostIds |
108
+ | `this.hostId` | Unique ID of the calling client |
109
+ | `this.req` | Original HTTP request |
110
+ | `this.socket` | WebSocket instance |
111
+ | `this.agent` | Parsed user-agent (browser, OS, device) |
112
+
113
+ ### Client
114
+
115
+ #### `ape.<path>.<method>(...args)`
116
+
117
+ Call a server function. Returns a Promise.
118
+
119
+ ```js
120
+ // Calls api/users/list.js
121
+ const users = await ape.users.list()
122
+
123
+ // Calls api/users/create.js with data
124
+ const user = await ape.users.create({ name: 'Alice' })
125
+
126
+ // Nested paths work too
127
+ // ape.admin.users -> api/admin/users.js
128
+ // ape.admin.users.delete -> api/admin/users/delete.js
129
+ await ape.admin.users.delete(userId)
130
+ ```
131
+
132
+ #### `ape.on(type, handler)`
133
+
134
+ Listen for server broadcasts.
135
+
136
+ ```js
137
+ ape.on('notification', ({ data, err, type }) => {
138
+ console.log('Received:', data)
139
+ })
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Configuration
145
+
146
+ ### Default Options
147
+
148
+ ```js
149
+ ape(app, {
150
+ where: 'api', // Controller directory
151
+ onConnent: undefined // Lifecycle hook (optional)
152
+ })
153
+ ```
154
+
155
+ ### Connection Lifecycle Hook
156
+
157
+ Customize behavior per connection:
158
+
159
+ ```js
160
+ ape(app, {
161
+ where: 'api',
162
+ onConnent(socket, req, hostId) {
163
+ return {
164
+ // Embed values into `this` for all controllers
165
+ embed: {
166
+ userId: req.session?.userId,
167
+ clientId: String(hostId)
168
+ },
169
+
170
+ // Before/after hooks
171
+ onReceive: (queryId, data, type) => {
172
+ console.log(`→ ${type}`)
173
+ return (err, result) => console.log(`← ${type}`, err || result)
174
+ },
175
+
176
+ onSend: (data, type) => {
177
+ console.log(`⇐ ${type}`)
178
+ return (err, result) => console.log(`Sent: ${type}`)
179
+ },
180
+
181
+ onError: (errStr) => console.error(errStr),
182
+ onDisconnent: () => console.log('Client left')
183
+ }
184
+ }
185
+ })
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Common Recipes
191
+
192
+ ### Broadcast to Other Clients
193
+
194
+ ```js
195
+ // api/message.js
196
+ module.exports = function(data) {
197
+ // Broadcast to all OTHER connected clients (not the sender)
198
+ this.broadcastOthers('message', data)
199
+ return { success: true }
200
+ }
201
+ ```
202
+
203
+ ### Broadcast to All Clients
204
+
205
+ ```js
206
+ // api/announcement.js
207
+ module.exports = function(announcement) {
208
+ // Broadcast to ALL connected clients including sender
209
+ this.broadcast('announcement', announcement)
210
+ return { sent: true }
211
+ }
212
+ ```
213
+
214
+ ### Get Online Count
215
+
216
+ ```js
217
+ // api/stats.js
218
+ module.exports = function() {
219
+ return {
220
+ online: this.online(),
221
+ clients: this.getClients()
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### Access Request Data
227
+
228
+ ```js
229
+ // api/profile.js
230
+ module.exports = function() {
231
+ // Access original HTTP request
232
+ const userId = this.req.session?.userId
233
+ const userAgent = this.agent.browser.name
234
+
235
+ return { userId, userAgent }
236
+ }
237
+ ```
238
+
239
+ ### Error Handling
240
+
241
+ ```js
242
+ // api/data.js
243
+ module.exports = async function(id) {
244
+ try {
245
+ const data = await fetchData(id)
246
+ return data
247
+ } catch (err) {
248
+ // Errors are automatically sent to client
249
+ throw new Error(`Failed to fetch: ${err.message}`)
250
+ }
251
+ }
252
+ ```
253
+
254
+ ### Client-Side Error Handling
255
+
256
+ ```js
257
+ try {
258
+ const result = await ape.data.get(id)
259
+ console.log(result)
260
+ } catch (err) {
261
+ console.error('Server error:', err)
262
+ }
263
+ ```
264
+
265
+ ---
266
+
267
+ ## JJS Encoding
268
+
269
+ api-ape uses **JJS (JSON SuperSet)** encoding, which extends JSON to support:
270
+
271
+ | Type | Supported |
272
+ |------|-----------|
273
+ | `Date` | ✅ Preserved as Date objects |
274
+ | `RegExp` | ✅ Preserved as RegExp |
275
+ | `Error` | ✅ Preserved with name, message, stack |
276
+ | `undefined` | ✅ Preserved (not converted to null) |
277
+ | `Set` | ✅ Preserved as Set |
278
+ | `Map` | ✅ Preserved as Map |
279
+ | Circular refs | ✅ Handled via pointers |
280
+
281
+ This is automatic — send a Date, receive a Date. No configuration needed.
282
+
283
+ ---
284
+
285
+ ## Examples & Demos
286
+
287
+ The repository contains working examples:
288
+
289
+ * **`example/ExpressJs/`** — Simple real-time chat app
290
+ - Minimal setup with Express.js
291
+ - Broadcast messages to other clients
292
+ - Message history
293
+
294
+ * **`example/NextJs/`** — Production-ready chat application
295
+ - Custom Next.js server integration
296
+ - React hooks integration
297
+ - User presence tracking
298
+ - Docker support
299
+ - Connection lifecycle hooks
300
+
301
+ ### Run an Example
302
+
303
+ **ExpressJs:**
304
+ ```bash
305
+ cd example/ExpressJs
306
+ npm install
307
+ npm start
308
+ # Open http://localhost:3000
309
+ ```
310
+
311
+ **NextJs:**
312
+ ```bash
313
+ cd example/NextJs
314
+ npm install
315
+ npm run dev
316
+ # Open http://localhost:3000
317
+ ```
318
+
319
+ Or with Docker:
320
+ ```bash
321
+ cd example/NextJs
322
+ docker-compose up --build
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Troubleshooting & FAQ
328
+
329
+ ### CORS Errors in Browser
330
+
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.
332
+
333
+ ### Controller Not Found
334
+
335
+ * Check that your controller file is in the `where` directory (default: `api/`)
336
+ * Ensure the file exports a function: `module.exports = function(...) { ... }`
337
+ * File paths map directly: `api/users/list.js` → `ape.users.list()`
338
+
339
+ ### Connection Drops Frequently
340
+
341
+ The client automatically reconnects with exponential backoff. If connections drop often:
342
+ * Check server WebSocket timeout settings
343
+ * Verify network stability
344
+ * Check server logs for errors
345
+
346
+ ### Binary Data / File Uploads
347
+
348
+ JJS encoding supports complex types, but for large binary data, consider:
349
+ * Sending file URLs instead of raw data
350
+ * Using a separate file upload endpoint
351
+ * Chunking large payloads
352
+
353
+ ### TypeScript Support
354
+
355
+ Type definitions are included (`index.d.ts`). For full type safety, you may need to:
356
+ * Define interfaces for your controller parameters and return types
357
+ * Use type assertions when calling `ape.<path>.<method>()`
358
+
359
+ ---
360
+
361
+ ## Tests & CI
362
+
363
+ ```bash
364
+ npm test # Run test suite
365
+ npm run test:watch # Watch mode
366
+ npm run test:cover # Coverage report
367
+ ```
368
+
369
+ **Test Commands:**
370
+ - `npm test` — Run all tests
371
+ - `npm run test:watch` — Watch mode for development
372
+ - `npm run test:cover` — Generate coverage report
373
+ - `npm run test:update` — Update snapshots
374
+
375
+ **Supported:** Node.js 14+, modern browsers (Chrome, Firefox, Safari, Edge)
376
+
377
+ ---
378
+
379
+ ## Contributing
380
+
381
+ Contributions welcome! Here's how to help:
382
+
383
+ 1. **Fork the repository**
384
+ 2. **Create a branch:** `git checkout -b feature/your-feature-name`
385
+ 3. **Make your changes** and add tests
386
+ 4. **Run tests:** `npm test`
387
+ 5. **Commit:** Follow conventional commit messages
388
+ 6. **Push and open a PR** with a clear description
389
+
390
+ **Guidelines:**
391
+ * Add tests for new features
392
+ * Keep code style consistent
393
+ * Update documentation if needed
394
+ * Ensure all tests pass
395
+
396
+ ---
397
+
398
+ ## Releases / Changelog
399
+
400
+ Versioning follows [Semantic Versioning](https://semver.org/).
401
+
402
+ **Current version:** See `package.json` or npm registry
403
+
404
+ **Release notes:** Check [GitHub releases](https://github.com/codemeasandwich/api-ape/releases) for detailed changelog.
405
+
406
+ ---
407
+
408
+ ## Security
409
+
410
+ **Reporting vulnerabilities:** Please report security issues via [GitHub Security Advisories](https://github.com/codemeasandwich/api-ape/security/advisories) or email the maintainer.
411
+
412
+ **Security considerations:**
413
+ * Validate all input in controllers
414
+ * Use authentication/authorization in `onConnent` hooks
415
+ * Sanitize data before broadcasting
416
+ * Keep dependencies up to date
417
+
418
+ ---
419
+
420
+ ## Project Structure
421
+
422
+ ```
423
+ api-ape/
424
+ ├── client/
425
+ │ ├── browser.js # Browser entry point (window.ape)
426
+ │ └── connectSocket.js # WebSocket client with auto-reconnect
427
+ ├── server/
428
+ │ ├── lib/
429
+ │ │ ├── main.js # Express integration
430
+ │ │ ├── loader.js # Auto-loads controller files
431
+ │ │ ├── broadcast.js # Client tracking & broadcast
432
+ │ │ └── wiring.js # WebSocket handler setup
433
+ │ ├── socket/
434
+ │ │ ├── receive.js # Incoming message handler
435
+ │ │ └── send.js # Outgoing message handler
436
+ │ └── security/
437
+ │ └── reply.js # Duplicate request protection
438
+ ├── utils/
439
+ │ ├── jss.js # JSON SuperSet encoder/decoder
440
+ │ └── messageHash.js # Request deduplication
441
+ └── example/
442
+ ├── ExpressJs/ # Chat app example
443
+ └── NextJs/ # Next.js integration
444
+ ```
445
+
446
+ ---
447
+
448
+ ## License & Authors
449
+
450
+ **License:** MIT
451
+
452
+ **Author:** [Brian Shannon](https://github.com/codemeasandwich)
453
+
454
+ **Repository:** [github.com/codemeasandwich/api-ape](https://github.com/codemeasandwich/api-ape)
455
+
456
+ ---
457
+
458
+ **Made with 🦍 by the api-ape community**
@@ -0,0 +1,69 @@
1
+ # 🦍 api-ape Client
2
+
3
+ WebSocket client library with auto-reconnection and proxy-based API calls.
4
+
5
+ ## Files
6
+
7
+ | File | Description |
8
+ |------|-------------|
9
+ | `browser.js` | Browser entry point - exposes `window.ape` |
10
+ | `connectSocket.js` | WebSocket client with auto-reconnect, queuing, and JJS encoding |
11
+
12
+ ## Usage
13
+
14
+ ### Browser (via script tag)
15
+
16
+ ```html
17
+ <script src="/api/ape.js"></script>
18
+ <script>
19
+ // Call server functions
20
+ ape.hello('World').then(result => console.log(result))
21
+
22
+ // Listen for broadcasts
23
+ ape.on('message', ({ data }) => console.log(data))
24
+ </script>
25
+ ```
26
+
27
+ ### ES Module Import
28
+
29
+ ```bash
30
+ npm i api-ape
31
+ ```
32
+
33
+ ```js
34
+ import ape from 'api-ape'
35
+
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 => ...)
45
+
46
+ // Listen for broadcasts
47
+ setOnReciver('newUser', ({ data }) => ...)
48
+ ```
49
+
50
+ ## Features
51
+
52
+ - **Proxy-based API** — `ape.path.method(data)` converts to WebSocket calls
53
+ - **Auto-reconnect** — Reconnects on disconnect with queued messages
54
+ - **Promise-based** — All calls return promises with matched responses via queryId
55
+ - **JJS encoding** — Supports Date, RegExp, Error, Set, Map, undefined over the wire
56
+ - **Request timeout** — Configurable timeout (default: 10s)
57
+
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`
@@ -0,0 +1,17 @@
1
+ import connectSocket from './connectSocket.js'
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 } = connectSocket()
8
+ connectSocket.autoReconnect()
9
+
10
+ // Global API - use defineProperty to bypass Proxy interception
11
+ window.ape = sender
12
+ Object.defineProperty(window.ape, 'on', {
13
+ value: setOnReciver,
14
+ writable: false,
15
+ enumerable: false,
16
+ configurable: false
17
+ })