binary.js (4090B)
1 /*global Blob,File*/ 2 3 /** 4 * Module requirements 5 */ 6 7 var isArray = require('isarray'); 8 var isBuf = require('./is-buffer'); 9 var toString = Object.prototype.toString; 10 var withNativeBlob = typeof Blob === 'function' || (typeof Blob !== 'undefined' && toString.call(Blob) === '[object BlobConstructor]'); 11 var withNativeFile = typeof File === 'function' || (typeof File !== 'undefined' && toString.call(File) === '[object FileConstructor]'); 12 13 /** 14 * Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder. 15 * Anything with blobs or files should be fed through removeBlobs before coming 16 * here. 17 * 18 * @param {Object} packet - socket.io event packet 19 * @return {Object} with deconstructed packet and list of buffers 20 * @api public 21 */ 22 23 exports.deconstructPacket = function(packet) { 24 var buffers = []; 25 var packetData = packet.data; 26 var pack = packet; 27 pack.data = _deconstructPacket(packetData, buffers); 28 pack.attachments = buffers.length; // number of binary 'attachments' 29 return {packet: pack, buffers: buffers}; 30 }; 31 32 function _deconstructPacket(data, buffers) { 33 if (!data) return data; 34 35 if (isBuf(data)) { 36 var placeholder = { _placeholder: true, num: buffers.length }; 37 buffers.push(data); 38 return placeholder; 39 } else if (isArray(data)) { 40 var newData = new Array(data.length); 41 for (var i = 0; i < data.length; i++) { 42 newData[i] = _deconstructPacket(data[i], buffers); 43 } 44 return newData; 45 } else if (typeof data === 'object' && !(data instanceof Date)) { 46 var newData = {}; 47 for (var key in data) { 48 newData[key] = _deconstructPacket(data[key], buffers); 49 } 50 return newData; 51 } 52 return data; 53 } 54 55 /** 56 * Reconstructs a binary packet from its placeholder packet and buffers 57 * 58 * @param {Object} packet - event packet with placeholders 59 * @param {Array} buffers - binary buffers to put in placeholder positions 60 * @return {Object} reconstructed packet 61 * @api public 62 */ 63 64 exports.reconstructPacket = function(packet, buffers) { 65 packet.data = _reconstructPacket(packet.data, buffers); 66 packet.attachments = undefined; // no longer useful 67 return packet; 68 }; 69 70 function _reconstructPacket(data, buffers) { 71 if (!data) return data; 72 73 if (data && data._placeholder) { 74 return buffers[data.num]; // appropriate buffer (should be natural order anyway) 75 } else if (isArray(data)) { 76 for (var i = 0; i < data.length; i++) { 77 data[i] = _reconstructPacket(data[i], buffers); 78 } 79 } else if (typeof data === 'object') { 80 for (var key in data) { 81 data[key] = _reconstructPacket(data[key], buffers); 82 } 83 } 84 85 return data; 86 } 87 88 /** 89 * Asynchronously removes Blobs or Files from data via 90 * FileReader's readAsArrayBuffer method. Used before encoding 91 * data as msgpack. Calls callback with the blobless data. 92 * 93 * @param {Object} data 94 * @param {Function} callback 95 * @api private 96 */ 97 98 exports.removeBlobs = function(data, callback) { 99 function _removeBlobs(obj, curKey, containingObject) { 100 if (!obj) return obj; 101 102 // convert any blob 103 if ((withNativeBlob && obj instanceof Blob) || 104 (withNativeFile && obj instanceof File)) { 105 pendingBlobs++; 106 107 // async filereader 108 var fileReader = new FileReader(); 109 fileReader.onload = function() { // this.result == arraybuffer 110 if (containingObject) { 111 containingObject[curKey] = this.result; 112 } 113 else { 114 bloblessData = this.result; 115 } 116 117 // if nothing pending its callback time 118 if(! --pendingBlobs) { 119 callback(bloblessData); 120 } 121 }; 122 123 fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer 124 } else if (isArray(obj)) { // handle array 125 for (var i = 0; i < obj.length; i++) { 126 _removeBlobs(obj[i], i, obj); 127 } 128 } else if (typeof obj === 'object' && !isBuf(obj)) { // and object 129 for (var key in obj) { 130 _removeBlobs(obj[key], key, obj); 131 } 132 } 133 } 134 135 var pendingBlobs = 0; 136 var bloblessData = data; 137 _removeBlobs(bloblessData); 138 if (!pendingBlobs) { 139 callback(bloblessData); 140 } 141 };