UNPKG

@cocalc/backend

Version:

CoCalc backend functionality: functionality used by either the hub, the next.js server or the project.

163 lines 6.43 kB
"use strict"; /* * This file is part of CoCalc: Copyright © 2022 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = __importDefault(require("@cocalc/backend/logger")); const message_1 = require("@cocalc/util/message"); const misc_1 = require("@cocalc/util/misc"); const winston = (0, logger_1.default)("tcp.enable"); function enable(socket, desc = "") { socket.setMaxListeners(500); // we use a lot of listeners for listening for messages let buf = null; let bufTargetLength = -1; const listenForMesg = (data) => { buf = buf == null ? data : Buffer.concat([buf, data]); while (true) { if (bufTargetLength === -1) { // starting to read a new message if (buf.length >= 4) { bufTargetLength = buf.readUInt32BE(0) + 4; } else { return; // have to wait for more data to find out message length } } if (bufTargetLength <= buf.length) { // read a new message from our buffer const type = buf.slice(4, 5).toString(); const mesg = buf.slice(5, bufTargetLength); switch (type) { case "j": // JSON const s = mesg.toString(); let obj; try { // Do not use "obj = JSON.parse(s)" obj = (0, misc_1.from_json_socket)(s); // this properly parses Date objects } catch (err) { winston.debug(`WARNING: failed to parse JSON message='${(0, misc_1.trunc)(s, 512)}' on socket ${desc} - ${err}`); // skip it. return; } socket.emit("mesg", "json", obj); break; case "b": // BLOB (tagged by a uuid) socket.emit("mesg", "blob", { uuid: mesg.slice(0, 36).toString(), blob: mesg.slice(36), }); break; default: // NOTE -- better to show warning than throw possibly uncaught exception, since // don't want malicious user to cause anything to crash. winston.debug(`WARNING: unknown message type: '${type}'`); return; } buf = buf.slice(bufTargetLength); bufTargetLength = -1; if (buf.length === 0) { return; } } else { // nothing to do but wait for more data return; } } }; socket.on("data", listenForMesg); socket.write_mesg = (type, data, cb) => { if (data == null) { // uncomment this to get a traceback to see what might be causing this... //throw Error(`write_mesg(type='${type}': data must be defined`); cb?.(`write_mesg(type='${type}': data must be defined`); return; } const send = function (s) { const buf = Buffer.alloc(4); // This line was 4 hours of work. It is absolutely // *critical* to change the (possibly a string) s into a // buffer before computing its length and sending it!! // Otherwise unicode characters will cause trouble. if (typeof s === "string") { s = Buffer.from(s); } buf.writeInt32BE(s.length, 0); if (!socket.writable) { cb?.("socket not writable"); return; } else { socket.write(buf); } if (!socket.writable) { cb?.("socket not writable"); return; } else { socket.write(s, cb); } }; switch (type) { case "json": send("j" + (0, misc_1.to_json_socket)(data)); return; case "blob": if (data.uuid == null) { cb?.("data object *must* have a uuid attribute"); return; } if (data.blob == null) { cb?.("data object *must* have a blob attribute"); return; } send(Buffer.concat([ Buffer.from("b"), Buffer.from(data.uuid), Buffer.from(data.blob), ])); return; default: cb?.(`unknown message type '${type}'`); return; } }; // Wait until we receive exactly *one* message of the given type // with the given id, then call the callback with that message. // (If the type is 'blob', with the given uuid.) socket.recv_mesg = ({ type, id, cb, timeout }) => { let done = false; let timeoutId = null; const f = (mesgType, mesg) => { if (type === mesgType && ((type === "json" && mesg.id === id) || (type === "blob" && mesg.uuid === id))) { if (done) return; socket.removeListener("mesg", f); done = true; if (timeoutId != null) { clearTimeout(timeoutId); } cb(mesg); } }; socket.on("mesg", f); if (timeout != null) { timeoutId = setTimeout(() => { if (done) return; done = true; socket.removeListener("mesg", f); cb((0, message_1.error)({ error: `Timed out after ${timeout} seconds.` })); }, timeout * 1000); } }; } exports.default = enable; //# sourceMappingURL=enable-messaging-protocol.js.map