123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- import { Emitter } from "@socket.io/component-emitter";
- import { deconstructPacket, reconstructPacket } from "./binary.js";
- import { isBinary, hasBinary } from "./is-binary.js";
- /**
- * Protocol version.
- *
- * @public
- */
- export const protocol = 5;
- export 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 || (PacketType = {}));
- /**
- * A socket.io Encoder instance
- */
- export class Encoder {
- /**
- * Encoder constructor
- *
- * @param {function} replacer - custom replacer to pass down to JSON.parse
- */
- constructor(replacer) {
- this.replacer = replacer;
- }
- /**
- * 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) {
- if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
- if (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, this.replacer);
- }
- 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 = 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
- }
- }
- /**
- * A socket.io Decoder instance
- *
- * @return {Object} decoder
- */
- export class Decoder extends Emitter {
- /**
- * Decoder constructor
- *
- * @param {function} reviver - custom reviver to pass down to JSON.stringify
- */
- constructor(reviver) {
- super();
- this.reviver = reviver;
- }
- /**
- * Decodes an encoded packet string into packet JSON.
- *
- * @param {String} obj - encoded packet
- */
- add(obj) {
- let packet;
- if (typeof obj === "string") {
- if (this.reconstructor) {
- throw new Error("got plaintext data when reconstructing a packet");
- }
- 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.emitReserved("decoded", packet);
- }
- }
- else {
- // non-binary full packet
- super.emitReserved("decoded", packet);
- }
- }
- else if (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.emitReserved("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 = this.tryParse(str.substr(i));
- if (Decoder.isPayloadValid(p.type, payload)) {
- p.data = payload;
- }
- else {
- throw new Error("invalid payload");
- }
- }
- return p;
- }
- tryParse(str) {
- try {
- return JSON.parse(str, this.reviver);
- }
- catch (e) {
- return false;
- }
- }
- 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();
- }
- }
- }
- /**
- * 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 = reconstructPacket(this.reconPack, this.buffers);
- this.finishedReconstruction();
- return packet;
- }
- return null;
- }
- /**
- * Cleans up binary packet reconstruction variables.
- */
- finishedReconstruction() {
- this.reconPack = null;
- this.buffers = [];
- }
- }
|