wspromisify 2.8.2 → 2.9.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/dist/bundle.cjs +1 -1
- package/dist/bundle.d.ts +2 -1
- package/dist/bundle.mjs +1 -1
- package/package.json +79 -79
- package/src/WSC.ts +69 -30
- package/src/types.ts +2 -3
- package/test/index.ts +23 -20
- package/test/mock/WS.ts +31 -0
- package/test/specs/existing-socket.ts +1 -3
- package/test/specs/stream-comprehensive.ts +80 -0
- package/test/specs/stream-real.ts +58 -0
- package/test/specs/stream-simple.ts +46 -0
- package/test/utils.ts +1 -1
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,u=e=>typeof e,a=e=>null===e,h={u:"U",b:"B",n:"N",s:"S",f:"F"},f=Symbol(),d=e=>{const t=u(e);return"object"===t?a(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(a(e)||a(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"==u(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))),j=E(!0),A=E(!1),W=i(((e,t)=>w(((t,n)=>v((t=>e(n,t)),t)?t:y(n,t)),[],t)))(m),q=r(((e,t,n)=>g(t)?(e=>a(e)||(e=>e===l)(e))(n)?e:b((s=>s in n?q(e,k(1,c,t),n[s]):e),P)(t):n));q(l),b(_(m(f),A,j),q(f));const C=i(((e,t)=>t.map(e))),{floor:O}=Math,Q="0123456789abcdefghijklmnopqrstuvwxyz",$=b((e=>Object.fromEntries(e)),C(((e,t)=>[e,t])),N(""));class z{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=Q.startsWith(e),this.c2pos=$(e)}zip(e){const{abc:t,abclen:n}=this;let s="";for(;e>0;)s=t[e%n]+s,e=O(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||Q+"ABCDEFGHIJKLMNOPQRSTUVWXYZ")}}const R=new z;R.setABC.bind(R),R.zip.bind(R),R.unzip.bind(R);const T=(()=>{try{return WebSocket||null}catch{return null}})(),I=(e,t,n)=>e.addEventListener(t,n),x=(e,t)=>setTimeout(t,e),B={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:{}}},D=Symbol("Placeholder"),L=e=>{let t=0;for(const n of e)n!==D&&t++;return t},U=(e,t)=>{const n=e.length,s=e.slice(),o=t.length;let i=o,r=0;for(;i&&r<n;r++)s[r]===D&&(s[r]=t[o-i],i--);for(r=n;i;r++,i--)s[r]=t[o-i];return s},F=(e,t,n)=>{const s=e.length-t.length-L(n);if(s<1)return e(...U(t,n));{const o=(...s)=>F(e,U(t,n),s);return o.$args_left=s,o}},J=e=>(...t)=>e.length>L(t)?F(e,[],t):e(...t);function M(e){return function(t,n){const s=t===D,o=arguments.length;if(1===o&&s)throw new Error("Senseless placeholder usage.");return o>1?s?(e=>function(t){return t===D?e:e(t)})((t=>e(t,n))):e(t,n):n=>e(t,n)}}function G(e){return J(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=M(((e,t)=>se(t)===e)),ie=e=>e.length,re=M(((e,t)=>e===t)),le=M(((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=M(((e,t)=>(t.push(e),t))),ue=G(((e,t,n)=>n.reduce(e,t))),ae=M(((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=J(((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===D?e[o]():e[o](n);return n},de=M(((e,t)=>t[e])),ge=G(((e,t,n)=>n.slice(e,Z(t)?t:V))),pe=de(0);ge(1,V);const me=M(((e,t)=>t.find(e))),ye=e=>()=>e,we=ye(!0),_e=ye(!1),be=M(((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=M(((e,t)=>ue(((t,n)=>me((t=>e(n,t)),t)?t:ce(n,t)),[],t)));ke(le);const Pe=e=>{let t,n=!1;return(...s)=>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,je=new z,Ae=be([]),We=Ee(oe("Number"),Ne(isNaN)),qe={_is_ping:!0};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(){ae(_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=x(1e3*e.interval,(async()=>{const{ping_timer:t,opened:n}=this;n?(await this.send(e.content,qe),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=x(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(),I(e,"close",(async(...e)=>{this.log("close"),this.ws=null,this.onCloseQueue.forEach(Ae),this.onCloseQueue.splice(0),this.call("close",...e);let{reconnect:t,reconnection_attempts:s}=n;if(We(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()}})),I(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(),this.resetIdle()}))}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)}));I(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)):I(n,"open",Pe((()=>{this.log("open"),this.initSocket(n),s(null)})))}))}get socket(){return this.ws}async ready(){return new Promise((e=>{this.opened?e():this.onReadyQueue.push(e)}))}on(e,t,n=we,s=!1){const o=e=>n(e)&&t(e);return s?I(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 send(e,t={}){this.log("send",e);const{config:n,queue:s}=this,o={},{pipes:i,server:{data_key:r}}=n,{top:l,_is_ping:c}=t,u=je.zip(2147483637*Math.random()|0);if("object"==typeof l){if(l[r])throw new Error("Attempting to set data key/token via send() options!");Object.assign(o,l)}for(const t of i)e=t(e);const[a,h]=await Promise.all([n.encode(u,e,n),this.connect()]);if(h)throw new Error("ERR while opening connection #"+h);return this.opened&&(this.ws.send(a),this.resetPing(),c||this.resetIdle()),new Promise(((t,o)=>{this.queue[u]={msg:a,ff(e){clearTimeout(this.timeout),delete s[u],t(e)},data_type:n.data_type,sent_time:n.timer?Date.now():null,timeout:x(n.timeout,(()=>{u in this.queue&&(this.call("timeout",e),o({"Websocket timeout expired":n.timeout,"for the message":e}),delete s[u])}))}}))}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({},B,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";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,u=e=>typeof e,a=e=>null===e,h={u:"U",b:"B",n:"N",s:"S",f:"F"},f=Symbol(),d=e=>{const t=u(e);return"object"===t?a(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(a(e)||a(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"==u(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=>a(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 T{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 O=new T;O.setABC.bind(O),O.zip.bind(O),O.unzip.bind(O);const R=(()=>{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))),ue=G(((e,t,n)=>n.reduce(e,t))),ae=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)=>ue(((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 T,We=be([]),$e=Ee(oe("Number"),Ne(isNaN)),je={_is_ping:!0},ze=e=>{const t=Ae.zip(2147483637*qe()|0);return t in e?ze(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(){ae(_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=ze(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[u,a]=await Promise.all([n.encode(c,e,n),this.connect()]);if(a)throw new Error("ERR while opening connection #"+a);this.opened&&(this.ws.send(u),this.resetPing(),l||this.resetIdle());const h=()=>delete this.queue[c];return{message_id:c,msg:u,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){clearTimeout(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,u=!1,a=null;for(r[n]={msg:s,ff:e=>{a&&(clearTimeout(a),a=null),e?.done&&(i(),u=!0),c(e)},data_type:l.data_type,sent_time:l.timer?Date.now():null};!u;)yield await new Promise(((e,t)=>{a=o(t),c=e}))}constructor(e={}){this.config=(e=>{if(null===R&&!("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()}};
|
package/dist/bundle.d.ts
CHANGED
|
@@ -56,7 +56,6 @@ declare namespace wsc {
|
|
|
56
56
|
ff(x: any): any;
|
|
57
57
|
data_type: DataType;
|
|
58
58
|
sent_time: number | null;
|
|
59
|
-
timeout: NodeJS.Timeout;
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
declare class WebSocketClient {
|
|
@@ -85,11 +84,13 @@ declare class WebSocketClient {
|
|
|
85
84
|
off(event_name: wsc.WSEvent, handler: (data: any) => any, raw?: boolean): void;
|
|
86
85
|
close(): wsc.AsyncErrCode;
|
|
87
86
|
open(): Promise<number | null> | undefined;
|
|
87
|
+
private prepareMessage;
|
|
88
88
|
/** .send(your_data) wraps request to server with {id: `hash`, data: `actually your data`},
|
|
89
89
|
returns a Promise that will be rejected after a timeout or
|
|
90
90
|
resolved if server returns the same signature: {id: `same_hash`, data: `response data`}.
|
|
91
91
|
*/
|
|
92
92
|
send<RequestDataType = any, ResponseDataType = any>(message_data: RequestDataType, opts?: wsc.SendOptions): Promise<ResponseDataType>;
|
|
93
|
+
stream<RequestDataType = any, ResponseDataType = any>(message_data: RequestDataType, opts?: wsc.SendOptions): AsyncGenerator<ResponseDataType, void, unknown>;
|
|
93
94
|
constructor(user_config?: wsc.UserConfig);
|
|
94
95
|
}
|
|
95
96
|
|
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,u=e=>typeof e,a=e=>null===e,h={u:"U",b:"B",n:"N",s:"S",f:"F"},f=Symbol(),d=e=>{const t=u(e);return"object"===t?a(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(a(e)||a(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"==u(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))),j=E(!0),A=E(!1),W=i(((e,t)=>w(((t,n)=>v((t=>e(n,t)),t)?t:y(n,t)),[],t)))(m),q=r(((e,t,n)=>g(t)?(e=>a(e)||(e=>e===l)(e))(n)?e:b((s=>s in n?q(e,k(1,c,t),n[s]):e),P)(t):n));q(l),b(_(m(f),A,j),q(f));const C=i(((e,t)=>t.map(e))),{floor:O}=Math,Q="0123456789abcdefghijklmnopqrstuvwxyz",$=b((e=>Object.fromEntries(e)),C(((e,t)=>[e,t])),N(""));class z{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=Q.startsWith(e),this.c2pos=$(e)}zip(e){const{abc:t,abclen:n}=this;let s="";for(;e>0;)s=t[e%n]+s,e=O(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||Q+"ABCDEFGHIJKLMNOPQRSTUVWXYZ")}}const R=new z;R.setABC.bind(R),R.zip.bind(R),R.unzip.bind(R);const T=(()=>{try{return WebSocket||null}catch{return null}})(),I=(e,t,n)=>e.addEventListener(t,n),x=(e,t)=>setTimeout(t,e),B={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:{}}},D=Symbol("Placeholder"),L=e=>{let t=0;for(const n of e)n!==D&&t++;return t},U=(e,t)=>{const n=e.length,s=e.slice(),o=t.length;let i=o,r=0;for(;i&&r<n;r++)s[r]===D&&(s[r]=t[o-i],i--);for(r=n;i;r++,i--)s[r]=t[o-i];return s},F=(e,t,n)=>{const s=e.length-t.length-L(n);if(s<1)return e(...U(t,n));{const o=(...s)=>F(e,U(t,n),s);return o.$args_left=s,o}},J=e=>(...t)=>e.length>L(t)?F(e,[],t):e(...t);function M(e){return function(t,n){const s=t===D,o=arguments.length;if(1===o&&s)throw new Error("Senseless placeholder usage.");return o>1?s?(e=>function(t){return t===D?e:e(t)})((t=>e(t,n))):e(t,n):n=>e(t,n)}}function G(e){return J(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=M(((e,t)=>se(t)===e)),ie=e=>e.length,re=M(((e,t)=>e===t)),le=M(((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=M(((e,t)=>(t.push(e),t))),ue=G(((e,t,n)=>n.reduce(e,t))),ae=M(((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=J(((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===D?e[o]():e[o](n);return n},de=M(((e,t)=>t[e])),ge=G(((e,t,n)=>n.slice(e,Z(t)?t:V))),pe=de(0);ge(1,V);const me=M(((e,t)=>t.find(e))),ye=e=>()=>e,we=ye(!0),_e=ye(!1),be=M(((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=M(((e,t)=>ue(((t,n)=>me((t=>e(n,t)),t)?t:ce(n,t)),[],t)));ke(le);const Pe=e=>{let t,n=!1;return(...s)=>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,je=new z,Ae=be([]),We=Ee(oe("Number"),Ne(isNaN)),qe={_is_ping:!0};class Ce{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(){ae(_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=x(1e3*e.interval,(async()=>{const{ping_timer:t,opened:n}=this;n?(await this.send(e.content,qe),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=x(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(),I(e,"close",(async(...e)=>{this.log("close"),this.ws=null,this.onCloseQueue.forEach(Ae),this.onCloseQueue.splice(0),this.call("close",...e);let{reconnect:t,reconnection_attempts:s}=n;if(We(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()}})),I(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(),this.resetIdle()}))}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)}));I(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)):I(n,"open",Pe((()=>{this.log("open"),this.initSocket(n),s(null)})))}))}get socket(){return this.ws}async ready(){return new Promise((e=>{this.opened?e():this.onReadyQueue.push(e)}))}on(e,t,n=we,s=!1){const o=e=>n(e)&&t(e);return s?I(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 send(e,t={}){this.log("send",e);const{config:n,queue:s}=this,o={},{pipes:i,server:{data_key:r}}=n,{top:l,_is_ping:c}=t,u=je.zip(2147483637*Math.random()|0);if("object"==typeof l){if(l[r])throw new Error("Attempting to set data key/token via send() options!");Object.assign(o,l)}for(const t of i)e=t(e);const[a,h]=await Promise.all([n.encode(u,e,n),this.connect()]);if(h)throw new Error("ERR while opening connection #"+h);return this.opened&&(this.ws.send(a),this.resetPing(),c||this.resetIdle()),new Promise(((t,o)=>{this.queue[u]={msg:a,ff(e){clearTimeout(this.timeout),delete s[u],t(e)},data_type:n.data_type,sent_time:n.timer?Date.now():null,timeout:x(n.timeout,(()=>{u in this.queue&&(this.call("timeout",e),o({"Websocket timeout expired":n.timeout,"for the message":e}),delete s[u])}))}}))}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({},B,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{Ce as default};
|
|
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 T{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 O=new T;O.setABC.bind(O),O.zip.bind(O),O.unzip.bind(O);const R=(()=>{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 T,We=be([]),$e=Ee(oe("Number"),Ne(isNaN)),je={_is_ping:!0},ze=e=>{const t=Ae.zip(2147483637*qe()|0);return t in e?ze(e):t};class Ce{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=ze(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){clearTimeout(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=>{u&&(clearTimeout(u),u=null),e?.done&&(i(),a=!0),c(e)},data_type:l.data_type,sent_time:l.timer?Date.now():null};!a;)yield await new Promise(((e,t)=>{u=o(t),c=e}))}constructor(e={}){this.config=(e=>{if(null===R&&!("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{Ce 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.
|
|
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": "^
|
|
56
|
-
"@rollup/plugin-node-resolve": "^16.0.
|
|
57
|
-
"@rollup/plugin-replace": "^6.0.
|
|
58
|
-
"@rollup/plugin-terser": "^0.4.4",
|
|
59
|
-
"@types/express": "^5.0.
|
|
60
|
-
"@types/node": "^
|
|
61
|
-
"@types/ws": "^8.18.1",
|
|
62
|
-
"codecov": "^3.8.3",
|
|
63
|
-
"cross-env": "^10.
|
|
64
|
-
"dts-bundle-generator": "^9.5.1",
|
|
65
|
-
"nyc": "^17.1.0",
|
|
66
|
-
"rollup": "^4.
|
|
67
|
-
"rollup-plugin-typescript2": "^0.36.0",
|
|
68
|
-
"ts-node": "^10.9.2",
|
|
69
|
-
"tsx": "^4.
|
|
70
|
-
"typescript": "^5.
|
|
71
|
-
"uvu": "^0.5.6",
|
|
72
|
-
"ws": "^8.18.3"
|
|
73
|
-
},
|
|
74
|
-
"types": "./dist/bundle.d.ts",
|
|
75
|
-
"dependencies": {
|
|
76
|
-
"pepka": "^1.6.
|
|
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.9.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.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
|
+
}
|
package/src/WSC.ts
CHANGED
|
@@ -2,9 +2,10 @@ import './types'
|
|
|
2
2
|
import { Zipnum } from 'zipnum'
|
|
3
3
|
import { add_event, rm_event, sett } from './utils'
|
|
4
4
|
import { processConfig } from './config'
|
|
5
|
-
import { AnyFunc, both, callWith, F, isNil, notf, once, qfilter, T, typeIs } from 'pepka'
|
|
5
|
+
import { AnyFunc, AnyObject, both, callWith, F, isNil, notf, once, qfilter, T, typeIs } from 'pepka'
|
|
6
6
|
|
|
7
7
|
const MAX_32 = 2**31 - 1
|
|
8
|
+
const { random } = Math
|
|
8
9
|
const zipnum = new Zipnum()
|
|
9
10
|
const callit = callWith([])
|
|
10
11
|
const isNumber = both(typeIs('Number'), notf(isNaN))
|
|
@@ -18,6 +19,10 @@ type EventHandlers = {
|
|
|
18
19
|
message: AnyFunc<any, [WebSocketEventMap['message'] & {data: any}]>[]
|
|
19
20
|
timeout: AnyFunc<any, [data: any]>[]
|
|
20
21
|
}
|
|
22
|
+
const genid = (q: AnyObject) => {
|
|
23
|
+
const id = zipnum.zip((random()*(MAX_32-10))|0)
|
|
24
|
+
return id in q ? genid(q) : id
|
|
25
|
+
}
|
|
21
26
|
|
|
22
27
|
class WebSocketClient {
|
|
23
28
|
private ws: wsc.Socket|null = null
|
|
@@ -130,7 +135,6 @@ class WebSocketClient {
|
|
|
130
135
|
console.error(err, `WSP: Decode error. Got: ${e.data}`)
|
|
131
136
|
}
|
|
132
137
|
this.resetPing()
|
|
133
|
-
this.resetIdle()
|
|
134
138
|
})
|
|
135
139
|
}
|
|
136
140
|
|
|
@@ -171,7 +175,8 @@ class WebSocketClient {
|
|
|
171
175
|
public get socket() { return this.ws }
|
|
172
176
|
public async ready() {
|
|
173
177
|
return new Promise<void>((ff) => {
|
|
174
|
-
if(this.
|
|
178
|
+
if(this.config.lazy) ff() // FIXME: (possibly) breaking change ?? At least minor ver bump with a notice!!!
|
|
179
|
+
else if(this.opened) ff()
|
|
175
180
|
else this.onReadyQueue.push(ff)
|
|
176
181
|
})
|
|
177
182
|
}
|
|
@@ -221,30 +226,27 @@ class WebSocketClient {
|
|
|
221
226
|
}
|
|
222
227
|
}
|
|
223
228
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
// TODO: Сделать сэттер элементов конфигурации чтобы двигать таймауты.
|
|
230
|
+
// И эвент, когда схема наша, а соответствующего элемента очереди не ма.
|
|
231
|
+
// Или добавить флажок к эвенту 'message'.
|
|
232
|
+
// И событие 'line' со значением on: boolean. Критерии?
|
|
233
|
+
private async prepareMessage<RequestDataType = any>(
|
|
229
234
|
message_data: RequestDataType,
|
|
230
235
|
opts = <wsc.SendOptions>{}
|
|
231
|
-
)
|
|
232
|
-
this.log('send', message_data)
|
|
236
|
+
) {
|
|
237
|
+
this.log(opts._is_ping ? 'ping' : 'send', message_data)
|
|
233
238
|
const {config, queue} = this
|
|
234
|
-
const message = {}
|
|
235
239
|
const {pipes, server: {data_key}} = config
|
|
236
240
|
const {top, _is_ping} = opts
|
|
237
|
-
|
|
238
|
-
const message_id = zipnum.zip((Math.random()*(MAX_32-10))|0)
|
|
241
|
+
const id = genid(queue)
|
|
239
242
|
if(typeof top === 'object') {
|
|
240
243
|
if(top[data_key]) {
|
|
241
|
-
throw new Error(
|
|
244
|
+
throw new Error(`Attempting to set data key/token via ${opts._is_ping ? 'ping' : 'send'}() options!`)
|
|
242
245
|
}
|
|
243
|
-
Object.assign(message, top)
|
|
244
246
|
}
|
|
245
247
|
for(const pipe of pipes) message_data = pipe(message_data)
|
|
246
248
|
const [msg, err] = await Promise.all([
|
|
247
|
-
config.encode(
|
|
249
|
+
config.encode(id, message_data, config),
|
|
248
250
|
this.connect()
|
|
249
251
|
])
|
|
250
252
|
if(err) throw new Error('ERR while opening connection #'+err)
|
|
@@ -253,24 +255,61 @@ class WebSocketClient {
|
|
|
253
255
|
this.resetPing()
|
|
254
256
|
if(!_is_ping) this.resetIdle()
|
|
255
257
|
}
|
|
258
|
+
const cleanup = () => delete this.queue[id]
|
|
259
|
+
const timeout = (rj: AnyFunc) => sett(config.timeout, () => {
|
|
260
|
+
if(id in queue) {
|
|
261
|
+
this.call('timeout', message_data)
|
|
262
|
+
rj({'Websocket timeout expired': config.timeout, 'for the message': message_data})
|
|
263
|
+
cleanup()
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
return { message_id: id, msg, timeout, cleanup }
|
|
267
|
+
}
|
|
256
268
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
/** .send(your_data) wraps request to server with {id: `hash`, data: `actually your data`},
|
|
270
|
+
returns a Promise that will be rejected after a timeout or
|
|
271
|
+
resolved if server returns the same signature: {id: `same_hash`, data: `response data`}.
|
|
272
|
+
*/
|
|
273
|
+
public async send<RequestDataType = any, ResponseDataType = any>(
|
|
274
|
+
message_data: RequestDataType,
|
|
275
|
+
opts = <wsc.SendOptions>{}
|
|
276
|
+
): Promise<ResponseDataType> {
|
|
277
|
+
const {message_id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
|
|
278
|
+
const {queue, config} = this
|
|
279
|
+
|
|
280
|
+
return new Promise<ResponseDataType>((ff, rj) => {
|
|
281
|
+
const to = timeout(rj)
|
|
282
|
+
queue[message_id] = {
|
|
283
|
+
msg,
|
|
264
284
|
data_type: config.data_type,
|
|
265
285
|
sent_time: config.timer ? Date.now() : null,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
delete queue[message_id]
|
|
271
|
-
}
|
|
272
|
-
})
|
|
286
|
+
ff(x: any) {
|
|
287
|
+
clearTimeout(to)
|
|
288
|
+
ff(x)
|
|
289
|
+
}
|
|
273
290
|
}
|
|
291
|
+
}).finally(cleanup)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public async *stream<RequestDataType = any, ResponseDataType = any>(
|
|
295
|
+
message_data: RequestDataType,
|
|
296
|
+
opts = <wsc.SendOptions>{}
|
|
297
|
+
): AsyncGenerator<ResponseDataType, void, unknown> {
|
|
298
|
+
const {message_id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
|
|
299
|
+
const {queue, config} = this
|
|
300
|
+
let done = false, fulfill: AnyFunc, to: NodeJS.Timeout|null = null
|
|
301
|
+
queue[message_id] = {
|
|
302
|
+
msg,
|
|
303
|
+
ff: (msg: ResponseDataType&{done?: boolean}) => {
|
|
304
|
+
if(to) {clearTimeout(to); to=null}
|
|
305
|
+
if(msg?.done) { cleanup(); done=true }
|
|
306
|
+
fulfill(msg)
|
|
307
|
+
},
|
|
308
|
+
data_type: config.data_type,
|
|
309
|
+
sent_time: config.timer ? Date.now() : null
|
|
310
|
+
}
|
|
311
|
+
while(!done) yield await new Promise<ResponseDataType>((ff, rj) => {
|
|
312
|
+
to = timeout(rj), fulfill=ff
|
|
274
313
|
})
|
|
275
314
|
}
|
|
276
315
|
|
package/src/types.ts
CHANGED
|
@@ -16,7 +16,7 @@ declare namespace wsc {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export type AsyncErrCode = Promise<number | null | {}>
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
export type EventHandler = (e: any) => void
|
|
21
21
|
|
|
22
22
|
export type DataPipe = (message: any) => any
|
|
@@ -63,7 +63,6 @@ declare namespace wsc {
|
|
|
63
63
|
export interface Message {
|
|
64
64
|
msg: any, ff(x: any): any,
|
|
65
65
|
data_type: DataType,
|
|
66
|
-
sent_time: number | null
|
|
67
|
-
timeout: NodeJS.Timeout
|
|
66
|
+
sent_time: number | null
|
|
68
67
|
}
|
|
69
68
|
}
|
package/test/index.ts
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import './specs/close'
|
|
2
|
-
import './specs/drops'
|
|
3
|
-
import './specs/echo'
|
|
4
|
-
import './specs/existing-socket'
|
|
5
|
-
import './specs/lazy'
|
|
6
|
-
import './specs/ready'
|
|
7
|
-
import './specs/reconnect'
|
|
8
|
-
import './specs/lazy-send-before-open'
|
|
9
|
-
import './specs/socket'
|
|
10
|
-
import './specs/no-native-throws'
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
test
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
import './specs/close'
|
|
2
|
+
import './specs/drops'
|
|
3
|
+
import './specs/echo'
|
|
4
|
+
import './specs/existing-socket'
|
|
5
|
+
import './specs/lazy'
|
|
6
|
+
import './specs/ready'
|
|
7
|
+
import './specs/reconnect'
|
|
8
|
+
import './specs/lazy-send-before-open'
|
|
9
|
+
import './specs/socket'
|
|
10
|
+
import './specs/no-native-throws'
|
|
11
|
+
import './specs/stream-simple'
|
|
12
|
+
import './specs/stream-comprehensive'
|
|
13
|
+
import './specs/stream-real'
|
|
14
|
+
import mockServer from './mock/server'
|
|
15
|
+
import {test} from './suite'
|
|
16
|
+
|
|
17
|
+
const {shutDown, isRunning} = await mockServer()
|
|
18
|
+
test.after(() => {
|
|
19
|
+
setTimeout(async () => {
|
|
20
|
+
if(isRunning()) await shutDown()
|
|
21
|
+
process.exit()
|
|
22
|
+
}, 100)
|
|
23
|
+
})
|
|
21
24
|
test.run()
|
package/test/mock/WS.ts
CHANGED
|
@@ -19,6 +19,37 @@ const createServer = (port = 40510) => new Promise<WebSocketServer>((ff, rj) =>
|
|
|
19
19
|
return null
|
|
20
20
|
} else if(data.echo) {
|
|
21
21
|
response = data
|
|
22
|
+
} else if(data.stream) {
|
|
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
|
|
26
|
+
|
|
27
|
+
if(data.multi) {
|
|
28
|
+
// 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
|
+
})
|
|
41
|
+
} else {
|
|
42
|
+
// Single response
|
|
43
|
+
socket.send(JSON.stringify({
|
|
44
|
+
id,
|
|
45
|
+
data: {
|
|
46
|
+
...data,
|
|
47
|
+
chunk: chunks[0],
|
|
48
|
+
done: true
|
|
49
|
+
}
|
|
50
|
+
}))
|
|
51
|
+
}
|
|
52
|
+
return null
|
|
22
53
|
}
|
|
23
54
|
socket.send(JSON.stringify({ id, data: response }))
|
|
24
55
|
return null
|
|
@@ -25,12 +25,10 @@ test('existing_socket', () => new Promise(async (ff, rj) => {
|
|
|
25
25
|
const ws2_0 = new WS(existing_addr)
|
|
26
26
|
|
|
27
27
|
ws2_0.addEventListener('open', async () => {
|
|
28
|
-
console.log('START')
|
|
29
28
|
const ws2 = await createNew({socket: ws2_0}, port)
|
|
30
|
-
console.log({ws1})
|
|
31
29
|
|
|
32
30
|
if(ws2.socket?.readyState !== 1) return rj('Bad echo.')
|
|
33
|
-
|
|
31
|
+
|
|
34
32
|
const msg2 = {echo: true, msg: 'existing_socket!'}
|
|
35
33
|
const response2 = await ws2.send(msg2)
|
|
36
34
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { equals } from 'pepka'
|
|
2
|
+
import { createNew, timeout } from '../utils'
|
|
3
|
+
import mockServer from '../mock/server'
|
|
4
|
+
import { test } from '../suite'
|
|
5
|
+
|
|
6
|
+
/** Comprehensive test for stream method. */
|
|
7
|
+
test('stream-comprehensive', timeout(1e4, () => new Promise<void>(async (ff, rj) => {
|
|
8
|
+
|
|
9
|
+
const {port} = await mockServer()
|
|
10
|
+
let to = setTimeout(() => rj('cannot create'), 2e2)
|
|
11
|
+
const ws = await createNew({}, port)
|
|
12
|
+
clearTimeout(to)
|
|
13
|
+
|
|
14
|
+
to = setTimeout(() => rj('cannot ready'), 2e2)
|
|
15
|
+
await ws.ready()
|
|
16
|
+
clearTimeout(to)
|
|
17
|
+
|
|
18
|
+
// Test 1: Basic stream functionality
|
|
19
|
+
const msg1 = {stream: true, test: 'stream1', chunks: [1], delay: 10}
|
|
20
|
+
to = setTimeout(() => rj('stream1 timeout'), 2e2)
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const stream1 = ws.stream(msg1)
|
|
24
|
+
const result1 = await stream1.next()
|
|
25
|
+
|
|
26
|
+
// Check that we got a valid response with the expected test property
|
|
27
|
+
if (result1.done || !result1.value?.test || result1.value.test !== msg1.test) {
|
|
28
|
+
clearTimeout(to)
|
|
29
|
+
return rj('stream1 failed')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clearTimeout(to)
|
|
33
|
+
|
|
34
|
+
// Test 2: Stream with for-await loop
|
|
35
|
+
const msg2 = {stream: true, test: 'stream2'}
|
|
36
|
+
to = setTimeout(() => rj('stream2 timeout'), 2e2)
|
|
37
|
+
|
|
38
|
+
const stream2 = ws.stream<typeof msg2, any>(msg2)
|
|
39
|
+
const results: any[] = []
|
|
40
|
+
|
|
41
|
+
for await (const chunk of stream2) {
|
|
42
|
+
results.push(chunk)
|
|
43
|
+
break // We expect only one chunk
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
clearTimeout(to)
|
|
47
|
+
|
|
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
|
+
// Test 3: Multiple concurrent streams
|
|
54
|
+
const msg3a = {stream: true, test: 'stream3a'}
|
|
55
|
+
const msg3b = {stream: true, test: 'stream3b'}
|
|
56
|
+
to = setTimeout(() => rj('stream3 timeout'), 2e2)
|
|
57
|
+
|
|
58
|
+
const stream3a = ws.stream(msg3a)
|
|
59
|
+
const stream3b = ws.stream(msg3b)
|
|
60
|
+
|
|
61
|
+
const result3a = await stream3a.next()
|
|
62
|
+
const result3b = await stream3b.next()
|
|
63
|
+
|
|
64
|
+
clearTimeout(to)
|
|
65
|
+
|
|
66
|
+
// For streaming messages, check that we got valid chunks with the expected properties
|
|
67
|
+
if (result3a.done || result3b.done ||
|
|
68
|
+
!result3a.value?.test || result3a.value.test !== msg3a.test ||
|
|
69
|
+
!result3b.value?.test || result3b.value.test !== msg3b.test) {
|
|
70
|
+
return rj('stream3 failed')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ff()
|
|
74
|
+
} catch (error) {
|
|
75
|
+
clearTimeout(to)
|
|
76
|
+
console.log('STREAM-COMPREHENSIVE TEST FAILED:', error)
|
|
77
|
+
rj('stream comprehensive error: ' + error)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
))
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { equals } from 'pepka'
|
|
2
|
+
import { createNew, timeout } from '../utils'
|
|
3
|
+
import mockServer from '../mock/server'
|
|
4
|
+
import { test } from '../suite'
|
|
5
|
+
|
|
6
|
+
/** Test real streaming functionality with multiple chunks. */
|
|
7
|
+
test('stream-real', timeout(1.5e4, () => new Promise<void>(async (ff, rj) => {
|
|
8
|
+
|
|
9
|
+
const {port} = await mockServer()
|
|
10
|
+
let to = setTimeout(() => {
|
|
11
|
+
console.log('STREAM-REAL: Timeout - cannot create')
|
|
12
|
+
rj('cannot create')
|
|
13
|
+
}, 2e2)
|
|
14
|
+
const ws = await createNew({}, port)
|
|
15
|
+
clearTimeout(to)
|
|
16
|
+
|
|
17
|
+
to = setTimeout(() => rj('cannot ready'), 2e2)
|
|
18
|
+
await ws.ready()
|
|
19
|
+
clearTimeout(to)
|
|
20
|
+
|
|
21
|
+
// Test real streaming with multiple chunks
|
|
22
|
+
const streamMsg = {stream: true, multi: true, test: 'real-stream', chunks: [1, 2, 3], delay: 50}
|
|
23
|
+
to = setTimeout(() => rj('stream timeout'), 5e3)
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const stream = ws.stream<typeof streamMsg, any>(streamMsg)
|
|
27
|
+
const chunks: any[] = []
|
|
28
|
+
|
|
29
|
+
for await (const chunk of stream) {
|
|
30
|
+
chunks.push(chunk)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
clearTimeout(to)
|
|
34
|
+
|
|
35
|
+
// Verify we got all chunks
|
|
36
|
+
if (chunks.length !== 3) {
|
|
37
|
+
return rj(`Expected 3 chunks, got ${chunks.length}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Verify chunks are in order and have correct data
|
|
41
|
+
for (let i = 0; i < 3; i++) {
|
|
42
|
+
if (chunks[i].chunk !== i + 1) {
|
|
43
|
+
return rj(`Chunk ${i} should be ${i + 1}, got ${chunks[i].chunk}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Verify last chunk has done flag
|
|
48
|
+
if (!chunks[2].done) {
|
|
49
|
+
return rj('Last chunk should have done flag')
|
|
50
|
+
}
|
|
51
|
+
ff()
|
|
52
|
+
} catch (error) {
|
|
53
|
+
clearTimeout(to)
|
|
54
|
+
console.log('STREAM-REAL TEST FAILED:', error)
|
|
55
|
+
rj('stream real error: ' + error)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { equals } from 'pepka'
|
|
2
|
+
import { createNew, timeout } from '../utils'
|
|
3
|
+
import mockServer from '../mock/server'
|
|
4
|
+
import { test } from '../suite'
|
|
5
|
+
|
|
6
|
+
/** Simple test for stream method basic functionality. */
|
|
7
|
+
test('stream-basic', timeout(5e3, () => new Promise<void>(async (ff, rj) => {
|
|
8
|
+
const {port} = await mockServer()
|
|
9
|
+
let to = setTimeout(() => rj('cannot create'), 2e2)
|
|
10
|
+
const ws = await createNew({}, port)
|
|
11
|
+
clearTimeout(to)
|
|
12
|
+
|
|
13
|
+
to = setTimeout(() => rj('cannot ready'), 2e2)
|
|
14
|
+
await ws.ready()
|
|
15
|
+
clearTimeout(to)
|
|
16
|
+
|
|
17
|
+
const msg = {stream: true, test: 'stream'}
|
|
18
|
+
to = setTimeout(() => rj('stream timeout'), 2e2)
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const stream = ws.stream(msg)
|
|
22
|
+
|
|
23
|
+
// Test that stream is an AsyncGenerator
|
|
24
|
+
if (typeof stream[Symbol.asyncIterator] !== 'function') {
|
|
25
|
+
clearTimeout(to)
|
|
26
|
+
return rj('stream is not an AsyncGenerator')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Test that we can iterate over it
|
|
30
|
+
const iterator = stream[Symbol.asyncIterator]()
|
|
31
|
+
const firstResult = await iterator.next()
|
|
32
|
+
|
|
33
|
+
clearTimeout(to)
|
|
34
|
+
|
|
35
|
+
// For streaming messages, check that we got a valid chunk with the expected properties
|
|
36
|
+
if (!firstResult.done && firstResult.value && firstResult.value.test === msg.test) {
|
|
37
|
+
ff()
|
|
38
|
+
} else {
|
|
39
|
+
rj(`stream did not return expected value. Got: ${JSON.stringify(firstResult.value)}, Expected test: ${msg.test}`)
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
clearTimeout(to)
|
|
43
|
+
rj('stream error: ' + error)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
))
|
package/test/utils.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const createNew = (config = {} as wsc.UserConfig, port: number) => new Pr
|
|
|
12
12
|
}, config))
|
|
13
13
|
if(ws.socket?.readyState===1 || config.lazy) return ff(ws)
|
|
14
14
|
ws.on('error', rj)
|
|
15
|
-
ws.on('open', () => {
|
|
15
|
+
ws.on('open', () => {ff(ws)})
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
// Inspired by tinchoz49 https://github.com/lukeed/uvu/issues/33#issuecomment-879870292
|