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