"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Decoder = exports.Encoder = exports.PacketType = exports.protocol = void 0; const Emitter = require("component-emitter"); const binary_1 = require("./binary"); const is_binary_1 = require("./is-binary"); const debug = require("debug")("socket.io-parser"); /** * Protocol version. * * @public */ exports.protocol = 5; var PacketType; (function (PacketType) { PacketType[PacketType["CONNECT"] = 0] = "CONNECT"; PacketType[PacketType["DISCONNECT"] = 1] = "DISCONNECT"; PacketType[PacketType["EVENT"] = 2] = "EVENT"; PacketType[PacketType["ACK"] = 3] = "ACK"; PacketType[PacketType["CONNECT_ERROR"] = 4] = "CONNECT_ERROR"; PacketType[PacketType["BINARY_EVENT"] = 5] = "BINARY_EVENT"; PacketType[PacketType["BINARY_ACK"] = 6] = "BINARY_ACK"; })(PacketType = exports.PacketType || (exports.PacketType = {})); /** * A socket.io Encoder instance */ class Encoder { /** * Encode a packet as a single string if non-binary, or as a * buffer sequence, depending on packet type. * * @param {Object} obj - packet object */ encode(obj) { debug("encoding packet %j", obj); if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) { if (is_binary_1.hasBinary(obj)) { obj.type = obj.type === PacketType.EVENT ? PacketType.BINARY_EVENT : PacketType.BINARY_ACK; return this.encodeAsBinary(obj); } } return [this.encodeAsString(obj)]; } /** * Encode packet as string. */ encodeAsString(obj) { // first is type let str = "" + obj.type; // attachments if we have them if (obj.type === PacketType.BINARY_EVENT || obj.type === PacketType.BINARY_ACK) { str += obj.attachments + "-"; } // if we have a namespace other than `/` // we append it followed by a comma `,` if (obj.nsp && "/" !== obj.nsp) { str += obj.nsp + ","; } // immediately followed by the id if (null != obj.id) { str += obj.id; } // json data if (null != obj.data) { str += JSON.stringify(obj.data); } debug("encoded %j as %s", obj, str); return str; } /** * Encode packet as 'buffer sequence' by removing blobs, and * deconstructing packet into object with placeholders and * a list of buffers. */ encodeAsBinary(obj) { const deconstruction = binary_1.deconstructPacket(obj); const pack = this.encodeAsString(deconstruction.packet); const buffers = deconstruction.buffers; buffers.unshift(pack); // add packet info to beginning of data list return buffers; // write all the buffers } } exports.Encoder = Encoder; /** * A socket.io Decoder instance * * @return {Object} decoder */ class Decoder extends Emitter { constructor() { super(); } /** * Decodes an encoded packet string into packet JSON. * * @param {String} obj - encoded packet */ add(obj) { let packet; if (typeof obj === "string") { packet = this.decodeString(obj); if (packet.type === PacketType.BINARY_EVENT || packet.type === PacketType.BINARY_ACK) { // binary packet's json this.reconstructor = new BinaryReconstructor(packet); // no attachments, labeled binary but no binary data to follow if (packet.attachments === 0) { super.emit("decoded", packet); } } else { // non-binary full packet super.emit("decoded", packet); } } else if (is_binary_1.isBinary(obj) || obj.base64) { // raw binary data if (!this.reconstructor) { throw new Error("got binary data when not reconstructing a packet"); } else { packet = this.reconstructor.takeBinaryData(obj); if (packet) { // received final buffer this.reconstructor = null; super.emit("decoded", packet); } } } else { throw new Error("Unknown type: " + obj); } } /** * Decode a packet String (JSON data) * * @param {String} str * @return {Object} packet */ decodeString(str) { let i = 0; // look up type const p = { type: Number(str.charAt(0)), }; if (PacketType[p.type] === undefined) { throw new Error("unknown packet type " + p.type); } // look up attachments if type binary if (p.type === PacketType.BINARY_EVENT || p.type === PacketType.BINARY_ACK) { const start = i + 1; while (str.charAt(++i) !== "-" && i != str.length) { } const buf = str.substring(start, i); if (buf != Number(buf) || str.charAt(i) !== "-") { throw new Error("Illegal attachments"); } p.attachments = Number(buf); } // look up namespace (if any) if ("/" === str.charAt(i + 1)) { const start = i + 1; while (++i) { const c = str.charAt(i); if ("," === c) break; if (i === str.length) break; } p.nsp = str.substring(start, i); } else { p.nsp = "/"; } // look up id const next = str.charAt(i + 1); if ("" !== next && Number(next) == next) { const start = i + 1; while (++i) { const c = str.charAt(i); if (null == c || Number(c) != c) { --i; break; } if (i === str.length) break; } p.id = Number(str.substring(start, i + 1)); } // look up json data if (str.charAt(++i)) { const payload = tryParse(str.substr(i)); if (Decoder.isPayloadValid(p.type, payload)) { p.data = payload; } else { throw new Error("invalid payload"); } } debug("decoded %s as %j", str, p); return p; } static isPayloadValid(type, payload) { switch (type) { case PacketType.CONNECT: return typeof payload === "object"; case PacketType.DISCONNECT: return payload === undefined; case PacketType.CONNECT_ERROR: return typeof payload === "string" || typeof payload === "object"; case PacketType.EVENT: case PacketType.BINARY_EVENT: return Array.isArray(payload) && typeof payload[0] === "string"; case PacketType.ACK: case PacketType.BINARY_ACK: return Array.isArray(payload); } } /** * Deallocates a parser's resources */ destroy() { if (this.reconstructor) { this.reconstructor.finishedReconstruction(); } } } exports.Decoder = Decoder; function tryParse(str) { try { return JSON.parse(str); } catch (e) { return false; } } /** * A manager of a binary event's 'buffer sequence'. Should * be constructed whenever a packet of type BINARY_EVENT is * decoded. * * @param {Object} packet * @return {BinaryReconstructor} initialized reconstructor */ class BinaryReconstructor { constructor(packet) { this.packet = packet; this.buffers = []; this.reconPack = packet; } /** * Method to be called when binary data received from connection * after a BINARY_EVENT packet. * * @param {Buffer | ArrayBuffer} binData - the raw binary data received * @return {null | Object} returns null if more binary data is expected or * a reconstructed packet object if all buffers have been received. */ takeBinaryData(binData) { this.buffers.push(binData); if (this.buffers.length === this.reconPack.attachments) { // done with buffer list const packet = binary_1.reconstructPacket(this.reconPack, this.buffers); this.finishedReconstruction(); return packet; } return null; } /** * Cleans up binary packet reconstruction variables. */ finishedReconstruction() { this.reconPack = null; this.buffers = []; } }