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 8.9KB

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