|
|
|
|
|
|
|
|
|
var keys = require('./keys'); |
|
var hasBinary = require('has-binary2'); |
|
var sliceBuffer = require('arraybuffer.slice'); |
|
var after = require('after'); |
|
var utf8 = require('./utf8'); |
|
|
|
var base64encoder; |
|
if (global && global.ArrayBuffer) { |
|
base64encoder = require('base64-arraybuffer'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var isAndroid = typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var isPhantomJS = typeof navigator !== 'undefined' && /PhantomJS/i.test(navigator.userAgent); |
|
|
|
|
|
|
|
|
|
|
|
var dontSendBlobs = isAndroid || isPhantomJS; |
|
|
|
|
|
|
|
|
|
|
|
exports.protocol = 3; |
|
|
|
|
|
|
|
|
|
|
|
var packets = exports.packets = { |
|
open: 0 |
|
, close: 1 |
|
, ping: 2 |
|
, pong: 3 |
|
, message: 4 |
|
, upgrade: 5 |
|
, noop: 6 |
|
}; |
|
|
|
var packetslist = keys(packets); |
|
|
|
|
|
|
|
|
|
|
|
var err = { type: 'error', data: 'parser error' }; |
|
|
|
|
|
|
|
|
|
|
|
var Blob = require('blob'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) { |
|
if (typeof supportsBinary === 'function') { |
|
callback = supportsBinary; |
|
supportsBinary = false; |
|
} |
|
|
|
if (typeof utf8encode === 'function') { |
|
callback = utf8encode; |
|
utf8encode = null; |
|
} |
|
|
|
var data = (packet.data === undefined) |
|
? undefined |
|
: packet.data.buffer || packet.data; |
|
|
|
if (global.ArrayBuffer && data instanceof ArrayBuffer) { |
|
return encodeArrayBuffer(packet, supportsBinary, callback); |
|
} else if (Blob && data instanceof global.Blob) { |
|
return encodeBlob(packet, supportsBinary, callback); |
|
} |
|
|
|
|
|
if (data && data.base64) { |
|
return encodeBase64Object(packet, callback); |
|
} |
|
|
|
|
|
var encoded = packets[packet.type]; |
|
|
|
|
|
if (undefined !== packet.data) { |
|
encoded += utf8encode ? utf8.encode(String(packet.data), { strict: false }) : String(packet.data); |
|
} |
|
|
|
return callback('' + encoded); |
|
|
|
}; |
|
|
|
function encodeBase64Object(packet, callback) { |
|
|
|
var message = 'b' + exports.packets[packet.type] + packet.data.data; |
|
return callback(message); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function encodeArrayBuffer(packet, supportsBinary, callback) { |
|
if (!supportsBinary) { |
|
return exports.encodeBase64Packet(packet, callback); |
|
} |
|
|
|
var data = packet.data; |
|
var contentArray = new Uint8Array(data); |
|
var resultBuffer = new Uint8Array(1 + data.byteLength); |
|
|
|
resultBuffer[0] = packets[packet.type]; |
|
for (var i = 0; i < contentArray.length; i++) { |
|
resultBuffer[i+1] = contentArray[i]; |
|
} |
|
|
|
return callback(resultBuffer.buffer); |
|
} |
|
|
|
function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) { |
|
if (!supportsBinary) { |
|
return exports.encodeBase64Packet(packet, callback); |
|
} |
|
|
|
var fr = new FileReader(); |
|
fr.onload = function() { |
|
packet.data = fr.result; |
|
exports.encodePacket(packet, supportsBinary, true, callback); |
|
}; |
|
return fr.readAsArrayBuffer(packet.data); |
|
} |
|
|
|
function encodeBlob(packet, supportsBinary, callback) { |
|
if (!supportsBinary) { |
|
return exports.encodeBase64Packet(packet, callback); |
|
} |
|
|
|
if (dontSendBlobs) { |
|
return encodeBlobAsArrayBuffer(packet, supportsBinary, callback); |
|
} |
|
|
|
var length = new Uint8Array(1); |
|
length[0] = packets[packet.type]; |
|
var blob = new Blob([length.buffer, packet.data]); |
|
|
|
return callback(blob); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.encodeBase64Packet = function(packet, callback) { |
|
var message = 'b' + exports.packets[packet.type]; |
|
if (Blob && packet.data instanceof global.Blob) { |
|
var fr = new FileReader(); |
|
fr.onload = function() { |
|
var b64 = fr.result.split(',')[1]; |
|
callback(message + b64); |
|
}; |
|
return fr.readAsDataURL(packet.data); |
|
} |
|
|
|
var b64data; |
|
try { |
|
b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data)); |
|
} catch (e) { |
|
|
|
var typed = new Uint8Array(packet.data); |
|
var basic = new Array(typed.length); |
|
for (var i = 0; i < typed.length; i++) { |
|
basic[i] = typed[i]; |
|
} |
|
b64data = String.fromCharCode.apply(null, basic); |
|
} |
|
message += global.btoa(b64data); |
|
return callback(message); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.decodePacket = function (data, binaryType, utf8decode) { |
|
if (data === undefined) { |
|
return err; |
|
} |
|
|
|
if (typeof data === 'string') { |
|
if (data.charAt(0) === 'b') { |
|
return exports.decodeBase64Packet(data.substr(1), binaryType); |
|
} |
|
|
|
if (utf8decode) { |
|
data = tryDecode(data); |
|
if (data === false) { |
|
return err; |
|
} |
|
} |
|
var type = data.charAt(0); |
|
|
|
if (Number(type) != type || !packetslist[type]) { |
|
return err; |
|
} |
|
|
|
if (data.length > 1) { |
|
return { type: packetslist[type], data: data.substring(1) }; |
|
} else { |
|
return { type: packetslist[type] }; |
|
} |
|
} |
|
|
|
var asArray = new Uint8Array(data); |
|
var type = asArray[0]; |
|
var rest = sliceBuffer(data, 1); |
|
if (Blob && binaryType === 'blob') { |
|
rest = new Blob([rest]); |
|
} |
|
return { type: packetslist[type], data: rest }; |
|
}; |
|
|
|
function tryDecode(data) { |
|
try { |
|
data = utf8.decode(data, { strict: false }); |
|
} catch (e) { |
|
return false; |
|
} |
|
return data; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.decodeBase64Packet = function(msg, binaryType) { |
|
var type = packetslist[msg.charAt(0)]; |
|
if (!base64encoder) { |
|
return { type: type, data: { base64: true, data: msg.substr(1) } }; |
|
} |
|
|
|
var data = base64encoder.decode(msg.substr(1)); |
|
|
|
if (binaryType === 'blob' && Blob) { |
|
data = new Blob([data]); |
|
} |
|
|
|
return { type: type, data: data }; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.encodePayload = function (packets, supportsBinary, callback) { |
|
if (typeof supportsBinary === 'function') { |
|
callback = supportsBinary; |
|
supportsBinary = null; |
|
} |
|
|
|
var isBinary = hasBinary(packets); |
|
|
|
if (supportsBinary && isBinary) { |
|
if (Blob && !dontSendBlobs) { |
|
return exports.encodePayloadAsBlob(packets, callback); |
|
} |
|
|
|
return exports.encodePayloadAsArrayBuffer(packets, callback); |
|
} |
|
|
|
if (!packets.length) { |
|
return callback('0:'); |
|
} |
|
|
|
function setLengthHeader(message) { |
|
return message.length + ':' + message; |
|
} |
|
|
|
function encodeOne(packet, doneCallback) { |
|
exports.encodePacket(packet, !isBinary ? false : supportsBinary, false, function(message) { |
|
doneCallback(null, setLengthHeader(message)); |
|
}); |
|
} |
|
|
|
map(packets, encodeOne, function(err, results) { |
|
return callback(results.join('')); |
|
}); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function map(ary, each, done) { |
|
var result = new Array(ary.length); |
|
var next = after(ary.length, done); |
|
|
|
var eachWithIndex = function(i, el, cb) { |
|
each(el, function(error, msg) { |
|
result[i] = msg; |
|
cb(error, result); |
|
}); |
|
}; |
|
|
|
for (var i = 0; i < ary.length; i++) { |
|
eachWithIndex(i, ary[i], next); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.decodePayload = function (data, binaryType, callback) { |
|
if (typeof data !== 'string') { |
|
return exports.decodePayloadAsBinary(data, binaryType, callback); |
|
} |
|
|
|
if (typeof binaryType === 'function') { |
|
callback = binaryType; |
|
binaryType = null; |
|
} |
|
|
|
var packet; |
|
if (data === '') { |
|
|
|
return callback(err, 0, 1); |
|
} |
|
|
|
var length = '', n, msg; |
|
|
|
for (var i = 0, l = data.length; i < l; i++) { |
|
var chr = data.charAt(i); |
|
|
|
if (chr !== ':') { |
|
length += chr; |
|
continue; |
|
} |
|
|
|
if (length === '' || (length != (n = Number(length)))) { |
|
|
|
return callback(err, 0, 1); |
|
} |
|
|
|
msg = data.substr(i + 1, n); |
|
|
|
if (length != msg.length) { |
|
|
|
return callback(err, 0, 1); |
|
} |
|
|
|
if (msg.length) { |
|
packet = exports.decodePacket(msg, binaryType, false); |
|
|
|
if (err.type === packet.type && err.data === packet.data) { |
|
|
|
return callback(err, 0, 1); |
|
} |
|
|
|
var ret = callback(packet, i + n, l); |
|
if (false === ret) return; |
|
} |
|
|
|
|
|
i += n; |
|
length = ''; |
|
} |
|
|
|
if (length !== '') { |
|
|
|
return callback(err, 0, 1); |
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.encodePayloadAsArrayBuffer = function(packets, callback) { |
|
if (!packets.length) { |
|
return callback(new ArrayBuffer(0)); |
|
} |
|
|
|
function encodeOne(packet, doneCallback) { |
|
exports.encodePacket(packet, true, true, function(data) { |
|
return doneCallback(null, data); |
|
}); |
|
} |
|
|
|
map(packets, encodeOne, function(err, encodedPackets) { |
|
var totalLength = encodedPackets.reduce(function(acc, p) { |
|
var len; |
|
if (typeof p === 'string'){ |
|
len = p.length; |
|
} else { |
|
len = p.byteLength; |
|
} |
|
return acc + len.toString().length + len + 2; |
|
}, 0); |
|
|
|
var resultArray = new Uint8Array(totalLength); |
|
|
|
var bufferIndex = 0; |
|
encodedPackets.forEach(function(p) { |
|
var isString = typeof p === 'string'; |
|
var ab = p; |
|
if (isString) { |
|
var view = new Uint8Array(p.length); |
|
for (var i = 0; i < p.length; i++) { |
|
view[i] = p.charCodeAt(i); |
|
} |
|
ab = view.buffer; |
|
} |
|
|
|
if (isString) { |
|
resultArray[bufferIndex++] = 0; |
|
} else { |
|
resultArray[bufferIndex++] = 1; |
|
} |
|
|
|
var lenStr = ab.byteLength.toString(); |
|
for (var i = 0; i < lenStr.length; i++) { |
|
resultArray[bufferIndex++] = parseInt(lenStr[i]); |
|
} |
|
resultArray[bufferIndex++] = 255; |
|
|
|
var view = new Uint8Array(ab); |
|
for (var i = 0; i < view.length; i++) { |
|
resultArray[bufferIndex++] = view[i]; |
|
} |
|
}); |
|
|
|
return callback(resultArray.buffer); |
|
}); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
exports.encodePayloadAsBlob = function(packets, callback) { |
|
function encodeOne(packet, doneCallback) { |
|
exports.encodePacket(packet, true, true, function(encoded) { |
|
var binaryIdentifier = new Uint8Array(1); |
|
binaryIdentifier[0] = 1; |
|
if (typeof encoded === 'string') { |
|
var view = new Uint8Array(encoded.length); |
|
for (var i = 0; i < encoded.length; i++) { |
|
view[i] = encoded.charCodeAt(i); |
|
} |
|
encoded = view.buffer; |
|
binaryIdentifier[0] = 0; |
|
} |
|
|
|
var len = (encoded instanceof ArrayBuffer) |
|
? encoded.byteLength |
|
: encoded.size; |
|
|
|
var lenStr = len.toString(); |
|
var lengthAry = new Uint8Array(lenStr.length + 1); |
|
for (var i = 0; i < lenStr.length; i++) { |
|
lengthAry[i] = parseInt(lenStr[i]); |
|
} |
|
lengthAry[lenStr.length] = 255; |
|
|
|
if (Blob) { |
|
var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]); |
|
doneCallback(null, blob); |
|
} |
|
}); |
|
} |
|
|
|
map(packets, encodeOne, function(err, results) { |
|
return callback(new Blob(results)); |
|
}); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.decodePayloadAsBinary = function (data, binaryType, callback) { |
|
if (typeof binaryType === 'function') { |
|
callback = binaryType; |
|
binaryType = null; |
|
} |
|
|
|
var bufferTail = data; |
|
var buffers = []; |
|
|
|
while (bufferTail.byteLength > 0) { |
|
var tailArray = new Uint8Array(bufferTail); |
|
var isString = tailArray[0] === 0; |
|
var msgLength = ''; |
|
|
|
for (var i = 1; ; i++) { |
|
if (tailArray[i] === 255) break; |
|
|
|
|
|
if (msgLength.length > 310) { |
|
return callback(err, 0, 1); |
|
} |
|
|
|
msgLength += tailArray[i]; |
|
} |
|
|
|
bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length); |
|
msgLength = parseInt(msgLength); |
|
|
|
var msg = sliceBuffer(bufferTail, 0, msgLength); |
|
if (isString) { |
|
try { |
|
msg = String.fromCharCode.apply(null, new Uint8Array(msg)); |
|
} catch (e) { |
|
|
|
var typed = new Uint8Array(msg); |
|
msg = ''; |
|
for (var i = 0; i < typed.length; i++) { |
|
msg += String.fromCharCode(typed[i]); |
|
} |
|
} |
|
} |
|
|
|
buffers.push(msg); |
|
bufferTail = sliceBuffer(bufferTail, msgLength); |
|
} |
|
|
|
var total = buffers.length; |
|
buffers.forEach(function(buffer, i) { |
|
callback(exports.decodePacket(buffer, binaryType, true), i, total); |
|
}); |
|
}; |
|
|