index.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Decoder = exports.Encoder = exports.PacketType = exports.protocol = void 0;
  4. const component_emitter_1 = require("@socket.io/component-emitter");
  5. const binary_js_1 = require("./binary.js");
  6. const is_binary_js_1 = require("./is-binary.js");
  7. const debug_1 = require("debug"); // debug()
  8. const debug = (0, debug_1.default)("socket.io-parser"); // debug()
  9. /**
  10. * These strings must not be used as event names, as they have a special meaning.
  11. */
  12. const RESERVED_EVENTS = [
  13. "connect",
  14. "connect_error",
  15. "disconnect",
  16. "disconnecting",
  17. "newListener",
  18. "removeListener", // used by the Node.js EventEmitter
  19. ];
  20. /**
  21. * Protocol version.
  22. *
  23. * @public
  24. */
  25. exports.protocol = 5;
  26. var PacketType;
  27. (function (PacketType) {
  28. PacketType[PacketType["CONNECT"] = 0] = "CONNECT";
  29. PacketType[PacketType["DISCONNECT"] = 1] = "DISCONNECT";
  30. PacketType[PacketType["EVENT"] = 2] = "EVENT";
  31. PacketType[PacketType["ACK"] = 3] = "ACK";
  32. PacketType[PacketType["CONNECT_ERROR"] = 4] = "CONNECT_ERROR";
  33. PacketType[PacketType["BINARY_EVENT"] = 5] = "BINARY_EVENT";
  34. PacketType[PacketType["BINARY_ACK"] = 6] = "BINARY_ACK";
  35. })(PacketType = exports.PacketType || (exports.PacketType = {}));
  36. /**
  37. * A socket.io Encoder instance
  38. */
  39. class Encoder {
  40. /**
  41. * Encoder constructor
  42. *
  43. * @param {function} replacer - custom replacer to pass down to JSON.parse
  44. */
  45. constructor(replacer) {
  46. this.replacer = replacer;
  47. }
  48. /**
  49. * Encode a packet as a single string if non-binary, or as a
  50. * buffer sequence, depending on packet type.
  51. *
  52. * @param {Object} obj - packet object
  53. */
  54. encode(obj) {
  55. debug("encoding packet %j", obj);
  56. if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
  57. if ((0, is_binary_js_1.hasBinary)(obj)) {
  58. return this.encodeAsBinary({
  59. type: obj.type === PacketType.EVENT
  60. ? PacketType.BINARY_EVENT
  61. : PacketType.BINARY_ACK,
  62. nsp: obj.nsp,
  63. data: obj.data,
  64. id: obj.id,
  65. });
  66. }
  67. }
  68. return [this.encodeAsString(obj)];
  69. }
  70. /**
  71. * Encode packet as string.
  72. */
  73. encodeAsString(obj) {
  74. // first is type
  75. let str = "" + obj.type;
  76. // attachments if we have them
  77. if (obj.type === PacketType.BINARY_EVENT ||
  78. obj.type === PacketType.BINARY_ACK) {
  79. str += obj.attachments + "-";
  80. }
  81. // if we have a namespace other than `/`
  82. // we append it followed by a comma `,`
  83. if (obj.nsp && "/" !== obj.nsp) {
  84. str += obj.nsp + ",";
  85. }
  86. // immediately followed by the id
  87. if (null != obj.id) {
  88. str += obj.id;
  89. }
  90. // json data
  91. if (null != obj.data) {
  92. str += JSON.stringify(obj.data, this.replacer);
  93. }
  94. debug("encoded %j as %s", obj, str);
  95. return str;
  96. }
  97. /**
  98. * Encode packet as 'buffer sequence' by removing blobs, and
  99. * deconstructing packet into object with placeholders and
  100. * a list of buffers.
  101. */
  102. encodeAsBinary(obj) {
  103. const deconstruction = (0, binary_js_1.deconstructPacket)(obj);
  104. const pack = this.encodeAsString(deconstruction.packet);
  105. const buffers = deconstruction.buffers;
  106. buffers.unshift(pack); // add packet info to beginning of data list
  107. return buffers; // write all the buffers
  108. }
  109. }
  110. exports.Encoder = Encoder;
  111. // see https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript
  112. function isObject(value) {
  113. return Object.prototype.toString.call(value) === "[object Object]";
  114. }
  115. /**
  116. * A socket.io Decoder instance
  117. *
  118. * @return {Object} decoder
  119. */
  120. class Decoder extends component_emitter_1.Emitter {
  121. /**
  122. * Decoder constructor
  123. *
  124. * @param {function} reviver - custom reviver to pass down to JSON.stringify
  125. */
  126. constructor(reviver) {
  127. super();
  128. this.reviver = reviver;
  129. }
  130. /**
  131. * Decodes an encoded packet string into packet JSON.
  132. *
  133. * @param {String} obj - encoded packet
  134. */
  135. add(obj) {
  136. let packet;
  137. if (typeof obj === "string") {
  138. if (this.reconstructor) {
  139. throw new Error("got plaintext data when reconstructing a packet");
  140. }
  141. packet = this.decodeString(obj);
  142. const isBinaryEvent = packet.type === PacketType.BINARY_EVENT;
  143. if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {
  144. packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;
  145. // binary packet's json
  146. this.reconstructor = new BinaryReconstructor(packet);
  147. // no attachments, labeled binary but no binary data to follow
  148. if (packet.attachments === 0) {
  149. super.emitReserved("decoded", packet);
  150. }
  151. }
  152. else {
  153. // non-binary full packet
  154. super.emitReserved("decoded", packet);
  155. }
  156. }
  157. else if ((0, is_binary_js_1.isBinary)(obj) || obj.base64) {
  158. // raw binary data
  159. if (!this.reconstructor) {
  160. throw new Error("got binary data when not reconstructing a packet");
  161. }
  162. else {
  163. packet = this.reconstructor.takeBinaryData(obj);
  164. if (packet) {
  165. // received final buffer
  166. this.reconstructor = null;
  167. super.emitReserved("decoded", packet);
  168. }
  169. }
  170. }
  171. else {
  172. throw new Error("Unknown type: " + obj);
  173. }
  174. }
  175. /**
  176. * Decode a packet String (JSON data)
  177. *
  178. * @param {String} str
  179. * @return {Object} packet
  180. */
  181. decodeString(str) {
  182. let i = 0;
  183. // look up type
  184. const p = {
  185. type: Number(str.charAt(0)),
  186. };
  187. if (PacketType[p.type] === undefined) {
  188. throw new Error("unknown packet type " + p.type);
  189. }
  190. // look up attachments if type binary
  191. if (p.type === PacketType.BINARY_EVENT ||
  192. p.type === PacketType.BINARY_ACK) {
  193. const start = i + 1;
  194. while (str.charAt(++i) !== "-" && i != str.length) { }
  195. const buf = str.substring(start, i);
  196. if (buf != Number(buf) || str.charAt(i) !== "-") {
  197. throw new Error("Illegal attachments");
  198. }
  199. p.attachments = Number(buf);
  200. }
  201. // look up namespace (if any)
  202. if ("/" === str.charAt(i + 1)) {
  203. const start = i + 1;
  204. while (++i) {
  205. const c = str.charAt(i);
  206. if ("," === c)
  207. break;
  208. if (i === str.length)
  209. break;
  210. }
  211. p.nsp = str.substring(start, i);
  212. }
  213. else {
  214. p.nsp = "/";
  215. }
  216. // look up id
  217. const next = str.charAt(i + 1);
  218. if ("" !== next && Number(next) == next) {
  219. const start = i + 1;
  220. while (++i) {
  221. const c = str.charAt(i);
  222. if (null == c || Number(c) != c) {
  223. --i;
  224. break;
  225. }
  226. if (i === str.length)
  227. break;
  228. }
  229. p.id = Number(str.substring(start, i + 1));
  230. }
  231. // look up json data
  232. if (str.charAt(++i)) {
  233. const payload = this.tryParse(str.substr(i));
  234. if (Decoder.isPayloadValid(p.type, payload)) {
  235. p.data = payload;
  236. }
  237. else {
  238. throw new Error("invalid payload");
  239. }
  240. }
  241. debug("decoded %s as %j", str, p);
  242. return p;
  243. }
  244. tryParse(str) {
  245. try {
  246. return JSON.parse(str, this.reviver);
  247. }
  248. catch (e) {
  249. return false;
  250. }
  251. }
  252. static isPayloadValid(type, payload) {
  253. switch (type) {
  254. case PacketType.CONNECT:
  255. return isObject(payload);
  256. case PacketType.DISCONNECT:
  257. return payload === undefined;
  258. case PacketType.CONNECT_ERROR:
  259. return typeof payload === "string" || isObject(payload);
  260. case PacketType.EVENT:
  261. case PacketType.BINARY_EVENT:
  262. return (Array.isArray(payload) &&
  263. (typeof payload[0] === "number" ||
  264. (typeof payload[0] === "string" &&
  265. RESERVED_EVENTS.indexOf(payload[0]) === -1)));
  266. case PacketType.ACK:
  267. case PacketType.BINARY_ACK:
  268. return Array.isArray(payload);
  269. }
  270. }
  271. /**
  272. * Deallocates a parser's resources
  273. */
  274. destroy() {
  275. if (this.reconstructor) {
  276. this.reconstructor.finishedReconstruction();
  277. this.reconstructor = null;
  278. }
  279. }
  280. }
  281. exports.Decoder = Decoder;
  282. /**
  283. * A manager of a binary event's 'buffer sequence'. Should
  284. * be constructed whenever a packet of type BINARY_EVENT is
  285. * decoded.
  286. *
  287. * @param {Object} packet
  288. * @return {BinaryReconstructor} initialized reconstructor
  289. */
  290. class BinaryReconstructor {
  291. constructor(packet) {
  292. this.packet = packet;
  293. this.buffers = [];
  294. this.reconPack = packet;
  295. }
  296. /**
  297. * Method to be called when binary data received from connection
  298. * after a BINARY_EVENT packet.
  299. *
  300. * @param {Buffer | ArrayBuffer} binData - the raw binary data received
  301. * @return {null | Object} returns null if more binary data is expected or
  302. * a reconstructed packet object if all buffers have been received.
  303. */
  304. takeBinaryData(binData) {
  305. this.buffers.push(binData);
  306. if (this.buffers.length === this.reconPack.attachments) {
  307. // done with buffer list
  308. const packet = (0, binary_js_1.reconstructPacket)(this.reconPack, this.buffers);
  309. this.finishedReconstruction();
  310. return packet;
  311. }
  312. return null;
  313. }
  314. /**
  315. * Cleans up binary packet reconstruction variables.
  316. */
  317. finishedReconstruction() {
  318. this.reconPack = null;
  319. this.buffers = [];
  320. }
  321. }