wspromisify 3.0.2 → 3.0.3
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 +82 -97
- package/dist/bundle.cjs +21 -13
- package/dist/bundle.d.ts +1 -1
- package/dist/bundle.mjs +21 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,85 +11,74 @@ const responseData = await ws.send({catSaid: 'Meow!'})
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
// If you detected some bug, want some stuff to be added, feel free to open an issue!
|
|
14
|
-
Large data support (chunking), plugins and different server-side implementations are coming.
|
|
15
14
|
To see a Node.js server-side part example, please, take a look on test/mock in github repo.
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
Makes a Promise-like WebSocket connection.
|
|
19
18
|
Features (almost all are tunable via constructor config below.)
|
|
20
|
-
-
|
|
21
|
-
- ES-module and
|
|
19
|
+
- Fully asynchronous with promises.
|
|
20
|
+
- ES-module and CommonJS versions.
|
|
22
21
|
- Types (d.ts) included.
|
|
23
|
-
- Automatically reconnects.
|
|
22
|
+
- Automatically reconnects, resends, closes when idle, pings. All parametrized.
|
|
24
23
|
- Streams are supported. For example, you can stream your AI response.
|
|
24
|
+
- Data piping, routing (via .route()).
|
|
25
|
+
- Handy logging API.
|
|
25
26
|
- Supports existent native WebSocket or ws-like implementation (ws npm package) via `socket` property.
|
|
26
|
-
- And provide your own socket instance via socket config prop.
|
|
27
|
+
- And provide your own socket instance via socket config fabric prop.
|
|
27
28
|
- Any id and data keys to negotiate with your back-end.
|
|
28
29
|
- Any (serialiser)/Decoder(deserialiser).
|
|
29
30
|
- Lazy connect: connects only if something sent, then send all of them!
|
|
30
|
-
- Supports middleware-adapter. E.g. you can use 'ws' package
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- Rejects if sent into closed socket or after some timeout without response.
|
|
35
|
-
- If something sent before connection is estabilished, it sends when it's ready.
|
|
36
|
-
- Pings to stay connected if necessary.
|
|
31
|
+
- Supports middleware-adapter. E.g. you can use 'ws' package.
|
|
32
|
+
- .on() method with or without condition: analog with .addEventListener.
|
|
33
|
+
|
|
34
|
+
for more, please, take a look at its' typescript types or ask in [discussions](https://github.com/houd1ni/WebsocketPromisify/discussions).
|
|
37
35
|
|
|
38
36
|
How it on Server Side ?
|
|
39
37
|
```
|
|
38
|
+
Let thisnk you use default (JSON) adapter with default (id, data) props. Then:
|
|
40
39
|
1. Serialized JSON is sent by this lib = {id: 'generated_id', data: your data}
|
|
41
|
-
... or some entity from your .encode function(message_id, message_data)
|
|
42
40
|
2. Some Server processing...
|
|
43
41
|
3. Serialized JSON is sent back by the Server = {id: 'the same generated_id', data: feedback data}
|
|
44
|
-
|
|
42
|
+
3.1 if it was stream, it may respond with multiple (id, data) pairs, but in the last message it must add `done: true` prop: {id, data, done: true} to let the library know that it is the last one.
|
|
45
43
|
```
|
|
46
44
|
|
|
47
45
|
|
|
48
|
-
Default constructor config is
|
|
49
|
-
```
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
server: {
|
|
85
|
-
id_key: 'id',
|
|
86
|
-
data_key: 'data'
|
|
87
|
-
},
|
|
88
|
-
// Pings to avoid interruptions. null to disable.
|
|
89
|
-
ping: {
|
|
90
|
-
interval: 55, // seconds.
|
|
91
|
-
content: {} // goes to `data` => { id, data: {} } by default.
|
|
92
|
-
}
|
|
46
|
+
Default constructor config is (all time units are in seconds):
|
|
47
|
+
```typescript
|
|
48
|
+
interface TimeFnParams { base: number, max: number, jitter: number }
|
|
49
|
+
interface Config {
|
|
50
|
+
log: ((event, time, message): any) // only one place where time is in milliseconds.
|
|
51
|
+
timer: false // add or not time deltas to the fn above.
|
|
52
|
+
// Required if `socket` is not set. URL to connect without a protocol.
|
|
53
|
+
// Can start with /, then current page host and port will be used.
|
|
54
|
+
url: 'localhost',
|
|
55
|
+
// Timeout after sending a message before it drops with error.
|
|
56
|
+
timeout: 1.4
|
|
57
|
+
reconnect: { // Reconnect timeout in seconds or false to disable.
|
|
58
|
+
stop_after: number // seconds before giving up.
|
|
59
|
+
on_timeout: boolean // should it reconnect on message timeout ?
|
|
60
|
+
on_break: boolean // should it reconnect on the connection break ?
|
|
61
|
+
time_fn: (params: TimeFnParams, attempt: number): number
|
|
62
|
+
params: { base: number, max: number, jitter: number } // params for the function.
|
|
63
|
+
}
|
|
64
|
+
max_idle_time: Infinity // Time in seconds after the connection is closed if nothing was sent explicitly by send() or stream().
|
|
65
|
+
lazy: false // Lazy connect: connects only if something sent.
|
|
66
|
+
socket: null // Existing socket if you already have one to augment with wspromisify.
|
|
67
|
+
adapter: ((host, protocols): new WebSocket(host, protocols)) // your own middleware here.
|
|
68
|
+
// You can replace original serialisation to your own or even binary stuff. Eg MessagePack or CBOR.
|
|
69
|
+
encode: (message_id, message_data, config): data
|
|
70
|
+
decode: (raw_message): { message_id, message_data } // id_key and data_key could be taken from the config argument.
|
|
71
|
+
protocols: [] // WebSocket constructor's protocol field.
|
|
72
|
+
server: { // Unique id's and data keys to negotiate with back-end.
|
|
73
|
+
id_key: 'id'
|
|
74
|
+
data_key: 'data'
|
|
75
|
+
}
|
|
76
|
+
ping: { // Pings to avoid interruptions or false to disable.
|
|
77
|
+
interval: number // inter-ping interval. default: 55.
|
|
78
|
+
timeout: number // when a server does not reply with its' `pong` in this time, close the connection. set to Infinity to disable. default: 30.
|
|
79
|
+
out: string|Uint8Array // frame content for pinging the server. default: 'ping'
|
|
80
|
+
in: string|Uint8Array // frame content the server must send back. default: 'pong'
|
|
81
|
+
}
|
|
93
82
|
}
|
|
94
83
|
```
|
|
95
84
|
|
|
@@ -102,55 +91,51 @@ Fields/Props:
|
|
|
102
91
|
|
|
103
92
|
Methods:
|
|
104
93
|
```javascript
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
ready()
|
|
108
|
-
// sends any type of message and returns a Promise.
|
|
109
|
-
send(message),
|
|
94
|
+
ready(timeout?: number) // Returns Promise that connection is open. Works even if it already opened.
|
|
95
|
+
send(message) // sends any type of message and returns a Promise.
|
|
110
96
|
// Streams as async generator, resolving in chunks.
|
|
111
97
|
// The server must send the same id for chunks then add done: true in the last one.
|
|
112
|
-
*stream(message)
|
|
98
|
+
*stream(message)
|
|
113
99
|
// .addEventListener with optional predicate that works after reconnections.
|
|
114
|
-
on(event_name, handler, predicate
|
|
100
|
+
on(event_name, handler, predicate?: ((WebSocketEvent) => boolean), raw?: boolean)
|
|
101
|
+
off(event_name, handler, raw?: boolean) // `raw` is to attach it to the socket itself.
|
|
102
|
+
addEventListener(event_name, handler, {predicate, raw}) // almost alias for .on()
|
|
103
|
+
removeEventListener(event_name, handler, {predicate, raw}) // almost alias for .off()
|
|
115
104
|
// Closes the connection and free up memory. Returns Promise that it has been done.
|
|
116
|
-
close()
|
|
105
|
+
close(timeout?: number)
|
|
106
|
+
// Routers or modifies frames before the library. Call next(data) to route it to the lib.
|
|
107
|
+
route(handler: (data: T, next: Function) => any)
|
|
117
108
|
```
|
|
118
109
|
|
|
119
110
|
Example (more in `tests` dir in the repo):
|
|
120
|
-
```
|
|
111
|
+
```typescript
|
|
121
112
|
|
|
122
|
-
import
|
|
113
|
+
import {WebSocketClient} from 'wspromisify' // or const WSP = require('wspromisify') in Node.
|
|
123
114
|
|
|
124
115
|
const somehost = 'example.com:8080'
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} else {
|
|
139
|
-
console.log(event, message)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
// You can wait for ready by calling await ws.ready() or send it right now:
|
|
146
|
-
// the messages will be sent as soon as the connection is opened.
|
|
147
|
-
const data = await ws.send({catSaid: 'Meow!'})
|
|
148
|
-
console.log({data})
|
|
149
|
-
} catch(error) {
|
|
150
|
-
console.error('Cannot send a message due to ', error)
|
|
116
|
+
type Protocol = Uint8Array // or string (in the native WebSocket by default).
|
|
117
|
+
|
|
118
|
+
const ws = new WebSocketClient<Protocol>({
|
|
119
|
+
// if url starts with /,
|
|
120
|
+
// it results in ws(s if in https)://currentHost:currentPort/thisUrl
|
|
121
|
+
url: 'ws://example.com/ws',
|
|
122
|
+
timeout: 2, // 2s by default.
|
|
123
|
+
timer: true, // false by default.
|
|
124
|
+
// to log data trips. Events: open, close, send, reconnect, error.
|
|
125
|
+
// if timer isn't enabled, the signature is log(event, message)
|
|
126
|
+
log(event, time, message = '') {
|
|
127
|
+
if(time !== null) console.log(event, `in ${time}ms`, message)
|
|
128
|
+
else console.log(event, message)
|
|
151
129
|
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
// You can wait for ready by calling await ws.ready() or send it right now:
|
|
134
|
+
// the messages will be sent as soon as the connection is opened.
|
|
135
|
+
const data = await ws.send({catSaid: 'Meow!'})
|
|
136
|
+
console.log({data})
|
|
137
|
+
} catch(error) {
|
|
138
|
+
console.error('Cannot send a message due to ', error)
|
|
152
139
|
}
|
|
153
140
|
|
|
154
|
-
someFunction()
|
|
155
|
-
|
|
156
141
|
```
|
package/dist/bundle.cjs
CHANGED
|
@@ -334,7 +334,7 @@ const timeout_rm = (q, ff, rj, timeout = .5) => {
|
|
|
334
334
|
};
|
|
335
335
|
const genid = (q) => {
|
|
336
336
|
const id = zipnum.zip((random() * (MAX_32 - 10)) | 0);
|
|
337
|
-
return id
|
|
337
|
+
return q.has(id) ? genid(q) : id;
|
|
338
338
|
};
|
|
339
339
|
const call_q = (q, ...args) => {
|
|
340
340
|
for (const fn of q)
|
|
@@ -415,21 +415,24 @@ class WebSocketClient {
|
|
|
415
415
|
async reconnect(attempt = 0) {
|
|
416
416
|
if (this._reconnecting && attempt === 0)
|
|
417
417
|
return;
|
|
418
|
+
const { reconnect } = this.config;
|
|
419
|
+
if (!reconnect)
|
|
420
|
+
throw new Error('WSC: reconnecting is disabled, but reconned h.b. called!');
|
|
418
421
|
this.log('reconnect');
|
|
419
422
|
this._reconnecting = true;
|
|
420
423
|
this.reconnect_start = now();
|
|
421
424
|
if (!isNil(this.ws))
|
|
422
425
|
this.terminate();
|
|
423
426
|
const { queue } = this;
|
|
424
|
-
if (attempt > 0 && isNil(await this.connect())) {
|
|
427
|
+
if (attempt > 0 && isNil(await this.connect())) { // connected.
|
|
425
428
|
clear_q(call_q(queue.on_ready));
|
|
426
429
|
clear_q(queue.on_ready_fail);
|
|
427
430
|
this._reconnecting = false;
|
|
428
431
|
this.reconnect_timeout = nil;
|
|
429
432
|
}
|
|
430
433
|
else {
|
|
431
|
-
const { stop_after, time_fn, params } =
|
|
432
|
-
if (now() - this.reconnect_start > stop_after) {
|
|
434
|
+
const { stop_after, time_fn, params } = reconnect;
|
|
435
|
+
if (now() - this.reconnect_start > stop_after) { // give up.
|
|
433
436
|
this.terminate();
|
|
434
437
|
clear_q(call_q(queue.on_ready_fail));
|
|
435
438
|
clear_q(queue.on_ready);
|
|
@@ -437,7 +440,8 @@ class WebSocketClient {
|
|
|
437
440
|
this.reconnect_timeout = nil;
|
|
438
441
|
}
|
|
439
442
|
else
|
|
440
|
-
this.reconnect_timeout = sett(
|
|
443
|
+
this.reconnect_timeout = sett(// try more.
|
|
444
|
+
ms(time_fn(params, attempt)), this.reconnect.bind(this, attempt + 1));
|
|
441
445
|
}
|
|
442
446
|
}
|
|
443
447
|
resetReconnect() {
|
|
@@ -448,6 +452,7 @@ class WebSocketClient {
|
|
|
448
452
|
}
|
|
449
453
|
initSocket(ws) {
|
|
450
454
|
const { queue, config, router } = this;
|
|
455
|
+
const { reconnect } = config;
|
|
451
456
|
this.ws = ws;
|
|
452
457
|
clear_q(call_q(this.queue.on_ready));
|
|
453
458
|
const { id_key, data_key } = config.server;
|
|
@@ -463,7 +468,7 @@ class WebSocketClient {
|
|
|
463
468
|
this.ws = nil;
|
|
464
469
|
clear_q(call_q(queue.on_close));
|
|
465
470
|
this.call('close', ...e);
|
|
466
|
-
if (!this.intentionally_closed &&
|
|
471
|
+
if (!this.intentionally_closed && reconnect && reconnect.on_break)
|
|
467
472
|
this.reconnect();
|
|
468
473
|
});
|
|
469
474
|
const { ping } = config;
|
|
@@ -566,7 +571,7 @@ class WebSocketClient {
|
|
|
566
571
|
this.ws = nil;
|
|
567
572
|
this.intentionally_closed = true;
|
|
568
573
|
}
|
|
569
|
-
|
|
574
|
+
close(timeout = .5) {
|
|
570
575
|
return new Promise((ff, rj) => {
|
|
571
576
|
if (isNull(this.ws))
|
|
572
577
|
ff(nil);
|
|
@@ -592,6 +597,7 @@ class WebSocketClient {
|
|
|
592
597
|
this.log('send', message_data);
|
|
593
598
|
const { config, queue: { send: send_q, on_ready_fail } } = this;
|
|
594
599
|
const { pipes, server: { data_key } } = config;
|
|
600
|
+
const { reconnect } = config;
|
|
595
601
|
const { top } = opts;
|
|
596
602
|
const id = genid(send_q);
|
|
597
603
|
if (isObj(top) && data_key in top)
|
|
@@ -611,12 +617,14 @@ class WebSocketClient {
|
|
|
611
617
|
const timeout = (rj) => sett(ms(timeout_time), () => {
|
|
612
618
|
if (send_q.has(id)) {
|
|
613
619
|
this.call('timeout', message_data);
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
+
const reject = () => {
|
|
621
|
+
cleanup();
|
|
622
|
+
rj({
|
|
623
|
+
'Websocket timeout expired': timeout_time,
|
|
624
|
+
'for the message': message_data
|
|
625
|
+
});
|
|
626
|
+
};
|
|
627
|
+
if (reconnect && reconnect.on_timeout) {
|
|
620
628
|
on_ready_fail.push(reject);
|
|
621
629
|
this.reconnect();
|
|
622
630
|
}
|
package/dist/bundle.d.ts
CHANGED
package/dist/bundle.mjs
CHANGED
|
@@ -332,7 +332,7 @@ const timeout_rm = (q, ff, rj, timeout = .5) => {
|
|
|
332
332
|
};
|
|
333
333
|
const genid = (q) => {
|
|
334
334
|
const id = zipnum.zip((random() * (MAX_32 - 10)) | 0);
|
|
335
|
-
return id
|
|
335
|
+
return q.has(id) ? genid(q) : id;
|
|
336
336
|
};
|
|
337
337
|
const call_q = (q, ...args) => {
|
|
338
338
|
for (const fn of q)
|
|
@@ -413,21 +413,24 @@ class WebSocketClient {
|
|
|
413
413
|
async reconnect(attempt = 0) {
|
|
414
414
|
if (this._reconnecting && attempt === 0)
|
|
415
415
|
return;
|
|
416
|
+
const { reconnect } = this.config;
|
|
417
|
+
if (!reconnect)
|
|
418
|
+
throw new Error('WSC: reconnecting is disabled, but reconned h.b. called!');
|
|
416
419
|
this.log('reconnect');
|
|
417
420
|
this._reconnecting = true;
|
|
418
421
|
this.reconnect_start = now();
|
|
419
422
|
if (!isNil(this.ws))
|
|
420
423
|
this.terminate();
|
|
421
424
|
const { queue } = this;
|
|
422
|
-
if (attempt > 0 && isNil(await this.connect())) {
|
|
425
|
+
if (attempt > 0 && isNil(await this.connect())) { // connected.
|
|
423
426
|
clear_q(call_q(queue.on_ready));
|
|
424
427
|
clear_q(queue.on_ready_fail);
|
|
425
428
|
this._reconnecting = false;
|
|
426
429
|
this.reconnect_timeout = nil;
|
|
427
430
|
}
|
|
428
431
|
else {
|
|
429
|
-
const { stop_after, time_fn, params } =
|
|
430
|
-
if (now() - this.reconnect_start > stop_after) {
|
|
432
|
+
const { stop_after, time_fn, params } = reconnect;
|
|
433
|
+
if (now() - this.reconnect_start > stop_after) { // give up.
|
|
431
434
|
this.terminate();
|
|
432
435
|
clear_q(call_q(queue.on_ready_fail));
|
|
433
436
|
clear_q(queue.on_ready);
|
|
@@ -435,7 +438,8 @@ class WebSocketClient {
|
|
|
435
438
|
this.reconnect_timeout = nil;
|
|
436
439
|
}
|
|
437
440
|
else
|
|
438
|
-
this.reconnect_timeout = sett(
|
|
441
|
+
this.reconnect_timeout = sett(// try more.
|
|
442
|
+
ms(time_fn(params, attempt)), this.reconnect.bind(this, attempt + 1));
|
|
439
443
|
}
|
|
440
444
|
}
|
|
441
445
|
resetReconnect() {
|
|
@@ -446,6 +450,7 @@ class WebSocketClient {
|
|
|
446
450
|
}
|
|
447
451
|
initSocket(ws) {
|
|
448
452
|
const { queue, config, router } = this;
|
|
453
|
+
const { reconnect } = config;
|
|
449
454
|
this.ws = ws;
|
|
450
455
|
clear_q(call_q(this.queue.on_ready));
|
|
451
456
|
const { id_key, data_key } = config.server;
|
|
@@ -461,7 +466,7 @@ class WebSocketClient {
|
|
|
461
466
|
this.ws = nil;
|
|
462
467
|
clear_q(call_q(queue.on_close));
|
|
463
468
|
this.call('close', ...e);
|
|
464
|
-
if (!this.intentionally_closed &&
|
|
469
|
+
if (!this.intentionally_closed && reconnect && reconnect.on_break)
|
|
465
470
|
this.reconnect();
|
|
466
471
|
});
|
|
467
472
|
const { ping } = config;
|
|
@@ -564,7 +569,7 @@ class WebSocketClient {
|
|
|
564
569
|
this.ws = nil;
|
|
565
570
|
this.intentionally_closed = true;
|
|
566
571
|
}
|
|
567
|
-
|
|
572
|
+
close(timeout = .5) {
|
|
568
573
|
return new Promise((ff, rj) => {
|
|
569
574
|
if (isNull(this.ws))
|
|
570
575
|
ff(nil);
|
|
@@ -590,6 +595,7 @@ class WebSocketClient {
|
|
|
590
595
|
this.log('send', message_data);
|
|
591
596
|
const { config, queue: { send: send_q, on_ready_fail } } = this;
|
|
592
597
|
const { pipes, server: { data_key } } = config;
|
|
598
|
+
const { reconnect } = config;
|
|
593
599
|
const { top } = opts;
|
|
594
600
|
const id = genid(send_q);
|
|
595
601
|
if (isObj(top) && data_key in top)
|
|
@@ -609,12 +615,14 @@ class WebSocketClient {
|
|
|
609
615
|
const timeout = (rj) => sett(ms(timeout_time), () => {
|
|
610
616
|
if (send_q.has(id)) {
|
|
611
617
|
this.call('timeout', message_data);
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
+
const reject = () => {
|
|
619
|
+
cleanup();
|
|
620
|
+
rj({
|
|
621
|
+
'Websocket timeout expired': timeout_time,
|
|
622
|
+
'for the message': message_data
|
|
623
|
+
});
|
|
624
|
+
};
|
|
625
|
+
if (reconnect && reconnect.on_timeout) {
|
|
618
626
|
on_ready_fail.push(reject);
|
|
619
627
|
this.reconnect();
|
|
620
628
|
}
|