/*global Blob,File*/ /** * Module requirements */ var isArray = require('isarray'); var isBuf = require('./is-buffer'); /** * Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder. * Anything with blobs or files should be fed through removeBlobs before coming * here. * * @param {Object} packet - socket.io event packet * @return {Object} with deconstructed packet and list of buffers * @api public */ exports.deconstructPacket = function(packet){ var buffers = []; var packetData = packet.data; function _deconstructPacket(data) { if (!data) return data; if (isBuf(data)) { var placeholder = { _placeholder: true, num: buffers.length }; buffers.push(data); return placeholder; } else if (isArray(data)) { var newData = new Array(data.length); for (var i = 0; i < data.length; i++) { newData[i] = _deconstructPacket(data[i]); } return newData; } else if ('object' == typeof data && !(data instanceof Date)) { var newData = {}; for (var key in data) { newData[key] = _deconstructPacket(data[key]); } return newData; } return data; } var pack = packet; pack.data = _deconstructPacket(packetData); pack.attachments = buffers.length; // number of binary 'attachments' return {packet: pack, buffers: buffers}; }; /** * Reconstructs a binary packet from its placeholder packet and buffers * * @param {Object} packet - event packet with placeholders * @param {Array} buffers - binary buffers to put in placeholder positions * @return {Object} reconstructed packet * @api public */ exports.reconstructPacket = function(packet, buffers) { var curPlaceHolder = 0; function _reconstructPacket(data) { if (data && data._placeholder) { var buf = buffers[data.num]; // appropriate buffer (should be natural order anyway) return buf; } else if (isArray(data)) { for (var i = 0; i < data.length; i++) { data[i] = _reconstructPacket(data[i]); } return data; } else if (data && 'object' == typeof data) { for (var key in data) { data[key] = _reconstructPacket(data[key]); } return data; } return data; } packet.data = _reconstructPacket(packet.data); packet.attachments = undefined; // no longer useful return packet; }; /** * Asynchronously removes Blobs or Files from data via * FileReader's readAsArrayBuffer method. Used before encoding * data as msgpack. Calls callback with the blobless data. * * @param {Object} data * @param {Function} callback * @api private */ exports.removeBlobs = function(data, callback) { function _removeBlobs(obj, curKey, containingObject) { if (!obj) return obj; // convert any blob if ((global.Blob && obj instanceof Blob) || (global.File && obj instanceof File)) { pendingBlobs++; // async filereader var fileReader = new FileReader(); fileReader.onload = function() { // this.result == arraybuffer if (containingObject) { containingObject[curKey] = this.result; } else { bloblessData = this.result; } // if nothing pending its callback time if(! --pendingBlobs) { callback(bloblessData); } }; fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer } else if (isArray(obj)) { // handle array for (var i = 0; i < obj.length; i++) { _removeBlobs(obj[i], i, obj); } } else if (obj && 'object' == typeof obj && !isBuf(obj)) { // and object for (var key in obj) { _removeBlobs(obj[key], key, obj); } } } var pendingBlobs = 0; var bloblessData = data; _removeBlobs(bloblessData); if (!pendingBlobs) { callback(bloblessData); } };