diff --git a/.#index.js b/.#index.js new file mode 120000 index 0000000..9f70976 --- /dev/null +++ b/.#index.js @@ -0,0 +1 @@ +avril@flan-laptop.2546:1595960333 \ No newline at end of file diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build/message-min.js b/build/message-min.js new file mode 100644 index 0000000..311d6a3 --- /dev/null +++ b/build/message-min.js @@ -0,0 +1,2 @@ +var MessageQueue=function(){"use strict";var t=function(t,s){return t(s={exports:{}},s.exports),s.exports}((function(t){t&&(t.exports=function(t){var s=this;this.array=t||[],this.over=!1,this.acceptInvalid=!1;var e={waiters:[],wait:function(){return new Promise(t=>e.waiters.push((function(){t()})))},signal:function(){e.waiters.length>0&&e.waiters.shift()()},flush:function(){for(;e.waiters.length>0;)e.waiters.shift()()}};this.take=async function(){if(!(s.over&&s.array.length<=0||(s.array.length<=0&&await e.wait(),s.over&&s.array.length<=0)))return s.array.shift()},this.poll=function(){return s.array},this.takeNB=function(){return s.array.length<=0?null:s.array.shift()},this.swallow0=async function(){for(var t,e=[];t=await s.take();)e.push(t);return e},this.swallow=function(t){return t?new Promise((function(e,o){var n=!0;s.swallow0().then((function(t){n&&e(t),n=!1})),t>0&&setTimeout(()=>{return s=new Error("swallow timeout reached ("+t+")"),n&&o(s),void(n=!1);var s},t)})):s.swallow0()},this.giveMany=function(t){for(let e of t)s.give(e)},this.give=function(t){(t||s.acceptInvalid)&&(s.array.push(t),e.signal())},this.commit=function(){return s.over=!0,e.flush(),s}})}));function s(){this.hooks=[]}s.prototype.hook=function(t){return this.hooks.push(t),this.hooks.length-1},s.prototype.unhook=function(t){this.hooks[t]=null},s.prototype.signal=function(t){for(const s of this.hooks)s&&s.apply(this,[t])},s.prototype.clear=function(){this.hooks=[]};const e=(t,s,e)=>(t.addEventListener(s,e),e);s.socket=(t,s)=>o(t);const o=o=>{const n=new s,i=new t;let r=!1;n.socket=o;const a=e(o,"message",t=>i.give(t.data)),c=e(o,"close",()=>i.commit());return(async()=>{let t;for(;void 0!==(t=await i.take());)n.signal({op:"message",value:t});n.signal({op:"close"})})(),n.close=t=>{r||(o.removeEventListener("message",a),o.removeEventListener("close",c),i.commit(),t&&o.close(),r=!0)},n.send=t=>{r||o.send(t)},n},n=t=>{const e=new s;let o=!1;e.socket=t.socket;const n=t.hook(t=>e.signal(t));e.close=s=>{o||(t.unhook(n),s&&t.close&&t.close(!0),o=!0)},e.send=s=>{o||t.socket.send(s)}};function i(e,r,a,c){this.socket=e instanceof s?n(e):e instanceof i?(t=>n(t.socket))(e):o(e),this.filter=r||(()=>!0),this.apply=a||(t=>t),this.map=c||(t=>t),this.premap=t=>t;const h=this.stage=new t,l=this;this.hook=this.socket.hook(t=>{if(t)switch(t.op){case"close":l.close();break;case"message":t=this.premap(t.value),this.filter(t)&&h.give(this.map(t))}})}i.JSON=(...t)=>{const s=new i(...t);return s.premap=t=>{try{return JSON.parse(t)}catch(t){return null}},s.apply=t=>JSON.stringify(s.apply(t)),s};const r=i.prototype;return r.clone=function(t,s,e){const o=new i(this.socket.socket,t||this.filter,s||this.apply,e||this.map);return o.premap=this.premap,o.on_close=this.on_close,o},r.close=function(t){this.on_close&&(this.on_close(this,t),this.on_close=null),this.socket.unhook(this.hook),this.stage.commit(),this.socket.close(t)},r.send=async function(t){for(;;){const s=this.stage.take();this.socket.send(this.apply(t));const e=await s;return e}},r.send0=function(t){this.socket.send(this.apply(t))},r.read=async function(){for(;;){const t=await this.stage.take();return t}},r.oneshot=async function(t){const s=await this.send(t);return this.close(),s},i}(); +//# sourceMappingURL=message-min.js.map diff --git a/build/message-min.js.map b/build/message-min.js.map new file mode 100644 index 0000000..c00b296 --- /dev/null +++ b/build/message-min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"message-min.js","sources":["../node_modules/@notflan/stage-js/stage.js","../dispatcher.js","../util.js","../message.js"],"sourcesContent":["\n/**\n * An async collection / item stream.\n * @param {Array} from Optional array to begin reading from.\n */\nfunction Stage(from) {\n var base = this;\n this.array = from || [];\n this.over = false;\n this.acceptInvalid = false;\n var next = {\n\twaiters: [],\n\twait: function() {\n\t return new Promise(resolve => next.waiters.push(function() { resolve(); }));\n\t},\n\tsignal: function() {\n\t if(next.waiters.length>0) {\n\t\tnext.waiters.shift()();\n\t }\n\t}, \n\tflush: function() {\n\t while(next.waiters.length>0) {\n\t\tnext.waiters.shift()();\n\t }\n\t}\n };\n\n /**\n * Take one item off then end. Function will yield if none available to produce yet.\n * @return {} undefined if there is no more items to produce (`commit` was called), otherwise the next item in the stream.\n */\n this.take = async function() {\n\tif(base.over && base.array.length<=0)\n\t return undefined;\n\t\n\tif(base.array.length<=0)\n\t await next.wait();\n\n\n\tif(base.over && base.array.length<=0)\n\t return undefined;\n\t\n\treturn base.array.shift();\n };\n\n /**\n Get all current items not yet produced.\n */\n this.poll = function() {\n\treturn base.array;\n };\n\n /**\n * Take without blocking. Returns `null` if there are no items to take.\n */\n this.takeNB = function() {\n\tif(base.array.length<=0)\n\t return null;\n\treturn base.array.shift();\n };\n\n /**\n * Await and take the rest of the items in this stream until `commit` is called.\n */\n this.swallow0 = async function() {\n\tvar ar = [];\n\tvar token;\n\twhile((token = await base.take())) {\n\t ar.push(token);\n\t}\n\treturn ar;\n };\n\n /**\n * Returns a promise that resolves to `swallow0`. Optionally takes a timeout that will reject the promise if it does not complete before the stream is closed with `commit`.\n */\n this.swallow = function(timeout) {\n\tif(!timeout) return base.swallow0();\n\telse {\n\t return new Promise(function(_resolve,_reject) {\n\t\tvar running=true;\n\t\tvar resolve = function(v) {\n\t\t if(running) _resolve(v);\n\t\t running=false;\n\t\t};\n\t\tvar reject= function(v) {\n\t\t if(running) _reject(v);\n\t\t running=false;\n\t\t};\n\t\tbase.swallow0().then(resolve);\n\t\tif(timeout>0)\n\t\t setTimeout(()=> reject(new Error(\"swallow timeout reached (\"+timeout+\")\")), timeout);\n\t });\n\t}\n };\n\n /**\n * Send a collection of values to the stream.\n */\n this.giveMany= function(values) {\n\tfor(let value of values)\n\t base.give(value);\n };\n\n /**\n * Send a single value to the stream\n */\n this.give = function(value) {\n\tif(!value && !base.acceptInvalid)\n\t return;\n\tbase.array.push(value);\n\tnext.signal();\n };\n\n /**\n * Close the stream. After this is called, once all elements already in the stream have been consumed, `take` will resolve immediately to `undefined` for any more consumers. It cannot be opened again.\n */\n this.commit = function() {\n\tbase.over = true;\n\tnext.flush();\n\n\treturn base;\n };\n};\n\nif(module)\n module.exports = Stage;\n","\nfunction Dispatcher() {\n this.hooks = [];\n}\n\nDispatcher.prototype.hook = function(cb) {\n this.hooks.push(cb);\n return this.hooks.length-1;\n};\n\nDispatcher.prototype.unhook = function(index) {\n this.hooks[index] = null;\n};\n\nDispatcher.prototype.signal = function(value) {\n for(const cb of this.hooks) {\n\tif(cb) cb.apply(this, [value]);\n }\n};\n\nDispatcher.prototype.clear = function() {\n this.hooks = [];\n};\n\nexport default Dispatcher;\n","function uuidv4() {\n if (crypto) {\n\treturn ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));\n }\n else return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n\tconst r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n\treturn v.toStirng(16);\n });\n}\n\nconst UUID_RE = /^(\\w{8}(-\\w{4}){3}-\\w{12}?)$/i;\nfunction is_uuid(id) {\n return (typeof id === 'string' || id instanceof String) && UUID_RE.test(id);\n}\n\nconst addEventListenerHack = (to, ev, lam) => {\n to.addEventListener(ev, lam);\n return lam;\n};\n\nexport {addEventListenerHack, uuidv4, is_uuid};\n","import Stage from \"@notflan/stage-js\";\nimport Dispatcher from \"./dispatcher\";\nimport {addEventListenerHack} from \"./util\";\n\nDispatcher.socket = (sock,cloned) => socket_dispatcher(sock,cloned);\n\nconst socket_dispatcher = (ws) => {\n const disp = new Dispatcher();\n const pusher = new Stage();\n let closed=false;\n \n disp.socket = ws;\n \n const h0 = addEventListenerHack(ws, 'message', (ev) => pusher.give(ev.data));\n const h1 = addEventListenerHack(ws, 'close', () => pusher.commit());\n\n (async () => {\n\tlet msg;\n\twhile ( (msg = await pusher.take()) !== undefined) \n\t disp.signal({op: 'message', value: msg});\n\tdisp.signal({op: 'close'});\n\n })();\n\n disp.close = (consume) => {\n\n\tif(!closed) {\n\t ws.removeEventListener('message', h0);\n\t ws.removeEventListener('close', h1);\n\t pusher.commit();\n\n\t if(consume)\n\t\tws.close();\n\t closed=true;\n\t}\n };\n\n disp.send = (msg) => {\n\tif(!closed) {\n\t ws.send(msg);\n\t}\n };\n \n return disp;\n};\n\nconst message_dispatcher = (ws) => {\n return dispatcher_dispatcher(ws.socket);\n};\n\nconst dispatcher_dispatcher = (ws) => {\n const disp = new Dispatcher();\n let closed=false;\n\n disp.socket = ws.socket;\n\n const hook = ws.hook((sig) => disp.signal(sig));//just pass it\n\n disp.close = (consume) => {\n\tif(!closed){\n\t ws.unhook(hook);\n\t if(consume && ws.close)\n\t\tws.close(true);\n\t closed=true;\n\t}\n };\n disp.send = (msg) => {\n\tif(!closed) ws.socket.send(msg);\n };\n};\n\nfunction MessageQueue(ws, filter, apply, map) {\n this.socket = (ws instanceof Dispatcher) ? dispatcher_dispatcher(ws) : (ws instanceof MessageQueue) ? message_dispatcher(ws) : socket_dispatcher(ws);\n \n this.filter = filter || (()=>true); // Filter incoming messages. Return true to keep, false to ignore.\n this.apply = apply || ((o) => o); // Apply this function to outgoing messages.\n this.map = map || ((o) => o); // Apply this function to incoming messages after `filter`\n this.premap = ((o) => o); // Apply this function to incoming messages before `filter`\n\n const stage = this.stage = new Stage();\n const base = this;\n\n this.hook = this.socket.hook((msg) => {\n\tif (msg)\n\t{\n\t switch(msg.op)\n\t {\n\t\tcase 'close':\n\t\tbase.close();\n\t\tbreak;\n\t\tcase 'message':\n\t\tmsg = this.premap(msg.value);\n\t\tif(this.filter(msg))\n\t\t stage.give(this.map(msg));\n\t\tbreak;\n\t\tdefault:\n\t\t//uhh\n\t\tbreak;\n\t }\n\t}\n });\n}\nMessageQueue.JSON = (...vars) => {\n const q = new MessageQueue(...vars);\n q.premap = x=> {\n\ttry {\n\t return JSON.parse(x);\n\t} catch(_) {\n\t return null;\n\t}\n };\n q.apply = x=> JSON.stringify(q.apply(x));\n \n return q;\n};\n\nconst MQ = MessageQueue.prototype;\n\nMQ.clone = function(filter, apply, map) {\n const mq = new MessageQueue(this.socket.socket, filter || this.filter, apply || this.apply, map || this.map);\n mq.premap = this.premap;\n mq.on_close = this.on_close;\n return mq;\n};\n\nMQ.close = function(consume) {\n if (this.on_close) {this.on_close(this, consume); this.on_close = null;}\n \n this.socket.unhook(this.hook);\n this.stage.commit();\n this.socket.close(consume);\n};\n\nMQ.send = async function(value) {\n while(true) {\n\tconst awaiter = this.stage.take();\n\tthis.socket.send(this.apply(value));\n\tconst ret = await awaiter;\n\n\tif(ret === undefined) return ret;\n\telse return ret;\n }\n};\n\nMQ.send0 = function(value) {\n this.socket.send(this.apply(value));\n};\n\nMQ.read = async function() {\n while(true) {\n\tconst ret = await this.stage.take();\n\n\tif(ret === undefined) return ret;\n\telse return ret;\n }\n};\n\nMQ.oneshot = async function(value) {\n const ret = await this.send(value);\n this.close();\n return ret;\n};\n\nexport default MessageQueue;\n"],"names":["module","from","base","this","array","over","acceptInvalid","next","waiters","wait","Promise","resolve","push","signal","length","shift","flush","take","async","poll","takeNB","swallow0","token","ar","swallow","timeout","_resolve","_reject","running","then","v","setTimeout","reject","Error","giveMany","values","value","give","commit","Dispatcher","hooks","prototype","hook","cb","unhook","index","apply","clear","addEventListenerHack","to","ev","lam","addEventListener","socket","sock","cloned","socket_dispatcher","ws","disp","pusher","Stage","closed","h0","data","h1","msg","undefined","op","close","consume","removeEventListener","send","dispatcher_dispatcher","sig","MessageQueue","filter","map","message_dispatcher","o","premap","stage","JSON","vars","q","x","parse","_","stringify","MQ","clone","mq","on_close","awaiter","ret","send0","read","oneshot"],"mappings":"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"} \ No newline at end of file diff --git a/build/test.js b/build/test.js new file mode 100644 index 0000000..69952a0 --- /dev/null +++ b/build/test.js @@ -0,0 +1,24 @@ +const sock = new WebSocket("ws://localhost:7777"); + +sock.addEventListener('open', () => { + (async ()=> { + const mw = new MessageQueue(sock); + + let msg = await mw.read(); + console.log(`=> ${msg}`); + let next = await mw.send("uwu"); + console.log(`now => ${next}`); + + mw.close(); + + + await (async () => { + const mw = new MessageQueue(sock); + + let msg = await mw.send("OWO"); + console.log(`other ${msg}`); + + mw.close(true); + })(); + })(); +}); diff --git a/dispatcher.js b/dispatcher.js new file mode 100644 index 0000000..aae3a62 --- /dev/null +++ b/dispatcher.js @@ -0,0 +1,25 @@ + +function Dispatcher() { + this.hooks = []; +} + +Dispatcher.prototype.hook = function(cb) { + this.hooks.push(cb); + return this.hooks.length-1; +}; + +Dispatcher.prototype.unhook = function(index) { + this.hooks[index] = null; +}; + +Dispatcher.prototype.signal = function(value) { + for(const cb of this.hooks) { + if(cb) cb.apply(this, [value]); + } +}; + +Dispatcher.prototype.clear = function() { + this.hooks = []; +}; + +export default Dispatcher; diff --git a/index.html b/index.html new file mode 100644 index 0000000..b1808b9 --- /dev/null +++ b/index.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..e20090e --- /dev/null +++ b/index.js @@ -0,0 +1,73 @@ +const MessageQueue = require("./message"); +const util = require("./util"); +const addEventListenerHack = util.addEventListenerHack; +const uuidv4 = util.uuidv4; +const is_uuid = util.is_uuid; + +/** + * + */ +function Pipe(ws) { + this.socket = ws; + + this.clients=0; + this.MAX_CLIENTS = 100; +} + +const P = Pipe.prototype; + +P.connect = async function(message) { + let msg; + const socket = new MessageQueue(this.socket); + try { + while ((msg = await socket.read()) !== undefined) { // Expects {com: 'open', id: uuidv4()} + try { + msg = JSON.parse(msg); + } catch(_) {continue;} + + if (msg?.com === 'open' && (!message || msg?.msg === message)) { + let id = validate_id(msg?.id); + if(id) { + if(this.clients < this.MAX_CLIENTS) { + const cli = new MessageQueue(this.socket); + let open = true; + cli.premap = (raw) => { + try { + return JSON.parse(raw); + }catch(_) {return null;} + }; + cli.filter = (json) => json?.id === id && (json?.com === 'msg' || ((json?.com === 'close') && cli.close() && false)); + cli.map = (json) => json?.message; + cli.apply = (raw) => { + const json = {message: raw, id: id, com: 'resp'}; + return JSON.stringify(json); + }; + cli.on_close = (self)=> { + if(open && self.id === id) { //in case of mistaken cloning + self.send0({com: 'resp', message: 'closing', id: id}); + this.clients -= 1; + open = false; + } + }; + cli.id= id; + + this.clients -=1; + socket.send0(JSON.stringify({com: 'resp', message: 'accepted', id: id})); + return cli; + } else { + socket.send0(JSON.stringify({com: 'resp', message: 'declined', id: id, reason: 'too many open connections'})); + } + } + } + } + } finally { + socket.close(); + } + return undefined; +}; + +P.close = function() { + this.socket.close(); +}; + +exports = Pipe; diff --git a/message.js b/message.js index c541bc4..fe7ba78 100644 --- a/message.js +++ b/message.js @@ -1,32 +1,8 @@ -const Stage = require('stage-js'); +import Stage from "@notflan/stage-js"; +import Dispatcher from "./dispatcher"; +import {addEventListenerHack} from "./util"; -function Dispatcher() { - this.hooks = []; -} - -Dispatcher.prototype.hook = function(cb) { - this.hooks.push(cb); - return this.hooks.length-1; -}; - -Dispatcher.prototype.unhook = function(index) { - this.hooks[index] = null; -}; - -Dispatcher.prototype.signal = function(value) { - for(const cb of this.hooks) { - if(cb) cb.apply(this, [value]); - } -}; - -Dispatcher.prototype.clear = function() { - this.hooks = []; -}; - -const addEventListenerHack = (to, ev, lam) => { - to.addEventListener(ev, lam); - return lam; -}; +Dispatcher.socket = (sock,cloned) => socket_dispatcher(sock,cloned); const socket_dispatcher = (ws) => { const disp = new Dispatcher(); @@ -35,20 +11,26 @@ const socket_dispatcher = (ws) => { disp.socket = ws; - const h0 = ws.addEventListener('message', (ev) => pusher.give(ev.data)); - const h1 = ws.addEventListener('close', () => pusher.commit()); + const h0 = addEventListenerHack(ws, 'message', (ev) => pusher.give(ev.data)); + const h1 = addEventListenerHack(ws, 'close', () => pusher.commit()); (async () => { - while ( (msg = await pusher.take()) !== undefined) + let msg; + while ( (msg = await pusher.take()) !== undefined) disp.signal({op: 'message', value: msg}); disp.signal({op: 'close'}); + })(); - disp.close = () => { + disp.close = (consume) => { + if(!closed) { ws.removeEventListener('message', h0); ws.removeEventListener('close', h1); pusher.commit(); + + if(consume) + ws.close(); closed=true; } }; @@ -62,10 +44,38 @@ const socket_dispatcher = (ws) => { return disp; }; -function MessageQueue(ws, filter, apply) { - this.socket = socket_dispatcher(ws); - this.filter = filter || (()=>true); - this.apply = apply || ((o) => o); +const message_dispatcher = (ws) => { + return dispatcher_dispatcher(ws.socket); +}; + +const dispatcher_dispatcher = (ws) => { + const disp = new Dispatcher(); + let closed=false; + + disp.socket = ws.socket; + + const hook = ws.hook((sig) => disp.signal(sig));//just pass it + + disp.close = (consume) => { + if(!closed){ + ws.unhook(hook); + if(consume && ws.close) + ws.close(true); + closed=true; + } + }; + disp.send = (msg) => { + if(!closed) ws.socket.send(msg); + }; +}; + +function MessageQueue(ws, filter, apply, map) { + this.socket = (ws instanceof Dispatcher) ? dispatcher_dispatcher(ws) : (ws instanceof MessageQueue) ? message_dispatcher(ws) : socket_dispatcher(ws); + + this.filter = filter || (()=>true); // Filter incoming messages. Return true to keep, false to ignore. + this.apply = apply || ((o) => o); // Apply this function to outgoing messages. + this.map = map || ((o) => o); // Apply this function to incoming messages after `filter` + this.premap = ((o) => o); // Apply this function to incoming messages before `filter` const stage = this.stage = new Stage(); const base = this; @@ -79,7 +89,9 @@ function MessageQueue(ws, filter, apply) { base.close(); break; case 'message': - stage.give(msg.value); + msg = this.premap(msg.value); + if(this.filter(msg)) + stage.give(this.map(msg)); break; default: //uhh @@ -88,29 +100,65 @@ function MessageQueue(ws, filter, apply) { } }); } +MessageQueue.JSON = (...vars) => { + const q = new MessageQueue(...vars); + q.premap = x=> { + try { + return JSON.parse(x); + } catch(_) { + return null; + } + }; + q.apply = x=> JSON.stringify(q.apply(x)); + + return q; +}; const MQ = MessageQueue.prototype; -MQ.close = () => { +MQ.clone = function(filter, apply, map) { + const mq = new MessageQueue(this.socket.socket, filter || this.filter, apply || this.apply, map || this.map); + mq.premap = this.premap; + mq.on_close = this.on_close; + return mq; +}; + +MQ.close = function(consume) { + if (this.on_close) {this.on_close(this, consume); this.on_close = null;} + this.socket.unhook(this.hook); this.stage.commit(); - this.socket.close(); + this.socket.close(consume); }; -MQ.send = async (value) => { - const awaiter = this.stage.take(); +MQ.send = async function(value) { + while(true) { + const awaiter = this.stage.take(); + this.socket.send(this.apply(value)); + const ret = await awaiter; + + if(ret === undefined) return ret; + else return ret; + } +}; + +MQ.send0 = function(value) { this.socket.send(this.apply(value)); - const ret = await awaiter; }; -MQ.read = () => this.stage.take(); +MQ.read = async function() { + while(true) { + const ret = await this.stage.take(); -MQ.oneshot = async (value) => { + if(ret === undefined) return ret; + else return ret; + } +}; + +MQ.oneshot = async function(value) { const ret = await this.send(value); this.close(); return ret; }; - -if(module) - module.exports = MessageQueue; +export default MessageQueue; diff --git a/package-lock.json b/package-lock.json index 3020df5..de0a815 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,10 +4,341 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, "@notflan/stage-js": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@notflan/stage-js/-/stage-js-0.1.0.tgz", "integrity": "sha512-oEkEgLBPB4ghtV1XE6j6gWoX6C3x8BcBjLCpdXTADTKU8q7qAJeM3tP7/fFgrVst5CXR9RtOcXri/kAltuek5g==" + }, + "@types/estree": { + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", + "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==", + "dev": true + }, + "@types/node": { + "version": "14.0.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.26.tgz", + "integrity": "sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA==", + "dev": true + }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "jest-worker": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.1.0.tgz", + "integrity": "sha512-Z9P5pZ6UC+kakMbNJn+tA2RdVdNX5WH1x+5UCBZ9MxIK24pjYtFt96fK+UwBTrjLYm232g1xz0L3eTh51OW+yQ==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "rollup": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz", + "integrity": "sha512-vLNmZFUGVwrnqNAJ/BvuLk1MtWzu4IuoqsH9UWK5AIdO3rt8/CSiJNvPvCIvfzrbNsqKbNzPAG1V2O4eTe2XZg==", + "dev": true, + "requires": { + "fsevents": "~2.1.2" + } + }, + "rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-node-resolve": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", + "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", + "dev": true, + "requires": { + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.11.1", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-6.1.0.tgz", + "integrity": "sha512-4fB3M9nuoWxrwm39habpd4hvrbrde2W2GG4zEGPQg1YITNkM3Tqur5jSuXlWNzbv/2aMLJ+dZJaySc3GCD8oDw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "jest-worker": "^26.0.0", + "serialize-javascript": "^3.0.0", + "terser": "^4.7.0" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true } } } diff --git a/package.json b/package.json index 826c6f0..57c2a11 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,11 @@ "name": "@username/message-queue", "version": "0.0.0", "description": "Await on websocket messages", - "private": true, + "entry": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "rollup -c -w", + "build": "rollup -c" }, "keywords": [ "async" @@ -14,5 +16,11 @@ "dependencies": { "@notflan/stage-js": "^0.1.0" }, - "devDependencies": {} + "devDependencies": { + "rollup": "^2.23.0", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-node-resolve": "^5.2.0", + "rollup-plugin-terser": "^6.1.0", + "ws": "^7.3.1" + } } diff --git a/passthrough.js b/passthrough.js new file mode 100644 index 0000000..8e8132e --- /dev/null +++ b/passthrough.js @@ -0,0 +1,62 @@ +import MessageQueue from "./message.js"; +import {addEventListenerHack, uuidv4, is_uuid} from "./util"; + + +function Pipe(ws) +{ + this.socket =ws; + this.id = uuidv4(); +} + +const P = Pipe.prototype; + +P.connect = async function(message) { + const socket = new MessageQueue(this.socket); + + socket.send0(JSON.stringify({com: 'open', id: this.id, msg: message})); + try { + let msg; + while ((msg = await socket.read()) !== undefined) { + try { + msg = JSON.parse(msg); + } catch(_){continue;} + + if (msg.id === id && msg.com === 'resp') { + if (msg.message === 'accepted') { + const cli = new MessageQueue(this.socket); + let open=true; + cli.id = id; + cli.apply = (raw) => { + return JSON.stringify({message: raw, com: 'msg', id: id}); + }; + cli.premap = (raw) => { + try { + return JSON.parse(raw); + }catch(_) {return null;} + }; + cli.filter = (json) => json?.id === 'id' && (json?.com === 'msg' || json?.com === 'resp' && json?.message === 'closing' && cli.close() && false); + cli.map = (json) => json?.message; + cli.on_close = (self) => { + if(open && self.id === id) { //in case of mistaken cloning + self.send0({com: 'close', message: 'closing', id: id}); + open = false; + } + }; + return cli; + } else { + console.log(`[${id}]: server responded with ${msg.message}: ${msg.reason}`); + return false; + } + } + } + } finally{ + socket.close(); + } + return undefined; +}; + +P.close = function() { + this.socket.close(); +}; + +export default Pipe; diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..8ed4a72 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,16 @@ +import commonjs from "rollup-plugin-commonjs"; +import resolve from "rollup-plugin-node-resolve"; +import {terser} from "rollup-plugin-terser"; + +export default { + input: "message.js", + output: { + sourcemap: true, + format: "iife", + name: "MessageQueue", + file: "build/message-min.js", + }, + plugins: [ + resolve({browser:true}), commonjs(),terser(), + ], +}; diff --git a/test.js b/test.js new file mode 100644 index 0000000..b45a2d7 --- /dev/null +++ b/test.js @@ -0,0 +1,15 @@ +const WebSocket = require('ws'); + +const wss = new WebSocket.Server({port: 7777}); + +wss.on('connection', ws => { + console.log(`conn ${ws}`); + ws.on('message', message => { + console.log(`got ${message}`); + ws.send(`Hello ${message}`); + }); + ws.send("Start"); + ws.on('close', () => { + console.log("closed"); + }); +}); diff --git a/util.js b/util.js new file mode 100644 index 0000000..7838c4f --- /dev/null +++ b/util.js @@ -0,0 +1,21 @@ +function uuidv4() { + if (crypto) { + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); + } + else return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toStirng(16); + }); +} + +const UUID_RE = /^(\w{8}(-\w{4}){3}-\w{12}?)$/i; +function is_uuid(id) { + return (typeof id === 'string' || id instanceof String) && UUID_RE.test(id); +} + +const addEventListenerHack = (to, ev, lam) => { + to.addEventListener(ev, lam); + return lam; +}; + +export {addEventListenerHack, uuidv4, is_uuid};