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 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 { createClient } from "durablews";
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 createClient({
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").then((r) => r.json());
44
- return response.token;
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({ action: "greet", data: { message: "Hello, World!" } });
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
- ### `createClient(config: WebSocketClientConfig): Promise<WebSocketClient>`
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 O;
13
+ let f;
14
14
  const u = () => {
15
- clearTimeout(O), O = setTimeout(() => {
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 f = (o = 0) => {
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
- () => f(o + 1),
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)) && f();
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 createClient
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 * from "./client";
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.0",
3
+ "version": "1.0.1",
4
4
  "description": "A resilient, TypeScript-based WebSocket client",
5
5
  "type": "module",
6
- "main": "./dist/durablews.umd.cjs",
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
- "import": "./dist/durablews.js",
12
- "require": "./dist/durablews.umd.cjs"
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
@@ -1,2 +0,0 @@
1
- import type { WebSocketClientConfig, WebSocketClient } from "./types";
2
- export declare function createClient(config: WebSocketClientConfig): Promise<WebSocketClient>;
@@ -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;