Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 9.1KB

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