@cocalc/backend
Version:
CoCalc backend functionality: functionality used by either the hub, the next.js server or the project.
163 lines • 6.43 kB
JavaScript
;
/*
* 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