api-ape 0.0.0 → 1.0.1
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 +261 -0
- package/client/README.md +69 -0
- package/client/browser.js +17 -0
- package/client/connectSocket.js +260 -0
- package/dist/ape.js +454 -0
- package/example/ExpressJs/README.md +97 -0
- package/example/ExpressJs/api/message.js +11 -0
- package/example/ExpressJs/backend.js +37 -0
- package/example/ExpressJs/index.html +88 -0
- package/example/ExpressJs/package-lock.json +834 -0
- package/example/ExpressJs/package.json +10 -0
- package/example/ExpressJs/styles.css +128 -0
- package/example/NextJs/.dockerignore +29 -0
- package/example/NextJs/Dockerfile +52 -0
- package/example/NextJs/Dockerfile.dev +27 -0
- package/example/NextJs/README.md +113 -0
- package/example/NextJs/ape/client.js +66 -0
- package/example/NextJs/ape/embed.js +12 -0
- package/example/NextJs/ape/index.js +23 -0
- package/example/NextJs/ape/logic/chat.js +62 -0
- package/example/NextJs/ape/onConnect.js +69 -0
- package/example/NextJs/ape/onDisconnect.js +13 -0
- package/example/NextJs/ape/onError.js +9 -0
- package/example/NextJs/ape/onReceive.js +15 -0
- package/example/NextJs/ape/onSend.js +15 -0
- package/example/NextJs/api/message.js +44 -0
- package/example/NextJs/docker-compose.yml +22 -0
- package/example/NextJs/next-env.d.ts +5 -0
- package/example/NextJs/next.config.js +8 -0
- package/example/NextJs/package-lock.json +5107 -0
- package/example/NextJs/package.json +25 -0
- package/example/NextJs/pages/_app.tsx +6 -0
- package/example/NextJs/pages/index.tsx +182 -0
- package/example/NextJs/public/favicon.ico +0 -0
- package/example/NextJs/public/vercel.svg +4 -0
- package/example/NextJs/server.js +40 -0
- package/example/NextJs/styles/Chat.module.css +194 -0
- package/example/NextJs/styles/Home.module.css +129 -0
- package/example/NextJs/styles/globals.css +26 -0
- package/example/NextJs/tsconfig.json +20 -0
- package/example/README.md +66 -0
- package/index.d.ts +179 -0
- package/index.js +11 -0
- package/package.json +11 -4
- package/server/README.md +93 -0
- package/server/index.js +6 -0
- package/server/lib/broadcast.js +63 -0
- package/server/lib/loader.js +10 -0
- package/server/lib/main.js +23 -0
- package/server/lib/wiring.js +94 -0
- package/server/security/extractRootDomain.js +21 -0
- package/server/security/origin.js +13 -0
- package/server/security/reply.js +21 -0
- package/server/socket/open.js +10 -0
- package/server/socket/receive.js +66 -0
- package/server/socket/send.js +55 -0
- package/server/utils/deepRequire.js +45 -0
- package/server/utils/genId.js +24 -0
- package/todo.md +85 -0
- package/utils/jss.js +273 -0
- package/utils/jss.test.js +261 -0
- package/utils/messageHash.js +43 -0
- package/utils/messageHash.test.js +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# 🦍 api-ape
|
|
2
|
+
|
|
3
|
+
**Remote Procedure Events (RPE)** — A lightweight WebSocket framework for real-time APIs.
|
|
4
|
+
|
|
5
|
+
Call server functions from the browser like local methods. Get real-time broadcasts with zero setup.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
// Client: call server function, get result
|
|
9
|
+
const pets = await ape.pets.list()
|
|
10
|
+
|
|
11
|
+
// Client: listen for broadcasts
|
|
12
|
+
ape.on('newPet', ({ data }) => console.log('New pet:', data))
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
file: api/pets/list.js
|
|
16
|
+
```js
|
|
17
|
+
// Server: define function, broadcast to others
|
|
18
|
+
module.exports = function list() {
|
|
19
|
+
return getPetList()
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
file: api/pets/newPet.js
|
|
24
|
+
```js
|
|
25
|
+
// Server: define function, broadcast to others
|
|
26
|
+
module.exports = function newPet(data) {
|
|
27
|
+
// broadcast to all other clients
|
|
28
|
+
this.broadcastOthers('newPet', data)
|
|
29
|
+
return savePet(data)
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **🔌 Auto-wiring** — Drop JS files in a folder, they become API endpoints
|
|
38
|
+
- **📡 Real-time** — Built-in broadcast to all or other clients
|
|
39
|
+
- **🔄 Reconnection** — Client auto-reconnects on disconnect
|
|
40
|
+
- **📦 JJS Encoding** — Supports Date, RegExp, Error, Set, Map, undefined over the wire
|
|
41
|
+
- **🎯 Simple API** — Promise-based calls with chainable paths
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install api-ape
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Server (Express.js)
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
const express = require('express')
|
|
59
|
+
const ape = require('api-ape')
|
|
60
|
+
|
|
61
|
+
const app = express()
|
|
62
|
+
|
|
63
|
+
// Wire up api-ape - loads controllers from ./api folder
|
|
64
|
+
ape(app, { where: 'api' })
|
|
65
|
+
|
|
66
|
+
app.listen(3000)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Controllers
|
|
70
|
+
|
|
71
|
+
Create files in your `api/` folder. Each export becomes an endpoint:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
// api/hello.js
|
|
75
|
+
module.exports = function(name) {
|
|
76
|
+
return `Hello, ${name}!`
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
// api/message.js
|
|
82
|
+
module.exports = function(data) {
|
|
83
|
+
// Broadcast to all OTHER connected clients
|
|
84
|
+
this.broadcastOthers('message', data)
|
|
85
|
+
return data
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Client (Browser)
|
|
90
|
+
|
|
91
|
+
Include the bundled client:
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<script src="/api/ape.js"></script>
|
|
95
|
+
<script>
|
|
96
|
+
// Call server functions
|
|
97
|
+
ape.hello('World').then(result => console.log(result)) // "Hello, World!"
|
|
98
|
+
|
|
99
|
+
// Listen for broadcasts
|
|
100
|
+
ape.on('message', ({ data }) => {
|
|
101
|
+
console.log('New message:', data)
|
|
102
|
+
})
|
|
103
|
+
</script>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## API Reference
|
|
109
|
+
|
|
110
|
+
### Server
|
|
111
|
+
|
|
112
|
+
#### `ape(app, options)`
|
|
113
|
+
|
|
114
|
+
Initialize api-ape on an Express app.
|
|
115
|
+
|
|
116
|
+
| Option | Type | Description |
|
|
117
|
+
|--------|------|-------------|
|
|
118
|
+
| `where` | `string` | Directory containing controller files |
|
|
119
|
+
| `onConnent` | `function` | Connection lifecycle hook (see below) |
|
|
120
|
+
|
|
121
|
+
#### Controller Context (`this`)
|
|
122
|
+
|
|
123
|
+
Inside controller functions, `this` provides:
|
|
124
|
+
|
|
125
|
+
| Property | Description |
|
|
126
|
+
|----------|-------------|
|
|
127
|
+
| `this.broadcast(type, data)` | Send to ALL connected clients |
|
|
128
|
+
| `this.broadcastOthers(type, data)` | Send to all EXCEPT the caller |
|
|
129
|
+
| `this.online()` | Get count of connected clients |
|
|
130
|
+
| `this.getClients()` | Get array of connected hostIds |
|
|
131
|
+
| `this.hostId` | Unique ID of the calling client |
|
|
132
|
+
| `this.req` | Original HTTP request |
|
|
133
|
+
| `this.socket` | WebSocket instance |
|
|
134
|
+
| `this.agent` | Parsed user-agent (browser, OS, device) |
|
|
135
|
+
|
|
136
|
+
#### Connection Lifecycle
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
ape(app, {
|
|
140
|
+
where: 'api',
|
|
141
|
+
onConnent(socket, req, send) {
|
|
142
|
+
return {
|
|
143
|
+
// Embed values into `this` for all controllers
|
|
144
|
+
embed: {
|
|
145
|
+
userId: req.session?.userId,
|
|
146
|
+
clientId: send + '' // hostId as string
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Before/after hooks
|
|
150
|
+
onReceive: (queryId, data, type) => {
|
|
151
|
+
console.log(`→ ${type}`)
|
|
152
|
+
return (err, result) => console.log(`← ${type}`, err || result)
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
onSend: (data, type) => {
|
|
156
|
+
console.log(`⇐ ${type}`)
|
|
157
|
+
return (err, result) => console.log(`Sent: ${type}`)
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
onError: (errStr) => console.error(errStr),
|
|
161
|
+
onDisconnent: () => console.log('Client left')
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Client
|
|
168
|
+
|
|
169
|
+
#### `ape.<path>.<method>(data)`
|
|
170
|
+
|
|
171
|
+
Call a server function. Returns a Promise.
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
// Calls api/users/list.js
|
|
175
|
+
ape.users.list().then(users => ...)
|
|
176
|
+
|
|
177
|
+
// Calls api/users/create.js with data
|
|
178
|
+
ape.users.create({ name: 'Alice' }).then(user => ...)
|
|
179
|
+
|
|
180
|
+
// Nested paths work too
|
|
181
|
+
ape.admin.users.delete(userId).then(() => ...)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `ape.on(type, handler)`
|
|
185
|
+
|
|
186
|
+
Listen for server broadcasts.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
ape.on('notification', ({ data, err, type }) => {
|
|
190
|
+
console.log('Received:', data)
|
|
191
|
+
})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## JJS Encoding
|
|
197
|
+
|
|
198
|
+
api-ape uses **JJS (JSON SuperSet)** encoding, which extends JSON to support:
|
|
199
|
+
|
|
200
|
+
| Type | Supported |
|
|
201
|
+
|------|-----------|
|
|
202
|
+
| `Date` | ✅ Preserved as Date objects |
|
|
203
|
+
| `RegExp` | ✅ Preserved as RegExp |
|
|
204
|
+
| `Error` | ✅ Preserved with name, message, stack |
|
|
205
|
+
| `undefined` | ✅ Preserved (not converted to null) |
|
|
206
|
+
| `Set` | ✅ Preserved as Set |
|
|
207
|
+
| `Map` | ✅ Preserved as Map |
|
|
208
|
+
| Circular refs | ✅ Handled via pointers |
|
|
209
|
+
|
|
210
|
+
This is automatic — send a Date, receive a Date.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Examples
|
|
215
|
+
|
|
216
|
+
See the [`example/`](./example) folder:
|
|
217
|
+
|
|
218
|
+
- **ExpressJs/** — Simple chat app with broadcast
|
|
219
|
+
- **NextJs/** — Integration with Next.js
|
|
220
|
+
|
|
221
|
+
Run the Express example:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
cd example/ExpressJs
|
|
225
|
+
npm install
|
|
226
|
+
npm start
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Project Structure
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
api-ape/
|
|
235
|
+
├── client/
|
|
236
|
+
│ ├── browser.js # Browser entry point (window.ape)
|
|
237
|
+
│ └── connectSocket.js # WebSocket client with auto-reconnect
|
|
238
|
+
├── server/
|
|
239
|
+
│ ├── lib/
|
|
240
|
+
│ │ ├── main.js # Express integration
|
|
241
|
+
│ │ ├── loader.js # Auto-loads controller files
|
|
242
|
+
│ │ ├── broadcast.js # Client tracking & broadcast
|
|
243
|
+
│ │ └── wiring.js # WebSocket handler setup
|
|
244
|
+
│ ├── socket/
|
|
245
|
+
│ │ ├── receive.js # Incoming message handler
|
|
246
|
+
│ │ └── send.js # Outgoing message handler
|
|
247
|
+
│ └── security/
|
|
248
|
+
│ └── reply.js # Duplicate request protection
|
|
249
|
+
├── utils/
|
|
250
|
+
│ ├── jss.js # JSON SuperSet encoder/decoder
|
|
251
|
+
│ └── messageHash.js # Request deduplication
|
|
252
|
+
└── example/
|
|
253
|
+
├── ExpressJs/ # Chat app example
|
|
254
|
+
└── NextJs/ # Next.js integration
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT © [Brian Shannon](https://github.com/codemeasandwich)
|
package/client/README.md
ADDED
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import messageHash from '../utils/messageHash'
|
|
2
|
+
import jss from '../utils/jss'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let connect;
|
|
6
|
+
|
|
7
|
+
// Configuration
|
|
8
|
+
let configuredPort = null
|
|
9
|
+
let configuredHost = null
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configure api-ape client connection
|
|
13
|
+
* @param {object} opts
|
|
14
|
+
* @param {number} [opts.port] - WebSocket port (default: 9010 for local, 443/80 for remote)
|
|
15
|
+
* @param {string} [opts.host] - WebSocket host (default: auto-detect from window.location)
|
|
16
|
+
*/
|
|
17
|
+
function configure(opts = {}) {
|
|
18
|
+
if (opts.port) configuredPort = opts.port
|
|
19
|
+
if (opts.host) configuredHost = opts.host
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get WebSocket URL - auto-detects from window.location, keeps /api/ape path
|
|
24
|
+
*/
|
|
25
|
+
function getSocketUrl() {
|
|
26
|
+
const hostname = configuredHost || window.location.hostname
|
|
27
|
+
const localServers = ["localhost", "127.0.0.1", "[::1]"]
|
|
28
|
+
const isLocal = localServers.includes(hostname)
|
|
29
|
+
const isHttps = window.location.protocol === "https:"
|
|
30
|
+
|
|
31
|
+
// Default port: 9010 for local dev, otherwise use window.location.port or implicit 443/80
|
|
32
|
+
const defaultPort = isLocal ? 9010 : (window.location.port || (isHttps ? 443 : 80))
|
|
33
|
+
const port = configuredPort || defaultPort
|
|
34
|
+
|
|
35
|
+
// Build URL - keep /api/ape path
|
|
36
|
+
const protocol = isHttps ? "wss" : "ws"
|
|
37
|
+
const portSuffix = (isLocal || port !== 80 && port !== 443) ? `:${port}` : ""
|
|
38
|
+
|
|
39
|
+
return `${protocol}://${hostname}${portSuffix}/api/ape`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let reconnect = false
|
|
43
|
+
const connentTimeout = 5000
|
|
44
|
+
const totalRequestTimeout = 10000
|
|
45
|
+
//const location = window.location
|
|
46
|
+
|
|
47
|
+
const joinKey = "/"
|
|
48
|
+
// Properties accessed directly on `ape` that should NOT be intercepted
|
|
49
|
+
const reservedKeys = new Set(['on'])
|
|
50
|
+
const handler = {
|
|
51
|
+
get(fn, key) {
|
|
52
|
+
// Skip proxy interception for reserved keys - return actual property
|
|
53
|
+
if (reservedKeys.has(key)) {
|
|
54
|
+
return fn[key]
|
|
55
|
+
}
|
|
56
|
+
const wrapperFn = function (a, b) {
|
|
57
|
+
let path = joinKey + key, body;
|
|
58
|
+
if (2 === arguments.length) {
|
|
59
|
+
path += a
|
|
60
|
+
body = b
|
|
61
|
+
} else {
|
|
62
|
+
body = a
|
|
63
|
+
}
|
|
64
|
+
return fn(path, body)
|
|
65
|
+
}
|
|
66
|
+
return new Proxy(wrapperFn, handler)
|
|
67
|
+
} // END get
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function wrap(api) {
|
|
71
|
+
return new Proxy(api, handler)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let __socket = false, ready = false, wsSend = false;
|
|
75
|
+
const waitingOn = {};
|
|
76
|
+
const reciverOn = [];
|
|
77
|
+
|
|
78
|
+
let aWaitingSend = []
|
|
79
|
+
const reciverOnAr = [];
|
|
80
|
+
const ofTypesOb = {};
|
|
81
|
+
|
|
82
|
+
function connectSocket() {
|
|
83
|
+
|
|
84
|
+
if (!__socket) {
|
|
85
|
+
__socket = new WebSocket(getSocketUrl())
|
|
86
|
+
|
|
87
|
+
__socket.onopen = event => {
|
|
88
|
+
//console.log('socket connected()');
|
|
89
|
+
ready = true;
|
|
90
|
+
aWaitingSend.forEach(({ type, data, next, err, waiting, createdAt, timer }) => {
|
|
91
|
+
clearTimeout(timer)
|
|
92
|
+
//TODO: clear throw of wait for server
|
|
93
|
+
const resultPromise = wsSend(type, data, createdAt)
|
|
94
|
+
if (waiting) {
|
|
95
|
+
resultPromise.then(next)
|
|
96
|
+
.catch(err)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
// cloudfler drops the connetion and the client has to remake,
|
|
100
|
+
// we clear the array as we dont need this info every RE-connent
|
|
101
|
+
aWaitingSend = []
|
|
102
|
+
} // END onopen
|
|
103
|
+
|
|
104
|
+
__socket.onmessage = function (event) {
|
|
105
|
+
//console.log('WebSocket message:', event);
|
|
106
|
+
const { err, type, queryId, data } = jss.parse(event.data)
|
|
107
|
+
|
|
108
|
+
// Messages with queryId must fulfill matching promise
|
|
109
|
+
if (queryId) {
|
|
110
|
+
if (waitingOn[queryId]) {
|
|
111
|
+
waitingOn[queryId](err, data)
|
|
112
|
+
delete waitingOn[queryId]
|
|
113
|
+
} else {
|
|
114
|
+
// No matching promise - error and ignore
|
|
115
|
+
console.error(`🦍 No matching queryId: ${queryId}`)
|
|
116
|
+
}
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Only messages WITHOUT queryId go to setOnReciver
|
|
121
|
+
if (ofTypesOb[type]) {
|
|
122
|
+
ofTypesOb[type].forEach(worker => worker({ err, type, data }))
|
|
123
|
+
} // if ofTypesOb[type]
|
|
124
|
+
reciverOnAr.forEach(worker => worker({ err, type, data }))
|
|
125
|
+
|
|
126
|
+
} // END onmessage
|
|
127
|
+
|
|
128
|
+
__socket.onerror = function (err) {
|
|
129
|
+
console.error('socket ERROR:', err);
|
|
130
|
+
} // END onerror
|
|
131
|
+
|
|
132
|
+
__socket.onclose = function (event) {
|
|
133
|
+
console.warn('socket disconnect:', event);
|
|
134
|
+
__socket = false
|
|
135
|
+
ready = false;
|
|
136
|
+
setTimeout(() => reconnect && connectSocket(), 500);
|
|
137
|
+
} // END onclose
|
|
138
|
+
|
|
139
|
+
} // END if ! __socket
|
|
140
|
+
wsSend = function (type, data, createdAt, dirctCall) {
|
|
141
|
+
let rej, promiseIsLive = false;
|
|
142
|
+
const timeLetForReqToBeMade = (createdAt + totalRequestTimeout) - Date.now()
|
|
143
|
+
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
if (promiseIsLive) {
|
|
146
|
+
rej(new Error("Request Timedout for :" + type))
|
|
147
|
+
}
|
|
148
|
+
}, timeLetForReqToBeMade);
|
|
149
|
+
const payload = {
|
|
150
|
+
type,
|
|
151
|
+
data,
|
|
152
|
+
//referer:window.location.href,
|
|
153
|
+
createdAt: new Date(createdAt),
|
|
154
|
+
requestedAt: dirctCall ? undefined
|
|
155
|
+
: new Date()
|
|
156
|
+
}
|
|
157
|
+
const message = jss.stringify(payload)
|
|
158
|
+
const queryId = messageHash(message);
|
|
159
|
+
|
|
160
|
+
const replyPromise = new Promise((resolve, reject) => {
|
|
161
|
+
rej = reject
|
|
162
|
+
waitingOn[queryId] = (err, result) => {
|
|
163
|
+
clearTimeout(timer)
|
|
164
|
+
replyPromise.then = next.bind(replyPromise)
|
|
165
|
+
if (err) {
|
|
166
|
+
reject(err)
|
|
167
|
+
} else {
|
|
168
|
+
resolve(result)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
__socket.send(message);
|
|
172
|
+
});
|
|
173
|
+
const next = replyPromise.then;
|
|
174
|
+
replyPromise.then = worker => {
|
|
175
|
+
promiseIsLive = true;
|
|
176
|
+
replyPromise.then = next.bind(replyPromise)
|
|
177
|
+
replyPromise.catch = err.bind(replyPromise)
|
|
178
|
+
return next.call(replyPromise, worker)
|
|
179
|
+
}
|
|
180
|
+
const err = replyPromise.catch;
|
|
181
|
+
replyPromise.catch = worker => {
|
|
182
|
+
promiseIsLive = true;
|
|
183
|
+
replyPromise.catch = err.bind(replyPromise)
|
|
184
|
+
replyPromise.then = next.bind(replyPromise)
|
|
185
|
+
return err.call(replyPromise, worker)
|
|
186
|
+
}
|
|
187
|
+
return replyPromise
|
|
188
|
+
} // END wsSend
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
const sender = (type, data) => {
|
|
192
|
+
if ("string" !== typeof type) {
|
|
193
|
+
throw new Error("Missing Path vaule")
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const createdAt = Date.now()
|
|
197
|
+
|
|
198
|
+
if (ready) {
|
|
199
|
+
return wsSend(type, data, createdAt, true)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const timeLetForReqToBeMade = (createdAt + connentTimeout) - Date.now() // 5sec for reconnent
|
|
203
|
+
|
|
204
|
+
const timer = setTimeout(() => {
|
|
205
|
+
const errMessage = "Request not sent for :" + type
|
|
206
|
+
if (payload.waiting) {
|
|
207
|
+
payload.err(new Error(errMessage))
|
|
208
|
+
} else {
|
|
209
|
+
throw new Error(errMessage)
|
|
210
|
+
}
|
|
211
|
+
}, timeLetForReqToBeMade);
|
|
212
|
+
|
|
213
|
+
const payload = { type, data, next: undefined, err: undefined, waiting: false, createdAt, timer };
|
|
214
|
+
const waitingOnOpen = new Promise((res, er) => { payload.next = res; payload.err = er; })
|
|
215
|
+
|
|
216
|
+
const waitingOnOpenThen = waitingOnOpen.then;
|
|
217
|
+
const waitingOnOpenCatch = waitingOnOpen.catch;
|
|
218
|
+
waitingOnOpen.then = worker => {
|
|
219
|
+
payload.waiting = true;
|
|
220
|
+
waitingOnOpen.then = waitingOnOpenThen.bind(waitingOnOpen)
|
|
221
|
+
waitingOnOpen.catch = waitingOnOpenCatch.bind(waitingOnOpen)
|
|
222
|
+
return waitingOnOpenThen.call(waitingOnOpen, worker)
|
|
223
|
+
}
|
|
224
|
+
waitingOnOpen.catch = worker => {
|
|
225
|
+
payload.waiting = true;
|
|
226
|
+
waitingOnOpen.catch = waitingOnOpenCatch.bind(waitingOnOpen)
|
|
227
|
+
waitingOnOpen.then = waitingOnOpenThen.bind(waitingOnOpen)
|
|
228
|
+
return waitingOnOpenCatch.call(waitingOnOpen, worker)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
aWaitingSend.push(payload)
|
|
232
|
+
if (!__socket) {
|
|
233
|
+
connectSocket()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return waitingOnOpen
|
|
237
|
+
} // END sender
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
sender: wrap(sender),
|
|
241
|
+
setOnReciver: (onTypeStFn, handlerFn) => {
|
|
242
|
+
if ("string" === typeof onTypeStFn) {
|
|
243
|
+
// Replace handler for this type (prevents duplicates in React StrictMode)
|
|
244
|
+
ofTypesOb[onTypeStFn] = [handlerFn]
|
|
245
|
+
} else {
|
|
246
|
+
// For general receivers, prevent duplicates by checking
|
|
247
|
+
if (!reciverOnAr.includes(onTypeStFn)) {
|
|
248
|
+
reciverOnAr.push(onTypeStFn)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} // END setOnReciver
|
|
252
|
+
} // END return
|
|
253
|
+
} // END connectSocket
|
|
254
|
+
|
|
255
|
+
connectSocket.autoReconnect = () => reconnect = true
|
|
256
|
+
connectSocket.configure = configure
|
|
257
|
+
connect = connectSocket
|
|
258
|
+
|
|
259
|
+
export default connect;
|
|
260
|
+
export { configure };
|