| "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) && payload.length > 0; |
| 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 = []; |
| } |
| } |