durablews 1.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 +13 -9
- package/dist/{durablews.js → durablews.es.js} +6 -6
- package/dist/durablews.umd.js +1 -0
- package/dist/index.d.ts +44 -1
- package/package.json +9 -6
- package/dist/client.d.ts +0 -2
- package/dist/durablews.umd.cjs +0 -1
- package/dist/types.d.ts +0 -34
package/README.md
CHANGED
|
@@ -7,7 +7,6 @@ DurableWS is a resilient, TypeScript-based WebSocket client designed for modern
|
|
|
7
7
|
- **Automatic Reconnection**: Implements exponential backoff strategy to handle disconnections gracefully.
|
|
8
8
|
- **Idle Detection**: Automatically detects idle states and triggers custom handlers.
|
|
9
9
|
- **Message Queueing**: Outgoing messages are queued when the connection is down and sent when it's restored.
|
|
10
|
-
- **Subscription Management**: Simplifies subscribing to topics/channels with ease.
|
|
11
10
|
- **TypeScript Support**: Fully typed interfaces for a better development experience.
|
|
12
11
|
|
|
13
12
|
## Installation
|
|
@@ -27,29 +26,35 @@ yarn add durablews
|
|
|
27
26
|
To get started with DurableWS, first import the client in your project:
|
|
28
27
|
|
|
29
28
|
```ts
|
|
30
|
-
import {
|
|
29
|
+
import { defineClient } from "durablews";
|
|
31
30
|
```
|
|
32
31
|
|
|
33
32
|
Create a new WebSocket client instance by specifying the configuration:
|
|
34
33
|
|
|
35
34
|
```ts
|
|
36
|
-
const client = await
|
|
35
|
+
const client = await defineClient({
|
|
37
36
|
url: "wss://your.websocket.server",
|
|
38
37
|
autoConnect: true, // Automatically connects
|
|
39
38
|
maxReconnectAttempts: 5,
|
|
40
39
|
maxQueueSize: 50,
|
|
41
40
|
idleTimeout: 300000, // 5 minutes
|
|
42
41
|
getToken: async () => {
|
|
43
|
-
const response = await fetch("/my/token/issuer")
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const response = await fetch("/my/token/issuer");
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
return data.token;
|
|
45
|
+
}
|
|
46
46
|
});
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
### Sending Messages
|
|
50
50
|
|
|
51
51
|
```typescript
|
|
52
|
-
client.send({
|
|
52
|
+
client.send({
|
|
53
|
+
action: "greet",
|
|
54
|
+
data: {
|
|
55
|
+
message: "Hello, World!"
|
|
56
|
+
}
|
|
57
|
+
});
|
|
53
58
|
```
|
|
54
59
|
|
|
55
60
|
### Handling Events
|
|
@@ -78,14 +83,13 @@ client.onIdle(() => {
|
|
|
78
83
|
|
|
79
84
|
## API Reference
|
|
80
85
|
|
|
81
|
-
### `
|
|
86
|
+
### `defineClient(config: WebSocketClientConfig): Promise<WebSocketClient>`
|
|
82
87
|
|
|
83
88
|
Initializes and returns a WebSocket client instance.
|
|
84
89
|
|
|
85
90
|
#### Config Options
|
|
86
91
|
|
|
87
92
|
- `url`: WebSocket server URL.
|
|
88
|
-
- `accessToken` (optional): Token for authentication.
|
|
89
93
|
- `autoConnect` (optional): Whether to connect immediately upon instantiation.
|
|
90
94
|
- `maxReconnectAttempts` (optional): Maximum number of reconnection attempts.
|
|
91
95
|
- `maxQueueSize` (optional): Maximum size of the message queue.
|
|
@@ -10,16 +10,16 @@ async function b(s) {
|
|
|
10
10
|
{ length: N },
|
|
11
11
|
(o, n) => Math.pow(2, n) * 1e3
|
|
12
12
|
), m = s.idleTimeout || 5 * 60 * 1e3;
|
|
13
|
-
let
|
|
13
|
+
let f;
|
|
14
14
|
const u = () => {
|
|
15
|
-
clearTimeout(
|
|
15
|
+
clearTimeout(f), f = setTimeout(() => {
|
|
16
16
|
r.idle.forEach((o) => o());
|
|
17
17
|
}, m);
|
|
18
18
|
};
|
|
19
19
|
window.addEventListener("mousemove", u), window.addEventListener("keydown", u), window.addEventListener("mousedown", u), u(), s.getToken ? d = await s.getToken() : !d && s.authTokenURL && (d = (await fetch(s.authTokenURL).then(
|
|
20
20
|
(n) => n.json()
|
|
21
21
|
)).token);
|
|
22
|
-
const
|
|
22
|
+
const O = (o = 0) => {
|
|
23
23
|
let n = s.url;
|
|
24
24
|
d && (n += `?token=${encodeURIComponent(d)}`), e = new WebSocket(n), e.onopen = () => {
|
|
25
25
|
let t;
|
|
@@ -50,7 +50,7 @@ async function b(s) {
|
|
|
50
50
|
}, e.onclose = (t) => {
|
|
51
51
|
e && e.readyState !== (WebSocket == null ? void 0 : WebSocket.CLOSED) && o < N - 1 && // Exclude normal closure from reconnection attempts
|
|
52
52
|
t.code !== 1e3 && setTimeout(
|
|
53
|
-
() =>
|
|
53
|
+
() => O(o + 1),
|
|
54
54
|
h[o]
|
|
55
55
|
), r.close.forEach((a) => a(t));
|
|
56
56
|
}, e.onerror = (t) => {
|
|
@@ -59,7 +59,7 @@ async function b(s) {
|
|
|
59
59
|
}, p = (o) => {
|
|
60
60
|
(e == null ? void 0 : e.readyState) === (WebSocket == null ? void 0 : WebSocket.OPEN) ? e.send(JSON.stringify(o)) : (e == null ? void 0 : e.readyState) !== (WebSocket == null ? void 0 : WebSocket.CLOSED) && (i.push(o), i.length > S && i.shift());
|
|
61
61
|
}, C = () => {
|
|
62
|
-
(!e || e.readyState === (WebSocket == null ? void 0 : WebSocket.CLOSED)) &&
|
|
62
|
+
(!e || e.readyState === (WebSocket == null ? void 0 : WebSocket.CLOSED)) && O();
|
|
63
63
|
}, T = () => {
|
|
64
64
|
e && e.close();
|
|
65
65
|
}, D = () => new Promise((o, n) => {
|
|
@@ -116,5 +116,5 @@ async function b(s) {
|
|
|
116
116
|
}, s.autoConnect !== !1 && C(), l;
|
|
117
117
|
}
|
|
118
118
|
export {
|
|
119
|
-
b as
|
|
119
|
+
b as defineClient
|
|
120
120
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(c,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(c=typeof globalThis<"u"?globalThis:c||self,a(c.durablews={}))})(this,function(c){"use strict";var a=(r=>(r.DISCONNECTED="DISCONNECTED",r.CONNECTING="CONNECTING",r.CONNECTED="CONNECTED",r.CLOSING="CLOSING",r.CLOSED="CLOSED",r))(a||{});let l=null;async function p(r){if(l)return l;let e=null;const h=r.maxQueueSize||10,d=[];let u;const N=r.maxReconnectAttempts||5,m=Array.from({length:N},(o,n)=>Math.pow(2,n)*1e3),T=r.idleTimeout||5*60*1e3;let O;const f=()=>{clearTimeout(O),O=setTimeout(()=>{s.idle.forEach(o=>o())},T)};window.addEventListener("mousemove",f),window.addEventListener("keydown",f),window.addEventListener("mousedown",f),f(),r.getToken?u=await r.getToken():!u&&r.authTokenURL&&(u=(await fetch(r.authTokenURL).then(n=>n.json())).token);const C=(o=0)=>{let n=r.url;u&&(n+=`?token=${encodeURIComponent(u)}`),e=new WebSocket(n),e.onopen=()=>{let t;for(;d.length>0;){t=d.shift();try{if(t&&(e==null?void 0:e.readyState)===(WebSocket==null?void 0:WebSocket.OPEN))e.send(JSON.stringify(t));else throw new Error("Message not sent due to WebSocket not being in OPEN state")}catch(i){console.error(i),d.unshift(t);break}}s.open.forEach(i=>i())},e.onmessage=t=>{try{const i=JSON.parse(t.data);s.message.forEach(E=>E(i))}catch(i){t!=null&&t.data&&typeof(t==null?void 0:t.data)=="string"||(t==null?void 0:t.data)instanceof String?s.message.forEach(E=>E({data:t==null?void 0:t.data})):(console.log(typeof t),console.log(t),console.error("Failed to parse message data:",i))}},e.onclose=t=>{e&&e.readyState!==(WebSocket==null?void 0:WebSocket.CLOSED)&&o<N-1&&t.code!==1e3&&setTimeout(()=>C(o+1),m[o]),s.close.forEach(i=>i(t))},e.onerror=t=>{s.error.forEach(i=>i(t))}},y=o=>{(e==null?void 0:e.readyState)===(WebSocket==null?void 0:WebSocket.OPEN)?e.send(JSON.stringify(o)):(e==null?void 0:e.readyState)!==(WebSocket==null?void 0:WebSocket.CLOSED)&&(d.push(o),d.length>h&&d.shift())},S=()=>{(!e||e.readyState===(WebSocket==null?void 0:WebSocket.CLOSED))&&C()},D=()=>{e&&e.close()},g=()=>new Promise((o,n)=>{(e==null?void 0:e.readyState)===(WebSocket==null?void 0:WebSocket.OPEN)?o():(e==null||e.addEventListener("open",()=>o(),{once:!0}),e==null||e.addEventListener("error",n,{once:!0}))}),k=()=>{if(!e)return a.DISCONNECTED;switch(e.readyState){case WebSocket.CONNECTING:return a.CONNECTING;case WebSocket.OPEN:return a.CONNECTED;case WebSocket.CLOSING:return a.CLOSING;case WebSocket.CLOSED:return a.CLOSED;default:return a.DISCONNECTED}},s={message:[],error:[],open:[],close:[],idle:[]};return l={send:y,onMessage:o=>(s.message.push(o),()=>{const n=s.message.indexOf(o);n!==-1&&s.message.splice(n,1)}),onError:o=>(s.error.push(o),()=>{const n=s.error.indexOf(o);n!==-1&&s.error.splice(n,1)}),onOpen:o=>(s.open.push(o),()=>{const n=s.open.indexOf(o);n!==-1&&s.open.splice(n,1)}),onClose:o=>(s.close.push(o),()=>{const n=s.close.indexOf(o);n!==-1&&s.close.splice(n,1)}),onIdle:o=>(s.idle.push(o),()=>{const n=s.idle.indexOf(o);n!==-1&&s.idle.splice(n,1)}),connect:S,disconnect:D,isReady:g,getStatus:k},r.autoConnect!==!1&&S(),l}c.defineClient=p,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,44 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function defineClient(config: WebSocketClientConfig): Promise<WebSocketClient>;
|
|
2
|
+
|
|
3
|
+
declare type OnCloseHandler = (event: CloseEvent) => void;
|
|
4
|
+
|
|
5
|
+
declare type OnErrorHandler = (error: Event) => void;
|
|
6
|
+
|
|
7
|
+
declare type OnIdleHandler = () => void;
|
|
8
|
+
|
|
9
|
+
declare type OnMessageHandler = (message: Record<string, unknown>) => void;
|
|
10
|
+
|
|
11
|
+
declare type OnOpenHandler = () => void;
|
|
12
|
+
|
|
13
|
+
declare interface WebSocketClient {
|
|
14
|
+
send: (message: Record<string, unknown>) => void;
|
|
15
|
+
connect: () => void;
|
|
16
|
+
disconnect: () => void;
|
|
17
|
+
isReady: () => Promise<void>;
|
|
18
|
+
getStatus: () => WebSocketClientState;
|
|
19
|
+
onMessage: (listener: OnMessageHandler) => void;
|
|
20
|
+
onError: (listener: OnErrorHandler) => void;
|
|
21
|
+
onOpen: (listener: OnOpenHandler) => void;
|
|
22
|
+
onClose: (listener: OnCloseHandler) => void;
|
|
23
|
+
onIdle: (listener: OnIdleHandler) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare interface WebSocketClientConfig {
|
|
27
|
+
url: string;
|
|
28
|
+
autoConnect?: boolean;
|
|
29
|
+
maxReconnectAttempts?: number;
|
|
30
|
+
maxQueueSize?: number;
|
|
31
|
+
idleTimeout?: number;
|
|
32
|
+
authTokenURL?: string;
|
|
33
|
+
getToken?: () => Promise<string> | string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare enum WebSocketClientState {
|
|
37
|
+
DISCONNECTED = "DISCONNECTED",
|
|
38
|
+
CONNECTING = "CONNECTING",
|
|
39
|
+
CONNECTED = "CONNECTED",
|
|
40
|
+
CLOSING = "CLOSING",
|
|
41
|
+
CLOSED = "CLOSED"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { }
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "durablews",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A resilient, TypeScript-based WebSocket client",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/durablews.umd.
|
|
7
|
-
"module": "./dist/durablews.js",
|
|
6
|
+
"main": "./dist/durablews.umd.js",
|
|
7
|
+
"module": "./dist/durablews.es.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/durablews.es.js",
|
|
13
|
+
"require": "./dist/durablews.umd.js"
|
|
13
14
|
}
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
@@ -33,12 +34,14 @@
|
|
|
33
34
|
"eslint": "^8.56.0",
|
|
34
35
|
"eslint-config-prettier": "^9.1.0",
|
|
35
36
|
"eslint-plugin-prettier": "^5.1.3",
|
|
37
|
+
"jsdom": "^24.0.0",
|
|
36
38
|
"prettier": "3.2.5",
|
|
37
39
|
"rimraf": "^5.0.5",
|
|
38
40
|
"typescript": "^5.3.3",
|
|
39
41
|
"vite": "^5.1.1",
|
|
40
42
|
"vite-plugin-dts": "^3.7.2",
|
|
41
|
-
"vitest": "^1.2.2"
|
|
43
|
+
"vitest": "^1.2.2",
|
|
44
|
+
"vitest-websocket-mock": "^0.3.0"
|
|
42
45
|
},
|
|
43
46
|
"repository": {
|
|
44
47
|
"type": "git",
|
package/dist/client.d.ts
DELETED
package/dist/durablews.umd.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(function(i,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(i=typeof globalThis<"u"?globalThis:i||self,a(i.durablews={}))})(this,function(i){"use strict";var a=(s=>(s.DISCONNECTED="DISCONNECTED",s.CONNECTING="CONNECTING",s.CONNECTED="CONNECTED",s.CLOSING="CLOSING",s.CLOSED="CLOSED",s))(a||{});let l=null;async function p(s){if(l)return l;let e=null;const h=s.maxQueueSize||10,d=[];let u;const N=s.maxReconnectAttempts||5,m=Array.from({length:N},(o,n)=>Math.pow(2,n)*1e3),T=s.idleTimeout||5*60*1e3;let O;const f=()=>{clearTimeout(O),O=setTimeout(()=>{r.idle.forEach(o=>o())},T)};window.addEventListener("mousemove",f),window.addEventListener("keydown",f),window.addEventListener("mousedown",f),f(),s.getToken?u=await s.getToken():!u&&s.authTokenURL&&(u=(await fetch(s.authTokenURL).then(n=>n.json())).token);const C=(o=0)=>{let n=s.url;u&&(n+=`?token=${encodeURIComponent(u)}`),e=new WebSocket(n),e.onopen=()=>{let t;for(;d.length>0;){t=d.shift();try{if(t&&(e==null?void 0:e.readyState)===(WebSocket==null?void 0:WebSocket.OPEN))e.send(JSON.stringify(t));else throw new Error("Message not sent due to WebSocket not being in OPEN state")}catch(c){console.error(c),d.unshift(t);break}}r.open.forEach(c=>c())},e.onmessage=t=>{try{const c=JSON.parse(t.data);r.message.forEach(E=>E(c))}catch(c){t!=null&&t.data&&typeof(t==null?void 0:t.data)=="string"||(t==null?void 0:t.data)instanceof String?r.message.forEach(E=>E({data:t==null?void 0:t.data})):(console.log(typeof t),console.log(t),console.error("Failed to parse message data:",c))}},e.onclose=t=>{e&&e.readyState!==(WebSocket==null?void 0:WebSocket.CLOSED)&&o<N-1&&t.code!==1e3&&setTimeout(()=>C(o+1),m[o]),r.close.forEach(c=>c(t))},e.onerror=t=>{r.error.forEach(c=>c(t))}},y=o=>{(e==null?void 0:e.readyState)===(WebSocket==null?void 0:WebSocket.OPEN)?e.send(JSON.stringify(o)):(e==null?void 0:e.readyState)!==(WebSocket==null?void 0:WebSocket.CLOSED)&&(d.push(o),d.length>h&&d.shift())},S=()=>{(!e||e.readyState===(WebSocket==null?void 0:WebSocket.CLOSED))&&C()},D=()=>{e&&e.close()},g=()=>new Promise((o,n)=>{(e==null?void 0:e.readyState)===(WebSocket==null?void 0:WebSocket.OPEN)?o():(e==null||e.addEventListener("open",()=>o(),{once:!0}),e==null||e.addEventListener("error",n,{once:!0}))}),k=()=>{if(!e)return a.DISCONNECTED;switch(e.readyState){case WebSocket.CONNECTING:return a.CONNECTING;case WebSocket.OPEN:return a.CONNECTED;case WebSocket.CLOSING:return a.CLOSING;case WebSocket.CLOSED:return a.CLOSED;default:return a.DISCONNECTED}},r={message:[],error:[],open:[],close:[],idle:[]};return l={send:y,onMessage:o=>(r.message.push(o),()=>{const n=r.message.indexOf(o);n!==-1&&r.message.splice(n,1)}),onError:o=>(r.error.push(o),()=>{const n=r.error.indexOf(o);n!==-1&&r.error.splice(n,1)}),onOpen:o=>(r.open.push(o),()=>{const n=r.open.indexOf(o);n!==-1&&r.open.splice(n,1)}),onClose:o=>(r.close.push(o),()=>{const n=r.close.indexOf(o);n!==-1&&r.close.splice(n,1)}),onIdle:o=>(r.idle.push(o),()=>{const n=r.idle.indexOf(o);n!==-1&&r.idle.splice(n,1)}),connect:S,disconnect:D,isReady:g,getStatus:k},s.autoConnect!==!1&&S(),l}i.createClient=p,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})});
|
package/dist/types.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export interface WebSocketClientConfig {
|
|
2
|
-
url: string;
|
|
3
|
-
accessToken?: string;
|
|
4
|
-
autoConnect?: boolean;
|
|
5
|
-
maxReconnectAttempts?: number;
|
|
6
|
-
maxQueueSize?: number;
|
|
7
|
-
idleTimeout?: number;
|
|
8
|
-
authTokenURL?: string;
|
|
9
|
-
getToken?: () => Promise<string> | string;
|
|
10
|
-
}
|
|
11
|
-
export interface WebSocketClient {
|
|
12
|
-
send: (message: Record<string, unknown>) => void;
|
|
13
|
-
connect: () => void;
|
|
14
|
-
disconnect: () => void;
|
|
15
|
-
isReady: () => Promise<void>;
|
|
16
|
-
getStatus: () => WebSocketClientState;
|
|
17
|
-
onMessage: (listener: OnMessageHandler) => void;
|
|
18
|
-
onError: (listener: OnErrorHandler) => void;
|
|
19
|
-
onOpen: (listener: OnOpenHandler) => void;
|
|
20
|
-
onClose: (listener: OnCloseHandler) => void;
|
|
21
|
-
onIdle: (listener: OnIdleHandler) => void;
|
|
22
|
-
}
|
|
23
|
-
export declare enum WebSocketClientState {
|
|
24
|
-
DISCONNECTED = "DISCONNECTED",
|
|
25
|
-
CONNECTING = "CONNECTING",
|
|
26
|
-
CONNECTED = "CONNECTED",
|
|
27
|
-
CLOSING = "CLOSING",
|
|
28
|
-
CLOSED = "CLOSED"
|
|
29
|
-
}
|
|
30
|
-
export type OnMessageHandler = (message: Record<string, unknown>) => void;
|
|
31
|
-
export type OnErrorHandler = (error: Event) => void;
|
|
32
|
-
export type OnOpenHandler = () => void;
|
|
33
|
-
export type OnCloseHandler = (event: CloseEvent) => void;
|
|
34
|
-
export type OnIdleHandler = () => void;
|