wspromisify 2.8.3 → 2.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +70 -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 +56 -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}})(),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:{}}},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=B(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=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(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()}})),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.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 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:B(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({},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";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}})(),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:{}}},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=B(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=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(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()}})),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.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 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:B(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({},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};
|
|
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.1",
|
|
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,13 +2,15 @@ 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))
|
|
11
12
|
const ping_send_opts: wsc.SendOptions = {_is_ping: true}
|
|
13
|
+
const clearTO = (to: NodeJS.Timeout|null) => to && clearTimeout(to)
|
|
12
14
|
|
|
13
15
|
type EventHandler<T extends keyof WebSocketEventMap> = AnyFunc<any, [WebSocketEventMap[T]]>
|
|
14
16
|
type EventHandlers = {
|
|
@@ -18,6 +20,10 @@ type EventHandlers = {
|
|
|
18
20
|
message: AnyFunc<any, [WebSocketEventMap['message'] & {data: any}]>[]
|
|
19
21
|
timeout: AnyFunc<any, [data: any]>[]
|
|
20
22
|
}
|
|
23
|
+
const genid = (q: AnyObject) => {
|
|
24
|
+
const id = zipnum.zip((random()*(MAX_32-10))|0)
|
|
25
|
+
return id in q ? genid(q) : id
|
|
26
|
+
}
|
|
21
27
|
|
|
22
28
|
class WebSocketClient {
|
|
23
29
|
private ws: wsc.Socket|null = null
|
|
@@ -170,7 +176,8 @@ class WebSocketClient {
|
|
|
170
176
|
public get socket() { return this.ws }
|
|
171
177
|
public async ready() {
|
|
172
178
|
return new Promise<void>((ff) => {
|
|
173
|
-
if(this.
|
|
179
|
+
if(this.config.lazy) ff() // FIXME: (possibly) breaking change ?? At least minor ver bump with a notice!!!
|
|
180
|
+
else if(this.opened) ff()
|
|
174
181
|
else this.onReadyQueue.push(ff)
|
|
175
182
|
})
|
|
176
183
|
}
|
|
@@ -220,30 +227,27 @@ class WebSocketClient {
|
|
|
220
227
|
}
|
|
221
228
|
}
|
|
222
229
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
230
|
+
// TODO: Сделать сэттер элементов конфигурации чтобы двигать таймауты.
|
|
231
|
+
// И эвент, когда схема наша, а соответствующего элемента очереди не ма.
|
|
232
|
+
// Или добавить флажок к эвенту 'message'.
|
|
233
|
+
// И событие 'line' со значением on: boolean. Критерии?
|
|
234
|
+
private async prepareMessage<RequestDataType = any>(
|
|
228
235
|
message_data: RequestDataType,
|
|
229
236
|
opts = <wsc.SendOptions>{}
|
|
230
|
-
)
|
|
231
|
-
this.log('send', message_data)
|
|
237
|
+
) {
|
|
238
|
+
this.log(opts._is_ping ? 'ping' : 'send', message_data)
|
|
232
239
|
const {config, queue} = this
|
|
233
|
-
const message = {}
|
|
234
240
|
const {pipes, server: {data_key}} = config
|
|
235
241
|
const {top, _is_ping} = opts
|
|
236
|
-
|
|
237
|
-
const message_id = zipnum.zip((Math.random()*(MAX_32-10))|0)
|
|
242
|
+
const id = genid(queue)
|
|
238
243
|
if(typeof top === 'object') {
|
|
239
244
|
if(top[data_key]) {
|
|
240
|
-
throw new Error(
|
|
245
|
+
throw new Error(`Attempting to set data key/token via ${opts._is_ping ? 'ping' : 'send'}() options!`)
|
|
241
246
|
}
|
|
242
|
-
Object.assign(message, top)
|
|
243
247
|
}
|
|
244
248
|
for(const pipe of pipes) message_data = pipe(message_data)
|
|
245
249
|
const [msg, err] = await Promise.all([
|
|
246
|
-
config.encode(
|
|
250
|
+
config.encode(id, message_data, config),
|
|
247
251
|
this.connect()
|
|
248
252
|
])
|
|
249
253
|
if(err) throw new Error('ERR while opening connection #'+err)
|
|
@@ -252,25 +256,61 @@ class WebSocketClient {
|
|
|
252
256
|
this.resetPing()
|
|
253
257
|
if(!_is_ping) this.resetIdle()
|
|
254
258
|
}
|
|
259
|
+
const cleanup = () => delete this.queue[id]
|
|
260
|
+
const timeout = (rj: AnyFunc) => sett(config.timeout, () => {
|
|
261
|
+
if(id in queue) {
|
|
262
|
+
this.call('timeout', message_data)
|
|
263
|
+
rj({'Websocket timeout expired': config.timeout, 'for the message': message_data})
|
|
264
|
+
cleanup()
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
return { message_id: id, msg, timeout, cleanup }
|
|
268
|
+
}
|
|
255
269
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
270
|
+
/** .send(your_data) wraps request to server with {id: `hash`, data: `actually your data`},
|
|
271
|
+
returns a Promise that will be rejected after a timeout or
|
|
272
|
+
resolved if server returns the same signature: {id: `same_hash`, data: `response data`}.
|
|
273
|
+
*/
|
|
274
|
+
public async send<RequestDataType = any, ResponseDataType = any>(
|
|
275
|
+
message_data: RequestDataType,
|
|
276
|
+
opts = <wsc.SendOptions>{}
|
|
277
|
+
): Promise<ResponseDataType> {
|
|
278
|
+
const {message_id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
|
|
279
|
+
const {queue, config} = this
|
|
280
|
+
|
|
281
|
+
return new Promise<ResponseDataType>((ff, rj) => {
|
|
282
|
+
const to = timeout(rj)
|
|
283
|
+
queue[message_id] = {
|
|
284
|
+
msg,
|
|
263
285
|
data_type: config.data_type,
|
|
264
286
|
sent_time: config.timer ? Date.now() : null,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
delete queue[message_id]
|
|
270
|
-
}
|
|
271
|
-
})
|
|
287
|
+
ff(x: any) {
|
|
288
|
+
clearTO(to)
|
|
289
|
+
ff(x)
|
|
290
|
+
}
|
|
272
291
|
}
|
|
273
|
-
})
|
|
292
|
+
}).finally(cleanup)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public async *stream<RequestDataType = any, ResponseDataType = any>(
|
|
296
|
+
message_data: RequestDataType,
|
|
297
|
+
opts = <wsc.SendOptions>{}
|
|
298
|
+
): AsyncGenerator<ResponseDataType, void, unknown> {
|
|
299
|
+
const {message_id, msg, timeout, cleanup} = await this.prepareMessage(message_data, opts)
|
|
300
|
+
const {queue, config} = this
|
|
301
|
+
let done = false, fulfill: AnyFunc, to: NodeJS.Timeout|null = null
|
|
302
|
+
queue[message_id] = {
|
|
303
|
+
msg,
|
|
304
|
+
ff: (msg: ResponseDataType&{done?: boolean}) => {
|
|
305
|
+
fulfill(msg)
|
|
306
|
+
if(msg?.done) { cleanup(); done=true }
|
|
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
|
+
clearTO(to), to=timeout(rj), fulfill=ff
|
|
313
|
+
}).finally(() => {clearTO(to), to=null})
|
|
274
314
|
}
|
|
275
315
|
|
|
276
316
|
// TODO: Add .on handlers to config!
|
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,56 @@
|
|
|
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) chunks.push(chunk)
|
|
30
|
+
|
|
31
|
+
clearTimeout(to)
|
|
32
|
+
|
|
33
|
+
// Verify we got all chunks
|
|
34
|
+
if (chunks.length !== 3) {
|
|
35
|
+
return rj(`Expected 3 chunks, got ${chunks.length}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Verify chunks are in order and have correct data
|
|
39
|
+
for (let i = 0; i < 3; i++) {
|
|
40
|
+
if (chunks[i].chunk !== i + 1) {
|
|
41
|
+
return rj(`Chunk ${i} should be ${i + 1}, got ${chunks[i].chunk}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Verify last chunk has done flag
|
|
46
|
+
if (!chunks[2].done) {
|
|
47
|
+
return rj('Last chunk should have done flag')
|
|
48
|
+
}
|
|
49
|
+
ff()
|
|
50
|
+
} catch (error) {
|
|
51
|
+
clearTimeout(to)
|
|
52
|
+
console.log('STREAM-REAL TEST FAILED:', error)
|
|
53
|
+
rj('stream real error: ' + error)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
))
|
|
@@ -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
|