@lowerdeck/websocket-client 1.0.0

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.
@@ -0,0 +1,12 @@
1
+
2
+ $ microbundle
3
+ No name was provided for external module '@lowerdeck/emitter' in output.globals – guessing 'emitter'
4
+ Build "@lowerdeck/websocket-client" to dist:
5
+ 840 B: index.cjs.gz
6
+ 735 B: index.cjs.br
7
+ 518 B: index.modern.js.gz
8
+ 453 B: index.modern.js.br
9
+ 844 B: index.module.js.gz
10
+ 737 B: index.module.js.br
11
+ 913 B: index.umd.js.gz
12
+ 802 B: index.umd.js.br
@@ -0,0 +1,11 @@
1
+
2
+ $ vitest run --passWithNoTests
3
+ [?25l
4
+  RUN  v3.2.4 /Users/tobias/code/metorial/metorial-enterprise/oss/src/packages/shared/websocket
5
+
6
+ No test files found, exiting with code 0
7
+
8
+ include: **/*.{test,spec}.?(c|m)[jt]s?(x)
9
+ exclude: **/node_modules/**, **/dist/**, **/cypress/**, **/.{idea,git,cache,output,temp}/**, **/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*
10
+
11
+ [?25h
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # `@lowerdeck/websocket-client`
2
+
3
+ Auto-reconnecting WebSocket client with exponential backoff. Handles connection failures and provides a familiar event-driven API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lowerdeck/websocket-client
9
+ yarn add @lowerdeck/websocket-client
10
+ bun add @lowerdeck/websocket-client
11
+ pnpm add @lowerdeck/websocket-client
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```typescript
17
+ import { ReconnectingWebSocketClient } from '@lowerdeck/websocket-client';
18
+
19
+ // Create client with automatic reconnection
20
+ const ws = new ReconnectingWebSocketClient('wss://example.com/socket', {
21
+ protocols: ['v1'],
22
+ maxAttempts: 5,
23
+ timeout: 5000,
24
+ onReconnect: (attempt) => {
25
+ console.log(`Reconnecting... Attempt ${attempt}`);
26
+ }
27
+ });
28
+
29
+ // Listen for events
30
+ ws.addEventListener('open', () => {
31
+ console.log('Connected');
32
+ });
33
+
34
+ ws.addEventListener('message', (event) => {
35
+ console.log('Received:', event.data);
36
+ });
37
+
38
+ ws.addEventListener('close', () => {
39
+ console.log('Disconnected');
40
+ });
41
+
42
+ ws.addEventListener('error', (error) => {
43
+ console.error('Error:', error);
44
+ });
45
+
46
+ ws.addEventListener('maximum', () => {
47
+ console.error('Max reconnection attempts reached');
48
+ });
49
+
50
+ // Send messages
51
+ ws.send('Hello server');
52
+ ws.send(JSON.stringify({ type: 'ping' }));
53
+
54
+ // Close connection
55
+ ws.close();
56
+
57
+ // Check connection state
58
+ console.log(ws.readyState); // 0: CONNECTING, 1: OPEN, 2: CLOSING, 3: CLOSED
59
+ ```
60
+
61
+ ### Reconnection Behavior
62
+
63
+ - Automatically reconnects on connection loss
64
+ - Exponential backoff between attempts
65
+ - Maximum retry attempts configurable
66
+ - Manual close prevents reconnection
67
+
68
+ ## License
69
+
70
+ This project is licensed under the Apache License 2.0.
71
+
72
+ <div align="center">
73
+ <sub>Built with ❤️ by <a href="https://metorial.com">Metorial</a></sub>
74
+ </div>
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ var t=require("@lowerdeck/emitter");function e(t){var e=function(t){if("object"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,"string");if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==typeof e?e:e+""}exports.ReconnectingWebSocketClient=/*#__PURE__*/function(){function i(e,i){var n;void 0===i&&(i={}),this.ws=void 0,this.attempts=0,this.timer=null,this.url=void 0,this.opts=void 0,this.maxAttempts=void 0,this.emitter=new t.Emitter,this.url=e,this.opts=i,this.maxAttempts=null!=(n=i.maxAttempts)?n:Infinity,this.open()}var n,r,o=i.prototype;return o.open=function(){var t,e=this;this.ws=new WebSocket(this.url,null!=(t=this.opts.protocols)?t:[]),this.ws.addEventListener("open",function(t){e.attempts=0,e.emitter.emit("open",t)}),this.ws.addEventListener("close",function(t){e.reconnect(t)}),this.ws.addEventListener("error",function(t){"ECONNREFUSED"===(null==t?void 0:t.code)?e.reconnect(t):e.emitter.emit("error",t)}),this.ws.addEventListener("message",function(t){e.emitter.emit("message",t)})},o.reconnect=function(t){var e,i=this;this.timer&&clearTimeout(this.timer),this.attempts++<this.maxAttempts?this.timer=setTimeout(function(){null==i.opts.onReconnect||i.opts.onReconnect(t),i.open()},null!=(e=this.opts.timeout)?e:1e3):(this.emitter.emit("maximum",t),this.emitter.emit("close",t),this.emitter.clear())},o.send=function(t){this.ws.send(t)},o.close=function(t,e){void 0===t&&(t=1e3),this.timer&&clearTimeout(this.timer),this.emitter.clear(),this.ws.close(t,e)},o.addEventListener=function(t,e){return this.emitter.on(t,e)},n=i,(r=[{key:"readyState",get:function(){return this.ws.readyState}}])&&function(t,i){for(var n=0;n<i.length;n++){var r=i[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,e(r.key),r)}}(n.prototype,r),Object.defineProperty(n,"prototype",{writable:!1}),n}();
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import { Emitter } from '@lowerdeck/emitter';\n\n// Highly inspired by https://github.com/lukeed/sockette/blob/master/src/index.js\n\ninterface ReconnectingWebSocketOptions {\n protocols?: string | string[];\n maxAttempts?: number;\n timeout?: number;\n\n onReconnect?: (event: Event) => void;\n}\n\nexport class ReconnectingWebSocketClient {\n private ws!: WebSocket;\n private attempts = 0;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private readonly url: string;\n private readonly opts: ReconnectingWebSocketOptions;\n private readonly maxAttempts: number;\n private emitter = new Emitter<any>();\n\n constructor(url: string, opts: ReconnectingWebSocketOptions = {}) {\n this.url = url;\n this.opts = opts;\n this.maxAttempts = opts.maxAttempts ?? Infinity;\n\n this.open();\n }\n\n private open(): void {\n this.ws = new WebSocket(this.url, this.opts.protocols ?? []);\n\n this.ws.addEventListener('open', event => {\n this.attempts = 0;\n this.emitter.emit('open', event);\n });\n\n this.ws.addEventListener('close', event => {\n // let shouldReconnect = ![1000, 1001, 1005].includes(event.code);\n // if (shouldReconnect) {\n this.reconnect(event);\n // } else {\n // this.emitter.emit('close', event);\n // }\n });\n\n this.ws.addEventListener('error', event => {\n let error = event as any;\n if (error?.code === 'ECONNREFUSED') {\n this.reconnect(event);\n } else {\n this.emitter.emit('error', event);\n }\n });\n\n this.ws.addEventListener('message', event => {\n this.emitter.emit('message', event);\n });\n }\n\n private reconnect(event: Event): void {\n if (this.timer) clearTimeout(this.timer);\n\n if (this.attempts++ < this.maxAttempts) {\n this.timer = setTimeout(() => {\n this.opts.onReconnect?.(event);\n this.open();\n }, this.opts.timeout ?? 1000);\n } else {\n this.emitter.emit('maximum', event);\n this.emitter.emit('close', event);\n this.emitter.clear();\n }\n }\n\n send(data: string | ArrayBufferLike): void {\n this.ws.send(data);\n }\n\n close(code: number = 1000, reason?: string): void {\n if (this.timer) clearTimeout(this.timer);\n this.emitter.clear();\n this.ws.close(code, reason);\n }\n\n addEventListener(event: string, callback: (data: any) => void) {\n return this.emitter.on(event, callback);\n }\n\n get readyState() {\n return this.ws.readyState;\n }\n}\n"],"names":["ReconnectingWebSocketClient","url","opts","_opts$maxAttempts","this","ws","attempts","timer","maxAttempts","emitter","Emitter","Infinity","open","_proto","prototype","_this$opts$protocols","_this","WebSocket","protocols","addEventListener","event","emit","reconnect","code","_this2","_this$opts$timeout","clearTimeout","setTimeout","onReconnect","timeout","clear","send","data","close","reason","callback","on","key","get","readyState"],"mappings":"oXAqBE,WAAA,SAAAA,EAAYC,EAAaC,GAAuCC,IAAAA,OAAvCD,IAAAA,IAAAA,EAAqC,CAAE,GAAAE,KARxDC,QAAE,EAAAD,KACFE,SAAW,EACXC,KAAAA,MAA8C,KACrCN,KAAAA,SACAC,EAAAA,KAAAA,UACAM,EAAAA,KAAAA,iBACTC,EAAAA,KAAAA,QAAU,IAAIC,EAAcA,QAGlCN,KAAKH,IAAMA,EACXG,KAAKF,KAAOA,EACZE,KAAKI,YAA8BL,OAAnBA,EAAGD,EAAKM,aAAWL,EAAIQ,SAEvCP,KAAKQ,MACP,CAAC,QAAAC,EAAAb,EAAAc,UA4DA,OA5DAD,EAEOD,KAAA,WAAIG,IAAAA,EAAAC,EACVZ,KAAAA,KAAKC,GAAK,IAAIY,UAAUb,KAAKH,IAAwBc,OAArBA,EAAEX,KAAKF,KAAKgB,WAASH,EAAI,IAEzDX,KAAKC,GAAGc,iBAAiB,OAAQ,SAAAC,GAC/BJ,EAAKV,SAAW,EAChBU,EAAKP,QAAQY,KAAK,OAAQD,EAC5B,GAEAhB,KAAKC,GAAGc,iBAAiB,QAAS,SAAAC,GAGhCJ,EAAKM,UAAUF,EAIjB,GAEAhB,KAAKC,GAAGc,iBAAiB,QAAS,SAAAC,GAEZ,kBAAX,MADGA,OACH,EADGA,EACDG,MACTP,EAAKM,UAAUF,GAEfJ,EAAKP,QAAQY,KAAK,QAASD,EAE/B,GAEAhB,KAAKC,GAAGc,iBAAiB,UAAW,SAAAC,GAClCJ,EAAKP,QAAQY,KAAK,UAAWD,EAC/B,EACF,EAACP,EAEOS,UAAA,SAAUF,GAAYI,IAGYC,EAHZD,EAC5BpB,KAAIA,KAAKG,OAAOmB,aAAatB,KAAKG,OAE9BH,KAAKE,WAAaF,KAAKI,YACzBJ,KAAKG,MAAQoB,WAAW,WACtBH,MAAAA,EAAKtB,KAAK0B,aAAVJ,EAAKtB,KAAK0B,YAAcR,GACxBI,EAAKZ,MACP,EAAoB,OAAnBa,EAAErB,KAAKF,KAAK2B,SAAOJ,EAAI,MAExBrB,KAAKK,QAAQY,KAAK,UAAWD,GAC7BhB,KAAKK,QAAQY,KAAK,QAASD,GAC3BhB,KAAKK,QAAQqB,QAEjB,EAACjB,EAEDkB,KAAA,SAAKC,GACH5B,KAAKC,GAAG0B,KAAKC,EACf,EAACnB,EAEDoB,MAAA,SAAMV,EAAqBW,QAArBX,IAAAA,IAAAA,EAAe,KACfnB,KAAKG,OAAOmB,aAAatB,KAAKG,OAClCH,KAAKK,QAAQqB,QACb1B,KAAKC,GAAG4B,MAAMV,EAAMW,EACtB,EAACrB,EAEDM,iBAAA,SAAiBC,EAAee,GAC9B,OAAO/B,KAAKK,QAAQ2B,GAAGhB,EAAOe,EAChC,IAACnC,KAAA,CAAA,CAAAqC,IAAA,aAAAC,IAED,WACE,OAAOlC,KAAKC,GAAGkC,UACjB,iPAAC,CAtED"}
@@ -0,0 +1,24 @@
1
+ interface ReconnectingWebSocketOptions {
2
+ protocols?: string | string[];
3
+ maxAttempts?: number;
4
+ timeout?: number;
5
+ onReconnect?: (event: Event) => void;
6
+ }
7
+ export declare class ReconnectingWebSocketClient {
8
+ private ws;
9
+ private attempts;
10
+ private timer;
11
+ private readonly url;
12
+ private readonly opts;
13
+ private readonly maxAttempts;
14
+ private emitter;
15
+ constructor(url: string, opts?: ReconnectingWebSocketOptions);
16
+ private open;
17
+ private reconnect;
18
+ send(data: string | ArrayBufferLike): void;
19
+ close(code?: number, reason?: string): void;
20
+ addEventListener(event: string, callback: (data: any) => void): () => void;
21
+ get readyState(): number;
22
+ }
23
+ export {};
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,UAAU,4BAA4B;IACpC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACtC;AAED,qBAAa,2BAA2B;IACtC,OAAO,CAAC,EAAE,CAAa;IACvB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA+B;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,OAAO,CAAsB;gBAEzB,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,4BAAiC;IAQhE,OAAO,CAAC,IAAI;IA+BZ,OAAO,CAAC,SAAS;IAejB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAI1C,KAAK,CAAC,IAAI,GAAE,MAAa,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAMjD,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI;IAI7D,IAAI,UAAU,WAEb;CACF"}
@@ -0,0 +1,2 @@
1
+ import{Emitter as t}from"@lowerdeck/emitter";class e{constructor(e,s={}){var i;this.ws=void 0,this.attempts=0,this.timer=null,this.url=void 0,this.opts=void 0,this.maxAttempts=void 0,this.emitter=new t,this.url=e,this.opts=s,this.maxAttempts=null!=(i=s.maxAttempts)?i:Infinity,this.open()}open(){var t;this.ws=new WebSocket(this.url,null!=(t=this.opts.protocols)?t:[]),this.ws.addEventListener("open",t=>{this.attempts=0,this.emitter.emit("open",t)}),this.ws.addEventListener("close",t=>{this.reconnect(t)}),this.ws.addEventListener("error",t=>{"ECONNREFUSED"===(null==t?void 0:t.code)?this.reconnect(t):this.emitter.emit("error",t)}),this.ws.addEventListener("message",t=>{this.emitter.emit("message",t)})}reconnect(t){var e;this.timer&&clearTimeout(this.timer),this.attempts++<this.maxAttempts?this.timer=setTimeout(()=>{var e,s;null==(e=(s=this.opts).onReconnect)||e.call(s,t),this.open()},null!=(e=this.opts.timeout)?e:1e3):(this.emitter.emit("maximum",t),this.emitter.emit("close",t),this.emitter.clear())}send(t){this.ws.send(t)}close(t=1e3,e){this.timer&&clearTimeout(this.timer),this.emitter.clear(),this.ws.close(t,e)}addEventListener(t,e){return this.emitter.on(t,e)}get readyState(){return this.ws.readyState}}export{e as ReconnectingWebSocketClient};
2
+ //# sourceMappingURL=index.modern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.modern.js","sources":["../src/index.ts"],"sourcesContent":["import { Emitter } from '@lowerdeck/emitter';\n\n// Highly inspired by https://github.com/lukeed/sockette/blob/master/src/index.js\n\ninterface ReconnectingWebSocketOptions {\n protocols?: string | string[];\n maxAttempts?: number;\n timeout?: number;\n\n onReconnect?: (event: Event) => void;\n}\n\nexport class ReconnectingWebSocketClient {\n private ws!: WebSocket;\n private attempts = 0;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private readonly url: string;\n private readonly opts: ReconnectingWebSocketOptions;\n private readonly maxAttempts: number;\n private emitter = new Emitter<any>();\n\n constructor(url: string, opts: ReconnectingWebSocketOptions = {}) {\n this.url = url;\n this.opts = opts;\n this.maxAttempts = opts.maxAttempts ?? Infinity;\n\n this.open();\n }\n\n private open(): void {\n this.ws = new WebSocket(this.url, this.opts.protocols ?? []);\n\n this.ws.addEventListener('open', event => {\n this.attempts = 0;\n this.emitter.emit('open', event);\n });\n\n this.ws.addEventListener('close', event => {\n // let shouldReconnect = ![1000, 1001, 1005].includes(event.code);\n // if (shouldReconnect) {\n this.reconnect(event);\n // } else {\n // this.emitter.emit('close', event);\n // }\n });\n\n this.ws.addEventListener('error', event => {\n let error = event as any;\n if (error?.code === 'ECONNREFUSED') {\n this.reconnect(event);\n } else {\n this.emitter.emit('error', event);\n }\n });\n\n this.ws.addEventListener('message', event => {\n this.emitter.emit('message', event);\n });\n }\n\n private reconnect(event: Event): void {\n if (this.timer) clearTimeout(this.timer);\n\n if (this.attempts++ < this.maxAttempts) {\n this.timer = setTimeout(() => {\n this.opts.onReconnect?.(event);\n this.open();\n }, this.opts.timeout ?? 1000);\n } else {\n this.emitter.emit('maximum', event);\n this.emitter.emit('close', event);\n this.emitter.clear();\n }\n }\n\n send(data: string | ArrayBufferLike): void {\n this.ws.send(data);\n }\n\n close(code: number = 1000, reason?: string): void {\n if (this.timer) clearTimeout(this.timer);\n this.emitter.clear();\n this.ws.close(code, reason);\n }\n\n addEventListener(event: string, callback: (data: any) => void) {\n return this.emitter.on(event, callback);\n }\n\n get readyState() {\n return this.ws.readyState;\n }\n}\n"],"names":["ReconnectingWebSocketClient","constructor","url","opts","_opts$maxAttempts","this","ws","attempts","timer","maxAttempts","emitter","Emitter","Infinity","open","_this$opts$protocols","WebSocket","protocols","addEventListener","event","emit","reconnect","error","code","_this$opts$timeout","clearTimeout","setTimeout","_this$opts$onReconnec","_this$opts","onReconnect","call","timeout","clear","send","data","close","reason","callback","on","readyState"],"mappings":"6CAYa,MAAAA,EASXC,WAAAA,CAAYC,EAAaC,EAAqC,CAAE,OAAAC,EAAAC,KARxDC,QAAE,EAAAD,KACFE,SAAW,EACXC,KAAAA,MAA8C,KAAIH,KACzCH,SACAC,EAAAA,KAAAA,UACAM,EAAAA,KAAAA,iBACTC,EAAAA,KAAAA,QAAU,IAAIC,EAGpBN,KAAKH,IAAMA,EACXG,KAAKF,KAAOA,EACZE,KAAKI,YAA8B,OAAnBL,EAAGD,EAAKM,aAAWL,EAAIQ,SAEvCP,KAAKQ,MACP,CAEQA,IAAAA,GAAI,IAAAC,EACVT,KAAKC,GAAK,IAAIS,UAAUV,KAAKH,IAAwBY,OAArBA,EAAET,KAAKF,KAAKa,WAASF,EAAI,IAEzDT,KAAKC,GAAGW,iBAAiB,OAAQC,IAC/Bb,KAAKE,SAAW,EAChBF,KAAKK,QAAQS,KAAK,OAAQD,KAG5Bb,KAAKC,GAAGW,iBAAiB,QAASC,IAGhCb,KAAKe,UAAUF,KAMjBb,KAAKC,GAAGW,iBAAiB,QAASC,IAEZ,kBAAhBG,MADQH,OACRG,EADQH,EACDI,MACTjB,KAAKe,UAAUF,GAEfb,KAAKK,QAAQS,KAAK,QAASD,KAI/Bb,KAAKC,GAAGW,iBAAiB,UAAWC,IAClCb,KAAKK,QAAQS,KAAK,UAAWD,IAEjC,CAEQE,SAAAA,CAAUF,GAGwBK,IAAAA,EAFpClB,KAAKG,OAAOgB,aAAanB,KAAKG,OAE9BH,KAAKE,WAAaF,KAAKI,YACzBJ,KAAKG,MAAQiB,WAAW,KAAK,IAAAC,EAAAC,SAC3BD,GAAAC,EAAAtB,KAAKF,MAAKyB,cAAVF,EAAAG,KAAAF,EAAwBT,GACxBb,KAAKQ,QACa,OAAnBU,EAAElB,KAAKF,KAAK2B,SAAOP,EAAI,MAExBlB,KAAKK,QAAQS,KAAK,UAAWD,GAC7Bb,KAAKK,QAAQS,KAAK,QAASD,GAC3Bb,KAAKK,QAAQqB,QAEjB,CAEAC,IAAAA,CAAKC,GACH5B,KAAKC,GAAG0B,KAAKC,EACf,CAEAC,KAAAA,CAAMZ,EAAe,IAAMa,GACrB9B,KAAKG,OAAOgB,aAAanB,KAAKG,OAClCH,KAAKK,QAAQqB,QACb1B,KAAKC,GAAG4B,MAAMZ,EAAMa,EACtB,CAEAlB,gBAAAA,CAAiBC,EAAekB,GAC9B,OAAW/B,KAACK,QAAQ2B,GAAGnB,EAAOkB,EAChC,CAEA,cAAIE,GACF,YAAYhC,GAAGgC,UACjB"}
@@ -0,0 +1,2 @@
1
+ import{Emitter as t}from"@lowerdeck/emitter";function e(t){var e=function(t){if("object"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,"string");if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==typeof e?e:e+""}var i=/*#__PURE__*/function(){function i(e,i){var r;void 0===i&&(i={}),this.ws=void 0,this.attempts=0,this.timer=null,this.url=void 0,this.opts=void 0,this.maxAttempts=void 0,this.emitter=new t,this.url=e,this.opts=i,this.maxAttempts=null!=(r=i.maxAttempts)?r:Infinity,this.open()}var r,n,o=i.prototype;return o.open=function(){var t,e=this;this.ws=new WebSocket(this.url,null!=(t=this.opts.protocols)?t:[]),this.ws.addEventListener("open",function(t){e.attempts=0,e.emitter.emit("open",t)}),this.ws.addEventListener("close",function(t){e.reconnect(t)}),this.ws.addEventListener("error",function(t){"ECONNREFUSED"===(null==t?void 0:t.code)?e.reconnect(t):e.emitter.emit("error",t)}),this.ws.addEventListener("message",function(t){e.emitter.emit("message",t)})},o.reconnect=function(t){var e,i=this;this.timer&&clearTimeout(this.timer),this.attempts++<this.maxAttempts?this.timer=setTimeout(function(){null==i.opts.onReconnect||i.opts.onReconnect(t),i.open()},null!=(e=this.opts.timeout)?e:1e3):(this.emitter.emit("maximum",t),this.emitter.emit("close",t),this.emitter.clear())},o.send=function(t){this.ws.send(t)},o.close=function(t,e){void 0===t&&(t=1e3),this.timer&&clearTimeout(this.timer),this.emitter.clear(),this.ws.close(t,e)},o.addEventListener=function(t,e){return this.emitter.on(t,e)},r=i,(n=[{key:"readyState",get:function(){return this.ws.readyState}}])&&function(t,i){for(var r=0;r<i.length;r++){var n=i[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,e(n.key),n)}}(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r}();export{i as ReconnectingWebSocketClient};
2
+ //# sourceMappingURL=index.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.module.js","sources":["../src/index.ts"],"sourcesContent":["import { Emitter } from '@lowerdeck/emitter';\n\n// Highly inspired by https://github.com/lukeed/sockette/blob/master/src/index.js\n\ninterface ReconnectingWebSocketOptions {\n protocols?: string | string[];\n maxAttempts?: number;\n timeout?: number;\n\n onReconnect?: (event: Event) => void;\n}\n\nexport class ReconnectingWebSocketClient {\n private ws!: WebSocket;\n private attempts = 0;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private readonly url: string;\n private readonly opts: ReconnectingWebSocketOptions;\n private readonly maxAttempts: number;\n private emitter = new Emitter<any>();\n\n constructor(url: string, opts: ReconnectingWebSocketOptions = {}) {\n this.url = url;\n this.opts = opts;\n this.maxAttempts = opts.maxAttempts ?? Infinity;\n\n this.open();\n }\n\n private open(): void {\n this.ws = new WebSocket(this.url, this.opts.protocols ?? []);\n\n this.ws.addEventListener('open', event => {\n this.attempts = 0;\n this.emitter.emit('open', event);\n });\n\n this.ws.addEventListener('close', event => {\n // let shouldReconnect = ![1000, 1001, 1005].includes(event.code);\n // if (shouldReconnect) {\n this.reconnect(event);\n // } else {\n // this.emitter.emit('close', event);\n // }\n });\n\n this.ws.addEventListener('error', event => {\n let error = event as any;\n if (error?.code === 'ECONNREFUSED') {\n this.reconnect(event);\n } else {\n this.emitter.emit('error', event);\n }\n });\n\n this.ws.addEventListener('message', event => {\n this.emitter.emit('message', event);\n });\n }\n\n private reconnect(event: Event): void {\n if (this.timer) clearTimeout(this.timer);\n\n if (this.attempts++ < this.maxAttempts) {\n this.timer = setTimeout(() => {\n this.opts.onReconnect?.(event);\n this.open();\n }, this.opts.timeout ?? 1000);\n } else {\n this.emitter.emit('maximum', event);\n this.emitter.emit('close', event);\n this.emitter.clear();\n }\n }\n\n send(data: string | ArrayBufferLike): void {\n this.ws.send(data);\n }\n\n close(code: number = 1000, reason?: string): void {\n if (this.timer) clearTimeout(this.timer);\n this.emitter.clear();\n this.ws.close(code, reason);\n }\n\n addEventListener(event: string, callback: (data: any) => void) {\n return this.emitter.on(event, callback);\n }\n\n get readyState() {\n return this.ws.readyState;\n }\n}\n"],"names":["ReconnectingWebSocketClient","url","opts","_opts$maxAttempts","this","ws","attempts","timer","maxAttempts","emitter","Emitter","Infinity","open","_proto","prototype","_this$opts$protocols","_this","WebSocket","protocols","addEventListener","event","emit","reconnect","code","_this2","_this$opts$timeout","clearTimeout","setTimeout","onReconnect","timeout","clear","send","data","close","reason","callback","on","key","get","readyState"],"mappings":"4UAYa,IAAAA,eASX,WAAA,SAAAA,EAAYC,EAAaC,GAAuCC,IAAAA,OAAvCD,IAAAA,IAAAA,EAAqC,CAAE,GAAAE,KARxDC,QAAE,EAAAD,KACFE,SAAW,EACXC,KAAAA,MAA8C,KACrCN,KAAAA,SACAC,EAAAA,KAAAA,UACAM,EAAAA,KAAAA,iBACTC,EAAAA,KAAAA,QAAU,IAAIC,EAGpBN,KAAKH,IAAMA,EACXG,KAAKF,KAAOA,EACZE,KAAKI,YAA8BL,OAAnBA,EAAGD,EAAKM,aAAWL,EAAIQ,SAEvCP,KAAKQ,MACP,CAAC,QAAAC,EAAAb,EAAAc,UA4DA,OA5DAD,EAEOD,KAAA,WAAIG,IAAAA,EAAAC,EACVZ,KAAAA,KAAKC,GAAK,IAAIY,UAAUb,KAAKH,IAAwBc,OAArBA,EAAEX,KAAKF,KAAKgB,WAASH,EAAI,IAEzDX,KAAKC,GAAGc,iBAAiB,OAAQ,SAAAC,GAC/BJ,EAAKV,SAAW,EAChBU,EAAKP,QAAQY,KAAK,OAAQD,EAC5B,GAEAhB,KAAKC,GAAGc,iBAAiB,QAAS,SAAAC,GAGhCJ,EAAKM,UAAUF,EAIjB,GAEAhB,KAAKC,GAAGc,iBAAiB,QAAS,SAAAC,GAEZ,kBAAX,MADGA,OACH,EADGA,EACDG,MACTP,EAAKM,UAAUF,GAEfJ,EAAKP,QAAQY,KAAK,QAASD,EAE/B,GAEAhB,KAAKC,GAAGc,iBAAiB,UAAW,SAAAC,GAClCJ,EAAKP,QAAQY,KAAK,UAAWD,EAC/B,EACF,EAACP,EAEOS,UAAA,SAAUF,GAAYI,IAGYC,EAHZD,EAC5BpB,KAAIA,KAAKG,OAAOmB,aAAatB,KAAKG,OAE9BH,KAAKE,WAAaF,KAAKI,YACzBJ,KAAKG,MAAQoB,WAAW,WACtBH,MAAAA,EAAKtB,KAAK0B,aAAVJ,EAAKtB,KAAK0B,YAAcR,GACxBI,EAAKZ,MACP,EAAoB,OAAnBa,EAAErB,KAAKF,KAAK2B,SAAOJ,EAAI,MAExBrB,KAAKK,QAAQY,KAAK,UAAWD,GAC7BhB,KAAKK,QAAQY,KAAK,QAASD,GAC3BhB,KAAKK,QAAQqB,QAEjB,EAACjB,EAEDkB,KAAA,SAAKC,GACH5B,KAAKC,GAAG0B,KAAKC,EACf,EAACnB,EAEDoB,MAAA,SAAMV,EAAqBW,QAArBX,IAAAA,IAAAA,EAAe,KACfnB,KAAKG,OAAOmB,aAAatB,KAAKG,OAClCH,KAAKK,QAAQqB,QACb1B,KAAKC,GAAG4B,MAAMV,EAAMW,EACtB,EAACrB,EAEDM,iBAAA,SAAiBC,EAAee,GAC9B,OAAO/B,KAAKK,QAAQ2B,GAAGhB,EAAOe,EAChC,IAACnC,KAAA,CAAA,CAAAqC,IAAA,aAAAC,IAED,WACE,OAAOlC,KAAKC,GAAGkC,UACjB,iPAAC,CAtED"}
@@ -0,0 +1,2 @@
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@lowerdeck/emitter")):"function"==typeof define&&define.amd?define(["exports","@lowerdeck/emitter"],e):e((t||self).websocketClient={},t.emitter)}(this,function(t,e){function i(t){var e=function(t){if("object"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,"string");if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==typeof e?e:e+""}t.ReconnectingWebSocketClient=/*#__PURE__*/function(){function t(t,i){var n;void 0===i&&(i={}),this.ws=void 0,this.attempts=0,this.timer=null,this.url=void 0,this.opts=void 0,this.maxAttempts=void 0,this.emitter=new e.Emitter,this.url=t,this.opts=i,this.maxAttempts=null!=(n=i.maxAttempts)?n:Infinity,this.open()}var n,o,r=t.prototype;return r.open=function(){var t,e=this;this.ws=new WebSocket(this.url,null!=(t=this.opts.protocols)?t:[]),this.ws.addEventListener("open",function(t){e.attempts=0,e.emitter.emit("open",t)}),this.ws.addEventListener("close",function(t){e.reconnect(t)}),this.ws.addEventListener("error",function(t){"ECONNREFUSED"===(null==t?void 0:t.code)?e.reconnect(t):e.emitter.emit("error",t)}),this.ws.addEventListener("message",function(t){e.emitter.emit("message",t)})},r.reconnect=function(t){var e,i=this;this.timer&&clearTimeout(this.timer),this.attempts++<this.maxAttempts?this.timer=setTimeout(function(){null==i.opts.onReconnect||i.opts.onReconnect(t),i.open()},null!=(e=this.opts.timeout)?e:1e3):(this.emitter.emit("maximum",t),this.emitter.emit("close",t),this.emitter.clear())},r.send=function(t){this.ws.send(t)},r.close=function(t,e){void 0===t&&(t=1e3),this.timer&&clearTimeout(this.timer),this.emitter.clear(),this.ws.close(t,e)},r.addEventListener=function(t,e){return this.emitter.on(t,e)},n=t,(o=[{key:"readyState",get:function(){return this.ws.readyState}}])&&function(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,i(o.key),o)}}(n.prototype,o),Object.defineProperty(n,"prototype",{writable:!1}),n}()});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/index.ts"],"sourcesContent":["import { Emitter } from '@lowerdeck/emitter';\n\n// Highly inspired by https://github.com/lukeed/sockette/blob/master/src/index.js\n\ninterface ReconnectingWebSocketOptions {\n protocols?: string | string[];\n maxAttempts?: number;\n timeout?: number;\n\n onReconnect?: (event: Event) => void;\n}\n\nexport class ReconnectingWebSocketClient {\n private ws!: WebSocket;\n private attempts = 0;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private readonly url: string;\n private readonly opts: ReconnectingWebSocketOptions;\n private readonly maxAttempts: number;\n private emitter = new Emitter<any>();\n\n constructor(url: string, opts: ReconnectingWebSocketOptions = {}) {\n this.url = url;\n this.opts = opts;\n this.maxAttempts = opts.maxAttempts ?? Infinity;\n\n this.open();\n }\n\n private open(): void {\n this.ws = new WebSocket(this.url, this.opts.protocols ?? []);\n\n this.ws.addEventListener('open', event => {\n this.attempts = 0;\n this.emitter.emit('open', event);\n });\n\n this.ws.addEventListener('close', event => {\n // let shouldReconnect = ![1000, 1001, 1005].includes(event.code);\n // if (shouldReconnect) {\n this.reconnect(event);\n // } else {\n // this.emitter.emit('close', event);\n // }\n });\n\n this.ws.addEventListener('error', event => {\n let error = event as any;\n if (error?.code === 'ECONNREFUSED') {\n this.reconnect(event);\n } else {\n this.emitter.emit('error', event);\n }\n });\n\n this.ws.addEventListener('message', event => {\n this.emitter.emit('message', event);\n });\n }\n\n private reconnect(event: Event): void {\n if (this.timer) clearTimeout(this.timer);\n\n if (this.attempts++ < this.maxAttempts) {\n this.timer = setTimeout(() => {\n this.opts.onReconnect?.(event);\n this.open();\n }, this.opts.timeout ?? 1000);\n } else {\n this.emitter.emit('maximum', event);\n this.emitter.emit('close', event);\n this.emitter.clear();\n }\n }\n\n send(data: string | ArrayBufferLike): void {\n this.ws.send(data);\n }\n\n close(code: number = 1000, reason?: string): void {\n if (this.timer) clearTimeout(this.timer);\n this.emitter.clear();\n this.ws.close(code, reason);\n }\n\n addEventListener(event: string, callback: (data: any) => void) {\n return this.emitter.on(event, callback);\n }\n\n get readyState() {\n return this.ws.readyState;\n }\n}\n"],"names":["ReconnectingWebSocketClient","url","opts","_opts$maxAttempts","this","ws","attempts","timer","maxAttempts","emitter","Emitter","Infinity","open","_proto","prototype","_this$opts$protocols","_this","WebSocket","protocols","addEventListener","event","emit","reconnect","code","_this2","_this$opts$timeout","clearTimeout","setTimeout","onReconnect","timeout","clear","send","data","close","reason","callback","on","key","get","readyState"],"mappings":"knBAqBE,WAAA,SAAAA,EAAYC,EAAaC,GAAuCC,IAAAA,OAAvCD,IAAAA,IAAAA,EAAqC,CAAE,GAAAE,KARxDC,QAAE,EAAAD,KACFE,SAAW,EACXC,KAAAA,MAA8C,KACrCN,KAAAA,SACAC,EAAAA,KAAAA,UACAM,EAAAA,KAAAA,iBACTC,EAAAA,KAAAA,QAAU,IAAIC,EAAcA,QAGlCN,KAAKH,IAAMA,EACXG,KAAKF,KAAOA,EACZE,KAAKI,YAA8BL,OAAnBA,EAAGD,EAAKM,aAAWL,EAAIQ,SAEvCP,KAAKQ,MACP,CAAC,QAAAC,EAAAb,EAAAc,UA4DA,OA5DAD,EAEOD,KAAA,WAAIG,IAAAA,EAAAC,EACVZ,KAAAA,KAAKC,GAAK,IAAIY,UAAUb,KAAKH,IAAwBc,OAArBA,EAAEX,KAAKF,KAAKgB,WAASH,EAAI,IAEzDX,KAAKC,GAAGc,iBAAiB,OAAQ,SAAAC,GAC/BJ,EAAKV,SAAW,EAChBU,EAAKP,QAAQY,KAAK,OAAQD,EAC5B,GAEAhB,KAAKC,GAAGc,iBAAiB,QAAS,SAAAC,GAGhCJ,EAAKM,UAAUF,EAIjB,GAEAhB,KAAKC,GAAGc,iBAAiB,QAAS,SAAAC,GAEZ,kBAAX,MADGA,OACH,EADGA,EACDG,MACTP,EAAKM,UAAUF,GAEfJ,EAAKP,QAAQY,KAAK,QAASD,EAE/B,GAEAhB,KAAKC,GAAGc,iBAAiB,UAAW,SAAAC,GAClCJ,EAAKP,QAAQY,KAAK,UAAWD,EAC/B,EACF,EAACP,EAEOS,UAAA,SAAUF,GAAYI,IAGYC,EAHZD,EAC5BpB,KAAIA,KAAKG,OAAOmB,aAAatB,KAAKG,OAE9BH,KAAKE,WAAaF,KAAKI,YACzBJ,KAAKG,MAAQoB,WAAW,WACtBH,MAAAA,EAAKtB,KAAK0B,aAAVJ,EAAKtB,KAAK0B,YAAcR,GACxBI,EAAKZ,MACP,EAAoB,OAAnBa,EAAErB,KAAKF,KAAK2B,SAAOJ,EAAI,MAExBrB,KAAKK,QAAQY,KAAK,UAAWD,GAC7BhB,KAAKK,QAAQY,KAAK,QAASD,GAC3BhB,KAAKK,QAAQqB,QAEjB,EAACjB,EAEDkB,KAAA,SAAKC,GACH5B,KAAKC,GAAG0B,KAAKC,EACf,EAACnB,EAEDoB,MAAA,SAAMV,EAAqBW,QAArBX,IAAAA,IAAAA,EAAe,KACfnB,KAAKG,OAAOmB,aAAatB,KAAKG,OAClCH,KAAKK,QAAQqB,QACb1B,KAAKC,GAAG4B,MAAMV,EAAMW,EACtB,EAACrB,EAEDM,iBAAA,SAAiBC,EAAee,GAC9B,OAAO/B,KAAKK,QAAQ2B,GAAGhB,EAAOe,EAChC,IAACnC,KAAA,CAAA,CAAAqC,IAAA,aAAAC,IAED,WACE,OAAOlC,KAAKC,GAAGkC,UACjB,iPAAC,CAtED"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@lowerdeck/websocket-client",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "author": "Tobias Herber",
8
+ "license": "Apache 2",
9
+ "type": "module",
10
+ "source": "src/index.ts",
11
+ "exports": {
12
+ "require": "./dist/index.cjs",
13
+ "default": "./dist/index.modern.js"
14
+ },
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.module.js",
17
+ "types": "dist/index.d.ts",
18
+ "unpkg": "./dist/index.umd.js",
19
+ "scripts": {
20
+ "test": "vitest run --passWithNoTests",
21
+ "lint": "prettier src/**/*.ts --check",
22
+ "build": "microbundle"
23
+ },
24
+ "dependencies": {
25
+ "@lowerdeck/emitter": "^1.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "microbundle": "^0.15.1",
29
+ "@lowerdeck/tsconfig": "^1.0.0",
30
+ "typescript": "^5.8.3",
31
+ "vitest": "^3.1.2"
32
+ }
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { Emitter } from '@lowerdeck/emitter';
2
+
3
+ // Highly inspired by https://github.com/lukeed/sockette/blob/master/src/index.js
4
+
5
+ interface ReconnectingWebSocketOptions {
6
+ protocols?: string | string[];
7
+ maxAttempts?: number;
8
+ timeout?: number;
9
+
10
+ onReconnect?: (event: Event) => void;
11
+ }
12
+
13
+ export class ReconnectingWebSocketClient {
14
+ private ws!: WebSocket;
15
+ private attempts = 0;
16
+ private timer: ReturnType<typeof setTimeout> | null = null;
17
+ private readonly url: string;
18
+ private readonly opts: ReconnectingWebSocketOptions;
19
+ private readonly maxAttempts: number;
20
+ private emitter = new Emitter<any>();
21
+
22
+ constructor(url: string, opts: ReconnectingWebSocketOptions = {}) {
23
+ this.url = url;
24
+ this.opts = opts;
25
+ this.maxAttempts = opts.maxAttempts ?? Infinity;
26
+
27
+ this.open();
28
+ }
29
+
30
+ private open(): void {
31
+ this.ws = new WebSocket(this.url, this.opts.protocols ?? []);
32
+
33
+ this.ws.addEventListener('open', event => {
34
+ this.attempts = 0;
35
+ this.emitter.emit('open', event);
36
+ });
37
+
38
+ this.ws.addEventListener('close', event => {
39
+ // let shouldReconnect = ![1000, 1001, 1005].includes(event.code);
40
+ // if (shouldReconnect) {
41
+ this.reconnect(event);
42
+ // } else {
43
+ // this.emitter.emit('close', event);
44
+ // }
45
+ });
46
+
47
+ this.ws.addEventListener('error', event => {
48
+ let error = event as any;
49
+ if (error?.code === 'ECONNREFUSED') {
50
+ this.reconnect(event);
51
+ } else {
52
+ this.emitter.emit('error', event);
53
+ }
54
+ });
55
+
56
+ this.ws.addEventListener('message', event => {
57
+ this.emitter.emit('message', event);
58
+ });
59
+ }
60
+
61
+ private reconnect(event: Event): void {
62
+ if (this.timer) clearTimeout(this.timer);
63
+
64
+ if (this.attempts++ < this.maxAttempts) {
65
+ this.timer = setTimeout(() => {
66
+ this.opts.onReconnect?.(event);
67
+ this.open();
68
+ }, this.opts.timeout ?? 1000);
69
+ } else {
70
+ this.emitter.emit('maximum', event);
71
+ this.emitter.emit('close', event);
72
+ this.emitter.clear();
73
+ }
74
+ }
75
+
76
+ send(data: string | ArrayBufferLike): void {
77
+ this.ws.send(data);
78
+ }
79
+
80
+ close(code: number = 1000, reason?: string): void {
81
+ if (this.timer) clearTimeout(this.timer);
82
+ this.emitter.clear();
83
+ this.ws.close(code, reason);
84
+ }
85
+
86
+ addEventListener(event: string, callback: (data: any) => void) {
87
+ return this.emitter.on(event, callback);
88
+ }
89
+
90
+ get readyState() {
91
+ return this.ws.readyState;
92
+ }
93
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "@lowerdeck/tsconfig/base.json",
4
+ "exclude": [
5
+ "dist"
6
+ ],
7
+ "include": [
8
+ "src"
9
+ ],
10
+ "compilerOptions": {
11
+ "outDir": "dist"
12
+ }
13
+ }