6 var debug = require('debug')('socket.io-parser');
7 var Emitter = require('component-emitter');
8 var binary = require('./binary');
9 var isArray = require('isarray');
10 var isBuf = require('./is-buffer');
37 * Packet type `connect`.
45 * Packet type `disconnect`.
50 exports.DISCONNECT = 1;
53 * Packet type `event`.
69 * Packet type `error`.
77 * Packet type 'binary event'
82 exports.BINARY_EVENT = 5;
85 * Packet type `binary ack`. For acks with binary arguments.
90 exports.BINARY_ACK = 6;
93 * Encoder constructor.
98 exports.Encoder = Encoder;
101 * Decoder constructor.
106 exports.Decoder = Decoder;
109 * A socket.io Encoder instance
114 function Encoder() {}
116 var ERROR_PACKET = exports.ERROR + '"encode error"';
119 * Encode a packet as a single string if non-binary, or as a
120 * buffer sequence, depending on packet type.
122 * @param {Object} obj - packet object
123 * @param {Function} callback - function to handle encodings (likely engine.write)
124 * @return Calls callback with Array of encodings
128 Encoder.prototype.encode = function(obj, callback){
129 debug('encoding packet %j', obj);
131 if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) {
132 encodeAsBinary(obj, callback);
134 var encoding = encodeAsString(obj);
135 callback([encoding]);
140 * Encode packet as string.
142 * @param {Object} packet
143 * @return {String} encoded
147 function encodeAsString(obj) {
150 var str = '' + obj.type;
152 // attachments if we have them
153 if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) {
154 str += obj.attachments + '-';
157 // if we have a namespace other than `/`
158 // we append it followed by a comma `,`
159 if (obj.nsp && '/' !== obj.nsp) {
160 str += obj.nsp + ',';
163 // immediately followed by the id
164 if (null != obj.id) {
169 if (null != obj.data) {
170 var payload = tryStringify(obj.data);
171 if (payload !== false) {
178 debug('encoded %j as %s', obj, str);
182 function tryStringify(str) {
184 return JSON.stringify(str);
191 * Encode packet as 'buffer sequence' by removing blobs, and
192 * deconstructing packet into object with placeholders and
195 * @param {Object} packet
196 * @return {Buffer} encoded
200 function encodeAsBinary(obj, callback) {
202 function writeEncoding(bloblessData) {
203 var deconstruction = binary.deconstructPacket(bloblessData);
204 var pack = encodeAsString(deconstruction.packet);
205 var buffers = deconstruction.buffers;
207 buffers.unshift(pack); // add packet info to beginning of data list
208 callback(buffers); // write all the buffers
211 binary.removeBlobs(obj, writeEncoding);
215 * A socket.io Decoder instance
217 * @return {Object} decoder
222 this.reconstructor = null;
226 * Mix in `Emitter` with Decoder.
229 Emitter(Decoder.prototype);
232 * Decodes an encoded packet string into packet JSON.
234 * @param {String} obj - encoded packet
235 * @return {Object} packet
239 Decoder.prototype.add = function(obj) {
241 if (typeof obj === 'string') {
242 packet = decodeString(obj);
243 if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { // binary packet's json
244 this.reconstructor = new BinaryReconstructor(packet);
246 // no attachments, labeled binary but no binary data to follow
247 if (this.reconstructor.reconPack.attachments === 0) {
248 this.emit('decoded', packet);
250 } else { // non-binary full packet
251 this.emit('decoded', packet);
253 } else if (isBuf(obj) || obj.base64) { // raw binary data
254 if (!this.reconstructor) {
255 throw new Error('got binary data when not reconstructing a packet');
257 packet = this.reconstructor.takeBinaryData(obj);
258 if (packet) { // received final buffer
259 this.reconstructor = null;
260 this.emit('decoded', packet);
264 throw new Error('Unknown type: ' + obj);
269 * Decode a packet String (JSON data)
271 * @param {String} str
272 * @return {Object} packet
276 function decodeString(str) {
280 type: Number(str.charAt(0))
283 if (null == exports.types[p.type]) {
284 return error('unknown packet type ' + p.type);
287 // look up attachments if type binary
288 if (exports.BINARY_EVENT === p.type || exports.BINARY_ACK === p.type) {
290 while (str.charAt(++i) !== '-' && i != str.length) {}
291 var buf = str.substring(start, i);
292 if (buf != Number(buf) || str.charAt(i) !== '-') {
293 throw new Error('Illegal attachments');
295 p.attachments = Number(buf);
298 // look up namespace (if any)
299 if ('/' === str.charAt(i + 1)) {
302 var c = str.charAt(i);
303 if (',' === c) break;
304 if (i === str.length) break;
306 p.nsp = str.substring(start, i);
312 var next = str.charAt(i + 1);
313 if ('' !== next && Number(next) == next) {
316 var c = str.charAt(i);
317 if (null == c || Number(c) != c) {
321 if (i === str.length) break;
323 p.id = Number(str.substring(start, i + 1));
327 if (str.charAt(++i)) {
328 var payload = tryParse(str.substr(i));
329 var isPayloadValid = payload !== false && (p.type === exports.ERROR || isArray(payload));
330 if (isPayloadValid) {
333 return error('invalid payload');
337 debug('decoded %s as %j', str, p);
341 function tryParse(str) {
343 return JSON.parse(str);
350 * Deallocates a parser's resources
355 Decoder.prototype.destroy = function() {
356 if (this.reconstructor) {
357 this.reconstructor.finishedReconstruction();
362 * A manager of a binary event's 'buffer sequence'. Should
363 * be constructed whenever a packet of type BINARY_EVENT is
366 * @param {Object} packet
367 * @return {BinaryReconstructor} initialized reconstructor
371 function BinaryReconstructor(packet) {
372 this.reconPack = packet;
377 * Method to be called when binary data received from connection
378 * after a BINARY_EVENT packet.
380 * @param {Buffer | ArrayBuffer} binData - the raw binary data received
381 * @return {null | Object} returns null if more binary data is expected or
382 * a reconstructed packet object if all buffers have been received.
386 BinaryReconstructor.prototype.takeBinaryData = function(binData) {
387 this.buffers.push(binData);
388 if (this.buffers.length === this.reconPack.attachments) { // done with buffer list
389 var packet = binary.reconstructPacket(this.reconPack, this.buffers);
390 this.finishedReconstruction();
397 * Cleans up binary packet reconstruction variables.
402 BinaryReconstructor.prototype.finishedReconstruction = function() {
403 this.reconPack = null;
407 function error(msg) {
410 data: 'parser error: ' + msg