index.js (6028B)
1 /* 2 * A socket.io encoder and decoder written in JavaScript complying with version 4 3 * of socket.io-protocol. Used by socket.io and socket.io-client. 4 * 5 * Copyright (c) 2014 Guillermo Rauch <guillermo@learnboost.com> 6 * 7 * This source code is licensed under the MIT license found in the 8 * LICENSE file in the root directory of the library source tree. 9 * 10 * https://github.com/socketio/socket.io-parser 11 */ 12 13 /* eslint-disable no-unused-vars */ 14 15 "use strict"; 16 17 const Emitter = require("resource://devtools/client/netmonitor/src/components/messages/parsers/socket-io/component-emitter.js"); 18 const binary = require("resource://devtools/client/netmonitor/src/components/messages/parsers/socket-io/binary.js"); 19 const isBuf = require("resource://devtools/client/netmonitor/src/components/messages/parsers/socket-io/is-buffer.js"); 20 21 /** 22 * Packet types 23 */ 24 25 const TYPES = [ 26 "CONNECT", 27 "DISCONNECT", 28 "EVENT", 29 "ACK", 30 "ERROR", 31 "BINARY_EVENT", 32 "BINARY_ACK", 33 ]; 34 35 /** 36 * Packet type `connect` 37 */ 38 39 const CONNECT = 0; 40 41 /** 42 * Packet type `disconnect` 43 */ 44 45 const DISCONNECT = 1; 46 47 /** 48 * Packet type `event` 49 */ 50 51 const EVENT = 2; 52 53 /** 54 * Packet type `ack` 55 */ 56 57 const ACK = 3; 58 59 /** 60 * Packet type `error` 61 */ 62 63 const ERROR = 4; 64 65 /** 66 * Packet type 'binary event' 67 */ 68 const BINARY_EVENT = 5; 69 70 /** 71 * Packet type `binary ack`. For acks with binary arguments 72 */ 73 74 const BINARY_ACK = 6; 75 76 /** 77 * A socket.io Decoder instance 78 * 79 * @return {object} decoder 80 * @public 81 */ 82 83 function Decoder() { 84 this.reconstructor = null; 85 } 86 87 /** 88 * Mix in `Emitter` with Decoder. 89 */ 90 91 Emitter(Decoder.prototype); 92 93 /** 94 * A manager of a binary event's 'buffer sequence'. Should 95 * be constructed whenever a packet of type BINARY_EVENT is 96 * decoded. 97 * 98 * @param {object} packet 99 * @return {BinaryReconstructor} initialized reconstructor 100 * @private 101 */ 102 103 function BinaryReconstructor(packet) { 104 this.reconPack = packet; 105 this.buffers = []; 106 } 107 108 /** 109 * Method to be called when binary data received from connection 110 * after a BINARY_EVENT packet. 111 * 112 * @param {Buffer | ArrayBuffer} binData - the raw binary data received 113 * @return {null | object} returns null if more binary data is expected or 114 * a reconstructed packet object if all buffers have been received. 115 * @private 116 */ 117 118 BinaryReconstructor.prototype.takeBinaryData = function (binData) { 119 this.buffers.push(binData); 120 if (this.buffers.length === this.reconPack.attachments) { 121 // done with buffer list 122 const packet = binary.reconstructPacket(this.reconPack, this.buffers); 123 this.finishedReconstruction(); 124 return packet; 125 } 126 return null; 127 }; 128 129 /** 130 * Cleans up binary packet reconstruction variables. 131 * 132 * @private 133 */ 134 135 BinaryReconstructor.prototype.finishedReconstruction = function () { 136 this.reconPack = null; 137 this.buffers = []; 138 }; 139 140 /** 141 * Decodes an encoded packet string into packet JSON. 142 * 143 * @param {string} obj - encoded packet 144 * @return {object} packet 145 * @public 146 */ 147 148 Decoder.prototype.add = function (obj) { 149 let packet; 150 if (typeof obj === "string") { 151 packet = decodeString(obj); 152 if (BINARY_EVENT === packet.type || BINARY_ACK === packet.type) { 153 // binary packet's json 154 this.reconstructor = new BinaryReconstructor(packet); 155 156 // no attachments, labeled binary but no binary data to follow 157 if (this.reconstructor.reconPack.attachments === 0) { 158 this.emit("decoded", packet); 159 } 160 } else { 161 // non-binary full packet 162 this.emit("decoded", packet); 163 } 164 } else if (isBuf(obj) || obj.base64) { 165 // raw binary data 166 if (!this.reconstructor) { 167 throw new Error("got binary data when not reconstructing a packet"); 168 } else { 169 packet = this.reconstructor.takeBinaryData(obj); 170 if (packet) { 171 // received final buffer 172 this.reconstructor = null; 173 this.emit("decoded", packet); 174 } 175 } 176 } else { 177 throw new Error("Unknown type: " + obj); 178 } 179 }; 180 181 /** 182 * Decode a packet String (JSON data) 183 * 184 * @param {string} str 185 * @return {object} packet 186 * @private 187 */ 188 // eslint-disable-next-line complexity 189 function decodeString(str) { 190 let i = 0; 191 // look up type 192 const p = { 193 type: Number(str.charAt(0)), 194 }; 195 196 if (TYPES[p.type] == null) { 197 return error("unknown packet type " + p.type); 198 } 199 200 // look up attachments if type binary 201 if (BINARY_EVENT === p.type || BINARY_ACK === p.type) { 202 let buf = ""; 203 while (str.charAt(++i) !== "-") { 204 buf += str.charAt(i); 205 if (i === str.length) { 206 break; 207 } 208 } 209 if (buf != Number(buf) || str.charAt(i) !== "-") { 210 throw new Error("Illegal attachments"); 211 } 212 p.attachments = Number(buf); 213 } 214 215 // look up namespace (if any) 216 if (str.charAt(i + 1) === "/") { 217 p.nsp = ""; 218 while (++i) { 219 const c = str.charAt(i); 220 if (c === ",") { 221 break; 222 } 223 p.nsp += c; 224 if (i === str.length) { 225 break; 226 } 227 } 228 } else { 229 p.nsp = "/"; 230 } 231 232 // look up id 233 const next = str.charAt(i + 1); 234 if (next !== "" && Number(next) == next) { 235 p.id = ""; 236 while (++i) { 237 const c = str.charAt(i); 238 if (c == null || Number(c) != c) { 239 --i; 240 break; 241 } 242 p.id += str.charAt(i); 243 if (i === str.length) { 244 break; 245 } 246 } 247 p.id = Number(p.id); 248 } 249 250 // look up json data 251 if (str.charAt(++i)) { 252 const payload = tryParse(str.substr(i)); 253 const isPayloadValid = 254 payload !== false && (p.type === ERROR || Array.isArray(payload)); 255 if (isPayloadValid) { 256 p.data = payload; 257 } else { 258 return error("invalid payload"); 259 } 260 } 261 262 return p; 263 } 264 265 function tryParse(str) { 266 try { 267 return JSON.parse(str); 268 } catch (e) { 269 return false; 270 } 271 } 272 273 /** 274 * Deallocates a parser's resources 275 * 276 * @public 277 */ 278 279 Decoder.prototype.destroy = function () { 280 if (this.reconstructor) { 281 this.reconstructor.finishedReconstruction(); 282 } 283 }; 284 285 function error(msg) { 286 return { 287 type: ERROR, 288 data: "parser error: " + msg, 289 }; 290 } 291 292 module.exports = Decoder;