wspromisify 2.9.3 → 2.10.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.
package/README.md CHANGED
@@ -11,7 +11,7 @@ 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, streams and different server-side implementations are coming.
14
+ Large data support (chunking), plugins and different server-side implementations are coming.
15
15
  To see a Node.js server-side part example, please, take a look on test/mock in github repo.
16
16
 
17
17
 
@@ -21,6 +21,7 @@ Features (almost all are tunable via constructor config below.)
21
21
  - ES-module and commonjs built-in.
22
22
  - Types (d.ts) included.
23
23
  - Automatically reconnects.
24
+ - Streams are supported. For example, you can stream your AI response.
24
25
  - Supports existent native WebSocket or ws-like implementation (ws npm package) via `socket` property.
25
26
  - And provide your own socket instance via socket config prop.
26
27
  - Any id and data keys to negotiate with your back-end.
@@ -71,7 +72,7 @@ Default constructor config is
71
72
  socket: null,
72
73
  // You can set your own middleware here.
73
74
  adapter: ((host, protocols) => new WebSocket(host, protocols)),
74
- // You can replace original serialisation to your own or even binary stuff.
75
+ // You can replace original serialisation to your own or even binary stuff. Eg MessagePack or CBOR.
75
76
  encode: (message_id, message_data, config) => data,
76
77
  // You can replace original deserialisation to your own or even
77
78
  // making the message object from binary data.
@@ -106,14 +107,16 @@ Methods:
106
107
  ready()
107
108
  // sends any type of message and returns a Promise.
108
109
  send(message),
110
+ // Streams as async generator, resolving in chunks.
111
+ // The server must send the same id for chunks then add done: true in the last one.
112
+ *stream(message),
109
113
  // .addEventListener with optional predicate that works after reconnections.
110
114
  on(event_name, handler, predicate = (WebSocketEvent) => true),
111
115
  // Closes the connection and free up memory. Returns Promise that it has been done.
112
116
  close()
113
-
114
117
  ```
115
118
 
116
- Example:
119
+ Example (more in `tests` dir in the repo):
117
120
  ```javascript
118
121
 
119
122
  import WSP from 'wspromisify' // or const WSP = require('wspromisify') in Node.
package/dist/bundle.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";const e=Symbol("Placeholder"),t=t=>{let n=0;for(const s of t)s!==e&&n++;return n},n=(t,n)=>{const s=t.length,o=t.slice(),i=n.length;let r=i,l=0;for(;r&&l<s;l++)o[l]===e&&(o[l]=n[i-r],r--);for(l=s;r;l++,r--)o[l]=n[i-r];return o},s=(e,o,i)=>{const r=e.length-o.length-t(i);if(r<1)return e(...n(o,i));{const t=(...t)=>s(e,n(o,i),t);return t.$args_left=r,t}},o=e=>(...n)=>e.length>t(n)?s(e,[],n):e(...n);function i(t){return function(n,s){const o=n===e,i=arguments.length;if(1===i&&o)throw new Error("Senseless placeholder usage.");return i>1?o?(t=>function(n){return n===e?t:t(n)})((e=>t(e,s))):t(n,s):e=>t(n,e)}}function r(e){return o(e)}const l=void 0,c=1/0,a=e=>typeof e,u=e=>null===e,h={u:"U",b:"B",n:"N",s:"S",f:"F"},f=Symbol(),d=e=>{const t=a(e);return"object"===t?u(e)?"Null":e.constructor.name:h[t[0]]+t.slice(1)},g=e=>e.length,p=i(((e,t)=>e===t)),m=i(((e,t)=>{const n=d(e);if(p(n,d(t))&&(p(n,"Object")||p(n,"Array"))){if(u(e)||u(t))return p(e,t);if(p(e,t))return!0;for(const n of[e,t])for(const s in n)if(!(p(n,t)&&s in e||p(n,e)&&s in t&&m(e[s],t[s])))return!1;return!0}return p(e,t)})),y=i(((e,t)=>(t.push(e),t))),w=r(((e,t,n)=>n.reduce(e,t))),_=o(((e,t,n,s)=>e(s)?t(s):n(s))),b=(...t)=>(...n)=>{let s,o=!0;for(let i=g(t)-1;i>-1;i--)o?(o=!1,s=t[i](...n)):s=s===e?t[i]():t[i](s);return s},S=i(((e,t)=>t[e])),k=r(((e,t,n)=>n.slice(e,(e=>"number"==a(e))(t)?t:c))),P=S(0);k(1,c);const v=i(((e,t)=>t.find(e))),E=e=>()=>e,N=i(((e,t)=>t.split(e))),q=E(!0),A=E(!1),W=i(((e,t)=>w(((t,n)=>v((t=>e(n,t)),t)?t:y(n,t)),[],t)))(m),$=r(((e,t,n)=>g(t)?(e=>u(e)||(e=>e===l)(e))(n)?e:b((s=>s in n?$(e,k(1,c,t),n[s]):e),P)(t):n));$(l),b(_(m(f),A,q),$(f));const j=i(((e,t)=>t.map(e))),{floor:z}=Math,C="0123456789abcdefghijklmnopqrstuvwxyz",Q=b((e=>Object.fromEntries(e)),j(((e,t)=>[e,t])),N(""));class O{abc;abclen;c2pos;standard;setABC(e){if(!b(m(g(t=e)),g,W,N(""))(t))throw new Error("Not all chars are unique!");var t;this.abc=e,this.abclen=e.length,this.standard=C.startsWith(e),this.c2pos=Q(e)}zip(e){const{abc:t,abclen:n}=this;let s="";for(;e>0;)s=t[e%n]+s,e=z(e/n);return s||"0"}unzip(e){const{standard:t,abclen:n,c2pos:s}=this;if(t)return parseInt(e,n);const o=e.length;let i=0;for(let t=0;t<o;t++)i+=s[e[t]]*n**(o-t-1);return i}constructor(e){this.setABC(e||C+"ABCDEFGHIJKLMNOPQRSTUVWXYZ")}}const R=new O;R.setABC.bind(R),R.zip.bind(R),R.unzip.bind(R);const T=(()=>{try{return WebSocket||null}catch{return null}})(),x=(e,t,n)=>e.addEventListener(t,n),B=(e,t)=>setTimeout(t,e),I={data_type:"json",log:()=>null,timer:!1,url:"localhost",timeout:1400,reconnect:2,reconnection_attempts:1/0,max_idle_time:1/0,lazy:!1,socket:null,adapter:(e,t)=>new WebSocket(e,t),encode:(e,t,{server:n})=>JSON.stringify({[n.id_key]:e,[n.data_key]:t}),decode:e=>JSON.parse(e),protocols:[],pipes:[],server:{id_key:"id",data_key:"data"},ping:{interval:55,content:{}}},M=Symbol("Placeholder"),D=e=>{let t=0;for(const n of e)n!==M&&t++;return t},L=(e,t)=>{const n=e.length,s=e.slice(),o=t.length;let i=o,r=0;for(;i&&r<n;r++)s[r]===M&&(s[r]=t[o-i],i--);for(r=n;i;r++,i--)s[r]=t[o-i];return s},U=(e,t,n)=>{const s=e.length-t.length-D(n);if(s<1)return e(...L(t,n));{const o=(...s)=>U(e,L(t,n),s);return o.$args_left=s,o}},F=e=>(...t)=>e.length>D(t)?U(e,[],t):e(...t);function J(e){return function(t,n){const s=t===M,o=arguments.length;if(1===o&&s)throw new Error("Senseless placeholder usage.");return o>1?s?(e=>function(t){return t===M?e:e(t)})((t=>e(t,n))):e(t,n):n=>e(t,n)}}function G(e){return F(e)}const H=/^(.*?)(8|16|32|64)(Clamped)?Array$/,K=void 0,V=1/0,X=e=>typeof e,Y=e=>null===e,Z=e=>"number"==X(e);const ee=e=>Y(e)||(e=>e===K)(e),te={u:"U",b:"B",n:"N",s:"S",f:"F"},ne=Symbol(),se=e=>{const t=X(e);return"object"===t?Y(e)?"Null":e.constructor.name:te[t[0]]+t.slice(1)},oe=J(((e,t)=>se(t)===e)),ie=e=>e.length,re=J(((e,t)=>e===t)),le=J(((e,t)=>{const n=se(e);if(re(n,se(t))&&(re(n,"Object")||re(n,"Array")||(e=>H.test(e))(n))){if(Y(e)||Y(t))return re(e,t);if(re(e,t))return!0;for(const n of[e,t])for(const s in n)if(!(re(n,t)&&s in e||re(n,e)&&s in t&&le(e[s],t[s])))return!1;return!0}return re(e,t)})),ce=J(((e,t)=>(t.push(e),t))),ae=G(((e,t,n)=>n.reduce(e,t))),ue=J(((e,t)=>{const n=(e=>Array.isArray(e))(t);let s,o;n&&(s=0,o=[]);for(let s in t)e(t[s],s)||(n?o.push(+s):delete t[s]);if(n)for(const e of o)t.splice(e-s++,1);return t})),he=F(((e,t,n,s)=>e(s)?t(s):n(s))),fe=(...e)=>(...t)=>{let n,s=!0;for(let o=ie(e)-1;o>-1;o--)s?(s=!1,n=e[o](...t)):n=n===M?e[o]():e[o](n);return n},de=J(((e,t)=>t[e])),ge=G(((e,t,n)=>n.slice(e,Z(t)?t:V))),pe=de(0);ge(1,V);const me=J(((e,t)=>t.find(e))),ye=e=>()=>e,we=ye(!0),_e=ye(!1),be=J(((e,t)=>t(...e))),Se=e=>(...t)=>{const n=e(...t),s=function(e){return"function"===X(e)}(n);return!s||s&&n.$args_left<=0?(e=>!e)(n):Se(n)},ke=J(((e,t)=>ae(((t,n)=>me((t=>e(n,t)),t)?t:ce(n,t)),[],t)));ke(le);const Pe=e=>{let t,n=!1;return function(...s){return n?t:(n=!0,t=e(...s))}},ve=G(((e,t,n)=>ie(t)?ee(n)?e:fe((s=>s in n?ve(e,ge(1,V,t),n[s]):e),pe)(t):n));ve(K),fe(he(le(ne),_e,we),ve(ne));const Ee=G(((e,t,n)=>t(n)&&e(n))),Ne=Se,{random:qe}=Math,Ae=new O,We=be([]),$e=Ee(oe("Number"),Ne(isNaN)),je={_is_ping:!0},ze=e=>e&&clearTimeout(e),Ce=e=>{const t=Ae.zip(2147483637*qe()|0);return t in e?Ce(e):t};module.exports=class{ws=null;intentionally_closed=!1;reconnect_timeout=null;queue={};onReadyQueue=[];onCloseQueue=[];handlers={open:[],close:[],message:[],error:[],timeout:[]};config={};ping_timer=null;idle_timer=null;get opened(){return 1===this.ws?.readyState}init_flush(){ue(_e,this.queue)}call(e,...t){for(const n of this.handlers[e])n(...t)}log(e,t=null,n=null){const{config:s}=this;null===n?s.timer?s.log(e,null,t):s.log(e,t):s.log(e,n,t)}resetPing(){const{config:{ping:e},ping_timer:t}=this;e&&(ee(t)||clearTimeout(t),this.ping_timer=B(1e3*e.interval,(async()=>{const{ping_timer:t,opened:n}=this;n?(await this.send(e.content,je),this.resetPing()):clearTimeout(t)})))}resetIdle(){const{config:{max_idle_time:e},idle_timer:t}=this;e!==1/0&&(ee(t)||clearTimeout(t),this.idle_timer=B(1e3*e,(()=>this.opened&&this.close())))}initSocket(e){const{queue:t,config:n}=this;this.ws=e,this.onReadyQueue.forEach((e=>e())),this.onReadyQueue.splice(0);const{id_key:s,data_key:o}=n.server;this.call("open",e);for(const n in t)e.send(t[n].msg);null!==this.reconnect_timeout&&(clearInterval(this.reconnect_timeout),this.reconnect_timeout=null),this.resetPing(),this.resetIdle(),x(e,"close",(async(...e)=>{this.log("close"),this.ws=null,this.onCloseQueue.forEach(We),this.onCloseQueue.splice(0),this.call("close",...e);let{reconnect:t,reconnection_attempts:s}=n;if($e(t)){const e=async()=>{if(this.intentionally_closed||!s)return;s--,this.log("reconnect"),ee(this.ws)||(this.ws.close(),this.ws=null);const n=await this.connect();ee(n)||(this.reconnect_timeout=setTimeout(e,1e3*t))};e()}})),x(e,"message",(e=>{try{const t=n.decode(e.data);if(this.call("message",{...e,data:t}),t[s]){const e=this.queue[t[s]];if(e){const n=e.sent_time?Date.now()-e.sent_time:null;this.log("message",t[o],n),e.ff(t[o])}}}catch(t){console.error(t,`WSP: Decode error. Got: ${e.data}`)}this.resetPing()}))}opening=!1;connect(){return new Promise((e=>{if(this.opened||this.opening)return e(null);this.opening=!0;const t=this.config,n=t.socket||t.adapter(t.url,t.protocols);if(!n||n.readyState>1)return this.opening=!1,this.ws=null,this.log("error","ready() on closing or closed state! status 2."),e(2);const s=Pe((t=>{this.opening=!1,e(t)}));x(n,"error",Pe((e=>{this.ws=null,this.log("error","status 3. Err: "+e.message),this.call("error",e),s(3)}))),n.readyState?(this.initSocket(n),s(null)):x(n,"open",Pe((()=>{this.log("open"),this.initSocket(n),s(null)})))}))}get socket(){return this.ws}async ready(){return new Promise((e=>{this.config.lazy||this.opened?e():this.onReadyQueue.push(e)}))}on(e,t,n=we,s=!1){const o=e=>n(e)&&t(e);return s?x(this.ws,e,o):this.handlers[e].push(o),o}off(e,t,n=!1){if(n)return((e,t,n)=>e.removeEventListener(t,n))(this.ws,e,t);const s=this.handlers[e],o=s.indexOf(t);~o&&s.splice(o,1)}async close(){return new Promise(((e,t)=>{null===this.ws?t("WSP: closing a non-inited socket!"):(this.onCloseQueue.push((()=>{this.init_flush(),e(null)})),this.ws.close(),this.ws=null,this.intentionally_closed=!0)}))}open(){if(!this.opened)return this.intentionally_closed=!1,this.connect()}async prepareMessage(e,t={}){this.log(t._is_ping?"ping":"send",e);const{config:n,queue:s}=this,{pipes:o,server:{data_key:i}}=n,{top:r,_is_ping:l}=t,c=Ce(s);if("object"==typeof r&&r[i])throw new Error(`Attempting to set data key/token via ${t._is_ping?"ping":"send"}() options!`);for(const t of o)e=t(e);const[a,u]=await Promise.all([n.encode(c,e,n),this.connect()]);if(u)throw new Error("ERR while opening connection #"+u);this.opened&&(this.ws.send(a),this.resetPing(),l||this.resetIdle());const h=()=>delete this.queue[c];return{message_id:c,msg:a,timeout:t=>B(n.timeout,(()=>{c in s&&(this.call("timeout",e),t({"Websocket timeout expired":n.timeout,"for the message":e}),h())})),cleanup:h}}async send(e,t={}){const{message_id:n,msg:s,timeout:o,cleanup:i}=await this.prepareMessage(e,t),{queue:r,config:l}=this;return new Promise(((e,t)=>{const i=o(t);r[n]={msg:s,data_type:l.data_type,sent_time:l.timer?Date.now():null,ff(t){ze(i),e(t)}}})).finally(i)}async*stream(e,t={}){const{message_id:n,msg:s,timeout:o,cleanup:i}=await this.prepareMessage(e,t),{queue:r,config:l}=this;let c,a=!1,u=null;for(r[n]={msg:s,ff:e=>{c(e),e?.done&&(i(),a=!0)},data_type:l.data_type,sent_time:l.timer?Date.now():null};!a;)yield await new Promise(((e,t)=>{u=o(t),c=e})).finally((()=>{ze(u),u=null}))}constructor(e={}){this.config=(e=>{if(null===T&&!("adapter"in e))throw new Error("\n This platform has no native WebSocket implementation.\n Please use 'ws' package as an adapter.\n See https://github.com/houd1ni/WebsocketPromisify/issues/23\n ");const t=Object.assign({},I,e),n=t.url;if("/"==n[0])try{const e=location.protocol.includes("s:")?"wss":"ws";t.url=`${e}://${location.hostname}:${location.port}${n}`}catch(e){throw new Error("WSP: URL starting with / in non-browser environment!")}return t})(e),this.config.lazy||this.connect()}};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const t=Symbol("Placeholder"),e=e=>{let n=0;for(const s of e)s!==t&&n++;return n},n=(e,n)=>{const s=e.length,i=e.slice(),o=n.length;let r=o,l=0;for(;r&&l<s;l++)i[l]===t&&(i[l]=n[o-r],r--);for(l=s;r;l++,r--)i[l]=n[o-r];return i},s=(t,i,o)=>{const r=t.length-i.length-e(o);if(r<1)return t(...n(i,o));{const e=(...e)=>s(t,n(i,o),e);return e.$args_left=r,e}},i=t=>(...n)=>t.length>e(n)?s(t,[],n):t(...n);function o(e){return function(n,...s){return s.length>0?n===t?(e=>function(n){return n===t?e:e(n)})((t=>e(t,s[0]))):e(n,s[0]):t=>e(n,t)}}function r(t){return i(t)}const l=t=>t.length,c=/^(.*?)(8|16|32|64)(Clamped)?Array$/,u=void 0,a=1/0,h=t=>typeof t,d=t=>null===t,f=t=>"number"==h(t);const p=t=>d(t)||(t=>t===u)(t),g={u:"U",b:"B",n:"N",s:"S",f:"F"},m=Symbol(),y=t=>{const e=h(t);return"object"===e?d(t)?"Null":t.constructor.name:g[e[0]]+e.slice(1)},_=o(((t,e)=>y(e)===t)),w=o(((t,e)=>t===e)),b=o(((t,e)=>{const n=y(t);if(w(n,y(e))&&(w(n,"Object")||w(n,"Array")||(t=>c.test(t))(n))){if(d(t)||d(e))return w(t,e);if(w(t,e))return!0;for(const n of[t,e])for(const s in n)if(!(w(n,e)&&s in t||w(n,t)&&s in e&&b(t[s],e[s])))return!1;return!0}return w(t,e)})),k=o(((t,e)=>(e.push(t),e))),S=r(((t,e,n)=>n.reduce(t,e))),P=o(((t,e)=>{const n=(t=>Array.isArray(t))(e);let s,i;n&&(s=0,i=[]);for(let s in e)t(e[s],s)||(n?i.push(+s):delete e[s]);if(n)for(const t of i)e.splice(t-s++,1);return e})),v=i(((t,e,n,s)=>t(s)?e(s):n(s))),E=(...e)=>(...n)=>{let s,i=!0;for(let o=l(e)-1;o>-1;o--)i?(i=!1,s=e[o](...n)):s=s===t?e[o]():e[o](s);return s},A=o(((t,e)=>e[t])),C=r(((t,e,n)=>n.slice(t,f(e)?e:a))),j=A(0);C(1,a);const N=o(((t,e)=>e.find(t))),W=t=>()=>t,$=o(((t,e)=>(t(e),e))),q=W(!0),O=W(!1),x=o(((t,e)=>e(...t))),z=t=>(...e)=>{const n=t(...e),s=function(t){return"function"===h(t)}(n);return s&&n.$args_left?z(n):(t=>!t)(n)},Q=o(((t,e)=>S(((e,n)=>N((e=>t(n,e)),e)?e:k(n,e)),[],e)));Q(b);const T=t=>{let e,n=!1;return function(...s){return n?e:(n=!0,e=t(...s))}},R=(t,e,n)=>l(e)?p(n)?t:E((s=>s in n?R(t,C(1,a,e),n[s]):t),j)(e):n,B=r(R);B(u),E(v(b(m),O,q),B(m));const M=r(((t,e,n)=>e(n)&&t(n))),I=z,D=Symbol("Placeholder"),L=t=>{let e=0;for(const n of t)n!==D&&e++;return e},U=(t,e)=>{const n=t.length,s=t.slice(),i=e.length;let o=i,r=0;for(;o&&r<n;r++)s[r]===D&&(s[r]=e[i-o],o--);for(r=n;o;r++,o--)s[r]=e[i-o];return s},F=(t,e,n)=>{const s=t.length-e.length-L(n);if(s<1)return t(...U(e,n));{const i=(...s)=>F(t,U(e,n),s);return i.$args_left=s,i}},J=t=>(...e)=>t.length>L(e)?F(t,[],e):t(...e);function G(t){return function(e,...n){return n.length>0?e===D?(t=>function(e){return e===D?t:t(e)})((e=>t(e,n[0]))):t(e,n[0]):n=>t(e,n)}}function H(t){return J(t)}const K=t=>t.length,V=/^(.*?)(8|16|32|64)(Clamped)?Array$/,X=void 0,Y=1/0,Z=t=>typeof t,tt=t=>null===t,et={u:"U",b:"B",n:"N",s:"S",f:"F"},nt=Symbol(),st=t=>{const e=Z(t);return"object"===e?tt(t)?"Null":t.constructor.name:et[e[0]]+e.slice(1)},it=G(((t,e)=>st(e)===t)),ot=G(((t,e)=>t===e)),rt=G(((t,e)=>{const n=st(t);if(ot(n,st(e))&&(ot(n,"Object")||ot(n,"Array")||(s=n,V.test(s)))){if(tt(t)||tt(e))return ot(t,e);if(ot(t,e))return!0;for(const n of[t,e])for(const s in n)if(!(ot(n,e)&&s in t||ot(n,t)&&s in e&&rt(t[s],e[s])))return!1;return!0}var s;return ot(t,e)})),lt=G(((t,e)=>(e.push(t),e))),ct=H(((t,e,n)=>n.reduce(t,e))),ut=J(((t,e,n,s)=>t(s)?e(s):n(s))),at=H(((t,e,n)=>ut(t,e,yt,n))),ht=(...t)=>(...e)=>{let n,s=!0;for(let i=K(t)-1;i>-1;i--)s?(s=!1,n=t[i](...e)):n=n===D?t[i]():t[i](n);return n},dt=G(((t,e)=>e[t])),ft=H(((t,e,n)=>n.slice(t,(t=>"number"==Z(t))(e)?e:Y))),pt=dt(0);ft(1,Y);const gt=G(((t,e)=>e.find(t))),mt=t=>()=>t,yt=t=>t,_t=G(((t,e)=>e.split(t))),wt=mt(!0),bt=mt(!1),kt=G(((t,e)=>ct(((e,n)=>gt((e=>t(n,e)),e)?e:lt(n,e)),[],e)))(rt),St=(t,e,n)=>K(e)?(t=>tt(t)||(t=>t===X)(t))(n)?t:ht((s=>s in n?St(t,ft(1,Y,e),n[s]):t),pt)(e):n,Pt=H(St);Pt(X),ht(ut(rt(nt),bt,wt),Pt(nt));const vt=G(((t,e)=>e.map(t))),{floor:Et}=Math,At="0123456789abcdefghijklmnopqrstuvwxyz",Ct=it("String"),jt=at(Ct,_t("")),Nt=ht((t=>Object.fromEntries(t)),vt(((t,e)=>[t,e])),jt);class Wt{is_str;delim;abc;abclen;c2pos;standard;setABC(t,e=""){if(this.is_str=Ct(t),this.delim=e,!ht(ot(K(n=t)),K,kt,jt)(n))throw new Error("Not all chars are unique!");var n;this.abc=t,this.abclen=K(t),this.standard=!!this.is_str&&At.startsWith(t),this.c2pos=Nt(t)}zip(t){const{abc:e,abclen:n,delim:s}=this;let i="",o=!0;for(;t>0;)i=e[t%n]+(o?"":s)+i,t=Et(t/n),o=!1;return i||"0"}unzip(t){const{standard:e,abclen:n,c2pos:s,delim:i,is_str:o}=this;if("0"===t)return 0;if(e)return parseInt(t,n);const r=o?t:t.split(i),l=K(r);let c=0;for(let t=0;t<l;t++)c+=s[r[t]]*n**(l-t-1);return c}constructor(t,e){e?this.setABC(t,e):this.setABC(t||At+"ABCDEFGHIJKLMNOPQRSTUVWXYZ")}}const $t=new Wt;$t.setABC.bind($t),$t.zip.bind($t),$t.unzip.bind($t);const qt=(()=>{try{return WebSocket||null}catch{return null}})(),Ot=(t,e,n)=>t.addEventListener(e,n),xt=(t,e)=>setTimeout(e,t),zt={data_type:"json",log:()=>null,timer:!1,url:"localhost",timeout:1400,reconnect:2,reconnection_attempts:1/0,max_idle_time:1/0,lazy:!1,socket:null,adapter:(t,e)=>new WebSocket(t,e),encode:(t,e,{server:n})=>JSON.stringify({[n.id_key]:t,[n.data_key]:e}),decode:t=>JSON.parse(t),protocols:[],pipes:[],server:{id_key:"id",data_key:"data"},ping:{interval:55,content:{}}},{random:Qt}=Math,Tt=new Wt,Rt=x([]),Bt=M(_("Number"),I(isNaN)),Mt={_is_ping:!0},It=t=>t&&clearTimeout(t),Dt=t=>{const e=Tt.zip(2147483637*Qt()|0);return e in t?Dt(t):e};class Lt{ws=null;intentionally_closed=!1;reconnect_timeout=null;queue={};onReadyQueue=[];onCloseQueue=[];handlers={open:[],close:[],message:[],error:[],timeout:[]};config={};ping_timer=null;idle_timer=null;get opened(){return 1===this.ws?.readyState}init_flush(){P(O,this.queue)}call(t,...e){for(const n of this.handlers[t])n(...e)}log(t,e=null,n=null){const{config:s}=this;setTimeout((()=>{null===n?s.timer?s.log(t,null,e):s.log(t,e):s.log(t,n,e)}))}resetPing(){const{config:{ping:t},ping_timer:e}=this;t&&(p(e)||clearTimeout(e),this.ping_timer=xt(1e3*t.interval,(async()=>{const{ping_timer:e,opened:n}=this;n?(await this.send(t.content,Mt),this.resetPing()):clearTimeout(e)})))}resetIdle(){const{config:{max_idle_time:t},idle_timer:e}=this;t!==1/0&&(p(e)||clearTimeout(e),this.idle_timer=xt(1e3*t,(()=>this.opened&&this.close())))}initSocket(t){const{queue:e,config:n}=this;this.ws=t,this.onReadyQueue.forEach((t=>t())),this.onReadyQueue.splice(0);const{id_key:s,data_key:i}=n.server;this.call("open",t);for(const n in e)t.send(e[n].msg);null!==this.reconnect_timeout&&(clearInterval(this.reconnect_timeout),this.reconnect_timeout=null),this.resetPing(),this.resetIdle(),Ot(t,"close",(async(...t)=>{this.log("close"),this.ws=null,this.onCloseQueue.forEach(Rt),this.onCloseQueue.splice(0),this.call("close",...t);let{reconnect:e,reconnection_attempts:s}=n;if(Bt(e)){const t=async()=>{if(this.intentionally_closed||!s)return;s--,this.log("reconnect"),p(this.ws)||(this.ws.close(),this.ws=null);const n=await this.connect();p(n)||(this.reconnect_timeout=setTimeout(t,1e3*e))};t()}})),Ot(t,"message",(t=>{try{const e=n.decode(t.data);this.call("message",{...t,data:e});let o=!1;if(_("Object",e)&&s in e){const t=this.queue[e[s]];if(t){const n=t.sent_time?Date.now()-t.sent_time:null;this.log("message",e[i],n),t.ff(e[i]),o=!0}}o||this.log("message-ext",e)}catch(e){console.error(e,`WSP: Decode error. Got: ${t.data}`)}this.resetPing()}))}opening=!1;connect(){return new Promise((t=>{if(this.opened||this.opening)return t(null);this.opening=!0;const e=this.config,n=e.socket||e.adapter(e.url,e.protocols);if(!n||n.readyState>1)return this.opening=!1,this.ws=null,this.log("error","ready() on closing or closed state! status 2."),t(2);const s=T((e=>{this.opening=!1,t(e)}));Ot(n,"error",T((t=>{this.ws=null,this.log("error","status 3. Err: "+t.message),this.call("error",t),s(3)}))),n.readyState?(this.initSocket(n),s(null)):Ot(n,"open",T((()=>{this.log("open"),this.initSocket(n),s(null)})))}))}get socket(){return this.ws}async ready(){return new Promise((t=>{this.config.lazy||this.opened?t():this.onReadyQueue.push(t)}))}on(t,e,n=q,s=!1){const i=t=>n(t)&&e(t);return s?Ot(this.ws,t,i):this.handlers[t].push(i),i}off(t,e,n=!1){if(n)return((t,e,n)=>t.removeEventListener(e,n))(this.ws,t,e);const s=this.handlers[t],i=s.indexOf(e);~i&&s.splice(i,1)}async close(){return new Promise(((t,e)=>{null===this.ws?e("WSP: closing a non-inited socket!"):(this.onCloseQueue.push((()=>{this.init_flush(),t(null)})),this.ws.close(),this.ws=null,this.intentionally_closed=!0)}))}open(){if(!this.opened)return this.intentionally_closed=!1,this.connect()}async prepareMessage(t,e={}){this.log(e._is_ping?"ping":"send",t);const{config:n,queue:s}=this,{pipes:i,server:{data_key:o}}=n,{top:r,_is_ping:l}=e,c=Dt(s);if("object"==typeof r&&r[o])throw new Error(`Attempting to set data key/token via ${e._is_ping?"ping":"send"}() options!`);for(const e of i)t=e(t);const[u,a]=await Promise.all([n.encode(c,t,n),this.connect()]);if(a)throw new Error("ERR while opening connection #"+a);const h=$((()=>delete this.queue[c]));return this.opened&&(xt(0,(()=>this.ws.send(u))),this.resetPing(),l||this.resetIdle()),{id:c,msg:u,timeout:e=>xt(n.timeout,(()=>{c in s&&(this.call("timeout",t),e({"Websocket timeout expired":n.timeout,"for the message":t}),h())})),cleanup:h}}async send(t,e={}){const{id:n,msg:s,timeout:i,cleanup:o}=await this.prepareMessage(t,e),{queue:r,config:l}=this;return new Promise(((t,e)=>{const o=i(e);r[n]={msg:s,data_type:l.data_type,sent_time:l.timer?Date.now():null,ff(e){It(o),t(e)}}})).finally(o)}async*stream(t,e={}){const{id:n,msg:s,timeout:i,cleanup:o}=await this.prepareMessage(t,e),{queue:r,config:l}=this;let c,u=!1,a=null;for(r[n]={msg:s,ff:t=>{c(t),t?.done&&(o(),u=!0)},data_type:l.data_type,sent_time:l.timer?Date.now():null};!u;)yield await new Promise(((t,e)=>{a=i(e),c=t})).catch((t=>o(t))).finally((()=>{It(a),a=null}))}constructor(t={}){this.config=(t=>{if(null===qt&&!("adapter"in t))throw new Error("\n This platform has no native WebSocket implementation.\n Please use 'ws' package as an adapter.\n See https://github.com/houd1ni/WebsocketPromisify/issues/23\n ");const e=Object.assign({},zt,t),n=e.url;if("/"==n[0])try{const t=location.protocol.includes("s:")?"wss":"ws";e.url=`${t}://${location.hostname}:${location.port}${n}`}catch(t){throw new Error("WSP: URL starting with / in non-browser environment!")}return e})(t),this.config.lazy||this.connect()}}exports.WebSocketClient=Lt,exports.default=Lt;
package/dist/bundle.d.ts CHANGED
@@ -4,7 +4,7 @@ declare namespace wsc {
4
4
  interface DataObject {
5
5
  [key: string]: any;
6
6
  }
7
- type WSEvent = "open" | "message" | "close" | "error" | "timeout";
7
+ type WSEvent = "open" | "message" | "message-ext" | "close" | "error" | "timeout" | "reconnect" | "send" | "ping";
8
8
  /** Minimal socket-like interface. */
9
9
  interface Socket {
10
10
  readyState: number;
@@ -58,7 +58,7 @@ declare namespace wsc {
58
58
  sent_time: number | null;
59
59
  }
60
60
  }
61
- declare class WebSocketClient {
61
+ export declare class WebSocketClient {
62
62
  private ws;
63
63
  private intentionally_closed;
64
64
  private reconnect_timeout;
package/dist/bundle.mjs CHANGED
@@ -1 +1 @@
1
- const e=Symbol("Placeholder"),t=t=>{let n=0;for(const s of t)s!==e&&n++;return n},n=(t,n)=>{const s=t.length,o=t.slice(),i=n.length;let r=i,l=0;for(;r&&l<s;l++)o[l]===e&&(o[l]=n[i-r],r--);for(l=s;r;l++,r--)o[l]=n[i-r];return o},s=(e,o,i)=>{const r=e.length-o.length-t(i);if(r<1)return e(...n(o,i));{const t=(...t)=>s(e,n(o,i),t);return t.$args_left=r,t}},o=e=>(...n)=>e.length>t(n)?s(e,[],n):e(...n);function i(t){return function(n,s){const o=n===e,i=arguments.length;if(1===i&&o)throw new Error("Senseless placeholder usage.");return i>1?o?(t=>function(n){return n===e?t:t(n)})((e=>t(e,s))):t(n,s):e=>t(n,e)}}function r(e){return o(e)}const l=void 0,c=1/0,a=e=>typeof e,u=e=>null===e,h={u:"U",b:"B",n:"N",s:"S",f:"F"},f=Symbol(),d=e=>{const t=a(e);return"object"===t?u(e)?"Null":e.constructor.name:h[t[0]]+t.slice(1)},g=e=>e.length,p=i(((e,t)=>e===t)),m=i(((e,t)=>{const n=d(e);if(p(n,d(t))&&(p(n,"Object")||p(n,"Array"))){if(u(e)||u(t))return p(e,t);if(p(e,t))return!0;for(const n of[e,t])for(const s in n)if(!(p(n,t)&&s in e||p(n,e)&&s in t&&m(e[s],t[s])))return!1;return!0}return p(e,t)})),y=i(((e,t)=>(t.push(e),t))),w=r(((e,t,n)=>n.reduce(e,t))),_=o(((e,t,n,s)=>e(s)?t(s):n(s))),b=(...t)=>(...n)=>{let s,o=!0;for(let i=g(t)-1;i>-1;i--)o?(o=!1,s=t[i](...n)):s=s===e?t[i]():t[i](s);return s},S=i(((e,t)=>t[e])),k=r(((e,t,n)=>n.slice(e,(e=>"number"==a(e))(t)?t:c))),P=S(0);k(1,c);const v=i(((e,t)=>t.find(e))),E=e=>()=>e,N=i(((e,t)=>t.split(e))),q=E(!0),A=E(!1),W=i(((e,t)=>w(((t,n)=>v((t=>e(n,t)),t)?t:y(n,t)),[],t)))(m),$=r(((e,t,n)=>g(t)?(e=>u(e)||(e=>e===l)(e))(n)?e:b((s=>s in n?$(e,k(1,c,t),n[s]):e),P)(t):n));$(l),b(_(m(f),A,q),$(f));const j=i(((e,t)=>t.map(e))),{floor:z}=Math,C="0123456789abcdefghijklmnopqrstuvwxyz",Q=b((e=>Object.fromEntries(e)),j(((e,t)=>[e,t])),N(""));class O{abc;abclen;c2pos;standard;setABC(e){if(!b(m(g(t=e)),g,W,N(""))(t))throw new Error("Not all chars are unique!");var t;this.abc=e,this.abclen=e.length,this.standard=C.startsWith(e),this.c2pos=Q(e)}zip(e){const{abc:t,abclen:n}=this;let s="";for(;e>0;)s=t[e%n]+s,e=z(e/n);return s||"0"}unzip(e){const{standard:t,abclen:n,c2pos:s}=this;if(t)return parseInt(e,n);const o=e.length;let i=0;for(let t=0;t<o;t++)i+=s[e[t]]*n**(o-t-1);return i}constructor(e){this.setABC(e||C+"ABCDEFGHIJKLMNOPQRSTUVWXYZ")}}const R=new O;R.setABC.bind(R),R.zip.bind(R),R.unzip.bind(R);const T=(()=>{try{return WebSocket||null}catch{return null}})(),x=(e,t,n)=>e.addEventListener(t,n),B=(e,t)=>setTimeout(t,e),I={data_type:"json",log:()=>null,timer:!1,url:"localhost",timeout:1400,reconnect:2,reconnection_attempts:1/0,max_idle_time:1/0,lazy:!1,socket:null,adapter:(e,t)=>new WebSocket(e,t),encode:(e,t,{server:n})=>JSON.stringify({[n.id_key]:e,[n.data_key]:t}),decode:e=>JSON.parse(e),protocols:[],pipes:[],server:{id_key:"id",data_key:"data"},ping:{interval:55,content:{}}},M=Symbol("Placeholder"),D=e=>{let t=0;for(const n of e)n!==M&&t++;return t},L=(e,t)=>{const n=e.length,s=e.slice(),o=t.length;let i=o,r=0;for(;i&&r<n;r++)s[r]===M&&(s[r]=t[o-i],i--);for(r=n;i;r++,i--)s[r]=t[o-i];return s},U=(e,t,n)=>{const s=e.length-t.length-D(n);if(s<1)return e(...L(t,n));{const o=(...s)=>U(e,L(t,n),s);return o.$args_left=s,o}},F=e=>(...t)=>e.length>D(t)?U(e,[],t):e(...t);function J(e){return function(t,n){const s=t===M,o=arguments.length;if(1===o&&s)throw new Error("Senseless placeholder usage.");return o>1?s?(e=>function(t){return t===M?e:e(t)})((t=>e(t,n))):e(t,n):n=>e(t,n)}}function G(e){return F(e)}const H=/^(.*?)(8|16|32|64)(Clamped)?Array$/,K=void 0,V=1/0,X=e=>typeof e,Y=e=>null===e,Z=e=>"number"==X(e);const ee=e=>Y(e)||(e=>e===K)(e),te={u:"U",b:"B",n:"N",s:"S",f:"F"},ne=Symbol(),se=e=>{const t=X(e);return"object"===t?Y(e)?"Null":e.constructor.name:te[t[0]]+t.slice(1)},oe=J(((e,t)=>se(t)===e)),ie=e=>e.length,re=J(((e,t)=>e===t)),le=J(((e,t)=>{const n=se(e);if(re(n,se(t))&&(re(n,"Object")||re(n,"Array")||(e=>H.test(e))(n))){if(Y(e)||Y(t))return re(e,t);if(re(e,t))return!0;for(const n of[e,t])for(const s in n)if(!(re(n,t)&&s in e||re(n,e)&&s in t&&le(e[s],t[s])))return!1;return!0}return re(e,t)})),ce=J(((e,t)=>(t.push(e),t))),ae=G(((e,t,n)=>n.reduce(e,t))),ue=J(((e,t)=>{const n=(e=>Array.isArray(e))(t);let s,o;n&&(s=0,o=[]);for(let s in t)e(t[s],s)||(n?o.push(+s):delete t[s]);if(n)for(const e of o)t.splice(e-s++,1);return t})),he=F(((e,t,n,s)=>e(s)?t(s):n(s))),fe=(...e)=>(...t)=>{let n,s=!0;for(let o=ie(e)-1;o>-1;o--)s?(s=!1,n=e[o](...t)):n=n===M?e[o]():e[o](n);return n},de=J(((e,t)=>t[e])),ge=G(((e,t,n)=>n.slice(e,Z(t)?t:V))),pe=de(0);ge(1,V);const me=J(((e,t)=>t.find(e))),ye=e=>()=>e,we=ye(!0),_e=ye(!1),be=J(((e,t)=>t(...e))),Se=e=>(...t)=>{const n=e(...t),s=function(e){return"function"===X(e)}(n);return!s||s&&n.$args_left<=0?(e=>!e)(n):Se(n)},ke=J(((e,t)=>ae(((t,n)=>me((t=>e(n,t)),t)?t:ce(n,t)),[],t)));ke(le);const Pe=e=>{let t,n=!1;return function(...s){return n?t:(n=!0,t=e(...s))}},ve=G(((e,t,n)=>ie(t)?ee(n)?e:fe((s=>s in n?ve(e,ge(1,V,t),n[s]):e),pe)(t):n));ve(K),fe(he(le(ne),_e,we),ve(ne));const Ee=G(((e,t,n)=>t(n)&&e(n))),Ne=Se,{random:qe}=Math,Ae=new O,We=be([]),$e=Ee(oe("Number"),Ne(isNaN)),je={_is_ping:!0},ze=e=>e&&clearTimeout(e),Ce=e=>{const t=Ae.zip(2147483637*qe()|0);return t in e?Ce(e):t};class Qe{ws=null;intentionally_closed=!1;reconnect_timeout=null;queue={};onReadyQueue=[];onCloseQueue=[];handlers={open:[],close:[],message:[],error:[],timeout:[]};config={};ping_timer=null;idle_timer=null;get opened(){return 1===this.ws?.readyState}init_flush(){ue(_e,this.queue)}call(e,...t){for(const n of this.handlers[e])n(...t)}log(e,t=null,n=null){const{config:s}=this;null===n?s.timer?s.log(e,null,t):s.log(e,t):s.log(e,n,t)}resetPing(){const{config:{ping:e},ping_timer:t}=this;e&&(ee(t)||clearTimeout(t),this.ping_timer=B(1e3*e.interval,(async()=>{const{ping_timer:t,opened:n}=this;n?(await this.send(e.content,je),this.resetPing()):clearTimeout(t)})))}resetIdle(){const{config:{max_idle_time:e},idle_timer:t}=this;e!==1/0&&(ee(t)||clearTimeout(t),this.idle_timer=B(1e3*e,(()=>this.opened&&this.close())))}initSocket(e){const{queue:t,config:n}=this;this.ws=e,this.onReadyQueue.forEach((e=>e())),this.onReadyQueue.splice(0);const{id_key:s,data_key:o}=n.server;this.call("open",e);for(const n in t)e.send(t[n].msg);null!==this.reconnect_timeout&&(clearInterval(this.reconnect_timeout),this.reconnect_timeout=null),this.resetPing(),this.resetIdle(),x(e,"close",(async(...e)=>{this.log("close"),this.ws=null,this.onCloseQueue.forEach(We),this.onCloseQueue.splice(0),this.call("close",...e);let{reconnect:t,reconnection_attempts:s}=n;if($e(t)){const e=async()=>{if(this.intentionally_closed||!s)return;s--,this.log("reconnect"),ee(this.ws)||(this.ws.close(),this.ws=null);const n=await this.connect();ee(n)||(this.reconnect_timeout=setTimeout(e,1e3*t))};e()}})),x(e,"message",(e=>{try{const t=n.decode(e.data);if(this.call("message",{...e,data:t}),t[s]){const e=this.queue[t[s]];if(e){const n=e.sent_time?Date.now()-e.sent_time:null;this.log("message",t[o],n),e.ff(t[o])}}}catch(t){console.error(t,`WSP: Decode error. Got: ${e.data}`)}this.resetPing()}))}opening=!1;connect(){return new Promise((e=>{if(this.opened||this.opening)return e(null);this.opening=!0;const t=this.config,n=t.socket||t.adapter(t.url,t.protocols);if(!n||n.readyState>1)return this.opening=!1,this.ws=null,this.log("error","ready() on closing or closed state! status 2."),e(2);const s=Pe((t=>{this.opening=!1,e(t)}));x(n,"error",Pe((e=>{this.ws=null,this.log("error","status 3. Err: "+e.message),this.call("error",e),s(3)}))),n.readyState?(this.initSocket(n),s(null)):x(n,"open",Pe((()=>{this.log("open"),this.initSocket(n),s(null)})))}))}get socket(){return this.ws}async ready(){return new Promise((e=>{this.config.lazy||this.opened?e():this.onReadyQueue.push(e)}))}on(e,t,n=we,s=!1){const o=e=>n(e)&&t(e);return s?x(this.ws,e,o):this.handlers[e].push(o),o}off(e,t,n=!1){if(n)return((e,t,n)=>e.removeEventListener(t,n))(this.ws,e,t);const s=this.handlers[e],o=s.indexOf(t);~o&&s.splice(o,1)}async close(){return new Promise(((e,t)=>{null===this.ws?t("WSP: closing a non-inited socket!"):(this.onCloseQueue.push((()=>{this.init_flush(),e(null)})),this.ws.close(),this.ws=null,this.intentionally_closed=!0)}))}open(){if(!this.opened)return this.intentionally_closed=!1,this.connect()}async prepareMessage(e,t={}){this.log(t._is_ping?"ping":"send",e);const{config:n,queue:s}=this,{pipes:o,server:{data_key:i}}=n,{top:r,_is_ping:l}=t,c=Ce(s);if("object"==typeof r&&r[i])throw new Error(`Attempting to set data key/token via ${t._is_ping?"ping":"send"}() options!`);for(const t of o)e=t(e);const[a,u]=await Promise.all([n.encode(c,e,n),this.connect()]);if(u)throw new Error("ERR while opening connection #"+u);this.opened&&(this.ws.send(a),this.resetPing(),l||this.resetIdle());const h=()=>delete this.queue[c];return{message_id:c,msg:a,timeout:t=>B(n.timeout,(()=>{c in s&&(this.call("timeout",e),t({"Websocket timeout expired":n.timeout,"for the message":e}),h())})),cleanup:h}}async send(e,t={}){const{message_id:n,msg:s,timeout:o,cleanup:i}=await this.prepareMessage(e,t),{queue:r,config:l}=this;return new Promise(((e,t)=>{const i=o(t);r[n]={msg:s,data_type:l.data_type,sent_time:l.timer?Date.now():null,ff(t){ze(i),e(t)}}})).finally(i)}async*stream(e,t={}){const{message_id:n,msg:s,timeout:o,cleanup:i}=await this.prepareMessage(e,t),{queue:r,config:l}=this;let c,a=!1,u=null;for(r[n]={msg:s,ff:e=>{c(e),e?.done&&(i(),a=!0)},data_type:l.data_type,sent_time:l.timer?Date.now():null};!a;)yield await new Promise(((e,t)=>{u=o(t),c=e})).finally((()=>{ze(u),u=null}))}constructor(e={}){this.config=(e=>{if(null===T&&!("adapter"in e))throw new Error("\n This platform has no native WebSocket implementation.\n Please use 'ws' package as an adapter.\n See https://github.com/houd1ni/WebsocketPromisify/issues/23\n ");const t=Object.assign({},I,e),n=t.url;if("/"==n[0])try{const e=location.protocol.includes("s:")?"wss":"ws";t.url=`${e}://${location.hostname}:${location.port}${n}`}catch(e){throw new Error("WSP: URL starting with / in non-browser environment!")}return t})(e),this.config.lazy||this.connect()}}export{Qe as default};
1
+ const t=Symbol("Placeholder"),e=e=>{let n=0;for(const s of e)s!==t&&n++;return n},n=(e,n)=>{const s=e.length,i=e.slice(),o=n.length;let r=o,l=0;for(;r&&l<s;l++)i[l]===t&&(i[l]=n[o-r],r--);for(l=s;r;l++,r--)i[l]=n[o-r];return i},s=(t,i,o)=>{const r=t.length-i.length-e(o);if(r<1)return t(...n(i,o));{const e=(...e)=>s(t,n(i,o),e);return e.$args_left=r,e}},i=t=>(...n)=>t.length>e(n)?s(t,[],n):t(...n);function o(e){return function(n,...s){return s.length>0?n===t?(e=>function(n){return n===t?e:e(n)})((t=>e(t,s[0]))):e(n,s[0]):t=>e(n,t)}}function r(t){return i(t)}const l=t=>t.length,c=/^(.*?)(8|16|32|64)(Clamped)?Array$/,a=void 0,u=1/0,h=t=>typeof t,f=t=>null===t,d=t=>"number"==h(t);const p=t=>f(t)||(t=>t===a)(t),g={u:"U",b:"B",n:"N",s:"S",f:"F"},m=Symbol(),y=t=>{const e=h(t);return"object"===e?f(t)?"Null":t.constructor.name:g[e[0]]+e.slice(1)},_=o(((t,e)=>y(e)===t)),w=o(((t,e)=>t===e)),b=o(((t,e)=>{const n=y(t);if(w(n,y(e))&&(w(n,"Object")||w(n,"Array")||(t=>c.test(t))(n))){if(f(t)||f(e))return w(t,e);if(w(t,e))return!0;for(const n of[t,e])for(const s in n)if(!(w(n,e)&&s in t||w(n,t)&&s in e&&b(t[s],e[s])))return!1;return!0}return w(t,e)})),k=o(((t,e)=>(e.push(t),e))),S=r(((t,e,n)=>n.reduce(t,e))),P=o(((t,e)=>{const n=(t=>Array.isArray(t))(e);let s,i;n&&(s=0,i=[]);for(let s in e)t(e[s],s)||(n?i.push(+s):delete e[s]);if(n)for(const t of i)e.splice(t-s++,1);return e})),v=i(((t,e,n,s)=>t(s)?e(s):n(s))),E=(...e)=>(...n)=>{let s,i=!0;for(let o=l(e)-1;o>-1;o--)i?(i=!1,s=e[o](...n)):s=s===t?e[o]():e[o](s);return s},A=o(((t,e)=>e[t])),C=r(((t,e,n)=>n.slice(t,d(e)?e:u))),N=A(0);C(1,u);const $=o(((t,e)=>e.find(t))),j=t=>()=>t,q=o(((t,e)=>(t(e),e))),W=j(!0),z=j(!1),O=o(((t,e)=>e(...t))),Q=t=>(...e)=>{const n=t(...e),s=function(t){return"function"===h(t)}(n);return s&&n.$args_left?Q(n):(t=>!t)(n)},T=o(((t,e)=>S(((e,n)=>$((e=>t(n,e)),e)?e:k(n,e)),[],e)));T(b);const R=t=>{let e,n=!1;return function(...s){return n?e:(n=!0,e=t(...s))}},x=(t,e,n)=>l(e)?p(n)?t:E((s=>s in n?x(t,C(1,u,e),n[s]):t),N)(e):n,B=r(x);B(a),E(v(b(m),z,W),B(m));const I=r(((t,e,n)=>e(n)&&t(n))),M=Q,D=Symbol("Placeholder"),L=t=>{let e=0;for(const n of t)n!==D&&e++;return e},U=(t,e)=>{const n=t.length,s=t.slice(),i=e.length;let o=i,r=0;for(;o&&r<n;r++)s[r]===D&&(s[r]=e[i-o],o--);for(r=n;o;r++,o--)s[r]=e[i-o];return s},F=(t,e,n)=>{const s=t.length-e.length-L(n);if(s<1)return t(...U(e,n));{const i=(...s)=>F(t,U(e,n),s);return i.$args_left=s,i}},J=t=>(...e)=>t.length>L(e)?F(t,[],e):t(...e);function G(t){return function(e,...n){return n.length>0?e===D?(t=>function(e){return e===D?t:t(e)})((e=>t(e,n[0]))):t(e,n[0]):n=>t(e,n)}}function H(t){return J(t)}const K=t=>t.length,V=/^(.*?)(8|16|32|64)(Clamped)?Array$/,X=void 0,Y=1/0,Z=t=>typeof t,tt=t=>null===t,et={u:"U",b:"B",n:"N",s:"S",f:"F"},nt=Symbol(),st=t=>{const e=Z(t);return"object"===e?tt(t)?"Null":t.constructor.name:et[e[0]]+e.slice(1)},it=G(((t,e)=>st(e)===t)),ot=G(((t,e)=>t===e)),rt=G(((t,e)=>{const n=st(t);if(ot(n,st(e))&&(ot(n,"Object")||ot(n,"Array")||(s=n,V.test(s)))){if(tt(t)||tt(e))return ot(t,e);if(ot(t,e))return!0;for(const n of[t,e])for(const s in n)if(!(ot(n,e)&&s in t||ot(n,t)&&s in e&&rt(t[s],e[s])))return!1;return!0}var s;return ot(t,e)})),lt=G(((t,e)=>(e.push(t),e))),ct=H(((t,e,n)=>n.reduce(t,e))),at=J(((t,e,n,s)=>t(s)?e(s):n(s))),ut=H(((t,e,n)=>at(t,e,yt,n))),ht=(...t)=>(...e)=>{let n,s=!0;for(let i=K(t)-1;i>-1;i--)s?(s=!1,n=t[i](...e)):n=n===D?t[i]():t[i](n);return n},ft=G(((t,e)=>e[t])),dt=H(((t,e,n)=>n.slice(t,(t=>"number"==Z(t))(e)?e:Y))),pt=ft(0);dt(1,Y);const gt=G(((t,e)=>e.find(t))),mt=t=>()=>t,yt=t=>t,_t=G(((t,e)=>e.split(t))),wt=mt(!0),bt=mt(!1),kt=G(((t,e)=>ct(((e,n)=>gt((e=>t(n,e)),e)?e:lt(n,e)),[],e)))(rt),St=(t,e,n)=>K(e)?(t=>tt(t)||(t=>t===X)(t))(n)?t:ht((s=>s in n?St(t,dt(1,Y,e),n[s]):t),pt)(e):n,Pt=H(St);Pt(X),ht(at(rt(nt),bt,wt),Pt(nt));const vt=G(((t,e)=>e.map(t))),{floor:Et}=Math,At="0123456789abcdefghijklmnopqrstuvwxyz",Ct=it("String"),Nt=ut(Ct,_t("")),$t=ht((t=>Object.fromEntries(t)),vt(((t,e)=>[t,e])),Nt);class jt{is_str;delim;abc;abclen;c2pos;standard;setABC(t,e=""){if(this.is_str=Ct(t),this.delim=e,!ht(ot(K(n=t)),K,kt,Nt)(n))throw new Error("Not all chars are unique!");var n;this.abc=t,this.abclen=K(t),this.standard=!!this.is_str&&At.startsWith(t),this.c2pos=$t(t)}zip(t){const{abc:e,abclen:n,delim:s}=this;let i="",o=!0;for(;t>0;)i=e[t%n]+(o?"":s)+i,t=Et(t/n),o=!1;return i||"0"}unzip(t){const{standard:e,abclen:n,c2pos:s,delim:i,is_str:o}=this;if("0"===t)return 0;if(e)return parseInt(t,n);const r=o?t:t.split(i),l=K(r);let c=0;for(let t=0;t<l;t++)c+=s[r[t]]*n**(l-t-1);return c}constructor(t,e){e?this.setABC(t,e):this.setABC(t||At+"ABCDEFGHIJKLMNOPQRSTUVWXYZ")}}const qt=new jt;qt.setABC.bind(qt),qt.zip.bind(qt),qt.unzip.bind(qt);const Wt=(()=>{try{return WebSocket||null}catch{return null}})(),zt=(t,e,n)=>t.addEventListener(e,n),Ot=(t,e)=>setTimeout(e,t),Qt={data_type:"json",log:()=>null,timer:!1,url:"localhost",timeout:1400,reconnect:2,reconnection_attempts:1/0,max_idle_time:1/0,lazy:!1,socket:null,adapter:(t,e)=>new WebSocket(t,e),encode:(t,e,{server:n})=>JSON.stringify({[n.id_key]:t,[n.data_key]:e}),decode:t=>JSON.parse(t),protocols:[],pipes:[],server:{id_key:"id",data_key:"data"},ping:{interval:55,content:{}}},{random:Tt}=Math,Rt=new jt,xt=O([]),Bt=I(_("Number"),M(isNaN)),It={_is_ping:!0},Mt=t=>t&&clearTimeout(t),Dt=t=>{const e=Rt.zip(2147483637*Tt()|0);return e in t?Dt(t):e};class Lt{ws=null;intentionally_closed=!1;reconnect_timeout=null;queue={};onReadyQueue=[];onCloseQueue=[];handlers={open:[],close:[],message:[],error:[],timeout:[]};config={};ping_timer=null;idle_timer=null;get opened(){return 1===this.ws?.readyState}init_flush(){P(z,this.queue)}call(t,...e){for(const n of this.handlers[t])n(...e)}log(t,e=null,n=null){const{config:s}=this;setTimeout((()=>{null===n?s.timer?s.log(t,null,e):s.log(t,e):s.log(t,n,e)}))}resetPing(){const{config:{ping:t},ping_timer:e}=this;t&&(p(e)||clearTimeout(e),this.ping_timer=Ot(1e3*t.interval,(async()=>{const{ping_timer:e,opened:n}=this;n?(await this.send(t.content,It),this.resetPing()):clearTimeout(e)})))}resetIdle(){const{config:{max_idle_time:t},idle_timer:e}=this;t!==1/0&&(p(e)||clearTimeout(e),this.idle_timer=Ot(1e3*t,(()=>this.opened&&this.close())))}initSocket(t){const{queue:e,config:n}=this;this.ws=t,this.onReadyQueue.forEach((t=>t())),this.onReadyQueue.splice(0);const{id_key:s,data_key:i}=n.server;this.call("open",t);for(const n in e)t.send(e[n].msg);null!==this.reconnect_timeout&&(clearInterval(this.reconnect_timeout),this.reconnect_timeout=null),this.resetPing(),this.resetIdle(),zt(t,"close",(async(...t)=>{this.log("close"),this.ws=null,this.onCloseQueue.forEach(xt),this.onCloseQueue.splice(0),this.call("close",...t);let{reconnect:e,reconnection_attempts:s}=n;if(Bt(e)){const t=async()=>{if(this.intentionally_closed||!s)return;s--,this.log("reconnect"),p(this.ws)||(this.ws.close(),this.ws=null);const n=await this.connect();p(n)||(this.reconnect_timeout=setTimeout(t,1e3*e))};t()}})),zt(t,"message",(t=>{try{const e=n.decode(t.data);this.call("message",{...t,data:e});let o=!1;if(_("Object",e)&&s in e){const t=this.queue[e[s]];if(t){const n=t.sent_time?Date.now()-t.sent_time:null;this.log("message",e[i],n),t.ff(e[i]),o=!0}}o||this.log("message-ext",e)}catch(e){console.error(e,`WSP: Decode error. Got: ${t.data}`)}this.resetPing()}))}opening=!1;connect(){return new Promise((t=>{if(this.opened||this.opening)return t(null);this.opening=!0;const e=this.config,n=e.socket||e.adapter(e.url,e.protocols);if(!n||n.readyState>1)return this.opening=!1,this.ws=null,this.log("error","ready() on closing or closed state! status 2."),t(2);const s=R((e=>{this.opening=!1,t(e)}));zt(n,"error",R((t=>{this.ws=null,this.log("error","status 3. Err: "+t.message),this.call("error",t),s(3)}))),n.readyState?(this.initSocket(n),s(null)):zt(n,"open",R((()=>{this.log("open"),this.initSocket(n),s(null)})))}))}get socket(){return this.ws}async ready(){return new Promise((t=>{this.config.lazy||this.opened?t():this.onReadyQueue.push(t)}))}on(t,e,n=W,s=!1){const i=t=>n(t)&&e(t);return s?zt(this.ws,t,i):this.handlers[t].push(i),i}off(t,e,n=!1){if(n)return((t,e,n)=>t.removeEventListener(e,n))(this.ws,t,e);const s=this.handlers[t],i=s.indexOf(e);~i&&s.splice(i,1)}async close(){return new Promise(((t,e)=>{null===this.ws?e("WSP: closing a non-inited socket!"):(this.onCloseQueue.push((()=>{this.init_flush(),t(null)})),this.ws.close(),this.ws=null,this.intentionally_closed=!0)}))}open(){if(!this.opened)return this.intentionally_closed=!1,this.connect()}async prepareMessage(t,e={}){this.log(e._is_ping?"ping":"send",t);const{config:n,queue:s}=this,{pipes:i,server:{data_key:o}}=n,{top:r,_is_ping:l}=e,c=Dt(s);if("object"==typeof r&&r[o])throw new Error(`Attempting to set data key/token via ${e._is_ping?"ping":"send"}() options!`);for(const e of i)t=e(t);const[a,u]=await Promise.all([n.encode(c,t,n),this.connect()]);if(u)throw new Error("ERR while opening connection #"+u);const h=q((()=>delete this.queue[c]));return this.opened&&(Ot(0,(()=>this.ws.send(a))),this.resetPing(),l||this.resetIdle()),{id:c,msg:a,timeout:e=>Ot(n.timeout,(()=>{c in s&&(this.call("timeout",t),e({"Websocket timeout expired":n.timeout,"for the message":t}),h())})),cleanup:h}}async send(t,e={}){const{id:n,msg:s,timeout:i,cleanup:o}=await this.prepareMessage(t,e),{queue:r,config:l}=this;return new Promise(((t,e)=>{const o=i(e);r[n]={msg:s,data_type:l.data_type,sent_time:l.timer?Date.now():null,ff(e){Mt(o),t(e)}}})).finally(o)}async*stream(t,e={}){const{id:n,msg:s,timeout:i,cleanup:o}=await this.prepareMessage(t,e),{queue:r,config:l}=this;let c,a=!1,u=null;for(r[n]={msg:s,ff:t=>{c(t),t?.done&&(o(),a=!0)},data_type:l.data_type,sent_time:l.timer?Date.now():null};!a;)yield await new Promise(((t,e)=>{u=i(e),c=t})).catch((t=>o(t))).finally((()=>{Mt(u),u=null}))}constructor(t={}){this.config=(t=>{if(null===Wt&&!("adapter"in t))throw new Error("\n This platform has no native WebSocket implementation.\n Please use 'ws' package as an adapter.\n See https://github.com/houd1ni/WebsocketPromisify/issues/23\n ");const e=Object.assign({},Qt,t),n=e.url;if("/"==n[0])try{const t=location.protocol.includes("s:")?"wss":"ws";e.url=`${t}://${location.hostname}:${location.port}${n}`}catch(t){throw new Error("WSP: URL starting with / in non-browser environment!")}return e})(t),this.config.lazy||this.connect()}}export{Lt as WebSocketClient,Lt as default};
package/package.json CHANGED
@@ -1,79 +1,79 @@
1
- {
2
- "author": {
3
- "name": "Michael Akiliev"
4
- },
5
- "bugs": {
6
- "url": "https://github.com/houd1ni/WebsocketPromisify/issues"
7
- },
8
- "deprecated": false,
9
- "description": "Wraps your WebSockets into Promise-based class with full d.ts typings on client & server",
10
- "homepage": "https://github.com/houd1ni/WebsocketPromisify#readme",
11
- "keywords": [
12
- "WebSockets",
13
- "WS",
14
- "Promise",
15
- "Socket",
16
- "REST",
17
- "Ajax",
18
- "Easy",
19
- "realtime",
20
- "Middleware",
21
- "JSON",
22
- "Data",
23
- "transport",
24
- "API",
25
- "async"
26
- ],
27
- "license": "MIT",
28
- "name": "wspromisify",
29
- "repository": {
30
- "type": "git",
31
- "url": "git+https://github.com/houd1ni/WebsocketPromisify.git"
32
- },
33
- "scripts": {
34
- "lint": "tslint src/*.ts",
35
- "test": "tsx test/index",
36
- "test:report": "nyc npm test && nyc report --reporter=text-lcov > coverage.lcov && codecov",
37
- "gentypes": "dts-bundle-generator --no-check -o dist/bundle.d.ts src/WSC.ts",
38
- "dev": "cross-env NODE_ENV=development BUILD=es rollup -c",
39
- "prod:cjs": "cross-env NODE_ENV=production BUILD=cjs rollup -c",
40
- "prod:es": "cross-env NODE_ENV=production BUILD=es rollup -c",
41
- "prod": "npm run gentypes && npm run prod:es && npm run prod:cjs",
42
- "all": "npm run dev && npm run prod"
43
- },
44
- "version": "2.9.3",
45
- "type": "module",
46
- "exports": {
47
- ".": {
48
- "types": "./dist/bundle.d.ts",
49
- "import": "./dist/bundle.mjs",
50
- "require": "./dist/bundle.cjs"
51
- },
52
- "./src": "./src/*"
53
- },
54
- "devDependencies": {
55
- "@rollup/plugin-commonjs": "^29.0.0",
56
- "@rollup/plugin-node-resolve": "^16.0.3",
57
- "@rollup/plugin-replace": "^6.0.3",
58
- "@rollup/plugin-terser": "^0.4.4",
59
- "@types/express": "^5.0.6",
60
- "@types/node": "^25.0.3",
61
- "@types/ws": "^8.18.1",
62
- "codecov": "^3.8.3",
63
- "cross-env": "^10.1.0",
64
- "dts-bundle-generator": "^9.5.1",
65
- "nyc": "^17.1.0",
66
- "rollup": "^4.54.0",
67
- "rollup-plugin-typescript2": "^0.36.0",
68
- "ts-node": "^10.9.2",
69
- "tsx": "^4.21.0",
70
- "typescript": "^5.9.3",
71
- "uvu": "^0.5.6",
72
- "ws": "^8.18.3"
73
- },
74
- "types": "./dist/bundle.d.ts",
75
- "dependencies": {
76
- "pepka": "^1.6.10",
77
- "zipnum": "^2.0.0"
78
- }
79
- }
1
+ {
2
+ "author": {
3
+ "name": "Michael Akiliev"
4
+ },
5
+ "bugs": {
6
+ "url": "https://github.com/houd1ni/WebsocketPromisify/issues"
7
+ },
8
+ "deprecated": false,
9
+ "description": "Wraps your WebSockets into Promise-based class with full d.ts typings on client & server",
10
+ "homepage": "https://github.com/houd1ni/WebsocketPromisify#readme",
11
+ "keywords": [
12
+ "WebSockets",
13
+ "WS",
14
+ "Promise",
15
+ "Socket",
16
+ "REST",
17
+ "Ajax",
18
+ "Easy",
19
+ "realtime",
20
+ "Middleware",
21
+ "JSON",
22
+ "Data",
23
+ "transport",
24
+ "API",
25
+ "async"
26
+ ],
27
+ "license": "MIT",
28
+ "name": "wspromisify",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/houd1ni/WebsocketPromisify.git"
32
+ },
33
+ "scripts": {
34
+ "lint": "tslint src/*.ts",
35
+ "test": "tsx test/index",
36
+ "test:report": "nyc npm test && nyc report --reporter=text-lcov > coverage.lcov && codecov",
37
+ "gentypes": "dts-bundle-generator --no-check -o dist/bundle.d.ts src/WSC.ts",
38
+ "dev": "cross-env NODE_ENV=development BUILD=es rollup -c",
39
+ "prod:cjs": "cross-env NODE_ENV=production BUILD=cjs rollup -c",
40
+ "prod:es": "cross-env NODE_ENV=production BUILD=es rollup -c",
41
+ "prod": "npm run gentypes && npm run prod:es && npm run prod:cjs",
42
+ "all": "npm run dev && npm run prod"
43
+ },
44
+ "version": "2.10.0",
45
+ "type": "module",
46
+ "exports": {
47
+ ".": {
48
+ "types": "./dist/bundle.d.ts",
49
+ "import": "./dist/bundle.mjs",
50
+ "require": "./dist/bundle.cjs"
51
+ },
52
+ "./src": "./src/*"
53
+ },
54
+ "devDependencies": {
55
+ "@rollup/plugin-commonjs": "^29.0.2",
56
+ "@rollup/plugin-node-resolve": "^16.0.3",
57
+ "@rollup/plugin-replace": "^6.0.3",
58
+ "@rollup/plugin-terser": "^1.0.0",
59
+ "@types/express": "^5.0.6",
60
+ "@types/node": "^25.4.0",
61
+ "@types/ws": "^8.18.1",
62
+ "codecov": "^3.6.3",
63
+ "cross-env": "^10.1.0",
64
+ "dts-bundle-generator": "^9.5.1",
65
+ "nyc": "^18.0.0",
66
+ "rollup": "^4.59.0",
67
+ "rollup-plugin-typescript2": "^0.36.0",
68
+ "ts-node": "^10.9.2",
69
+ "tsx": "^4.21.0",
70
+ "typescript": "^5.9.3",
71
+ "uvu": "^0.5.6",
72
+ "ws": "^8.19.0"
73
+ },
74
+ "types": "./dist/bundle.d.ts",
75
+ "dependencies": {
76
+ "pepka": "^1.6.23",
77
+ "zipnum": "^2.1.3"
78
+ }
79
+ }
package/rollup.config.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import commonjs from '@rollup/plugin-commonjs'
2
2
  import resolve from '@rollup/plugin-node-resolve'
3
- import typescript from 'rollup-plugin-typescript2'
4
- import terser from '@rollup/plugin-terser'
5
3
  import replace from '@rollup/plugin-replace'
4
+ import terser from '@rollup/plugin-terser'
5
+ import typescript from 'rollup-plugin-typescript2'
6
6
  import tsc from 'typescript'
7
7
 
8
8
  export default {
@@ -32,9 +32,7 @@ export default {
32
32
  process.env.NODE_ENV!='development' && terser(),
33
33
  replace({
34
34
  preventAssignment: true,
35
- values: {
36
- 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
37
- }
35
+ values: { 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) }
38
36
  })
39
37
  ]
40
38
  }
package/src/WSC.ts CHANGED
@@ -1,8 +1,8 @@
1
- import './types'
1
+ import { AnyFunc, AnyObject, both, callWith, F, isNil, notf, once, qfilter, T, tap, typeIs } from 'pepka'
2
2
  import { Zipnum } from 'zipnum'
3
- import { add_event, rm_event, sett } from './utils'
4
3
  import { processConfig } from './config'
5
- import { AnyFunc, AnyObject, both, callWith, F, isNil, notf, once, qfilter, T, typeIs } from 'pepka'
4
+ import './types'
5
+ import { add_event, rm_event, sett } from './utils'
6
6
 
7
7
  const MAX_32 = 2**31 - 1
8
8
  const { random } = Math
@@ -25,7 +25,7 @@ const genid = (q: AnyObject) => {
25
25
  return id in q ? genid(q) : id
26
26
  }
27
27
 
28
- class WebSocketClient {
28
+ export class WebSocketClient {
29
29
  private ws: wsc.Socket|null = null
30
30
  private intentionally_closed = false
31
31
  private reconnect_timeout: NodeJS.Timeout|null = null
@@ -45,16 +45,16 @@ class WebSocketClient {
45
45
  private call(event_name: wsc.WSEvent, ...args: any[]) {
46
46
  for(const h of this.handlers[event_name]) h(...args)
47
47
  }
48
-
49
- private log(event: string, message: any = null, time: number|null = null): void {
48
+ private log(event: wsc.WSEvent, message: any = null, time: number|null = null): void {
50
49
  const {config} = this
51
- if(time === null)
52
- if(config.timer) config.log(event, null, message)
53
- else config.log(event, message)
54
- else
55
- config.log(event, time, message)
50
+ setTimeout(() => {
51
+ if(time === null)
52
+ if(config.timer) config.log(event, null, message)
53
+ else config.log(event, message)
54
+ else
55
+ config.log(event, time, message)
56
+ })
56
57
  }
57
-
58
58
  private resetPing() {
59
59
  const {config: {ping}, ping_timer} = this
60
60
  if(ping) {
@@ -70,6 +70,7 @@ class WebSocketClient {
70
70
  }
71
71
  }
72
72
 
73
+ // FIXME: Make some version where it could work faster (for streaming).
73
74
  private resetIdle() {
74
75
  const {config: {max_idle_time: time}, idle_timer} = this
75
76
  if(time!==Infinity) {
@@ -122,7 +123,8 @@ class WebSocketClient {
122
123
  try {
123
124
  const data = config.decode(e.data)
124
125
  this.call('message', {...e, data})
125
- if(data[id_key]) {
126
+ let internal = false
127
+ if(typeIs('Object', data) && id_key in data) {
126
128
  const q = this.queue[data[id_key]]
127
129
  if(q) {
128
130
  // Debug, Log.
@@ -130,15 +132,16 @@ class WebSocketClient {
130
132
  this.log('message', data[data_key], time)
131
133
  // Play.
132
134
  q.ff(data[data_key])
135
+ internal = true
133
136
  }
134
137
  }
138
+ if(!internal) this.log('message-ext', data)
135
139
  } catch (err) {
136
140
  console.error(err, `WSP: Decode error. Got: ${e.data}`)
137
141
  }
138
142
  this.resetPing()
139
143
  })
140
144
  }
141
-
142
145
  private opening = false
143
146
  private connect() { // returns status if won't open or null if ok.
144
147
  return new Promise<null|number>((ff) => {
@@ -176,8 +179,7 @@ class WebSocketClient {
176
179
  public get socket() { return this.ws }
177
180
  public async ready() {
178
181
  return new Promise<void>((ff) => {
179
- if(this.config.lazy) ff() // FIXME: (possibly) breaking change ?? At least minor ver bump with a notice!!!
180
- else if(this.opened) ff()
182
+ if(this.config.lazy || this.opened) ff() // FIXME: (possibly) breaking change ?? At least minor ver bump with a notice!!!
181
183
  else this.onReadyQueue.push(ff)
182
184
  })
183
185
  }
@@ -203,7 +205,6 @@ class WebSocketClient {
203
205
  const i = handlers.indexOf(handler)
204
206
  if(~i) handlers.splice(i, 1)
205
207
  }
206
-
207
208
  public async close(): wsc.AsyncErrCode {
208
209
  return new Promise((ff, rj) => {
209
210
  if(this.ws === null) {
@@ -219,17 +220,15 @@ class WebSocketClient {
219
220
  }
220
221
  })
221
222
  }
222
-
223
223
  public open() {
224
224
  if(!this.opened) {
225
225
  this.intentionally_closed = false
226
226
  return this.connect()
227
227
  }
228
228
  }
229
-
230
229
  // TODO: Сделать сэттер элементов конфигурации чтобы двигать таймауты.
231
230
  // И эвент, когда схема наша, а соответствующего элемента очереди не ма.
232
- // Или добавить флажок к эвенту 'message'.
231
+ // Или добавить флажок к эвенту 'message'.F
233
232
  // И событие 'line' со значением on: boolean. Критерии?
234
233
  private async prepareMessage<RequestDataType = any>(
235
234
  message_data: RequestDataType,
@@ -251,12 +250,7 @@ class WebSocketClient {
251
250
  this.connect()
252
251
  ])
253
252
  if(err) throw new Error('ERR while opening connection #'+err)
254
- if(this.opened) {
255
- this.ws!.send(msg)
256
- this.resetPing()
257
- if(!_is_ping) this.resetIdle()
258
- }
259
- const cleanup = () => delete this.queue[id]
253
+ const cleanup = tap(() => delete this.queue[id])
260
254
  const timeout = (rj: AnyFunc) => sett(config.timeout, () => {
261
255
  if(id in queue) {
262
256
  this.call('timeout', message_data)
@@ -264,9 +258,13 @@ class WebSocketClient {
264
258
  cleanup()
265
259
  }
266
260
  })
267
- return { message_id: id, msg, timeout, cleanup }
261
+ if(this.opened) {
262
+ sett(0, () => this.ws!.send(msg))
263
+ this.resetPing()
264
+ if(!_is_ping) this.resetIdle()
265
+ }
266
+ return { id, msg, timeout, cleanup }
268
267
  }
269
-
270
268
  /** .send(your_data) wraps request to server with {id: `hash`, data: `actually your data`},
271
269
  returns a Promise that will be rejected after a timeout or
272
270
  resolved if server returns the same signature: {id: `same_hash`, data: `response data`}.
@@ -275,12 +273,11 @@ class WebSocketClient {
275
273
  message_data: RequestDataType,
276
274
  opts = <wsc.SendOptions>{}
277
275
  ): Promise<ResponseDataType> {
278
- const {message_id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
276
+ const {id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
279
277
  const {queue, config} = this
280
-
281
278
  return new Promise<ResponseDataType>((ff, rj) => {
282
279
  const to = timeout(rj)
283
- queue[message_id] = {
280
+ queue[id] = {
284
281
  msg,
285
282
  data_type: config.data_type,
286
283
  sent_time: config.timer ? Date.now() : null,
@@ -291,15 +288,15 @@ class WebSocketClient {
291
288
  }
292
289
  }).finally(cleanup)
293
290
  }
294
-
291
+ // FIXME: rejects into ff somehow.
295
292
  public async *stream<RequestDataType = any, ResponseDataType = any>(
296
293
  message_data: RequestDataType,
297
294
  opts = <wsc.SendOptions>{}
298
295
  ): AsyncGenerator<ResponseDataType, void, unknown> {
299
- const {message_id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
296
+ const {id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
300
297
  const {queue, config} = this
301
298
  let done = false, fulfill: AnyFunc, to: NodeJS.Timeout|null = null
302
- queue[message_id] = {
299
+ queue[id] = {
303
300
  msg,
304
301
  ff: (msg: ResponseDataType&{done?: boolean}) => {
305
302
  fulfill(msg)
@@ -310,9 +307,8 @@ class WebSocketClient {
310
307
  }
311
308
  while(!done) yield await new Promise<ResponseDataType>((ff, rj) => {
312
309
  to=timeout(rj); fulfill=ff
313
- }).finally(() => {clearTO(to); to=null})
310
+ }).catch((e) => cleanup(e)).finally(() => {clearTO(to); to=null})
314
311
  }
315
-
316
312
  // TODO: Add .on handlers to config!
317
313
  constructor(user_config: wsc.UserConfig = {}) {
318
314
  this.config = processConfig(user_config)
package/src/types.ts CHANGED
@@ -4,7 +4,7 @@ declare namespace wsc {
4
4
  [key: string]: any
5
5
  }
6
6
 
7
- export type WSEvent = 'open' | 'message' | 'close' | 'error' | 'timeout'
7
+ export type WSEvent = 'open' | 'message' | 'message-ext' | 'close' | 'error' | 'timeout' | 'reconnect' | 'send' | 'ping'
8
8
 
9
9
  /** Minimal socket-like interface. */
10
10
  interface Socket {
package/test/mock/WS.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import WebSocket, { WebSocketServer } from 'ws'
3
- import {noop} from 'pepka'
3
+ import {add, compose, genBy, identity, noop, wait} from 'pepka'
4
4
 
5
5
  let server: WebSocketServer|null = null
6
6
 
@@ -8,7 +8,7 @@ const createServer = (port = 40510) => new Promise<WebSocketServer>((ff, rj) =>
8
8
  if(server) return rj('The server is already running!')
9
9
  server = new WebSocketServer({ port }, () => {
10
10
  server!.on('connection', (socket: WebSocket&{isAlive: boolean}) => {
11
- socket.on('message', (rawMessage: string) => {
11
+ socket.on('message', async (rawMessage: string) => {
12
12
  // console.log({rawMessage: rawMessage.toString()})
13
13
  const {id, data} = JSON.parse(rawMessage)
14
14
  let response = ''
@@ -21,23 +21,23 @@ const createServer = (port = 40510) => new Promise<WebSocketServer>((ff, rj) =>
21
21
  response = data
22
22
  } else if(data.stream) {
23
23
  // Handle streaming responses
24
- const chunks = data.chunks || [1, 2, 3] // Default to 3 chunks
25
- const delay = data.delay || 100 // Default delay between chunks
24
+ const chunks = genBy(compose(add(1), identity), 20) // Generate 20 chunks
25
+ // console.log(chunks)
26
+ const delay = 2 // 20ms delay between chunks for reliable delivery
26
27
 
27
28
  if(data.multi) {
28
29
  // Multi-chunk streaming
29
- chunks.forEach((chunk: any, index: number) => {
30
- setTimeout(() => {
31
- socket.send(JSON.stringify({
32
- id,
33
- data: {
34
- ...data,
35
- chunk: chunk,
36
- done: index === chunks.length - 1 // Last chunk gets done: true
37
- }
38
- }))
39
- }, index * delay)
40
- })
30
+ for(const i in chunks) {
31
+ const chunk = chunks[i]
32
+ socket.send(JSON.stringify({
33
+ id,
34
+ data: {
35
+ ...data, chunk,
36
+ done: +i === chunks.length - 1 // Last chunk gets done: true
37
+ }
38
+ }))
39
+ await wait(delay)
40
+ }
41
41
  } else {
42
42
  // Single response
43
43
  socket.send(JSON.stringify({
@@ -1,4 +1,3 @@
1
- import { equals } from 'pepka'
2
1
  import { createNew, timeout } from '../utils'
3
2
  import mockServer from '../mock/server'
4
3
  import { test } from '../suite'
@@ -33,7 +32,7 @@ test('stream-comprehensive', timeout(1e4, () => new Promise<void>(async (ff, rj)
33
32
 
34
33
  // Test 2: Stream with for-await loop
35
34
  const msg2 = {stream: true, test: 'stream2'}
36
- to = setTimeout(() => rj('stream2 timeout'), 2e2)
35
+ to = setTimeout(() => rj('stream2 timeout'), 2e4)
37
36
 
38
37
  const stream2 = ws.stream<typeof msg2, any>(msg2)
39
38
  const results: any[] = []
@@ -45,15 +44,10 @@ test('stream-comprehensive', timeout(1e4, () => new Promise<void>(async (ff, rj)
45
44
 
46
45
  clearTimeout(to)
47
46
 
48
- // For streaming messages, check that we got a valid chunk with the expected properties
49
- if (results.length !== 1 || !results[0].test || results[0].test !== msg2.test) {
50
- return rj('stream2 failed')
51
- }
52
-
53
47
  // Test 3: Multiple concurrent streams
54
48
  const msg3a = {stream: true, test: 'stream3a'}
55
49
  const msg3b = {stream: true, test: 'stream3b'}
56
- to = setTimeout(() => rj('stream3 timeout'), 2e2)
50
+ to = setTimeout(() => rj('stream3 timeout'), 2e4)
57
51
 
58
52
  const stream3a = ws.stream(msg3a)
59
53
  const stream3b = ws.stream(msg3b)
@@ -1,7 +1,7 @@
1
- import { equals } from 'pepka'
2
1
  import { createNew, timeout } from '../utils'
3
2
  import mockServer from '../mock/server'
4
3
  import { test } from '../suite'
4
+ import { last } from 'pepka'
5
5
 
6
6
  /** Test real streaming functionality with multiple chunks. */
7
7
  test('stream-real', timeout(1.5e4, () => new Promise<void>(async (ff, rj) => {
@@ -10,11 +10,11 @@ test('stream-real', timeout(1.5e4, () => new Promise<void>(async (ff, rj) => {
10
10
  let to = setTimeout(() => {
11
11
  console.log('STREAM-REAL: Timeout - cannot create')
12
12
  rj('cannot create')
13
- }, 2e2)
13
+ }, 2e4)
14
14
  const ws = await createNew({}, port)
15
15
  clearTimeout(to)
16
16
 
17
- to = setTimeout(() => rj('cannot ready'), 2e2)
17
+ to = setTimeout(() => rj('cannot ready'), 2e4)
18
18
  await ws.ready()
19
19
  clearTimeout(to)
20
20
 
@@ -30,22 +30,27 @@ test('stream-real', timeout(1.5e4, () => new Promise<void>(async (ff, rj) => {
30
30
 
31
31
  clearTimeout(to)
32
32
 
33
- // Verify we got all chunks
34
- if (chunks.length !== 3) {
35
- return rj(`Expected 3 chunks, got ${chunks.length}`)
33
+ // Verify we got all 20 chunks in perfect order
34
+ if (chunks.length !== 20) {
35
+ return rj(`Expected exactly 20 chunks, got ${chunks.length}`)
36
36
  }
37
-
38
- // Verify chunks are in order and have correct data
39
- for (let i = 0; i < 3; i++) {
37
+
38
+ // Check that all chunks are present and in perfect order (1-20)
39
+ for (let i = 0; i < 20; i++) {
40
40
  if (chunks[i].chunk !== i + 1) {
41
41
  return rj(`Chunk ${i} should be ${i + 1}, got ${chunks[i].chunk}`)
42
42
  }
43
43
  }
44
-
44
+
45
45
  // Verify last chunk has done flag
46
- if (!chunks[2].done) {
46
+ if (!chunks[20 - 1].done) {
47
47
  return rj('Last chunk should have done flag')
48
48
  }
49
+
50
+ // Verify last chunk has done flag
51
+ // if (!last(chunks as any).done) {
52
+ // return rj('Last chunk should have done flag')
53
+ // }
49
54
  ff()
50
55
  } catch (error) {
51
56
  clearTimeout(to)
@@ -1,21 +1,20 @@
1
- import { equals } from 'pepka'
2
1
  import { createNew, timeout } from '../utils'
3
2
  import mockServer from '../mock/server'
4
3
  import { test } from '../suite'
5
4
 
6
5
  /** Simple test for stream method basic functionality. */
7
- test('stream-basic', timeout(5e3, () => new Promise<void>(async (ff, rj) => {
6
+ test('stream-basic', timeout(7e3, () => new Promise<void>(async (ff, rj) => {
8
7
  const {port} = await mockServer()
9
- let to = setTimeout(() => rj('cannot create'), 2e2)
8
+ let to = setTimeout(() => rj('cannot create'), 2e4)
10
9
  const ws = await createNew({}, port)
11
10
  clearTimeout(to)
12
11
 
13
- to = setTimeout(() => rj('cannot ready'), 2e2)
12
+ to = setTimeout(() => rj('cannot ready'), 2e4)
14
13
  await ws.ready()
15
14
  clearTimeout(to)
16
15
 
17
16
  const msg = {stream: true, test: 'stream'}
18
- to = setTimeout(() => rj('stream timeout'), 2e2)
17
+ to = setTimeout(() => rj('stream timeout'), 2e4)
19
18
 
20
19
  try {
21
20
  const stream = ws.stream(msg)