├── .npmignore ├── .gitignore ├── test ├── run.sh ├── overflow │ ├── run.sh │ ├── client.js │ └── server.js ├── boundary │ ├── run.sh │ ├── client.js │ └── server.js └── mix │ ├── client-browser.js │ ├── client-node.js │ ├── index.html │ ├── run.sh │ ├── client.js │ └── server.js ├── lib ├── browser │ ├── split.js │ ├── upgrade-middleware.js │ ├── request-middleware.js │ └── emitter.js ├── standalone-promise.js ├── emitter.js ├── receptor.js ├── node │ ├── connection-listener.js │ ├── messaging.js │ └── emitter.js └── mock │ └── emitter.js ├── package.json ├── LICENSE └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | /test/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | /test/client-browser-bundle.js -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | cd overflow 2 | sh run.sh 3 | cd ../boundary 4 | sh run.sh 5 | cd ../mix 6 | sh run.sh 7 | cd ../ -------------------------------------------------------------------------------- /test/overflow/run.sh: -------------------------------------------------------------------------------- 1 | rm /tmp/antena-test-node-large.sock 2 | node server.js /tmp/antena-test-node-large.sock & 3 | sleep 1 4 | node client.js /tmp/antena-test-node-large.sock 5 | wait $! -------------------------------------------------------------------------------- /test/boundary/run.sh: -------------------------------------------------------------------------------- 1 | rm /tmp/antena-test-node-boundary.sock ; node server.js /tmp/antena-test-node-boundary.sock & 2 | sleep 1 3 | node client.js /tmp/antena-test-node-boundary.sock 4 | wait $! -------------------------------------------------------------------------------- /test/mix/client-browser.js: -------------------------------------------------------------------------------- 1 | const Client = require("./client.js"); 2 | Client("antena-traffic", "browser-session", (error) => { 3 | if (error) 4 | throw error; 5 | alert("ok"); 6 | }); -------------------------------------------------------------------------------- /test/mix/client-node.js: -------------------------------------------------------------------------------- 1 | const Client = require("./client.js"); 2 | Client(process.argv[2], "node-session-"+String(process.argv[2]), (error) => { 3 | if (error) 4 | throw error; 5 | console.log("ok"); 6 | }); -------------------------------------------------------------------------------- /test/mix/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Antena Browser 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/browser/split.js: -------------------------------------------------------------------------------- 1 | module.exports = (url, splitter) => { 2 | if (url.startsWith("http://")) 3 | url = url.substring(url.indexOf("/", 7)); 4 | if (url.startsWith("/") && url.indexOf(splitter) === 1) 5 | return url.substring(splitter.length + 2); 6 | return null; 7 | }; -------------------------------------------------------------------------------- /test/mix/run.sh: -------------------------------------------------------------------------------- 1 | browserify client-browser.js > client-browser-bundle.js 2 | rm /tmp/antena-test.sock 3 | node server.js /tmp/antena-test.sock 8000 8080 & 4 | sleep 1 5 | node client-node.js /tmp/antena-test.sock 6 | node client-node.js 8000 7 | node client-node.js [::1]:8000 8 | node client-node.js 127.0.0.1:8000 9 | open http://localhost:8080/index.html 10 | sleep 1 11 | rm client-browser-bundle.js 12 | wait $! -------------------------------------------------------------------------------- /lib/standalone-promise.js: -------------------------------------------------------------------------------- 1 | 2 | // https://tc39.github.io/ecma262/#sec-properties-of-promise-instances 3 | 4 | module.exports = () => { 5 | let reject, resolve; 6 | const promise = new Promise((closure1, closure2) => { 7 | resolve = closure1; 8 | reject = closure2; 9 | }); 10 | promise._pending = true; 11 | promise._reject = reject; 12 | promise._resolve = resolve; 13 | return promise; 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antena", 3 | "description": "Isomorphic communication library for node and browsers", 4 | "version": "4.0.10", 5 | "author": { 6 | "name": "Laurent Christophe", 7 | "email": "lachrist@vub.ac.be" 8 | }, 9 | "repository": "lachrist/antena", 10 | "license": "MIT", 11 | "dependencies": { 12 | "posix-socket": "0.0.11", 13 | "ws": "^6.2.0" 14 | }, 15 | "keywords": [ 16 | "Request", 17 | "Connect", 18 | "Http", 19 | "WebSocket", 20 | "Upgrade" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /test/boundary/client.js: -------------------------------------------------------------------------------- 1 | const Emitter = require("../../lib/emitter.js"); 2 | const session = "overflow"; 3 | Emitter(process.argv[process.argv.length - 1], session, (error, emitter) => { 4 | if (error) 5 | throw error; 6 | let state = true; 7 | emitter.onpush = (message) => { 8 | console.log("onpush", message); 9 | if (state) { 10 | state = false; 11 | if (message !== "ab") throw new Error("Wrong first message"); 12 | } else { 13 | if (message !== "cd") throw new Error("Wrong second message"); 14 | emitter.terminate(); 15 | } 16 | }; 17 | }); -------------------------------------------------------------------------------- /test/mix/client.js: -------------------------------------------------------------------------------- 1 | 2 | const Emitter = require("../../lib/emitter.js") 3 | 4 | module.exports = (address, session, callback) => { 5 | console.log(session+"..."); 6 | Emitter(address, session, (error, emitter) => { 7 | if (error) 8 | return callback(error); 9 | emitter.then(callback, callback); 10 | emitter.onpush = (message) => { 11 | console.assert(message === "barbar", "Wrong post -> push"); 12 | emitter.terminate(); 13 | }; 14 | emitter.onterminate = () => { 15 | console.assert(emitter.pull("qux") === "quxqux", "Wrong termination pull"); 16 | }; 17 | console.assert(emitter.pull("foo") === "foofoo", "Wrong pull"); 18 | emitter.post("bar"); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /test/overflow/client.js: -------------------------------------------------------------------------------- 1 | const Emitter = require("../../lib/emitter.js"); 2 | const session = "overflow"; 3 | Emitter(process.argv[process.argv.length - 1], session, (error, emitter) => { 4 | if (error) 5 | throw error; 6 | emitter.then(() => {}, (error) => { throw error }); 7 | console.log("pull", session); 8 | const response = emitter.pull("a".repeat(100000)); 9 | if (response !== "b".repeat(100000)) 10 | throw new Error("Pull mismatch"); 11 | console.log("post", session); 12 | emitter.post("c".repeat(100000)); 13 | emitter.onpush = (message) => { 14 | console.log("onpush", session); 15 | if (message !== "d".repeat(100000)) 16 | throw new Error("Push mismatch"); 17 | emitter.terminate(0); 18 | }; 19 | }); -------------------------------------------------------------------------------- /test/overflow/server.js: -------------------------------------------------------------------------------- 1 | 2 | const Net = require("net"); 3 | const Receptor = require("../../lib/receptor.js"); 4 | 5 | const server = Net.createServer(); 6 | server.listen(process.argv[process.argv.length - 1]); 7 | 8 | const receptor = Receptor(); 9 | server.on("connection", receptor.ConnectionListener()); 10 | 11 | receptor.onpull = (session, message, callback) => { 12 | console.log("onpull", session); 13 | if (message !== "a".repeat(100000)) 14 | throw new Error("Pull missmatch"); 15 | callback("b".repeat(100000)); 16 | }; 17 | 18 | receptor.onpost = (session, message) => { 19 | console.log("onpost", session); 20 | if (message !== "c".repeat(100000)) 21 | throw new Error("Post mismatch"); 22 | console.log("push", session); 23 | receptor.push(session, "d".repeat(100000)); 24 | }; 25 | 26 | setTimeout(() => { server.close() }, 2000); 27 | -------------------------------------------------------------------------------- /test/boundary/server.js: -------------------------------------------------------------------------------- 1 | 2 | const Net = require("net"); 3 | const Receptor = require("../../lib/receptor.js"); 4 | 5 | const server = Net.createServer(); 6 | server.listen(process.argv[process.argv.length - 1]); 7 | 8 | const receptor = Receptor(); 9 | const listener = receptor.ConnectionListener(); 10 | server.on("connection", (socket) => { 11 | listener(socket); 12 | setTimeout(() => { 13 | const buffer = Buffer.alloc(5); 14 | buffer.writeUInt32LE(6, 0); 15 | buffer.write("a", 4, 1, "utf8"); 16 | socket.write(buffer); 17 | setTimeout(() => { 18 | const buffer = Buffer.alloc(7); 19 | buffer.write("b", 0, 1, "utf8"); 20 | buffer.writeUInt32LE(6, 1); 21 | buffer.write("cd", 5, 2, "utf8"); 22 | socket.write(buffer); 23 | }, 1000); 24 | }, 1000); 25 | }); 26 | 27 | setTimeout(() => { server.close() }, 2000); 28 | -------------------------------------------------------------------------------- /lib/emitter.js: -------------------------------------------------------------------------------- 1 | const process = (function () { return this } ()).process; 2 | // https://github.com/iliakan/detect-node 3 | if (Object.prototype.toString.call(process) === "[object process]") { 4 | const browserify_do_no_require1 = __dirname + "/mock/emitter.js"; 5 | const browserify_do_no_require2 = __dirname + "/node/emitter.js"; 6 | // In case antena is browserified and then fed to a node-based headless browser, 7 | // we want to use the real node require and not the shim provided by Browserify. 8 | const MockEmitter = process.mainModule.require(browserify_do_no_require1); 9 | const NodeEmitter = process.mainModule.require(browserify_do_no_require2); 10 | module.exports = (address, session, callback) => (typeof address === "object" ? MockEmitter : NodeEmitter)(address, session, callback); 11 | } else { 12 | module.exports = require("./browser/emitter.js"); 13 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Laurent Christophe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/browser/upgrade-middleware.js: -------------------------------------------------------------------------------- 1 | 2 | const Ws = require("ws"); 3 | const Split = require("./split.js"); 4 | 5 | module.exports = function (splitter = "__antena__") { 6 | const wss = new Ws.Server({noServer:true}); 7 | return (request, socket, head, next) => { 8 | const session = Split(request.url, splitter); 9 | if (session) { 10 | wss.handleUpgrade(request, socket, head, (websocket) => { 11 | websocket._antena_push = push; 12 | websocket._antena_session = session; 13 | websocket._antena_receptor = this; 14 | if (this._connect(session, websocket)) { 15 | websocket.on("close", onclose); 16 | websocket.on("message", onmessage); 17 | } else { 18 | websocket.close(1002, "Already Connected"); 19 | this.onerror(session, new Error("Websocket already connected")); 20 | } 21 | }); 22 | } else if (next) { 23 | next(); 24 | } 25 | return Boolean(session); 26 | }; 27 | }; 28 | 29 | function push (message) { 30 | this.send(message); 31 | } 32 | 33 | function onclose (code, reason) { 34 | this._antena_receptor._disconnect(this._antena_session, this); 35 | } 36 | 37 | function onmessage (message) { 38 | this._antena_receptor.onpost(this._antena_session, message); 39 | } 40 | -------------------------------------------------------------------------------- /test/mix/server.js: -------------------------------------------------------------------------------- 1 | 2 | const Fs = require("fs"); 3 | const Net = require("net"); 4 | const Http = require("http"); 5 | const Path = require("path"); 6 | const Client = require("./client.js"); 7 | const Receptor = require("../../lib/receptor.js"); 8 | 9 | const server1 = Net.createServer(); 10 | const server2 = Net.createServer(); 11 | const server3 = Http.createServer(); 12 | 13 | server1.listen(process.argv[2]); 14 | server2.listen(Number(process.argv[3])); 15 | server3.listen(Number(process.argv[4])); 16 | 17 | const receptor = Receptor(); 18 | 19 | receptor.onpost = (session, message) => { 20 | receptor.push(session, message + message); 21 | }; 22 | 23 | receptor.onpull = (session, message, callback) => { 24 | callback(message + message); 25 | }; 26 | 27 | Client(receptor, "mock-session", (error) => { 28 | if (error) 29 | throw error; 30 | console.log("ok"); 31 | }); 32 | 33 | server1.on("connection", receptor.ConnectionListener()); 34 | 35 | server2.on("connection", receptor.ConnectionListener()); 36 | 37 | const request_middleware = receptor.RequestMiddleware("antena-traffic"); 38 | server3.on("request", (request, response) => { 39 | if (!request_middleware(request, response)) { 40 | if (request.url === "/index.html" || request.url === "/client-browser-bundle.js") { 41 | Fs.createReadStream(Path.join(__dirname, request.url)).pipe(response); 42 | } else { 43 | response.writeHead(404); 44 | response.end(); 45 | } 46 | } 47 | }); 48 | 49 | const upgrade_middleware = receptor.UpgradeMiddleware("antena-traffic"); 50 | server3.on("upgrade", (request, socket, head) => { 51 | if (!upgrade_middleware(request, socket, head)) { 52 | throw new Error("Upgrade request not handled"); 53 | } 54 | }); 55 | 56 | setTimeout(() => { 57 | server1.close(() => { 58 | console.log("server1 closed"); 59 | }); 60 | server2.close(() => { 61 | console.log("server2 closed"); 62 | }); 63 | server3.close(() => { 64 | console.log("server3 closed"); 65 | }); 66 | }, 4000); 67 | -------------------------------------------------------------------------------- /lib/browser/request-middleware.js: -------------------------------------------------------------------------------- 1 | 2 | const Split = require("./split.js"); 3 | 4 | module.exports = function (splitter = "__antena__") { 5 | return (request, response, next) => { 6 | const prefix = Split(request.url, splitter); 7 | if (prefix) { 8 | const token = prefix.substring(1); 9 | const session = this._authentify(token); 10 | response.sendDate = false; 11 | response.setHeader("Content-Type", "text/plain;charset=UTF-8"); 12 | if (session) { 13 | if (request.method !== "GET") { 14 | request._antena_session = session; 15 | request._antena_message = ""; 16 | request._antena_receptor = this; 17 | request.on("data", ondata); 18 | } 19 | if (prefix[0] === "+") { 20 | if (request.method === "GET") { 21 | this.onpull(session, "", (message) => { 22 | response.end(message, "utf8"); 23 | }); 24 | } else { 25 | request._antena_response = response; 26 | request.on("end", onend1); 27 | } 28 | } else if (prefix[0] === "*") { 29 | request.on("end", onend2); 30 | response.end(); 31 | } else if (prefix[0] === ".") { 32 | if (!this._revoke(token)) 33 | response.writeHead(400, "Fail to revoke token"); 34 | response.end(); 35 | } else { 36 | response.writeHead(400, "Invalid message"); 37 | response.end(); 38 | } 39 | } else { 40 | response.writeHead(400, "Token Revoked"); 41 | response.end(); 42 | } 43 | } else if (next) { 44 | next(); 45 | } 46 | return Boolean(prefix); 47 | } 48 | }; 49 | 50 | function ondata (data) { 51 | this._antena_message += data; 52 | } 53 | 54 | function onend1 () { 55 | this._antena_receptor.onpull(this._antena_session, this._antena_message, (message) => { 56 | this._antena_response.end(message, "utf8"); 57 | }); 58 | } 59 | 60 | function onend2 () { 61 | this._antena_receptor.onpost(this._antena_session, this._antena_message); 62 | } 63 | -------------------------------------------------------------------------------- /lib/receptor.js: -------------------------------------------------------------------------------- 1 | 2 | const Crypto = require("crypto"); 3 | 4 | const ConnectionListener = require("./node/connection-listener.js"); 5 | const RequestMiddleware = require("./browser/request-middleware.js"); 6 | const UpgradeMiddleware = require("./browser/upgrade-middleware.js"); 7 | 8 | const onpost = (session, message) => { 9 | throw new Error("Post lost from "+session+": "+message); 10 | }; 11 | 12 | const onpull = (session, message) => { 13 | throw new Error("Pull lost from "+session+": "+message); 14 | }; 15 | 16 | module.exports = () => ({ 17 | _connections: {__proto__:null}, 18 | _sessions: {__proto__:null}, 19 | _messagess: {__proto__:null}, 20 | _connect: connect, 21 | _disconnect: disconnect, 22 | _authentify: authentify, 23 | _revoke: revoke, 24 | push, 25 | onpost, 26 | onpull, 27 | ConnectionListener, 28 | RequestMiddleware, 29 | UpgradeMiddleware, 30 | }); 31 | 32 | function authentify (token) { 33 | return this._sessions[token]; 34 | } 35 | 36 | function revoke (token) { 37 | const session = this._sessions[token]; 38 | if (session) 39 | this._sessions[token] = undefined; 40 | return Boolean(session); 41 | } 42 | 43 | function connect (session, connection) { 44 | if (session in this._connections) 45 | return false; 46 | for (let token in this._sessions) { 47 | if (this._sessions[token] === session) { 48 | this._sessions[token] = undefined; 49 | } 50 | } 51 | let token 52 | do { 53 | token = Crypto.randomBytes(6).toString("base64").replace(/\+/g, "-").replace(/\//g, "_"); 54 | } while (token in this._sessions); 55 | this._sessions[token] = session; 56 | this._connections[session] = connection; 57 | connection._antena_push(token); 58 | if (session in this._messagess) { 59 | const messages = this._messagess[session]; 60 | for (let index = 0; index < messages.lenght; index++) 61 | connection._antena_push(messages[index]); 62 | delete this._messagess[session]; 63 | } 64 | return true; 65 | } 66 | 67 | function disconnect (session, connection) { 68 | if (!this._connections[session]) 69 | return false; 70 | if (this._connections[session] !== connection) 71 | return false; 72 | delete this._connections[session]; 73 | return true; 74 | } 75 | 76 | function push (session, message) { 77 | if (session in this._connections) { 78 | this._connections[session]._antena_push(message); 79 | } else if (session in this._messagess) { 80 | this._messagess[session].push(message); 81 | } else { 82 | this._messagess[session] = [message]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/node/connection-listener.js: -------------------------------------------------------------------------------- 1 | 2 | const Messaging = require("./messaging.js"); 3 | 4 | module.exports = function () { 5 | return (socket) => { 6 | socket.setNoDelay(true); 7 | Messaging(socket); 8 | socket._antena_receptor = this; 9 | socket._antena_receive = receive0; 10 | socket._antena_session = null; 11 | socket._antena_token = null; 12 | socket._antena_push = null; 13 | socket.on("close", onclose0); 14 | socket.on("end", onend); 15 | }; 16 | }; 17 | 18 | function onclose0 () { 19 | this.destroy(new Error("'close' before session message")); 20 | } 21 | 22 | function onerror1 (error) { 23 | this._antena_receptor._disconnect(this._antena_session, this); 24 | } 25 | 26 | function onend () { 27 | if (this.writable) { 28 | this.end(); 29 | } 30 | } 31 | 32 | function onend1 () { 33 | this._antena_receptor._disconnect(this._antena_session, this); 34 | } 35 | 36 | function receive0 (message) { 37 | this.removeListener("close", onclose0); 38 | if (message[0] === "@") { 39 | const session = message.substring(1); 40 | this._antena_session = session; 41 | this._antena_push = this._antena_send; 42 | if (this._antena_receptor._connect(session, this)) { 43 | this.on("end", onend1); 44 | this.on("error", onerror1); 45 | this._antena_receive = receive1; 46 | } else { 47 | this.destroy(new Error("Already connected")); 48 | } 49 | } else if (message[0] === "$") { 50 | this._antena_token = message.substring(1); 51 | this._antena_session = this._antena_receptor._authentify(this._antena_token); 52 | if (this._antena_session) { 53 | this._antena_receive = receive2; 54 | } else { 55 | this.destroy(new Error("Invalid token")); 56 | } 57 | } else { 58 | this.destroy(new Error("Invalid session message")); 59 | } 60 | } 61 | 62 | function receive1 (message) { 63 | this.destroy(new Error("Synchronous node socket should not receive any message")); 64 | } 65 | 66 | function receive2 (message) { 67 | if (this._antena_receptor._authentify(this._antena_token) === this._antena_session) { 68 | if (message[0] === "?") { 69 | this._antena_receptor.onpull(this._antena_session, message.substring(1), (message) => { 70 | this._antena_send(message); 71 | }); 72 | } else if (message[0] === "!") { 73 | this._antena_receptor.onpost(this._antena_session, message.substring(1)); 74 | } else if (message === ".") { 75 | this._antena_receptor._revoke(this._antena_token); 76 | } else { 77 | this.destroy(new Error("Asynchronous node socket got an invalid message")); 78 | } 79 | } else { 80 | this.destroy(this, new Error("Asynchronous node socket used a rvoked token")); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/node/messaging.js: -------------------------------------------------------------------------------- 1 | 2 | // https://github.com/nodejs/node/issues/26607 3 | // let BUFFER = Buffer.allocUnsafe(1024); 4 | // function send (message) { 5 | // let bytelength = BUFFER.write(message, 4, "utf8") + 4; 6 | // if (bytelength > BUFFER.length - 8) { 7 | // bytelength = Buffer.byteLength(message, "utf8") + 4; 8 | // BUFFER = Buffer.allocUnsafe(bytelength + 8); 9 | // BUFFER.write(message, 4, "utf8"); 10 | // } 11 | // BUFFER.writeUInt32LE(bytelength, 0); 12 | // this.write(BUFFER.slice(0, bytelength)); 13 | // }; 14 | 15 | module.exports = (socket) => { 16 | socket._antena_buffer = Buffer.allocUnsafe(1024); 17 | socket._antena_length = 0; 18 | socket._antena_send = send; 19 | socket.on("data", ondata); 20 | } 21 | 22 | function send (message) { 23 | const body = Buffer.from(message, "utf8"); 24 | const head = Buffer.allocUnsafe(4); 25 | head.writeUInt32LE(body.length + 4, 0); 26 | this.write(head); 27 | this.write(body); 28 | } 29 | 30 | function ondata (buffer) { 31 | while (buffer.length) { 32 | // Not enough data to compute the message's bytelength 33 | if (this._antena_length + buffer.length < 4) { 34 | buffer.copy(this._antena_buffer, this._antena_length); 35 | this._antena_length += buffer.length; 36 | break; 37 | } 38 | // Optimization when the antena buffer is empty 39 | if (this._antena_length === 0) { 40 | const target = buffer.readUInt32LE(0); 41 | if (buffer.length >= target) { 42 | this._antena_receive(buffer.toString("utf8", 4, target)); 43 | buffer = buffer.slice(target); 44 | continue; 45 | } 46 | } 47 | // Make sure the antena buffer has enough byte to read the message's bytelength 48 | if (this._antena_length < 4) { 49 | buffer.copy(this._antena_buffer, this._antena_length, 0, 4 - this._antena_length); 50 | buffer = buffer.slice(4 - this._antena_length); 51 | this._antena_length = 4; 52 | } 53 | // Read the message's bytelength 54 | const target = this._antena_buffer.readUInt32LE(0); 55 | // Copy the part of the input buffer that is still in the current message boundary 56 | const tocopy = Math.min(buffer.length, target - this._antena_length); 57 | if (this._antena_buffer.length < target + tocopy) { 58 | const temporary = this._antena_buffer; 59 | this._antena_buffer = Buffer.allocUnsafe(target + tocopy); 60 | temporary.copy(this._antena_buffer, 0, 0, this._antena_length); 61 | } 62 | buffer.copy(this._antena_buffer, this._antena_length, 0, tocopy); 63 | buffer = buffer.slice(tocopy); 64 | this._antena_length += tocopy; 65 | // We reached the message boundary 66 | if (this._antena_length === target) { 67 | this._antena_receive(this._antena_buffer.toString("utf8", 4, target)); 68 | this._antena_length = 0; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/mock/emitter.js: -------------------------------------------------------------------------------- 1 | 2 | const StandalonePromise = require("../standalone-promise.js") 3 | 4 | module.exports = (receptor, session, callback) => { 5 | const emitter = StandalonePromise(); 6 | emitter._receptor = receptor; 7 | emitter._token = null; 8 | emitter._terminated = false; 9 | emitter._antena_push = push; 10 | emitter.session = session; 11 | emitter.pull = pull; 12 | emitter.post = post; 13 | emitter.terminate = terminate; 14 | emitter.destroy = destroy; 15 | emitter.onpush = onpush; 16 | emitter.onterminate = onterminate; 17 | if (receptor._connect(session, emitter)) { 18 | callback(null, emitter); 19 | } else { 20 | callback(new Error("Emitter already connected")); 21 | } 22 | }; 23 | 24 | //////////// 25 | // Helper // 26 | //////////// 27 | 28 | const failure = (emitter, error) => { 29 | emitter._receptor._disconnect(emitter.session, emitter); 30 | emitter._pending = false; 31 | emitter._reject(error); 32 | }; 33 | 34 | const success = (emitter) => { 35 | emitter.onterminate(); 36 | emitter._receptor._revoke(emitter._token); 37 | emitter._pending = false; 38 | emitter._resolve(null); 39 | }; 40 | 41 | const pushcb = (emitter, message) => { 42 | emitter.onpush(message); 43 | }; 44 | 45 | const postcb = (emitter, message) => { 46 | emitter._receptor.onpost(emitter.session, message); 47 | }; 48 | 49 | const terminatecb = (emitter) => { 50 | if (emitter._receptor._disconnect(emitter.session, emitter)) { 51 | setImmediate(success, emitter); 52 | } 53 | }; 54 | 55 | //////////////// 56 | // Connection // 57 | //////////////// 58 | 59 | function push (message) { 60 | if (this._token === null) { 61 | this._token = message; 62 | } else { 63 | setImmediate(pushcb, this, message); 64 | } 65 | } 66 | 67 | ///////////// 68 | // Emitter // 69 | ///////////// 70 | 71 | const onterminate = () => {}; 72 | 73 | const onpush = (message) => { 74 | throw new Error("Lost push message: "+message); 75 | }; 76 | 77 | function pull (message) { 78 | if (this._pending) { 79 | let result = null; 80 | let done = false; 81 | this._receptor.onpull(this.session, message, (argument0) => { 82 | done = true; 83 | result = argument0; 84 | }); 85 | if (done) 86 | return result; 87 | failure(this, new Error("Callback not synchronously called")); 88 | } 89 | return null; 90 | } 91 | 92 | function terminate () { 93 | if (!this._pending || this._terminated) 94 | return false; 95 | this._terminated = true; 96 | setImmediate(terminatecb, this); 97 | return true; 98 | } 99 | 100 | function post (message) { 101 | if (this._pending) 102 | setImmediate(postcb, this, message); 103 | return this._pending; 104 | }; 105 | 106 | function destroy () { 107 | if (!this._pending) 108 | return false; 109 | failure(this, new Error("Emitter destroyed by the user")); 110 | return true; 111 | }; 112 | -------------------------------------------------------------------------------- /lib/browser/emitter.js: -------------------------------------------------------------------------------- 1 | 2 | const StandalonePromise = require("../standalone-promise.js") 3 | 4 | const CONNECTING = 0; 5 | const OPEN = 1; 6 | const CLOSING = 2; 7 | const CLOSED = 3; 8 | 9 | module.exports = (options = {}, session, callback) => { 10 | if (typeof options === "string") 11 | options = {splitter:options}; 12 | const secure = options.secure || location.protocol === "https:" ? "s" : ""; 13 | const hostname = options.hostname || location.hostname; 14 | const port = (options.port||location.port) ? ":" + (options.port||location.port) : ""; 15 | const splitter = options.splitter || "__antena__"; 16 | const websocket = new WebSocket("ws"+secure+"://"+hostname+port+"/"+splitter+"/"+session); 17 | websocket.onclose = (event) => { 18 | callback(new Error("Websocket closed: "+event.code+" "+event.reason)); 19 | }; 20 | websocket.onmessage = ({data:token}) => { 21 | const emitter = StandalonePromise(); 22 | emitter._pending = true; 23 | emitter._url1 = "http"+secure+"://"+hostname+port+"/"+splitter+"/+"+token; 24 | emitter._url2 = "http"+secure+"://"+hostname+port+"/"+splitter+"/*"+token; 25 | emitter._url3 = "http"+secure+"://"+hostname+port+"/"+splitter+"/."+token; 26 | emitter._websocket = websocket; 27 | emitter.destroy = destroy; 28 | emitter.terminate = terminate; 29 | emitter.pull = pull; 30 | emitter.post = post; 31 | emitter.onpush = onpush; 32 | emitter.onterminate = onterminate; 33 | websocket._antena_emitter = emitter; 34 | websocket.onmessage = onmessage; 35 | websocket.onclose = onclose; 36 | callback(null, emitter); 37 | }; 38 | }; 39 | 40 | //////////// 41 | // Helper // 42 | //////////// 43 | 44 | const failure = (emitter, error) => { 45 | if (emitter._pending) { 46 | if (emitter._websocket.readyState !== OPEN) 47 | emitter._websocket.close(1001); 48 | emitter._pending = false; 49 | emitter._reject(error); 50 | } 51 | }; 52 | 53 | const success = (emitter) => { 54 | if (emitter._pending) { 55 | emitter.onterminate(); 56 | if (emitter._pending) { 57 | const request = new XMLHttpRequest(); 58 | request.open("GET", emitter._url3, false); 59 | request.setRequestHeader("Cache-Control", "no-cache"); 60 | request.setRequestHeader("User-Agent", "*"); 61 | try { 62 | request.send(); 63 | } catch (error) { 64 | return failure(emitter, error); 65 | } 66 | if (request.status !== 200) 67 | return failure(emitter, new Error("HTTP Error: "+request.status+" "+request.statusText)); 68 | emitter._pending = false; 69 | emitter._resolve(null); 70 | } 71 | } 72 | }; 73 | 74 | /////////////// 75 | // WebSocket // 76 | /////////////// 77 | 78 | function onmessage ({data}) { 79 | this._antena_emitter.onpush(data); 80 | } 81 | 82 | function onclose ({wasClean, code, reason}) { 83 | if (wasClean) { 84 | if (code === 1000) { 85 | success(this._antena_emitter) 86 | } else { 87 | failure(this._antena_emitter, new Error("Abnormal websocket closure: "+code+" "+reason)); 88 | } 89 | } else { 90 | failure(this._antena_emitter, new Error("Unclean websocket closure")); 91 | } 92 | } 93 | 94 | ////////////// 95 | // Emitter // 96 | ////////////// 97 | 98 | const onpush = (message) => { 99 | throw new Error("Lost a push message: "+message); 100 | }; 101 | 102 | const onterminate = () => {}; 103 | 104 | function terminate () { 105 | if (this._websocket.readyState !== OPEN) 106 | return false; 107 | this._websocket.close(1000); 108 | return true; 109 | } 110 | 111 | function destroy () { 112 | if (!this._url1) 113 | return false; 114 | failure(this, new Error("Destroyed by the user")); 115 | return true; 116 | } 117 | 118 | function post (message) { 119 | if (this._pending) { 120 | if (this._websocket.readyState === OPEN) { 121 | this._websocket.send(message); 122 | } else { 123 | const request = new XMLHttpRequest(); 124 | request.open("PUT", this._url2, true); 125 | request.setRequestHeader("User-Agent", "*"); 126 | try { 127 | request.send(message); 128 | } catch (error) { 129 | failure(this, error); 130 | } 131 | } 132 | } 133 | return this._pending; 134 | } 135 | 136 | function pull (message) { 137 | if (this._pending) { 138 | const request = new XMLHttpRequest(); 139 | if (message) { 140 | request.open("PUT", this._url1, false); 141 | } else { 142 | request.open("GET", this._url1, false); 143 | request.setRequestHeader("Cache-Control", "no-cache"); 144 | } 145 | request.setRequestHeader("User-Agent", "*"); 146 | try { 147 | request.send(message); 148 | } catch (error) { 149 | failure(this, error); 150 | return null; 151 | } 152 | if (request.status === 200) 153 | return request.responseText; 154 | failure(this, new Error("HTTP Error: "+request.status+" "+request.statusText)); 155 | } 156 | return null; 157 | } 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Antena 2 | 3 | Antena is yet an other JavaScript communication library. 4 | Antena normalises the server-client model for node and browsers. 5 | In Antena, the server is called receptor and its clients are called emitters. 6 | Both receptors and emitters can perform push notifications but only emitters can perform synchronous pull requests. 7 | For node emitters, synchronous pull requests are implemented using [posix-socket](https://www.npmjs.com/package/posix-socket) which is much faster than using the synchronous methods of `child_process` or `fs`. 8 | 9 | ```js 10 | const AntenaReceptor = require("antena/lib/receptor"); 11 | const receptor = AntenaReceptor(); 12 | receptor.onpull = (session, message, callback) => { 13 | console.assert(session === "antena-rocks"); 14 | callback(message+"World"); 15 | }; 16 | receptor.onpost = (session, message) => { 17 | console.assert(session === "antena-rocks"); 18 | receptor.push(session, message+"Bar"); 19 | }; 20 | receptor.terminate = (session, (error) => { 21 | // Politely 22 | }); 23 | // For Node Emitters 24 | const server1 = require("net").createServer(8080); 25 | const onconnection = receptor.ConnectionListener() 26 | server1.on("connection", onconnection); 27 | // For Browser Emitters 28 | const server2 = require("http").createServer(8000); 29 | const onrequest = receptor.RequestMiddleware("_ANTENA_"); 30 | server2.on("request", (request, response) => { 31 | onrequest(request, response, () => { 32 | // Request not handled by antena 33 | }); 34 | }); 35 | const onupgrade = receptor.UpgradeMiddleware("_ANTENA_"); 36 | server2.on("upgrade", (request, socket, head) => { 37 | onupgrade(request, response, () => { 38 | // Upgrade not handled by Antena 39 | }); 40 | }); 41 | ``` 42 | 43 | ```js 44 | const AntenaEmitter = require("antena/lib/emitter"); 45 | const callback = (error, emitter) => { 46 | if (error) 47 | throw error; 48 | emitter.then(() => { 49 | // The emitter closed normally. 50 | // The emitter can no longer be used. 51 | }, (error) => { 52 | // An (a)synchronous error occurred. 53 | // The emitter can no longer be used. 54 | }); 55 | console.assert(emitter.pull("Hello") === "HelloWorld"); 56 | emitter.push("Foo"); 57 | emitter.onpush = (message) => { 58 | console.assert(messsage === "FooBar"); 59 | emitter.terminate(); 60 | // Emitter is still fully usable after calling terminate. 61 | }; 62 | emitter.onterminate = () => { 63 | // Emitter will no longer receive push events. 64 | // Post an pull can still be performed in this callback. 65 | // After returning, the emitter will be resolved. 66 | }; 67 | }; 68 | // Mock Emitter 69 | AntenaEmitter(receptor, "antena-rocks", callback); 70 | // Node Emitter 71 | AntenaEmitter(8080, "antena-rocks", callback); 72 | // Browser Emitter 73 | AntenaEmitter("_ANTENA_", "antena-rocks", callback); 74 | ``` 75 | 76 | ## Receptor 77 | 78 | ### `receptor = require("antena/receptor")()` 79 | 80 | Create a new receptor. 81 | 82 | * `receptor :: antena.Receptor` 83 | 84 | ### `receptor.push(session, message)` 85 | 86 | Push a message to an emitter identified by its session. 87 | 88 | * `session :: string` 89 | * `message :: string` 90 | 91 | ### `receptor.onpost = (session, message) => { ... }` 92 | 93 | Handler for `emitter.post(message)`. 94 | 95 | * `session :: string` 96 | * `message :: string` 97 | 98 | ### `receptor.onpull = (session, message, callback) => { ... }` 99 | 100 | Handler for `emitter.pull(message)`. 101 | 102 | * `receptor :: antena.Receptor` 103 | * `session :: string` 104 | * `message :: string` 105 | * `callback(message)` 106 | * `message :: String` 107 | 108 | ### `onconnection = receptor.ConnectionListener()` 109 | 110 | Create a listener for the `connection` event of a `net.Server`. 111 | 112 | * `receptor :: antena.Receptor` 113 | * `onconnection(socket)` 114 | * `socket :: net.Socket` 115 | 116 | ### `onrequest = receptor.RequestMiddleware([splitter])` 117 | 118 | Create a middleware for the `request` event of a `http(s).Server`. 119 | 120 | * `receptor :: antena.Receptor` 121 | * `splitter :: string`, default: `"__antena__"` 122 | A string used to single out the traffic from an emitter. 123 | * `handled = onrequest(request, response, [next])` 124 | * `request :: (http|https).IncomingMessage` 125 | * `response :: (http|https).ServerResponse` 126 | * `next()`: 127 | Called if the request was not handled by Antena (if defined). 128 | * `handled :: boolean`: 129 | Indicates whether the request was handled by Antena. 130 | 131 | ### `onupgrade = receptor.UpgradeMiddleware([splitter])` 132 | 133 | Create a middleware for the `upgrade` event of a `http(s).Server`. 134 | 135 | * `receptor :: antena.Receptor` 136 | * `splitter :: string`, default: `"__antena__"` 137 | A string used to single out the traffic from an emitter. 138 | * `handled = onupgrage(request, socket, head, [next])` 139 | * `request :: http.IncomingMessage` 140 | * `socket :: (net|tls).Socket` 141 | * `head :: Buffer` 142 | * `next()`: 143 | Called if the upgrade request was not handled by Antena (if defined). 144 | * `handled :: boolean` 145 | Indicate whether the upgrade request was handled by Antena. 146 | 147 | ## Emitter 148 | 149 | Emitter are [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which represents the lifetime of the connection. 150 | 151 | ### `emitter = require("antena/emitter")(address, session)` 152 | 153 | * `address :: object | string | number | antena.Receptor`: 154 | Antena will choose one of the below three mode: 155 | * If `window` is defined, Antena will perform a browser connection. 156 | The address should then be: 157 | * `object`: 158 | An option object with the following fields: 159 | * `secure :: boolean`, default: `location.protocol === "https:` 160 | * `hostname :: string`, default: `location.hostname` 161 | * `port :: number`, default: `location.port` 162 | * `splitter :: string`, default: `"__antena__"` 163 | * `string`, alias for `{splitter:address}`. 164 | * Else, if `address` is not an object, Antena will perform a node connection. 165 | The address should then be: 166 | * `string` 167 | * Port string; eg `"8080"`: alias for `"[::1]:8080"` 168 | * Local socket address; eg `/tmp/antena.sock` 169 | * IPv4 and port; eg `127.0.0.1:8080` 170 | * IPv6 and port: eg `[::1]:8080` 171 | * `number`, alias for `"[::1]:"+address` 172 | * Else, the address should be `antena.Receptor` and Antena will perform a local mock connection. 173 | * `session :: string`: 174 | All subsequent push/pull requests will be tagged with this string. 175 | 176 | ### `emitter.session :: string` 177 | 178 | ### `emitter.terminate()` 179 | 180 | Attempt to perform a graceful shutdown, the emitter is still fully usable. 181 | 182 | * `callback(error)` 183 | * `error :: Error` 184 | 185 | ### `emitter.destroy()` 186 | 187 | Immediately destroy the emitter, messages might be lost. 188 | 189 | ### `emitter.post(message)` 190 | 191 | Immediately send a message to the receptor, may throw an error. 192 | 193 | * `message :: string` 194 | 195 | ### `result = emitter.pull(query)` 196 | 197 | Perform a synchronous request to the receptor, may throw an error. 198 | 199 | * `query :: string` 200 | * `result :: string` 201 | 202 | ### `emitter.onpush = (message) => { ... }` 203 | 204 | * `message :: string` 205 | -------------------------------------------------------------------------------- /lib/node/emitter.js: -------------------------------------------------------------------------------- 1 | 2 | const Net = require("net"); 3 | const PosixSocket = require("posix-socket"); 4 | const Messaging = require("./messaging.js"); 5 | const StandalonePromise = require("../standalone-promise.js") 6 | 7 | let BUFFER = Buffer.from(new ArrayBuffer(1024)); 8 | 9 | const noop = () => {}; 10 | 11 | module.exports = (address, session, callback) => { 12 | address = convert(address); 13 | let sockfd; 14 | const socket = Net.connect(address.net); 15 | const cleanup = (error) => { 16 | if (callback) { 17 | if (error === false) 18 | error = new Error("Could not receive token before closure"); 19 | socket.destroy(); 20 | if (sockfd) { 21 | try { 22 | PosixSocket.close(sockfd); 23 | } catch (e) { 24 | error = new Error(error.message + " AND " + e.message); 25 | } 26 | } 27 | process.nextTick(callback, error); 28 | callback = null; 29 | } 30 | }; 31 | socket.once("error", cleanup); 32 | socket.once("close", cleanup); 33 | socket.once("connect", () => { 34 | Messaging(socket); 35 | socket._antena_send("@"+session); 36 | socket._antena_receive = (token) => { 37 | try { 38 | sockfd = PosixSocket.socket(address.domain, PosixSocket.SOCK_STREAM, 0); 39 | if (address.domain !== PosixSocket.AF_LOCAL) 40 | PosixSocket.setsockopt(sockfd, PosixSocket.IPPROTO_TCP, PosixSocket.TCP_NODELAY, 1); 41 | PosixSocket.connect(sockfd, address.posix); 42 | output(sockfd, "$"+token); 43 | } catch (error) { 44 | return cleanup(error); 45 | } 46 | const emitter = StandalonePromise(); 47 | emitter._sockfd = sockfd; 48 | emitter._socket = socket; 49 | emitter.onpush = onpush; 50 | emitter.onterminate = onterminate; 51 | emitter.session = session; 52 | emitter.destroy = destroy; 53 | emitter.terminate = terminate; 54 | emitter.post = post; 55 | emitter.pull = pull; 56 | socket.removeListener("error", cleanup); 57 | socket.removeListener("close", cleanup); 58 | socket._antena_emitter = emitter; 59 | socket._antena_receive = receive; 60 | socket.on("error", onerror); 61 | socket.on("close", onclose); 62 | callback(null, emitter); 63 | }; 64 | }); 65 | }; 66 | 67 | //////////// 68 | // Helper // 69 | //////////// 70 | 71 | const convert = (address) => { 72 | if (typeof address === "number" || /^[0-9]+$/.test(address)) 73 | address = "[::1]:"+address; 74 | const ipv4 = /^([0-9.]+)\:([0-9]+)$/.exec(address); 75 | if (ipv4) { 76 | return { 77 | domain: PosixSocket.AF_INET, 78 | posix: { 79 | sin_family: PosixSocket.AF_INET, 80 | sin_port: parseInt(ipv4[2]), 81 | sin_addr: ipv4[1] 82 | }, 83 | net: { 84 | port: parseInt(ipv4[2]), 85 | host: ipv4[1], 86 | family: 4 87 | } 88 | }; 89 | } 90 | const ipv6 = /^\[([0-9a-fA-F:]+)\]\:([0-9]+)$/.exec(address); 91 | if (ipv6) { 92 | return { 93 | domain: PosixSocket.AF_INET6, 94 | posix: { 95 | sin6_family: PosixSocket.AF_INET6, 96 | sin6_port: parseInt(ipv6[2]), 97 | sin6_flowinfo: 0, 98 | sin6_addr: ipv6[1], 99 | sin6_scope_id: 0 100 | }, 101 | net: { 102 | port: parseInt(ipv6[2]), 103 | host: ipv6[1], 104 | family: 6 105 | } 106 | }; 107 | } 108 | return { 109 | domain: PosixSocket.AF_LOCAL, 110 | posix: { 111 | sun_family: PosixSocket.AF_LOCAL, 112 | sun_path: address 113 | }, 114 | net: { 115 | path: address 116 | } 117 | }; 118 | }; 119 | 120 | const output = (sockfd, message) => { 121 | let bytelength = BUFFER.write(message, 4, "utf8") + 4; 122 | if (bytelength > BUFFER.length - 8) { 123 | bytelength = Buffer.byteLength(message, "utf8") + 4; 124 | BUFFER = Buffer.from(new ArrayBuffer(bytelength + 8)); 125 | BUFFER.write(message, 4, "utf8"); 126 | } 127 | BUFFER.writeUInt32LE(bytelength, 0); 128 | if (PosixSocket.send(sockfd, BUFFER.buffer, bytelength, 0) < bytelength) { 129 | throw new Error("Could not send the entire message ("+bytelength+" bytes)"); 130 | } 131 | }; 132 | 133 | const success = (emitter) => { 134 | if (emitter._pending) { 135 | emitter.onterminate(); 136 | if (emitter._pending) { 137 | try { 138 | output(emitter._sockfd, "."); 139 | PosixSocket.shutdown(emitter._sockfd, PosixSocket.SHUT_WR); 140 | if (PosixSocket.recv(emitter._sockfd, BUFFER.buffer, 1, 0) !== 0) { 141 | throw new Error("Received some data instead of a FIN packet"); 142 | } 143 | } catch (error) { 144 | return failure(emitter, error); 145 | } 146 | try { 147 | PosixSocket.close(emitter._sockfd); 148 | } catch (error) { 149 | emitter._sockfd = null; 150 | return failure(emitter, error); 151 | } 152 | emitter._pending = false; 153 | emitter._resolve(null); 154 | } 155 | } 156 | }; 157 | 158 | const failure = (emitter, error) => { 159 | if (emitter.pending) { 160 | if (emitter._sockfd) { 161 | try { 162 | PosixSocket.close(emitter._sockfd); 163 | } catch (caught) { 164 | error = new Error(error.message + " AND " + caught.message); 165 | } 166 | } 167 | if (!emitter._socket.destroyed) 168 | emitter._socket.destroy(); 169 | emitter._pending = false; 170 | emitter._reject(error); 171 | } 172 | }; 173 | 174 | //////////////// 175 | // net.Socket // 176 | //////////////// 177 | 178 | function receive (message) { 179 | this._antena_emitter.onpush(message); 180 | } 181 | 182 | function onerror (error) { 183 | failure(this._antena_emitter, error); 184 | } 185 | 186 | function onclose () { 187 | success(this._antena_emitter); 188 | } 189 | 190 | ///////////// 191 | // Emitter // 192 | ///////////// 193 | 194 | const onpush = (message) => { 195 | throw new Error("Lost push message: "+message); 196 | }; 197 | 198 | const onterminate = () => {}; 199 | 200 | function destroy () { 201 | if (!this._pending) 202 | return false; 203 | failure(this, new Error("Destroyed by the user")); 204 | return true; 205 | } 206 | 207 | function terminate () { 208 | if (!this._socket.writable) 209 | return false; 210 | this._socket.end(); 211 | return true; 212 | } 213 | 214 | function post (message) { 215 | if (this._pending) { 216 | try { 217 | output(this._sockfd, "!"+message); 218 | } catch (error) { 219 | failure(this, error); 220 | } 221 | } 222 | return this._pending; 223 | } 224 | 225 | function pull (string) { 226 | if (this._pending) { 227 | try { 228 | output(this._sockfd, "?" + string); 229 | if (PosixSocket.recv(this._sockfd, BUFFER.buffer, 4, PosixSocket.MSG_WAITALL) < 4) 230 | throw new Error("Could not recv the head of the pull response (4 bytes)"); 231 | const bytelength = BUFFER.readUInt32LE(0) - 4; 232 | if (bytelength > BUFFER.byteLength) 233 | BUFFER = Buffer.from(new ArrayBuffer(bytelength)); 234 | if (PosixSocket.recv(this._sockfd, BUFFER.buffer, bytelength, PosixSocket.MSG_WAITALL) < bytelength) 235 | throw new Error("Could not recv the body of the pull response ("+bytelength+" bytes)"); 236 | return BUFFER.toString("utf8", 0, bytelength); 237 | } catch (error) { 238 | failure(this, error); 239 | } 240 | } 241 | return null; 242 | } 243 | --------------------------------------------------------------------------------