diff --git a/dist/netflux.es2015.js b/dist/netflux.es2015.js index 60e3555a..714a5b2f 100644 --- a/dist/netflux.es2015.js +++ b/dist/netflux.es2015.js @@ -53,6 +53,7 @@ module.exports.browserShim = chromeShim; chromeShim.shimGetUserMedia(); + chromeShim.shimMediaStream(); chromeShim.shimSourceObject(); chromeShim.shimPeerConnection(); chromeShim.shimOnTrack(); @@ -80,6 +81,7 @@ // Export to the adapter global object visible in the browser. module.exports.browserShim = edgeShim; + edgeShim.shimGetUserMedia(); edgeShim.shimPeerConnection(); break; case 'safari': @@ -99,6 +101,7 @@ })(); },{"./chrome/chrome_shim":3,"./edge/edge_shim":1,"./firefox/firefox_shim":5,"./safari/safari_shim":7,"./utils":8}],3:[function(require,module,exports){ + /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -112,6 +115,10 @@ var browserDetails = require('../utils.js').browserDetails; var chromeShim = { + shimMediaStream: function() { + window.MediaStream = window.MediaStream || window.webkitMediaStream; + }, + shimOnTrack: function() { if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { @@ -231,9 +238,21 @@ return standardReport; }; + // shim getStats with maplike support + var makeMapStats = function(stats, legacyStats) { + var map = new Map(Object.keys(stats).map(function(key) { + return[key, stats[key]]; + })); + legacyStats = legacyStats || stats; + Object.keys(legacyStats).forEach(function(key) { + map[key] = legacyStats[key]; + }); + return map; + }; + if (arguments.length >= 2) { var successCallbackWrapper_ = function(response) { - args[1](fixChromeStats_(response)); + args[1](makeMapStats(fixChromeStats_(response))); }; return origGetStats.apply(this, [successCallbackWrapper_, @@ -243,14 +262,19 @@ // promise-support return new Promise(function(resolve, reject) { if (args.length === 1 && typeof selector === 'object') { - origGetStats.apply(self, - [function(response) { - resolve.apply(null, [fixChromeStats_(response)]); - }, reject]); + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); } else { - origGetStats.apply(self, [resolve, reject]); + // Preserve legacy chrome stats only on legacy access of stats obj + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response), + response.result())); + }, reject]); } - }); + }).then(successCallback, errorCallback); }; return pc; @@ -266,46 +290,55 @@ }); } - // add promise support - ['createOffer', 'createAnswer'].forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - var self = this; - if (arguments.length < 1 || (arguments.length === 1 && - typeof(arguments[0]) === 'object')) { - var opts = arguments.length === 1 ? arguments[0] : undefined; - return new Promise(function(resolve, reject) { - nativeMethod.apply(self, [resolve, reject, opts]); + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var self = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; }); - } - return nativeMethod.apply(this, arguments); - }; - }); + } + // shim implicit creation of RTCSessionDescription/RTCIceCandidate ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] .forEach(function(method) { var nativeMethod = webkitRTCPeerConnection.prototype[method]; webkitRTCPeerConnection.prototype[method] = function() { - var args = arguments; - var self = this; - args[0] = new ((method === 'addIceCandidate')? - RTCIceCandidate : RTCSessionDescription)(args[0]); - return new Promise(function(resolve, reject) { - nativeMethod.apply(self, [args[0], - function() { - resolve(); - if (args.length >= 2) { - args[1].apply(null, []); - } - }, - function(err) { - reject(err); - if (args.length >= 3) { - args[2].apply(null, [err]); - } - }] - ); - }); + arguments[0] = new ((method === 'addIceCandidate') ? + RTCIceCandidate : RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); }; }); }, @@ -335,6 +368,7 @@ // Expose public methods. module.exports = { + shimMediaStream: chromeShim.shimMediaStream, shimOnTrack: chromeShim.shimOnTrack, shimSourceObject: chromeShim.shimSourceObject, shimPeerConnection: chromeShim.shimPeerConnection, @@ -408,17 +442,69 @@ return cc; }; - var getUserMedia_ = function(constraints, onSuccess, onError) { + var shimConstraints_ = function(constraints, func) { constraints = JSON.parse(JSON.stringify(constraints)); - if (constraints.audio) { + if (constraints && constraints.audio) { constraints.audio = constraintsToChrome_(constraints.audio); } - if (constraints.video) { + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile, where it defaults to "user". + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode)) { + delete constraints.video.facingMode; + if (face.exact === 'environment' || face.ideal === 'environment') { + // Look for "back" in label, or use last cam (typically back cam). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var back = devices.find(function(d) { + return d.label.toLowerCase().indexOf('back') !== -1; + }) || (devices.length && devices[devices.length - 1]); + if (back) { + constraints.video.deviceId = face.exact ? {exact: back.deviceId} : + {ideal: back.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } constraints.video = constraintsToChrome_(constraints.video); } logging('chrome: ' + JSON.stringify(constraints)); - return navigator.webkitGetUserMedia(constraints, onSuccess, onError); + return func(constraints); }; + + var shimError_ = function(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError', + ConstraintNotSatisfiedError: 'OverconstrainedError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + onError(shimError_(e)); + }); + }); + }; + navigator.getUserMedia = getUserMedia_; // Returns the result of getUserMedia as a Promise. @@ -459,15 +545,13 @@ // constraints. var origGetUserMedia = navigator.mediaDevices.getUserMedia. bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function(c) { - if (c) { - logging('spec: ' + JSON.stringify(c)); // whitespace for alignment - c.audio = constraintsToChrome_(c.audio); - c.video = constraintsToChrome_(c.video); - logging('chrome: ' + JSON.stringify(c)); - } - return origGetUserMedia(c); - }.bind(this); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; } // Dummy devicechange event methods. @@ -545,6 +629,10 @@ }, shimPeerConnection: function() { + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } // The RTCPeerConnection object. if (!window.RTCPeerConnection) { window.RTCPeerConnection = function(pcConfig, pcConstraints) { @@ -595,11 +683,30 @@ .forEach(function(method) { var nativeMethod = RTCPeerConnection.prototype[method]; RTCPeerConnection.prototype[method] = function() { - arguments[0] = new ((method === 'addIceCandidate')? + arguments[0] = new ((method === 'addIceCandidate') ? RTCIceCandidate : RTCSessionDescription)(arguments[0]); return nativeMethod.apply(this, arguments); }; }); + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var nativeGetStats = RTCPeerConnection.prototype.getStats; + RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + return makeMapStats(stats); + }) + .then(onSucc, onErr); + }; }, shimGetUserMedia: function() { @@ -743,6 +850,22 @@ // Expose public methods. module.exports = function() { + var shimError_ = e => ({ + name: { + SecurityError: 'NotAllowedError', + PermissionDeniedError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the user ' + + 'agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }); + + // getUserMedia constraints shim. var getUserMedia_ = function(constraints, onSuccess, onError) { var constraintsToFF37_ = function(c) { @@ -799,7 +922,8 @@ } logging('ff37: ' + JSON.stringify(constraints)); } - return navigator.mozGetUserMedia(constraints, onSuccess, onError); + return navigator.mozGetUserMedia(constraints, onSuccess, + e => onError(shimError_(e))); }; navigator.getUserMedia = getUserMedia_; @@ -842,6 +966,12 @@ }); }; } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = c => + origGetUserMedia(c).catch(e => Promise.reject(shimError_(e))); + } }; },{"../utils":8}],7:[function(require,module,exports){ @@ -1854,88 +1984,245 @@ } } - // Max message size sent on Channel: 16kb - const MAX_CHANNEL_MSG_BYTE_SIZE = 16384 + const MAX_USER_MSG_SIZE = 16366 + + const USER_MSG_OFFSET = 18 + + const HEADER_OFFSET = 9 + + const MAX_MSG_ID_SIZE = 65535 + + const ARRAY_BUFFER_TYPE = 1 + const U_INT_8_ARRAY_TYPE = 2 + const STRING_TYPE = 3 + const INT_8_ARRAY_TYPE = 4 + const U_INT_8_CLAMPED_ARRAY_TYPE = 5 + const INT_16_ARRAY_TYPE = 6 + const U_INT_16_ARRAY_TYPE = 7 + const INT_32_ARRAY_TYPE = 8 + const U_INT_32_ARRAY_TYPE = 9 + const FLOAT_32_ARRAY_TYPE = 10 + const FLOAT_64_ARRAY_TYPE = 11 + const DATA_VIEW_TYPE = 12 + + const buffers = new Map() + + class MessageBuilder extends Interface$1 { + handleUserMessage (data, senderId, recipientId, action) { + let workingData = this.userDataToType(data) + let dataUint8Array = workingData.content + if (dataUint8Array.byteLength <= MAX_USER_MSG_SIZE) { + let dataView = this.writeHeader(USER_DATA, senderId, recipientId, + dataUint8Array.byteLength + USER_MSG_OFFSET + ) + dataView.setUint32(HEADER_OFFSET, dataUint8Array.byteLength) + dataView.setUint8(13, workingData.type) + let resultUint8Array = new Uint8Array(dataView.buffer) + resultUint8Array.set(dataUint8Array, USER_MSG_OFFSET) + action(resultUint8Array.buffer) + } else { + const msgId = Math.ceil(Math.random() * MAX_MSG_ID_SIZE) + const totalChunksNb = Math.ceil(dataUint8Array.byteLength / MAX_USER_MSG_SIZE) + for (let chunkNb = 0; chunkNb < totalChunksNb; chunkNb++) { + let currentChunkMsgByteLength = Math.min( + MAX_USER_MSG_SIZE, + dataUint8Array.byteLength - MAX_USER_MSG_SIZE * chunkNb + ) + let dataView = this.writeHeader( + USER_DATA, + senderId, + recipientId, + USER_MSG_OFFSET + currentChunkMsgByteLength + ) + dataView.setUint32(9, dataUint8Array.byteLength) + dataView.setUint8(13, workingData.type) + dataView.setUint16(14, msgId) + dataView.setUint16(16, chunkNb) + let resultUint8Array = new Uint8Array(dataView.buffer) + let j = USER_MSG_OFFSET + let startIndex = MAX_USER_MSG_SIZE * chunkNb + let endIndex = startIndex + currentChunkMsgByteLength + for (let i = startIndex; i < endIndex; i++) { + resultUint8Array[j++] = dataUint8Array[i] + } + action(resultUint8Array.buffer) + } + } + } + + msg (code, data = {}) { + let msgEncoded = (new TextEncoder()).encode(JSON.stringify(data)) + let msgSize = msgEncoded.byteLength + HEADER_OFFSET + let dataView = this.writeHeader(code, null, null, msgSize) + let fullMsg = new Uint8Array(dataView.buffer) + fullMsg.set(msgEncoded, HEADER_OFFSET) + return fullMsg + } - const USER_MSG_BYTE_OFFSET = 18 + readUserMessage (wcId, senderId, data, action) { + let dataView = new DataView(data) + let msgSize = dataView.getUint32(HEADER_OFFSET) + let dataType = dataView.getUint8(13) + if (msgSize > MAX_USER_MSG_SIZE) { + let msgId = dataView.getUint16(14) + let chunk = dataView.getUint16(16) + let buffer = this.getBuffer(wcId, senderId, msgId) + if (buffer === undefined) { + this.setBuffer(wcId, senderId, msgId, + new Buffer(msgSize, data, chunk, (fullData) => { + action(this.extractUserData(fullData, dataType)) + }) + ) + } else { + buffer.add(data, chunk) + } + } else { + let dataArray = new Uint8Array(data) + let userData = new Uint8Array(data.byteLength - USER_MSG_OFFSET) + let j = USER_MSG_OFFSET + for (let i in userData) { + userData[i] = dataArray[j++] + } + action(this.extractUserData(userData.buffer, dataType)) + } + } - const STRING_TYPE = 100 + readInternalMessage (data) { + let uInt8Array = new Uint8Array(data) + return JSON.parse((new TextDecoder()) + .decode(uInt8Array.subarray(HEADER_OFFSET, uInt8Array.byteLength)) + ) + } - const UINT8ARRAY_TYPE = 101 + readHeader (data) { + let dataView = new DataView(data) + return { + code: dataView.getUint8(0), + senderId: dataView.getUint32(1), + recepientId: dataView.getUint32(5) + } + } - const ARRAYBUFFER_TYPE = 102 + writeHeader (code, senderId, recipientId, dataSize) { + let dataView = new DataView(new ArrayBuffer(dataSize)) + dataView.setUint8(0, code) + dataView.setUint32(1, senderId) + dataView.setUint32(5, recipientId) + return dataView + } - class MessageFormatter extends Interface$1 { - splitUserMessage (data, code, senderId, recipientId, action) { - const dataType = this.getDataType(data) - let uInt8Array - switch (dataType) { + extractUserData (buffer, type) { + switch (type) { + case ARRAY_BUFFER_TYPE: + return buffer + case U_INT_8_ARRAY_TYPE: + return new Uint8Array(buffer) case STRING_TYPE: - uInt8Array = new TextEncoder().encode(data) - break - case UINT8ARRAY_TYPE: - uInt8Array = data - break - case ARRAYBUFFER_TYPE: - uInt8Array = new Uint8Array(data) - break - default: - return + return new TextDecoder().decode(new Uint8Array(buffer)) + case INT_8_ARRAY_TYPE: + return new Int8Array(buffer) + case U_INT_8_CLAMPED_ARRAY_TYPE: + return new Uint8ClampedArray(buffer) + case INT_16_ARRAY_TYPE: + return new Int16Array(buffer) + case U_INT_16_ARRAY_TYPE: + return new Uint16Array(buffer) + case INT_32_ARRAY_TYPE: + return new Int32Array(buffer) + case U_INT_32_ARRAY_TYPE: + return new Uint32Array(buffer) + case FLOAT_32_ARRAY_TYPE: + return new Float32Array(buffer) + case FLOAT_64_ARRAY_TYPE: + return new Float64Array(buffer) + case DATA_VIEW_TYPE: + return new DataView(buffer) } + } - const maxUserDataLength = this.getMaxMsgByteLength() - const msgId = this.generateMsgId() - const totalChunksNb = Math.ceil(uInt8Array.byteLength / maxUserDataLength) - for (let chunkNb = 0; chunkNb < totalChunksNb; chunkNb++) { - let chunkMsgByteLength = Math.min(maxUserDataLength, uInt8Array.byteLength - maxUserDataLength * chunkNb) - let index = maxUserDataLength * chunkNb - let totalChunkByteLength = USER_MSG_BYTE_OFFSET + chunkMsgByteLength - let dataView = new DataView(new ArrayBuffer(totalChunkByteLength)) - dataView.setUint8(0, code) - dataView.setUint8(1, dataType) - dataView.setUint32(2, senderId) - dataView.setUint32(6, recipientId) - dataView.setUint16(10, msgId) - dataView.setUint32(12, uInt8Array.byteLength) - dataView.setUint16(16, chunkNb) - let resultUint8Array = new Uint8Array(dataView.buffer) - let j = USER_MSG_BYTE_OFFSET - for (let i = index; i < index + chunkMsgByteLength; i++) { - resultUint8Array[j++] = uInt8Array[i] + userDataToType (data) { + let result = {} + if (data instanceof ArrayBuffer) { + result.type = ARRAY_BUFFER_TYPE + result.content = new Uint8Array(data) + } else if (data instanceof Uint8Array) { + result.type = U_INT_8_ARRAY_TYPE + result.content = data + } else if (typeof data === 'string' || data instanceof String) { + result.type = STRING_TYPE + result.content = new TextEncoder().encode(data) + } else { + result.content = new Uint8Array(data.buffer) + if (data instanceof Int8Array) { + result.type = INT_8_ARRAY_TYPE + } else if (data instanceof Uint8ClampedArray) { + result.type = U_INT_8_CLAMPED_ARRAY_TYPE + } else if (data instanceof Int16Array) { + result.type = INT_16_ARRAY_TYPE + } else if (data instanceof Uint16Array) { + result.type = U_INT_16_ARRAY_TYPE + } else if (data instanceof Int32Array) { + result.type = INT_32_ARRAY_TYPE + } else if (data instanceof Uint32Array) { + result.type = U_INT_32_ARRAY_TYPE + } else if (data instanceof Float32Array) { + result.type = FLOAT_32_ARRAY_TYPE + } else if (data instanceof Float64Array) { + result.type = FLOAT_64_ARRAY_TYPE + } else if (data instanceof DataView) { + result.type = DATA_VIEW_TYPE + } else { + throw new Error('Unknown data object') } - action(resultUint8Array) } + return result } - msg (code, data = {}) { - let msgEncoded = (new TextEncoder()).encode(JSON.stringify(data)) - let i8array = new Uint8Array(1 + msgEncoded.length) - i8array[0] = code - let index = 1 - for (let i in msgEncoded) { - i8array[index++] = msgEncoded[i] + getBuffer (wcId, peerId, msgId) { + let wcBuffer = buffers.get(wcId) + if (wcBuffer !== undefined) { + let peerBuffer = wcBuffer.get(peerId) + if (peerBuffer !== undefined) { + return peerBuffer.get(msgId) + } } - return i8array + return undefined } - getMaxMsgByteLength () { - return MAX_CHANNEL_MSG_BYTE_SIZE - USER_MSG_BYTE_OFFSET + setBuffer (wcId, peerId, msgId, buffer) { + let wcBuffer = buffers.get(wcId) + if (wcBuffer === undefined) { + wcBuffer = new Map() + buffers.set(wcId, wcBuffer) + } + let peerBuffer = wcBuffer.get(peerId) + if (peerBuffer === undefined) { + peerBuffer = new Map() + wcBuffer.set(peerId, peerBuffer) + } + peerBuffer.set(msgId, buffer) } + } - generateMsgId () { - const MAX = 16777215 - return Math.round(Math.random() * MAX) + class Buffer { + constructor (fullDataSize, data, chunkNb, action) { + this.fullData = new Uint8Array(fullDataSize) + this.currentSize = 0 + this.action = action + this.add(data, chunkNb) } - getDataType (data) { - if (typeof data === 'string' || data instanceof String) { - return STRING_TYPE - } else if (data instanceof Uint8Array) { - return UINT8ARRAY_TYPE - } else if (data instanceof ArrayBuffer) { - return ARRAYBUFFER_TYPE + add (data, chunkNb) { + let dataChunk = new Uint8Array(data) + let dataChunkSize = data.byteLength + this.currentSize += dataChunkSize - USER_MSG_OFFSET + let index = chunkNb * MAX_USER_MSG_SIZE + for (let i = USER_MSG_OFFSET; i < dataChunkSize; i++) { + this.fullData[index++] = dataChunk[i] + } + if (this.currentSize === this.fullData.byteLength) { + this.action(this.fullData.buffer) } - return 0 } } @@ -1958,7 +2245,7 @@ */ const FULLY_CONNECTED = 'FullyConnectedService' - const MESSAGE_FORMATTER = 'MessageFormatterService' + const MESSAGE_FORMATTER = 'MessageBuilderService' const services = new Map() @@ -1984,7 +2271,7 @@ services.set(name, service) return service case MESSAGE_FORMATTER: - service = new MessageFormatter() + service = new MessageBuilder() services.set(name, service) return service default: @@ -2037,32 +2324,10 @@ } } - const formatter$1 = provide(MESSAGE_FORMATTER) - - class Buffer { - constructor (totalByteLength, action) { - this.totalByteLength = totalByteLength - this.currentByteLength = 0 - this.i8array = new Uint8Array(this.totalByteLength) - this.action = action - } - - add (data, chunkNb) { - const maxSize = formatter$1.getMaxMsgByteLength() - let intU8Array = new Uint8Array(data) - this.currentByteLength += data.byteLength - USER_MSG_BYTE_OFFSET - let index = chunkNb * maxSize - for (let i = USER_MSG_BYTE_OFFSET; i < data.byteLength; i++) { - this.i8array[index++] = intU8Array[i] - } - if (this.currentByteLength === this.totalByteLength) { - this.action(this.i8array) - } - } - } - const formatter = provide(MESSAGE_FORMATTER) + const MAX_ID = 4294967295 + /** * Constant used to build a message designated to API user. * @type {int} @@ -2185,8 +2450,6 @@ */ this.myId = this.generateId() - this.buffers = new Map() - this.onJoining = (id) => {} this.onMessage = (id, msg) => {} this.onLeaving = (id) => {} @@ -2204,7 +2467,7 @@ * @param {string} data Message */ send (data) { - formatter.splitUserMessage(data, USER_DATA, this.myId, null, (dataChunk) => { + formatter.handleUserMessage(data, this.myId, null, (dataChunk) => { this.manager.broadcast(this, dataChunk) }) } @@ -2216,7 +2479,7 @@ * @param {type} data Message */ sendTo (id, data) { - formatter.splitUserMessage(data, USER_DATA, this.myId, id, (dataChunk) => { + formatter.handleUserMessage(data, this.myId, id, (dataChunk) => { this.manager.sendTo(id, this, dataChunk) }) } @@ -2358,48 +2621,14 @@ } onChannelMessage (channel, data) { - let decoder = new TextDecoder() - let dataView = new DataView(data) - let code = dataView.getUint8(0) - if (code === USER_DATA) { - let totalMsgByteLength = dataView.getUint32(12) - let senderId = dataView.getUint32(2) - if (totalMsgByteLength > formatter.getMaxMsgByteLength()) { - let msgId = dataView.getUint32(10) - let msgMap - if (this.buffers.has(senderId)) { - msgMap = this.buffers.get(senderId) - } else { - msgMap = new Map() - this.buffers.set(senderId, msgMap) - } - let chunkNb = dataView.getUint16(16) - if (msgMap.has(msgId)) { - msgMap.get(msgId).add(dataView.buffer, chunkNb) - } else { - let buf = new Buffer(totalMsgByteLength, (i8array) => { - this.onMessage(senderId, decoder.decode(i8array)) - msgMap.delete(msgId) - }) - buf.add(dataView.buffer, chunkNb) - msgMap.set(msgId, buf) - } - } else { - let uInt8Array = new Uint8Array(data) - let str = decoder.decode(uInt8Array.subarray(USER_MSG_BYTE_OFFSET, uInt8Array.byteLength)) - this.onMessage(senderId, str) - } - return + let header = formatter.readHeader(data) + if (header.code === USER_DATA) { + formatter.readUserMessage(this.id, header.senderId, data, (fullData) => { + this.onMessage(header.senderId, fullData) + }) } else { - let msg = {} - let uInt8Array = new Uint8Array(data) - let str = decoder.decode(uInt8Array.subarray(1, uInt8Array.byteLength)) - msg = JSON.parse(str) - let jp - switch (code) { - // case USER_DATA: - // this.webChannel.onMessage(msg.id, msg.data) - // break + let msg = formatter.readInternalMessage(data) + switch (header.code) { case LEAVE: this.onLeaving(msg.id) for (let c of this.channels) { @@ -2419,7 +2648,7 @@ this.topology = msg.manager this.myId = msg.id channel.peerId = msg.intermediaryId - jp = new JoiningPeer(this.myId, channel.peerId) + let jp = new JoiningPeer(this.myId, channel.peerId) jp.intermediaryChannel = channel this.addJoiningPeer(jp) break @@ -2593,18 +2822,14 @@ } generateId () { - const MAX = 16777215 let id do { - id = Math.floor(Math.random() * MAX) + id = Math.ceil(Math.random() * MAX_ID) for (let c of this.channels) { - if (c.peerId === id) { - continue - } - } - if (this.myId === id) { - continue + if (id === c.peerId) continue } + if (this.hasJoiningPeer(id)) continue + if (id === this.myId) continue break } while (true) return id diff --git a/dist/netflux.js b/dist/netflux.js index 90b66833..4961c6a9 100644 --- a/dist/netflux.js +++ b/dist/netflux.js @@ -85,7 +85,7 @@ return /******/ (function(modules) { // webpackBootstrap } }); - __webpack_require__(12); + __webpack_require__(15); /***/ }, /* 1 */ @@ -96,9 +96,7 @@ return /******/ (function(modules) { // webpackBootstrap Object.defineProperty(exports, "__esModule", { value: true }); - exports.WebChannel = undefined; - - var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; + exports.WebChannel = exports.USER_DATA = undefined; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); @@ -106,16 +104,10 @@ return /******/ (function(modules) { // webpackBootstrap var _serviceProvider2 = _interopRequireDefault(_serviceProvider); - var _MessageFormatterService = __webpack_require__(9); - - var _Channel = __webpack_require__(10); + var _Channel = __webpack_require__(14); var _Channel2 = _interopRequireDefault(_Channel); - var _Buffer = __webpack_require__(11); - - var _Buffer2 = _interopRequireDefault(_Buffer); - var _JoiningPeer = __webpack_require__(6); var _JoiningPeer2 = _interopRequireDefault(_JoiningPeer); @@ -126,11 +118,13 @@ return /******/ (function(modules) { // webpackBootstrap var formatter = (0, _serviceProvider2.default)(_serviceProvider.MESSAGE_FORMATTER); + var MAX_ID = 4294967295; + /** * Constant used to build a message designated to API user. * @type {int} */ - var USER_DATA = 0; + var USER_DATA = exports.USER_DATA = 0; /** * Constant used to build a message designated to a specific service. @@ -254,8 +248,6 @@ return /******/ (function(modules) { // webpackBootstrap */ this.myId = this.generateId(); - this.buffers = new Map(); - this.onJoining = function (id) {}; this.onMessage = function (id, msg) {}; this.onLeaving = function (id) {}; @@ -282,7 +274,7 @@ return /******/ (function(modules) { // webpackBootstrap value: function send(data) { var _this = this; - formatter.splitUserMessage(data, USER_DATA, this.myId, null, function (dataChunk) { + formatter.handleUserMessage(data, this.myId, null, function (dataChunk) { _this.manager.broadcast(_this, dataChunk); }); } @@ -299,7 +291,7 @@ return /******/ (function(modules) { // webpackBootstrap value: function sendTo(id, data) { var _this2 = this; - formatter.splitUserMessage(data, USER_DATA, this.myId, id, function (dataChunk) { + formatter.handleUserMessage(data, this.myId, id, function (dataChunk) { _this2.manager.sendTo(id, _this2, dataChunk); }); } @@ -487,56 +479,14 @@ return /******/ (function(modules) { // webpackBootstrap value: function onChannelMessage(channel, data) { var _this5 = this; - var decoder = new TextDecoder(); - var dataView = new DataView(data); - var code = dataView.getUint8(0); - if (code === USER_DATA) { - var _ret = function () { - var totalMsgByteLength = dataView.getUint32(12); - var senderId = dataView.getUint32(2); - if (totalMsgByteLength > formatter.getMaxMsgByteLength()) { - (function () { - var msgId = dataView.getUint32(10); - var msgMap = void 0; - if (_this5.buffers.has(senderId)) { - msgMap = _this5.buffers.get(senderId); - } else { - msgMap = new Map(); - _this5.buffers.set(senderId, msgMap); - } - var chunkNb = dataView.getUint16(16); - if (msgMap.has(msgId)) { - msgMap.get(msgId).add(dataView.buffer, chunkNb); - } else { - var buf = new _Buffer2.default(totalMsgByteLength, function (i8array) { - _this5.onMessage(senderId, decoder.decode(i8array)); - msgMap.delete(msgId); - }); - buf.add(dataView.buffer, chunkNb); - msgMap.set(msgId, buf); - } - })(); - } else { - var uInt8Array = new Uint8Array(data); - var str = decoder.decode(uInt8Array.subarray(_MessageFormatterService.USER_MSG_BYTE_OFFSET, uInt8Array.byteLength)); - _this5.onMessage(senderId, str); - } - return { - v: void 0 - }; - }(); - - if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + var header = formatter.readHeader(data); + if (header.code === USER_DATA) { + formatter.readUserMessage(this.id, header.senderId, data, function (fullData) { + _this5.onMessage(header.senderId, fullData); + }); } else { - var msg = {}; - var uInt8Array = new Uint8Array(data); - var str = decoder.decode(uInt8Array.subarray(1, uInt8Array.byteLength)); - msg = JSON.parse(str); - var jp = void 0; - switch (code) { - // case USER_DATA: - // this.webChannel.onMessage(msg.id, msg.data) - // break + var msg = formatter.readInternalMessage(data); + switch (header.code) { case LEAVE: this.onLeaving(msg.id); var _iteratorNormalCompletion2 = true; @@ -578,7 +528,7 @@ return /******/ (function(modules) { // webpackBootstrap this.topology = msg.manager; this.myId = msg.id; channel.peerId = msg.intermediaryId; - jp = new _JoiningPeer2.default(this.myId, channel.peerId); + var jp = new _JoiningPeer2.default(this.myId, channel.peerId); jp.intermediaryChannel = channel; this.addJoiningPeer(jp); break; @@ -844,10 +794,9 @@ return /******/ (function(modules) { // webpackBootstrap }, { key: 'generateId', value: function generateId() { - var MAX = 16777215; var id = void 0; do { - id = Math.floor(Math.random() * MAX); + id = Math.ceil(Math.random() * MAX_ID); var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; @@ -856,9 +805,7 @@ return /******/ (function(modules) { // webpackBootstrap for (var _iterator6 = this.channels[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var c = _step6.value; - if (c.peerId === id) { - continue; - } + if (id === c.peerId) continue; } } catch (err) { _didIteratorError6 = true; @@ -875,9 +822,8 @@ return /******/ (function(modules) { // webpackBootstrap } } - if (this.myId === id) { - continue; - } + if (this.hasJoiningPeer(id)) continue; + if (id === this.myId) continue; break; } while (true); return id; @@ -918,9 +864,9 @@ return /******/ (function(modules) { // webpackBootstrap var _WebRTCService2 = _interopRequireDefault(_WebRTCService); - var _MessageFormatterService = __webpack_require__(9); + var _MessageBuilderService = __webpack_require__(9); - var _MessageFormatterService2 = _interopRequireDefault(_MessageFormatterService); + var _MessageBuilderService2 = _interopRequireDefault(_MessageBuilderService); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -943,7 +889,7 @@ return /******/ (function(modules) { // webpackBootstrap */ var FULLY_CONNECTED = exports.FULLY_CONNECTED = 'FullyConnectedService'; - var MESSAGE_FORMATTER = exports.MESSAGE_FORMATTER = 'MessageFormatterService'; + var MESSAGE_FORMATTER = exports.MESSAGE_FORMATTER = 'MessageBuilderService'; var services = new Map(); @@ -971,7 +917,7 @@ return /******/ (function(modules) { // webpackBootstrap services.set(name, service); return service; case MESSAGE_FORMATTER: - service = new _MessageFormatterService2.default(); + service = new _MessageBuilderService2.default(); services.set(name, service); return service; default: @@ -2177,12 +2123,12 @@ return /******/ (function(modules) { // webpackBootstrap /* 9 */ /***/ function(module, exports, __webpack_require__) { - 'use strict'; + /* WEBPACK VAR INJECTION */(function(Buffer) {'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); - exports.ARRAYBUFFER_TYPE = exports.UINT8ARRAY_TYPE = exports.STRING_TYPE = exports.USER_MSG_BYTE_OFFSET = exports.MAX_CHANNEL_MSG_BYTE_SIZE = undefined; + exports.HEADER_OFFSET = exports.USER_MSG_OFFSET = exports.MAX_USER_MSG_SIZE = exports.MAX_MSG_SIZE = undefined; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); @@ -2190,6 +2136,8 @@ return /******/ (function(modules) { // webpackBootstrap var service = _interopRequireWildcard(_service); + var _WebChannel = __webpack_require__(1); + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } @@ -2199,65 +2147,71 @@ return /******/ (function(modules) { // webpackBootstrap function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // Max message size sent on Channel: 16kb - var MAX_CHANNEL_MSG_BYTE_SIZE = exports.MAX_CHANNEL_MSG_BYTE_SIZE = 16384; + var MAX_MSG_SIZE = exports.MAX_MSG_SIZE = 16384; - var USER_MSG_BYTE_OFFSET = exports.USER_MSG_BYTE_OFFSET = 18; + var MAX_USER_MSG_SIZE = exports.MAX_USER_MSG_SIZE = 16366; - var STRING_TYPE = exports.STRING_TYPE = 100; + var USER_MSG_OFFSET = exports.USER_MSG_OFFSET = 18; - var UINT8ARRAY_TYPE = exports.UINT8ARRAY_TYPE = 101; + var HEADER_OFFSET = exports.HEADER_OFFSET = 9; - var ARRAYBUFFER_TYPE = exports.ARRAYBUFFER_TYPE = 102; + var MAX_MSG_ID_SIZE = 65535; - var MessageFormatter = function (_service$Interface) { - _inherits(MessageFormatter, _service$Interface); + var ARRAY_BUFFER_TYPE = 1; + var U_INT_8_ARRAY_TYPE = 2; + var STRING_TYPE = 3; + var INT_8_ARRAY_TYPE = 4; + var U_INT_8_CLAMPED_ARRAY_TYPE = 5; + var INT_16_ARRAY_TYPE = 6; + var U_INT_16_ARRAY_TYPE = 7; + var INT_32_ARRAY_TYPE = 8; + var U_INT_32_ARRAY_TYPE = 9; + var FLOAT_32_ARRAY_TYPE = 10; + var FLOAT_64_ARRAY_TYPE = 11; + var DATA_VIEW_TYPE = 12; - function MessageFormatter() { - _classCallCheck(this, MessageFormatter); + var buffers = new Map(); - return _possibleConstructorReturn(this, Object.getPrototypeOf(MessageFormatter).apply(this, arguments)); - } + var MessageBuilder = function (_service$Interface) { + _inherits(MessageBuilder, _service$Interface); - _createClass(MessageFormatter, [{ - key: 'splitUserMessage', - value: function splitUserMessage(data, code, senderId, recipientId, action) { - var dataType = this.getDataType(data); - var uInt8Array = void 0; - switch (dataType) { - case STRING_TYPE: - uInt8Array = new TextEncoder().encode(data); - break; - case UINT8ARRAY_TYPE: - uInt8Array = data; - break; - case ARRAYBUFFER_TYPE: - uInt8Array = new Uint8Array(data); - break; - default: - return; - } + function MessageBuilder() { + _classCallCheck(this, MessageBuilder); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(MessageBuilder).apply(this, arguments)); + } - var maxUserDataLength = this.getMaxMsgByteLength(); - var msgId = this.generateMsgId(); - var totalChunksNb = Math.ceil(uInt8Array.byteLength / maxUserDataLength); - for (var chunkNb = 0; chunkNb < totalChunksNb; chunkNb++) { - var chunkMsgByteLength = Math.min(maxUserDataLength, uInt8Array.byteLength - maxUserDataLength * chunkNb); - var index = maxUserDataLength * chunkNb; - var totalChunkByteLength = USER_MSG_BYTE_OFFSET + chunkMsgByteLength; - var dataView = new DataView(new ArrayBuffer(totalChunkByteLength)); - dataView.setUint8(0, code); - dataView.setUint8(1, dataType); - dataView.setUint32(2, senderId); - dataView.setUint32(6, recipientId); - dataView.setUint16(10, msgId); - dataView.setUint32(12, uInt8Array.byteLength); - dataView.setUint16(16, chunkNb); + _createClass(MessageBuilder, [{ + key: 'handleUserMessage', + value: function handleUserMessage(data, senderId, recipientId, action) { + var workingData = this.userDataToType(data); + var dataUint8Array = workingData.content; + if (dataUint8Array.byteLength <= MAX_USER_MSG_SIZE) { + var dataView = this.writeHeader(_WebChannel.USER_DATA, senderId, recipientId, dataUint8Array.byteLength + USER_MSG_OFFSET); + dataView.setUint32(HEADER_OFFSET, dataUint8Array.byteLength); + dataView.setUint8(13, workingData.type); var resultUint8Array = new Uint8Array(dataView.buffer); - var j = USER_MSG_BYTE_OFFSET; - for (var i = index; i < index + chunkMsgByteLength; i++) { - resultUint8Array[j++] = uInt8Array[i]; + resultUint8Array.set(dataUint8Array, USER_MSG_OFFSET); + action(resultUint8Array.buffer); + } else { + var msgId = Math.ceil(Math.random() * MAX_MSG_ID_SIZE); + var totalChunksNb = Math.ceil(dataUint8Array.byteLength / MAX_USER_MSG_SIZE); + for (var chunkNb = 0; chunkNb < totalChunksNb; chunkNb++) { + var currentChunkMsgByteLength = Math.min(MAX_USER_MSG_SIZE, dataUint8Array.byteLength - MAX_USER_MSG_SIZE * chunkNb); + var _dataView = this.writeHeader(_WebChannel.USER_DATA, senderId, recipientId, USER_MSG_OFFSET + currentChunkMsgByteLength); + _dataView.setUint32(9, dataUint8Array.byteLength); + _dataView.setUint8(13, workingData.type); + _dataView.setUint16(14, msgId); + _dataView.setUint16(16, chunkNb); + var _resultUint8Array = new Uint8Array(_dataView.buffer); + var j = USER_MSG_OFFSET; + var startIndex = MAX_USER_MSG_SIZE * chunkNb; + var endIndex = startIndex + currentChunkMsgByteLength; + for (var i = startIndex; i < endIndex; i++) { + _resultUint8Array[j++] = dataUint8Array[i]; + } + action(_resultUint8Array.buffer); } - action(resultUint8Array); } } }, { @@ -2266,227 +2220,2113 @@ return /******/ (function(modules) { // webpackBootstrap var data = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var msgEncoded = new TextEncoder().encode(JSON.stringify(data)); - var i8array = new Uint8Array(1 + msgEncoded.length); - i8array[0] = code; - var index = 1; - for (var i in msgEncoded) { - i8array[index++] = msgEncoded[i]; + var msgSize = msgEncoded.byteLength + HEADER_OFFSET; + var dataView = this.writeHeader(code, null, null, msgSize); + var fullMsg = new Uint8Array(dataView.buffer); + fullMsg.set(msgEncoded, HEADER_OFFSET); + return fullMsg; + } + }, { + key: 'readUserMessage', + value: function readUserMessage(wcId, senderId, data, action) { + var _this2 = this; + + var dataView = new DataView(data); + var msgSize = dataView.getUint32(HEADER_OFFSET); + var dataType = dataView.getUint8(13); + if (msgSize > MAX_USER_MSG_SIZE) { + var msgId = dataView.getUint16(14); + var chunk = dataView.getUint16(16); + var buffer = this.getBuffer(wcId, senderId, msgId); + if (buffer === undefined) { + this.setBuffer(wcId, senderId, msgId, new Buffer(msgSize, data, chunk, function (fullData) { + action(_this2.extractUserData(fullData, dataType)); + })); + } else { + buffer.add(data, chunk); + } + } else { + var dataArray = new Uint8Array(data); + var userData = new Uint8Array(data.byteLength - USER_MSG_OFFSET); + var j = USER_MSG_OFFSET; + for (var i in userData) { + userData[i] = dataArray[j++]; + } + action(this.extractUserData(userData.buffer, dataType)); } - return i8array; } }, { - key: 'getMaxMsgByteLength', - value: function getMaxMsgByteLength() { - return MAX_CHANNEL_MSG_BYTE_SIZE - USER_MSG_BYTE_OFFSET; + key: 'readInternalMessage', + value: function readInternalMessage(data) { + var uInt8Array = new Uint8Array(data); + return JSON.parse(new TextDecoder().decode(uInt8Array.subarray(HEADER_OFFSET, uInt8Array.byteLength))); + } + }, { + key: 'readHeader', + value: function readHeader(data) { + var dataView = new DataView(data); + return { + code: dataView.getUint8(0), + senderId: dataView.getUint32(1), + recepientId: dataView.getUint32(5) + }; + } + }, { + key: 'writeHeader', + value: function writeHeader(code, senderId, recipientId, dataSize) { + var dataView = new DataView(new ArrayBuffer(dataSize)); + dataView.setUint8(0, code); + dataView.setUint32(1, senderId); + dataView.setUint32(5, recipientId); + return dataView; } }, { - key: 'generateMsgId', - value: function generateMsgId() { - var MAX = 16777215; - return Math.round(Math.random() * MAX); + key: 'extractUserData', + value: function extractUserData(buffer, type) { + switch (type) { + case ARRAY_BUFFER_TYPE: + return buffer; + case U_INT_8_ARRAY_TYPE: + return new Uint8Array(buffer); + case STRING_TYPE: + return new TextDecoder().decode(new Uint8Array(buffer)); + case INT_8_ARRAY_TYPE: + return new Int8Array(buffer); + case U_INT_8_CLAMPED_ARRAY_TYPE: + return new Uint8ClampedArray(buffer); + case INT_16_ARRAY_TYPE: + return new Int16Array(buffer); + case U_INT_16_ARRAY_TYPE: + return new Uint16Array(buffer); + case INT_32_ARRAY_TYPE: + return new Int32Array(buffer); + case U_INT_32_ARRAY_TYPE: + return new Uint32Array(buffer); + case FLOAT_32_ARRAY_TYPE: + return new Float32Array(buffer); + case FLOAT_64_ARRAY_TYPE: + return new Float64Array(buffer); + case DATA_VIEW_TYPE: + return new DataView(buffer); + } } }, { - key: 'getDataType', - value: function getDataType(data) { - if (typeof data === 'string' || data instanceof String) { - return STRING_TYPE; + key: 'userDataToType', + value: function userDataToType(data) { + var result = {}; + if (data instanceof ArrayBuffer) { + result.type = ARRAY_BUFFER_TYPE; + result.content = new Uint8Array(data); } else if (data instanceof Uint8Array) { - return UINT8ARRAY_TYPE; - } else if (data instanceof ArrayBuffer) { - return ARRAYBUFFER_TYPE; + result.type = U_INT_8_ARRAY_TYPE; + result.content = data; + } else if (typeof data === 'string' || data instanceof String) { + result.type = STRING_TYPE; + result.content = new TextEncoder().encode(data); + } else { + result.content = new Uint8Array(data.buffer); + if (data instanceof Int8Array) { + result.type = INT_8_ARRAY_TYPE; + } else if (data instanceof Uint8ClampedArray) { + result.type = U_INT_8_CLAMPED_ARRAY_TYPE; + } else if (data instanceof Int16Array) { + result.type = INT_16_ARRAY_TYPE; + } else if (data instanceof Uint16Array) { + result.type = U_INT_16_ARRAY_TYPE; + } else if (data instanceof Int32Array) { + result.type = INT_32_ARRAY_TYPE; + } else if (data instanceof Uint32Array) { + result.type = U_INT_32_ARRAY_TYPE; + } else if (data instanceof Float32Array) { + result.type = FLOAT_32_ARRAY_TYPE; + } else if (data instanceof Float64Array) { + result.type = FLOAT_64_ARRAY_TYPE; + } else if (data instanceof DataView) { + result.type = DATA_VIEW_TYPE; + } else { + throw new Error('Unknown data object'); + } + } + return result; + } + }, { + key: 'getBuffer', + value: function getBuffer(wcId, peerId, msgId) { + var wcBuffer = buffers.get(wcId); + if (wcBuffer !== undefined) { + var peerBuffer = wcBuffer.get(peerId); + if (peerBuffer !== undefined) { + return peerBuffer.get(msgId); + } + } + return undefined; + } + }, { + key: 'setBuffer', + value: function setBuffer(wcId, peerId, msgId, buffer) { + var wcBuffer = buffers.get(wcId); + if (wcBuffer === undefined) { + wcBuffer = new Map(); + buffers.set(wcId, wcBuffer); + } + var peerBuffer = wcBuffer.get(peerId); + if (peerBuffer === undefined) { + peerBuffer = new Map(); + wcBuffer.set(peerId, peerBuffer); } - return 0; + peerBuffer.set(msgId, buffer); } }]); - return MessageFormatter; + return MessageBuilder; }(service.Interface); - exports.default = MessageFormatter; + var Buffer = function () { + function Buffer(fullDataSize, data, chunkNb, action) { + _classCallCheck(this, Buffer); + + this.fullData = new Uint8Array(fullDataSize); + this.currentSize = 0; + this.action = action; + this.add(data, chunkNb); + } + + _createClass(Buffer, [{ + key: 'add', + value: function add(data, chunkNb) { + var dataChunk = new Uint8Array(data); + var dataChunkSize = data.byteLength; + this.currentSize += dataChunkSize - USER_MSG_OFFSET; + var index = chunkNb * MAX_USER_MSG_SIZE; + for (var i = USER_MSG_OFFSET; i < dataChunkSize; i++) { + this.fullData[index++] = dataChunk[i]; + } + if (this.currentSize === this.fullData.byteLength) { + this.action(this.fullData.buffer); + } + } + }]); + + return Buffer; + }(); + + exports.default = MessageBuilder; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(10).Buffer)) /***/ }, /* 10 */ -/***/ function(module, exports) { +/***/ function(module, exports, __webpack_require__) { - 'use strict'; + /* WEBPACK VAR INJECTION */(function(Buffer, global) {/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ + /* eslint-disable no-proto */ - Object.defineProperty(exports, "__esModule", { - value: true - }); + 'use strict' - var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + var base64 = __webpack_require__(11) + var ieee754 = __webpack_require__(12) + var isArray = __webpack_require__(13) - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + exports.Buffer = Buffer + exports.SlowBuffer = SlowBuffer + exports.INSPECT_MAX_BYTES = 50 + Buffer.poolSize = 8192 // not used by this implementation + + var rootParent = {} /** - * Channel interface. - * [RTCDataChannel]{@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel} - * and - * [WebSocket]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket} - * implement it implicitly. Any other channel must implement this interface. + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Use Object implementation (most compatible, even IE6) * - * @interface + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * Due to various browser bugs, sometimes the Object implementation will be used even + * when the browser supports typed arrays. + * + * Note: + * + * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances, + * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438. + * + * - Safari 5-7 lacks support for changing the `Object.prototype.constructor` property + * on objects. + * + * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function. + * + * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of + * incorrect length in some situations. + + * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they + * get the Object implementation, which is slower but behaves correctly. */ + Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined + ? global.TYPED_ARRAY_SUPPORT + : typedArraySupport() + + function typedArraySupport () { + function Bar () {} + try { + var arr = new Uint8Array(1) + arr.foo = function () { return 42 } + arr.constructor = Bar + return arr.foo() === 42 && // typed array instances can be augmented + arr.constructor === Bar && // constructor can be set + typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray` + arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray` + } catch (e) { + return false + } + } - var Channel = function () { - function Channel(channel, webChannel, peerId) { - _classCallCheck(this, Channel); + function kMaxLength () { + return Buffer.TYPED_ARRAY_SUPPORT + ? 0x7fffffff + : 0x3fffffff + } - channel.binaryType = 'arraybuffer'; - this.channel = channel; - this.webChannel = webChannel; - this.peerId = peerId; + /** + * Class: Buffer + * ============= + * + * The Buffer constructor returns instances of `Uint8Array` that are augmented + * with function properties for all the node `Buffer` API functions. We use + * `Uint8Array` so that square bracket notation works as expected -- it returns + * a single octet. + * + * By augmenting the instances, we can avoid modifying the `Uint8Array` + * prototype. + */ + function Buffer (arg) { + if (!(this instanceof Buffer)) { + // Avoid going through an ArgumentsAdaptorTrampoline in the common case. + if (arguments.length > 1) return new Buffer(arg, arguments[1]) + return new Buffer(arg) } - _createClass(Channel, [{ - key: 'config', - value: function config() { - var _this = this; + if (!Buffer.TYPED_ARRAY_SUPPORT) { + this.length = 0 + this.parent = undefined + } - this.channel.onmessage = function (msgEvt) { - _this.webChannel.onChannelMessage(_this, msgEvt.data); - }; - this.channel.onerror = function (evt) { - _this.webChannel.onChannelError(evt); - }; - this.channel.onclose = function (evt) { - _this.webChannel.onChannelClose(evt); - }; - } + // Common case. + if (typeof arg === 'number') { + return fromNumber(this, arg) + } - /** - * send - description. - * - * @abstract - * @param {string} msg - Message in stringified JSON format. - */ + // Slightly less common case. + if (typeof arg === 'string') { + return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8') + } - }, { - key: 'send', - value: function send(data) { - if (this.channel.readyState !== 'closed') { - this.channel.send(data); - } + // Unusual. + return fromObject(this, arg) + } + + function fromNumber (that, length) { + that = allocate(that, length < 0 ? 0 : checked(length) | 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) { + for (var i = 0; i < length; i++) { + that[i] = 0 } + } + return that + } - /** - * Close channel. - * - * @abstract - */ + function fromString (that, string, encoding) { + if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8' - }, { - key: 'close', - value: function close() { - this.channel.close(); - } - }]); + // Assumption: byteLength() return value is always < kMaxLength. + var length = byteLength(string, encoding) | 0 + that = allocate(that, length) - return Channel; - }(); + that.write(string, encoding) + return that + } - exports.default = Channel; + function fromObject (that, object) { + if (Buffer.isBuffer(object)) return fromBuffer(that, object) -/***/ }, -/* 11 */ -/***/ function(module, exports, __webpack_require__) { + if (isArray(object)) return fromArray(that, object) - 'use strict'; + if (object == null) { + throw new TypeError('must start with number, buffer, array or string') + } - Object.defineProperty(exports, "__esModule", { - value: true - }); + if (typeof ArrayBuffer !== 'undefined') { + if (object.buffer instanceof ArrayBuffer) { + return fromTypedArray(that, object) + } + if (object instanceof ArrayBuffer) { + return fromArrayBuffer(that, object) + } + } - var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + if (object.length) return fromArrayLike(that, object) - var _serviceProvider = __webpack_require__(2); + return fromJsonObject(that, object) + } - var _serviceProvider2 = _interopRequireDefault(_serviceProvider); + function fromBuffer (that, buffer) { + var length = checked(buffer.length) | 0 + that = allocate(that, length) + buffer.copy(that, 0, 0, length) + return that + } - var _MessageFormatterService = __webpack_require__(9); + function fromArray (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that + } - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + // Duplicate of fromArray() to keep fromArray() monomorphic. + function fromTypedArray (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + // Truncating the elements is probably not what people expect from typed + // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior + // of the old Buffer constructor. + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that + } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + function fromArrayBuffer (that, array) { + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + array.byteLength + that = Buffer._augment(new Uint8Array(array)) + } else { + // Fallback: Return an object instance of the Buffer class + that = fromTypedArray(that, new Uint8Array(array)) + } + return that + } - var formatter = (0, _serviceProvider2.default)(_serviceProvider.MESSAGE_FORMATTER); + function fromArrayLike (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that + } - var Buffer = function () { - function Buffer(totalByteLength, action) { - _classCallCheck(this, Buffer); + // Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object. + // Returns a zero-length buffer for inputs that don't conform to the spec. + function fromJsonObject (that, object) { + var array + var length = 0 - this.totalByteLength = totalByteLength; - this.currentByteLength = 0; - this.i8array = new Uint8Array(this.totalByteLength); - this.action = action; + if (object.type === 'Buffer' && isArray(object.data)) { + array = object.data + length = checked(array.length) | 0 } + that = allocate(that, length) - _createClass(Buffer, [{ - key: 'add', - value: function add(data, chunkNb) { - var maxSize = formatter.getMaxMsgByteLength(); - var intU8Array = new Uint8Array(data); - this.currentByteLength += data.byteLength - _MessageFormatterService.USER_MSG_BYTE_OFFSET; - var index = chunkNb * maxSize; - for (var i = _MessageFormatterService.USER_MSG_BYTE_OFFSET; i < data.byteLength; i++) { - this.i8array[index++] = intU8Array[i]; - } - if (this.currentByteLength === this.totalByteLength) { - this.action(this.i8array); - } - } - }]); + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that + } - return Buffer; - }(); + if (Buffer.TYPED_ARRAY_SUPPORT) { + Buffer.prototype.__proto__ = Uint8Array.prototype + Buffer.__proto__ = Uint8Array + } else { + // pre-set for values that may exist in the future + Buffer.prototype.length = undefined + Buffer.prototype.parent = undefined + } - exports.default = Buffer; + function allocate (that, length) { + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + that = Buffer._augment(new Uint8Array(length)) + that.__proto__ = Buffer.prototype + } else { + // Fallback: Return an object instance of the Buffer class + that.length = length + that._isBuffer = true + } -/***/ }, -/* 12 */ -/***/ function(module, exports, __webpack_require__) { + var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1 + if (fromPool) that.parent = rootParent - var require;var require;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return require(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= kMaxLength()) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + kMaxLength().toString(16) + ' bytes') + } + return length | 0 + } - 'use strict'; + function SlowBuffer (subject, encoding) { + if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding) - // Shimming starts here. - (function() { - // Utils. - var logging = require('./utils').log; - var browserDetails = require('./utils').browserDetails; - // Export to the adapter global object visible in the browser. - module.exports.browserDetails = browserDetails; - module.exports.extractVersion = require('./utils').extractVersion; - module.exports.disableLog = require('./utils').disableLog; + var buf = new Buffer(subject, encoding) + delete buf.parent + return buf + } - // Comment out the line below if you want logging to occur, including logging - // for the switch statement below. Can also be turned on in the browser via - // adapter.disableLog(false), but then logging from the switch statement below - // will not appear. - require('./utils').disableLog(true); + Buffer.isBuffer = function isBuffer (b) { + return !!(b != null && b._isBuffer) + } - // Browser shims. - var chromeShim = require('./chrome/chrome_shim') || null; - var edgeShim = require('./edge/edge_shim') || null; - var firefoxShim = require('./firefox/firefox_shim') || null; - var safariShim = require('./safari/safari_shim') || null; + Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } - // Shim browser if found. - switch (browserDetails.browser) { - case 'opera': // fallthrough as it uses chrome shims - case 'chrome': - if (!chromeShim || !chromeShim.shimPeerConnection) { + if (a === b) return 0 + + var x = a.length + var y = b.length + + var i = 0 + var len = Math.min(x, y) + while (i < len) { + if (a[i] !== b[i]) break + + ++i + } + + if (i !== len) { + x = a[i] + y = b[i] + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 + } + + Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'binary': + case 'base64': + case 'raw': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } + } + + Buffer.concat = function concat (list, length) { + if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.') + + if (list.length === 0) { + return new Buffer(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; i++) { + length += list[i].length + } + } + + var buf = new Buffer(length) + var pos = 0 + for (i = 0; i < list.length; i++) { + var item = list[i] + item.copy(buf, pos) + pos += item.length + } + return buf + } + + function byteLength (string, encoding) { + if (typeof string !== 'string') string = '' + string + + var len = string.length + if (len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'binary': + // Deprecated + case 'raw': + case 'raws': + return len + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) return utf8ToBytes(string).length // assume utf8 + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } + } + Buffer.byteLength = byteLength + + function slowToString (encoding, start, end) { + var loweredCase = false + + start = start | 0 + end = end === undefined || end === Infinity ? this.length : end | 0 + + if (!encoding) encoding = 'utf8' + if (start < 0) start = 0 + if (end > this.length) end = this.length + if (end <= start) return '' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'binary': + return binarySlice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } + } + + Buffer.prototype.toString = function toString () { + var length = this.length | 0 + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) + } + + Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 + } + + Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + if (this.length > 0) { + str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') + if (this.length > max) str += ' ... ' + } + return '' + } + + Buffer.prototype.compare = function compare (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return 0 + return Buffer.compare(this, b) + } + + Buffer.prototype.indexOf = function indexOf (val, byteOffset) { + if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff + else if (byteOffset < -0x80000000) byteOffset = -0x80000000 + byteOffset >>= 0 + + if (this.length === 0) return -1 + if (byteOffset >= this.length) return -1 + + // Negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0) + + if (typeof val === 'string') { + if (val.length === 0) return -1 // special case: looking for empty string always fails + return String.prototype.indexOf.call(this, val, byteOffset) + } + if (Buffer.isBuffer(val)) { + return arrayIndexOf(this, val, byteOffset) + } + if (typeof val === 'number') { + if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') { + return Uint8Array.prototype.indexOf.call(this, val, byteOffset) + } + return arrayIndexOf(this, [ val ], byteOffset) + } + + function arrayIndexOf (arr, val, byteOffset) { + var foundIndex = -1 + for (var i = 0; byteOffset + i < arr.length; i++) { + if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex + } else { + foundIndex = -1 + } + } + return -1 + } + + throw new TypeError('val must be string, number or Buffer') + } + + // `get` is deprecated + Buffer.prototype.get = function get (offset) { + console.log('.get() is deprecated. Access using array indexes instead.') + return this.readUInt8(offset) + } + + // `set` is deprecated + Buffer.prototype.set = function set (v, offset) { + console.log('.set() is deprecated. Access using array indexes instead.') + return this.writeUInt8(v, offset) + } + + function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + // must be an even number of digits + var strLen = string.length + if (strLen % 2 !== 0) throw new Error('Invalid hex string') + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; i++) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (isNaN(parsed)) throw new Error('Invalid hex string') + buf[offset + i] = parsed + } + return i + } + + function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) + } + + function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) + } + + function binaryWrite (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) + } + + function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) + } + + function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) + } + + Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset | 0 + if (isFinite(length)) { + length = length | 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + // legacy write(string, encoding, offset, length) - remove in v0.13 + } else { + var swap = encoding + encoding = offset + offset = length | 0 + length = swap + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'binary': + return binaryWrite(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } + } + + Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } + } + + function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } + } + + function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) + } + + // Based on http://stackoverflow.com/a/22747272/680742, the browser with + // the lowest limit is Chrome, with 0x10000 args. + // We go 1 magnitude less, for safety + var MAX_ARGUMENTS_LENGTH = 0x1000 + + function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res + } + + function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret + } + + function binarySlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + ret += String.fromCharCode(buf[i]) + } + return ret + } + + function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; i++) { + out += toHex(buf[i]) + } + return out + } + + function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) + } + return res + } + + Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf + if (Buffer.TYPED_ARRAY_SUPPORT) { + newBuf = Buffer._augment(this.subarray(start, end)) + } else { + var sliceLen = end - start + newBuf = new Buffer(sliceLen, undefined) + for (var i = 0; i < sliceLen; i++) { + newBuf[i] = this[i + start] + } + } + + if (newBuf.length) newBuf.parent = this.parent || this + + return newBuf + } + + /* + * Need to make sure that buffer isn't trying to write out of bounds. + */ + function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') + } + + Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val + } + + Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val + } + + Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] + } + + Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) + } + + Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] + } + + Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) + } + + Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) + } + + Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val + } + + Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val + } + + Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) + } + + Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val + } + + Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val + } + + Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) + } + + Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) + } + + Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) + } + + Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) + } + + Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) + } + + Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) + } + + function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance') + if (value > max || value < min) throw new RangeError('value is out of bounds') + if (offset + ext > buf.length) throw new RangeError('index out of range') + } + + Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength + } + + Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength + } + + Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + this[offset] = (value & 0xff) + return offset + 1 + } + + function objectWriteUInt16 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) { + buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>> + (littleEndian ? i : 1 - i) * 8 + } + } + + Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 + } + + Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 + } + + function objectWriteUInt32 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffffffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) { + buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff + } + } + + Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 + } + + Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 + } + + Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = value < 0 ? 1 : 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength + } + + Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = value < 0 ? 1 : 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength + } + + Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 + } + + Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 + } + + Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 + } + + Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 + } + + Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 + } + + function checkIEEE754 (buf, value, offset, ext, max, min) { + if (value > max || value < min) throw new RangeError('value is out of bounds') + if (offset + ext > buf.length) throw new RangeError('index out of range') + if (offset < 0) throw new RangeError('index out of range') + } + + function writeFloat (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 + } + + Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) + } + + Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) + } + + function writeDouble (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 + } + + Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) + } + + Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) + } + + // copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) + Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + var i + + if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (i = len - 1; i >= 0; i--) { + target[i + targetStart] = this[i + start] + } + } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) { + // ascending copy from start + for (i = 0; i < len; i++) { + target[i + targetStart] = this[i + start] + } + } else { + target._set(this.subarray(start, start + len), targetStart) + } + + return len + } + + // fill(value, start=0, end=buffer.length) + Buffer.prototype.fill = function fill (value, start, end) { + if (!value) value = 0 + if (!start) start = 0 + if (!end) end = this.length + + if (end < start) throw new RangeError('end < start') + + // Fill 0 bytes; we're done + if (end === start) return + if (this.length === 0) return + + if (start < 0 || start >= this.length) throw new RangeError('start out of bounds') + if (end < 0 || end > this.length) throw new RangeError('end out of bounds') + + var i + if (typeof value === 'number') { + for (i = start; i < end; i++) { + this[i] = value + } + } else { + var bytes = utf8ToBytes(value.toString()) + var len = bytes.length + for (i = start; i < end; i++) { + this[i] = bytes[i % len] + } + } + + return this + } + + /** + * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance. + * Added in Node 0.12. Only available in browsers that support ArrayBuffer. + */ + Buffer.prototype.toArrayBuffer = function toArrayBuffer () { + if (typeof Uint8Array !== 'undefined') { + if (Buffer.TYPED_ARRAY_SUPPORT) { + return (new Buffer(this)).buffer + } else { + var buf = new Uint8Array(this.length) + for (var i = 0, len = buf.length; i < len; i += 1) { + buf[i] = this[i] + } + return buf.buffer + } + } else { + throw new TypeError('Buffer.toArrayBuffer not supported in this browser') + } + } + + // HELPER FUNCTIONS + // ================ + + var BP = Buffer.prototype + + /** + * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods + */ + Buffer._augment = function _augment (arr) { + arr.constructor = Buffer + arr._isBuffer = true + + // save reference to original Uint8Array set method before overwriting + arr._set = arr.set + + // deprecated + arr.get = BP.get + arr.set = BP.set + + arr.write = BP.write + arr.toString = BP.toString + arr.toLocaleString = BP.toString + arr.toJSON = BP.toJSON + arr.equals = BP.equals + arr.compare = BP.compare + arr.indexOf = BP.indexOf + arr.copy = BP.copy + arr.slice = BP.slice + arr.readUIntLE = BP.readUIntLE + arr.readUIntBE = BP.readUIntBE + arr.readUInt8 = BP.readUInt8 + arr.readUInt16LE = BP.readUInt16LE + arr.readUInt16BE = BP.readUInt16BE + arr.readUInt32LE = BP.readUInt32LE + arr.readUInt32BE = BP.readUInt32BE + arr.readIntLE = BP.readIntLE + arr.readIntBE = BP.readIntBE + arr.readInt8 = BP.readInt8 + arr.readInt16LE = BP.readInt16LE + arr.readInt16BE = BP.readInt16BE + arr.readInt32LE = BP.readInt32LE + arr.readInt32BE = BP.readInt32BE + arr.readFloatLE = BP.readFloatLE + arr.readFloatBE = BP.readFloatBE + arr.readDoubleLE = BP.readDoubleLE + arr.readDoubleBE = BP.readDoubleBE + arr.writeUInt8 = BP.writeUInt8 + arr.writeUIntLE = BP.writeUIntLE + arr.writeUIntBE = BP.writeUIntBE + arr.writeUInt16LE = BP.writeUInt16LE + arr.writeUInt16BE = BP.writeUInt16BE + arr.writeUInt32LE = BP.writeUInt32LE + arr.writeUInt32BE = BP.writeUInt32BE + arr.writeIntLE = BP.writeIntLE + arr.writeIntBE = BP.writeIntBE + arr.writeInt8 = BP.writeInt8 + arr.writeInt16LE = BP.writeInt16LE + arr.writeInt16BE = BP.writeInt16BE + arr.writeInt32LE = BP.writeInt32LE + arr.writeInt32BE = BP.writeInt32BE + arr.writeFloatLE = BP.writeFloatLE + arr.writeFloatBE = BP.writeFloatBE + arr.writeDoubleLE = BP.writeDoubleLE + arr.writeDoubleBE = BP.writeDoubleBE + arr.fill = BP.fill + arr.inspect = BP.inspect + arr.toArrayBuffer = BP.toArrayBuffer + + return arr + } + + var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g + + function base64clean (str) { + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = stringtrim(str).replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str + } + + function stringtrim (str) { + if (str.trim) return str.trim() + return str.replace(/^\s+|\s+$/g, '') + } + + function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) + } + + function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; i++) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes + } + + function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; i++) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray + } + + function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; i++) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray + } + + function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) + } + + function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; i++) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i + } + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(10).Buffer, (function() { return this; }()))) + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + ;(function (exports) { + 'use strict'; + + var Arr = (typeof Uint8Array !== 'undefined') + ? Uint8Array + : Array + + var PLUS = '+'.charCodeAt(0) + var SLASH = '/'.charCodeAt(0) + var NUMBER = '0'.charCodeAt(0) + var LOWER = 'a'.charCodeAt(0) + var UPPER = 'A'.charCodeAt(0) + var PLUS_URL_SAFE = '-'.charCodeAt(0) + var SLASH_URL_SAFE = '_'.charCodeAt(0) + + function decode (elt) { + var code = elt.charCodeAt(0) + if (code === PLUS || + code === PLUS_URL_SAFE) + return 62 // '+' + if (code === SLASH || + code === SLASH_URL_SAFE) + return 63 // '/' + if (code < NUMBER) + return -1 //no match + if (code < NUMBER + 10) + return code - NUMBER + 26 + 26 + if (code < UPPER + 26) + return code - UPPER + if (code < LOWER + 26) + return code - LOWER + 26 + } + + function b64ToByteArray (b64) { + var i, j, l, tmp, placeHolders, arr + + if (b64.length % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + var len = b64.length + placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0 + + // base64 is 4/3 + up to two characters of the original data + arr = new Arr(b64.length * 3 / 4 - placeHolders) + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? b64.length - 4 : b64.length + + var L = 0 + + function push (v) { + arr[L++] = v + } + + for (i = 0, j = 0; i < l; i += 4, j += 3) { + tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3)) + push((tmp & 0xFF0000) >> 16) + push((tmp & 0xFF00) >> 8) + push(tmp & 0xFF) + } + + if (placeHolders === 2) { + tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4) + push(tmp & 0xFF) + } else if (placeHolders === 1) { + tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2) + push((tmp >> 8) & 0xFF) + push(tmp & 0xFF) + } + + return arr + } + + function uint8ToBase64 (uint8) { + var i, + extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes + output = "", + temp, length + + function encode (num) { + return lookup.charAt(num) + } + + function tripletToBase64 (num) { + return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F) + } + + // go through the array every three bytes, we'll deal with trailing stuff later + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) + output += tripletToBase64(temp) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + switch (extraBytes) { + case 1: + temp = uint8[uint8.length - 1] + output += encode(temp >> 2) + output += encode((temp << 4) & 0x3F) + output += '==' + break + case 2: + temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]) + output += encode(temp >> 10) + output += encode((temp >> 4) & 0x3F) + output += encode((temp << 2) & 0x3F) + output += '=' + break + } + + return output + } + + exports.toByteArray = b64ToByteArray + exports.fromByteArray = uint8ToBase64 + }( false ? (this.base64js = {}) : exports)) + + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) + } + + exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 + } + + +/***/ }, +/* 13 */ +/***/ function(module, exports) { + + var toString = {}.toString; + + module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; + }; + + +/***/ }, +/* 14 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /** + * Channel interface. + * [RTCDataChannel]{@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel} + * and + * [WebSocket]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket} + * implement it implicitly. Any other channel must implement this interface. + * + * @interface + */ + + var Channel = function () { + function Channel(channel, webChannel, peerId) { + _classCallCheck(this, Channel); + + channel.binaryType = 'arraybuffer'; + this.channel = channel; + this.webChannel = webChannel; + this.peerId = peerId; + } + + _createClass(Channel, [{ + key: 'config', + value: function config() { + var _this = this; + + this.channel.onmessage = function (msgEvt) { + _this.webChannel.onChannelMessage(_this, msgEvt.data); + }; + this.channel.onerror = function (evt) { + _this.webChannel.onChannelError(evt); + }; + this.channel.onclose = function (evt) { + _this.webChannel.onChannelClose(evt); + }; + } + + /** + * send - description. + * + * @abstract + * @param {string} msg - Message in stringified JSON format. + */ + + }, { + key: 'send', + value: function send(data) { + if (this.channel.readyState !== 'closed') { + this.channel.send(data); + } + } + + /** + * Close channel. + * + * @abstract + */ + + }, { + key: 'close', + value: function close() { + this.channel.close(); + } + }]); + + return Channel; + }(); + + exports.default = Channel; + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + var require;var require;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return require(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 2) { var successCallbackWrapper_ = function(response) { - args[1](fixChromeStats_(response)); + args[1](makeMapStats(fixChromeStats_(response))); }; return origGetStats.apply(this, [successCallbackWrapper_, @@ -2685,14 +4544,19 @@ return /******/ (function(modules) { // webpackBootstrap // promise-support return new Promise(function(resolve, reject) { if (args.length === 1 && typeof selector === 'object') { - origGetStats.apply(self, - [function(response) { - resolve.apply(null, [fixChromeStats_(response)]); - }, reject]); + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); } else { - origGetStats.apply(self, [resolve, reject]); + // Preserve legacy chrome stats only on legacy access of stats obj + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response), + response.result())); + }, reject]); } - }); + }).then(successCallback, errorCallback); }; return pc; @@ -2708,46 +4572,55 @@ return /******/ (function(modules) { // webpackBootstrap }); } - // add promise support - ['createOffer', 'createAnswer'].forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - var self = this; - if (arguments.length < 1 || (arguments.length === 1 && - typeof(arguments[0]) === 'object')) { - var opts = arguments.length === 1 ? arguments[0] : undefined; - return new Promise(function(resolve, reject) { - nativeMethod.apply(self, [resolve, reject, opts]); + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var self = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; }); - } - return nativeMethod.apply(this, arguments); - }; - }); + } + // shim implicit creation of RTCSessionDescription/RTCIceCandidate ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] .forEach(function(method) { var nativeMethod = webkitRTCPeerConnection.prototype[method]; webkitRTCPeerConnection.prototype[method] = function() { - var args = arguments; - var self = this; - args[0] = new ((method === 'addIceCandidate')? - RTCIceCandidate : RTCSessionDescription)(args[0]); - return new Promise(function(resolve, reject) { - nativeMethod.apply(self, [args[0], - function() { - resolve(); - if (args.length >= 2) { - args[1].apply(null, []); - } - }, - function(err) { - reject(err); - if (args.length >= 3) { - args[2].apply(null, [err]); - } - }] - ); - }); + arguments[0] = new ((method === 'addIceCandidate') ? + RTCIceCandidate : RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); }; }); }, @@ -2777,6 +4650,7 @@ return /******/ (function(modules) { // webpackBootstrap // Expose public methods. module.exports = { + shimMediaStream: chromeShim.shimMediaStream, shimOnTrack: chromeShim.shimOnTrack, shimSourceObject: chromeShim.shimSourceObject, shimPeerConnection: chromeShim.shimPeerConnection, @@ -2850,17 +4724,69 @@ return /******/ (function(modules) { // webpackBootstrap return cc; }; - var getUserMedia_ = function(constraints, onSuccess, onError) { + var shimConstraints_ = function(constraints, func) { constraints = JSON.parse(JSON.stringify(constraints)); - if (constraints.audio) { + if (constraints && constraints.audio) { constraints.audio = constraintsToChrome_(constraints.audio); } - if (constraints.video) { + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile, where it defaults to "user". + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode)) { + delete constraints.video.facingMode; + if (face.exact === 'environment' || face.ideal === 'environment') { + // Look for "back" in label, or use last cam (typically back cam). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var back = devices.find(function(d) { + return d.label.toLowerCase().indexOf('back') !== -1; + }) || (devices.length && devices[devices.length - 1]); + if (back) { + constraints.video.deviceId = face.exact ? {exact: back.deviceId} : + {ideal: back.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } constraints.video = constraintsToChrome_(constraints.video); } logging('chrome: ' + JSON.stringify(constraints)); - return navigator.webkitGetUserMedia(constraints, onSuccess, onError); + return func(constraints); + }; + + var shimError_ = function(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError', + ConstraintNotSatisfiedError: 'OverconstrainedError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + onError(shimError_(e)); + }); + }); }; + navigator.getUserMedia = getUserMedia_; // Returns the result of getUserMedia as a Promise. @@ -2901,15 +4827,13 @@ return /******/ (function(modules) { // webpackBootstrap // constraints. var origGetUserMedia = navigator.mediaDevices.getUserMedia. bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function(c) { - if (c) { - logging('spec: ' + JSON.stringify(c)); // whitespace for alignment - c.audio = constraintsToChrome_(c.audio); - c.video = constraintsToChrome_(c.video); - logging('chrome: ' + JSON.stringify(c)); - } - return origGetUserMedia(c); - }.bind(this); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; } // Dummy devicechange event methods. @@ -2987,6 +4911,10 @@ return /******/ (function(modules) { // webpackBootstrap }, shimPeerConnection: function() { + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } // The RTCPeerConnection object. if (!window.RTCPeerConnection) { window.RTCPeerConnection = function(pcConfig, pcConstraints) { @@ -3037,11 +4965,30 @@ return /******/ (function(modules) { // webpackBootstrap .forEach(function(method) { var nativeMethod = RTCPeerConnection.prototype[method]; RTCPeerConnection.prototype[method] = function() { - arguments[0] = new ((method === 'addIceCandidate')? + arguments[0] = new ((method === 'addIceCandidate') ? RTCIceCandidate : RTCSessionDescription)(arguments[0]); return nativeMethod.apply(this, arguments); }; }); + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var nativeGetStats = RTCPeerConnection.prototype.getStats; + RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + return makeMapStats(stats); + }) + .then(onSucc, onErr); + }; }, shimGetUserMedia: function() { @@ -3185,6 +5132,22 @@ return /******/ (function(modules) { // webpackBootstrap // Expose public methods. module.exports = function() { + var shimError_ = e => ({ + name: { + SecurityError: 'NotAllowedError', + PermissionDeniedError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the user ' + + 'agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }); + + // getUserMedia constraints shim. var getUserMedia_ = function(constraints, onSuccess, onError) { var constraintsToFF37_ = function(c) { @@ -3241,7 +5204,8 @@ return /******/ (function(modules) { // webpackBootstrap } logging('ff37: ' + JSON.stringify(constraints)); } - return navigator.mozGetUserMedia(constraints, onSuccess, onError); + return navigator.mozGetUserMedia(constraints, onSuccess, + e => onError(shimError_(e))); }; navigator.getUserMedia = getUserMedia_; @@ -3284,6 +5248,12 @@ return /******/ (function(modules) { // webpackBootstrap }); }; } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = c => + origGetUserMedia(c).catch(e => Promise.reject(shimError_(e))); + } }; },{"../utils":8}],7:[function(require,module,exports){ diff --git a/dist/netflux.min.js b/dist/netflux.min.js index c94186a7..af5d9253 100644 --- a/dist/netflux.min.js +++ b/dist/netflux.min.js @@ -1,2 +1,5446 @@ -!function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.nf=n():e.nf=n()}(this,function(){return function(e){function n(r){if(t[r])return t[r].exports;var i=t[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}var t={};return n.m=e,n.c=t,n.p="",n(0)}([function(e,n,t){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.FULLY_CONNECTED=n.WEBRTC=n.WebChannel=void 0;var r=t(1);Object.defineProperty(n,"WebChannel",{enumerable:!0,get:function(){return r.WebChannel}});var i=t(2);Object.defineProperty(n,"WEBRTC",{enumerable:!0,get:function(){return i.WEBRTC}}),Object.defineProperty(n,"FULLY_CONNECTED",{enumerable:!0,get:function(){return i.FULLY_CONNECTED}}),t(12)},function(e,n,t){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0}),n.WebChannel=void 0;var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},a=function(){function e(e,n){for(var t=0;tg.getMaxMsgByteLength())!function(){var n=i.getUint32(10),a=void 0;t.buffers.has(o)?a=t.buffers.get(o):(a=new Map,t.buffers.set(o,a));var s=i.getUint16(16);if(a.has(n))a.get(n).add(i.buffer,s);else{var c=new h["default"](e,function(e){t.onMessage(o,r.decode(e)),a["delete"](n)});c.add(i.buffer,s),a.set(n,c)}}();else{var a=new Uint8Array(n),s=r.decode(a.subarray(u.USER_MSG_BYTE_OFFSET,a.byteLength));t.onMessage(o,s)}return{v:void 0}}();if("object"===("undefined"==typeof s?"undefined":o(s)))return s.v}else{var d={},f=new Uint8Array(n),l=r.decode(f.subarray(1,f.byteLength));d=JSON.parse(l);var v=void 0;switch(a){case b:this.onLeaving(d.id);var M=!0,T=!1,S=void 0;try{for(var j,_=this.channels[Symbol.iterator]();!(M=(j=_.next()).done);M=!0){var R=j.value;R.peerId===d.id&&this.channels["delete"](R)}}catch(I){T=!0,S=I}finally{try{!M&&_["return"]&&_["return"]()}finally{if(T)throw S}}break;case m:this.myId===d.recepient?(0,c["default"])(d.serviceName,this.settings).onMessage(this,e,d.data):this.sendSrvMsg(d.serviceName,d.recepient,d.data);break;case w:this.topology=d.manager,this.myId=d.id,e.peerId=d.intermediaryId,v=new p["default"](this.myId,e.peerId),v.intermediaryChannel=e,this.addJoiningPeer(v);break;case C:this.addJoiningPeer(new p["default"](d.id,d.intermediaryId));break;case O:this.removeJoiningPeer(d.id);break;case k:this.joinSuccess(this.myId),this.manager.broadcast(this,g.msg(P,{id:this.myId})),this.onJoin();break;case P:this.joinSuccess(d.id),this.onJoining(d.id);break;case E:e.onPong(),delete e.onPong}}}},{key:"onChannelError",value:function(e){console.log("DATA_CHANNEL ERROR: ",e)}},{key:"onChannelClose",value:function(e){console.log("DATA_CHANNEL CLOSE: ",e)}},{key:"initChannel",value:function(e,n){var t=this,r=arguments.length<=2||void 0===arguments[2]?-1:arguments[2];return new Promise(function(i,o){-1===r&&(r=t.generateId());var a=new f["default"](e,t,r);n?(a.config(),a.onPong=function(){return i(a)},e.send("ping")):e.onmessage=function(e){"ping"===e.data&&(a.config(),a.send(g.msg(E)),i(a))}})}},{key:"joinSuccess",value:function(e){var n=this,t=this.getJoiningPeer(e);t.channelsToAdd.forEach(function(e){n.channels.add(e),n.joiningPeers["delete"](t)})}},{key:"getJoiningPeer",value:function(e){var n=!0,t=!1,r=void 0;try{for(var i,o=this.joiningPeers[Symbol.iterator]();!(n=(i=o.next()).done);n=!0){var a=i.value;if(a.id===e)return a}}catch(s){t=!0,r=s}finally{try{!n&&o["return"]&&o["return"]()}finally{if(t)throw r}}throw new Error("Joining peer not found!")}},{key:"addJoiningPeer",value:function(e){if(this.hasJoiningPeer(e.id))throw new Error("Joining peer already exists!");this.joiningPeers.add(e)}},{key:"removeJoiningPeer",value:function(e){this.hasJoiningPeer(e)&&this.joiningPeers["delete"](this.getJoiningPeer(e))}},{key:"isJoining",value:function(){var e=!0,n=!1,t=void 0;try{for(var r,i=this.joiningPeers[Symbol.iterator]();!(e=(r=i.next()).done);e=!0){var o=r.value;if(o.id===this.myId)return!0}}catch(a){n=!0,t=a}finally{try{!e&&i["return"]&&i["return"]()}finally{if(n)throw t}}return!1}},{key:"hasJoiningPeer",value:function(e){var n=!0,t=!1,r=void 0;try{for(var i,o=this.joiningPeers[Symbol.iterator]();!(n=(i=o.next()).done);n=!0){var a=i.value;if(a.id===e)return!0}}catch(s){t=!0,r=s}finally{try{!n&&o["return"]&&o["return"]()}finally{if(t)throw r}}return!1}},{key:"generateKey",value:function(){for(var e=2,n=0,t="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",r="",i=e+Math.round(Math.random()*n),o=0;i>o;o++)r+=t[Math.round(Math.random()*(t.length-1))];return r}},{key:"generateId",value:function(){for(var e=16777215,n=void 0;;){n=Math.floor(Math.random()*e);var t=!0,r=!1,i=void 0;try{for(var o,a=this.channels[Symbol.iterator]();!(t=(o=a.next()).done);t=!0){var s=o.value;s.peerId!==n}}catch(c){r=!0,i=c}finally{try{!t&&a["return"]&&a["return"]()}finally{if(r)throw i}}if(this.myId!==n)break}return n}},{key:"topology",set:function(e){this.settings.topology=e,this.manager=(0,c["default"])(this.settings.topology)},get:function(){return this.settings.topology}}]),e}();n.WebChannel=M},function(e,n,t){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e){var n=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];if(v.has(e))return v.get(e);var t=void 0;switch(e){case f:return new c["default"](n);case l:return t=new a["default"],v.set(e,t),t;case h:return t=new d["default"],v.set(e,t),t;default:return null}}Object.defineProperty(n,"__esModule",{value:!0}),n.MESSAGE_FORMATTER=n.FULLY_CONNECTED=n.WEBRTC=void 0,n["default"]=i;var o=t(3),a=r(o),s=t(7),c=r(s),u=t(9),d=r(u),f=n.WEBRTC="WebRTCService",l=n.FULLY_CONNECTED="FullyConnectedService",h=n.MESSAGE_FORMATTER="MessageFormatterService",v=new Map},function(e,n,t){"use strict";function r(e){if(e&&e.__esModule)return e;var n={};if(null!=e)for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(n[t]=e[t]);return n["default"]=e,n}function i(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}function o(e,n){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!n||"object"!=typeof n&&"function"!=typeof n?e:n}function a(e,n){if("function"!=typeof n&&null!==n)throw new TypeError("Super expression must either be null or a function, not "+typeof n);e.prototype=Object.create(n&&n.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),n&&(Object.setPrototypeOf?Object.setPrototypeOf(e,n):e.__proto__=n)}Object.defineProperty(n,"__esModule",{value:!0});var s=function(){function e(e,n){for(var t=0;t0?y+Math.log10(e):y}},{key:"reUseIntermediaryChannelIfPossible",value:function(e,n,t){var r=null,i=void 0;return e.isJoining()?(i=e.getJoiningPeer(n),-1!==t.indexOf(i.intermediaryId)&&(r=i.intermediaryId)):-1!==t.indexOf(n)&&(i=e.getJoiningPeer(n),null!==i.intermediaryChannel&&(r=n)),null!==r&&(i.toAddList(i.intermediaryChannel),e.sendSrvMsg(this.name,r,{code:m,jpId:n}),t.splice(t.indexOf(r),1)),t}},{key:"add",value:function(e){throw new Error("Must be implemented by subclass!")}},{key:"broadcast",value:function(e,n){throw new Error("Must be implemented by subclass!")}},{key:"sendTo",value:function(e,n,t){throw new Error("Must be implemented by subclass!")}},{key:"leave",value:function(e){throw new Error("Must be implemented by subclass!")}}]),n}(d.Interface);n.Interface=w},function(e,n){"use strict";function t(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,n){for(var t=0;td;d++){var p=Math.min(s,a.byteLength-s*d),g=s*d,y=f+p,m=new DataView(new ArrayBuffer(y));m.setUint8(0,n),m.setUint8(1,o),m.setUint32(2,t),m.setUint32(6,r),m.setUint16(10,c),m.setUint32(12,a.byteLength),m.setUint16(16,d);for(var b=new Uint8Array(m.buffer),w=f,C=g;g+p>C;C++)b[w++]=a[C];i(b)}}},{key:"msg",value:function(e){var n=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],t=(new TextEncoder).encode(JSON.stringify(n)),r=new Uint8Array(1+t.length);r[0]=e;var i=1;for(var o in t)r[i++]=t[o];return r}},{key:"getMaxMsgByteLength",value:function(){return d-f}},{key:"generateMsgId",value:function(){var e=16777215;return Math.round(Math.random()*e)}},{key:"getDataType",value:function(e){return"string"==typeof e||e instanceof String?l:e instanceof Uint8Array?h:e instanceof ArrayBuffer?v:0}}]),n}(u.Interface);n["default"]=p},function(e,n){"use strict";function t(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,n){for(var t=0;t0&&"function"==typeof e)return i(e,n);var a=function(e){var n={},t=e.result();return t.forEach(function(e){var t={id:e.id,timestamp:e.timestamp,type:e.type};e.names().forEach(function(n){t[n]=e.stat(n)}),n[t.id]=t}),n};if(arguments.length>=2){var s=function(e){o[1](a(e))};return i.apply(this,[s,arguments[0]])}return new Promise(function(n,t){1===o.length&&"object"==typeof e?i.apply(r,[function(e){n.apply(null,[a(e)])},t]):i.apply(r,[n,t])})},t},window.RTCPeerConnection.prototype=webkitRTCPeerConnection.prototype,webkitRTCPeerConnection.generateCertificate&&Object.defineProperty(window.RTCPeerConnection,"generateCertificate",{get:function(){return webkitRTCPeerConnection.generateCertificate}}),["createOffer","createAnswer"].forEach(function(e){var n=webkitRTCPeerConnection.prototype[e];webkitRTCPeerConnection.prototype[e]=function(){var e=this;if(arguments.length<1||1===arguments.length&&"object"==typeof arguments[0]){var t=1===arguments.length?arguments[0]:void 0;return new Promise(function(r,i){n.apply(e,[r,i,t])})}return n.apply(this,arguments)}}),["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(e){var n=webkitRTCPeerConnection.prototype[e];webkitRTCPeerConnection.prototype[e]=function(){var t=arguments,r=this;return t[0]=new("addIceCandidate"===e?RTCIceCandidate:RTCSessionDescription)(t[0]),new Promise(function(e,i){n.apply(r,[t[0],function(){e(),t.length>=2&&t[1].apply(null,[])},function(e){i(e),t.length>=3&&t[2].apply(null,[e])}])})}})},attachMediaStream:function(e,n){r("DEPRECATED, attachMediaStream will soon be removed."),i.version>=43?e.srcObject=n:"undefined"!=typeof e.src?e.src=URL.createObjectURL(n):r("Error attaching stream to element.")},reattachMediaStream:function(e,n){r("DEPRECATED, reattachMediaStream will soon be removed."),i.version>=43?e.srcObject=n.srcObject:e.src=n.src}};n.exports={shimOnTrack:o.shimOnTrack,shimSourceObject:o.shimSourceObject,shimPeerConnection:o.shimPeerConnection,shimGetUserMedia:e("./getusermedia"),attachMediaStream:o.attachMediaStream,reattachMediaStream:o.reattachMediaStream}},{"../utils.js":8,"./getusermedia":4}],4:[function(e,n,t){"use strict";var r=e("../utils.js").log;n.exports=function(){var e=function(e){if("object"!=typeof e||e.mandatory||e.optional)return e;var n={};return Object.keys(e).forEach(function(t){if("require"!==t&&"advanced"!==t&&"mediaSource"!==t){var r="object"==typeof e[t]?e[t]:{ideal:e[t]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);var i=function(e,n){return e?e+n.charAt(0).toUpperCase()+n.slice(1):"deviceId"===n?"sourceId":n};if(void 0!==r.ideal){n.optional=n.optional||[];var o={};"number"==typeof r.ideal?(o[i("min",t)]=r.ideal,n.optional.push(o),o={},o[i("max",t)]=r.ideal,n.optional.push(o)):(o[i("",t)]=r.ideal,n.optional.push(o))}void 0!==r.exact&&"number"!=typeof r.exact?(n.mandatory=n.mandatory||{},n.mandatory[i("",t)]=r.exact):["min","max"].forEach(function(e){void 0!==r[e]&&(n.mandatory=n.mandatory||{},n.mandatory[i(e,t)]=r[e])})}}),e.advanced&&(n.optional=(n.optional||[]).concat(e.advanced)),n},n=function(n,t,i){return n=JSON.parse(JSON.stringify(n)),n.audio&&(n.audio=e(n.audio)),n.video&&(n.video=e(n.video)),r("chrome: "+JSON.stringify(n)),navigator.webkitGetUserMedia(n,t,i)};navigator.getUserMedia=n;var t=function(e){return new Promise(function(n,t){navigator.getUserMedia(e,n,t)})};if(navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:t,enumerateDevices:function(){return new Promise(function(e){var n={audio:"audioinput",video:"videoinput"};return MediaStreamTrack.getSources(function(t){e(t.map(function(e){return{label:e.label,kind:n[e.kind],deviceId:e.id,groupId:""}}))})})}}),navigator.mediaDevices.getUserMedia){var i=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(n){return n&&(r("spec: "+JSON.stringify(n)),n.audio=e(n.audio),n.video=e(n.video),r("chrome: "+JSON.stringify(n))),i(n)}.bind(this)}else navigator.mediaDevices.getUserMedia=function(e){return t(e)};"undefined"==typeof navigator.mediaDevices.addEventListener&&(navigator.mediaDevices.addEventListener=function(){r("Dummy mediaDevices.addEventListener called.")}),"undefined"==typeof navigator.mediaDevices.removeEventListener&&(navigator.mediaDevices.removeEventListener=function(){r("Dummy mediaDevices.removeEventListener called.")})}},{"../utils.js":8}],5:[function(e,n,t){"use strict";var r=e("../utils").log,i=e("../utils").browserDetails,o={shimOnTrack:function(){"object"!=typeof window||!window.RTCPeerConnection||"ontrack"in window.RTCPeerConnection.prototype||Object.defineProperty(window.RTCPeerConnection.prototype,"ontrack",{get:function(){return this._ontrack},set:function(e){this._ontrack&&(this.removeEventListener("track",this._ontrack),this.removeEventListener("addstream",this._ontrackpoly)),this.addEventListener("track",this._ontrack=e),this.addEventListener("addstream",this._ontrackpoly=function(e){e.stream.getTracks().forEach(function(n){var t=new Event("track");t.track=n,t.receiver={track:n},t.streams=[e.stream],this.dispatchEvent(t)}.bind(this))}.bind(this))}})},shimSourceObject:function(){"object"==typeof window&&(!window.HTMLMediaElement||"srcObject"in window.HTMLMediaElement.prototype||Object.defineProperty(window.HTMLMediaElement.prototype,"srcObject",{get:function(){return this.mozSrcObject},set:function(e){this.mozSrcObject=e}}))},shimPeerConnection:function(){window.RTCPeerConnection||(window.RTCPeerConnection=function(e,n){if(i.version<38&&e&&e.iceServers){for(var t=[],r=0;r=t&&parseInt(r[t],10)},detectBrowser:function(){var e={};if(e.browser=null,e.version=null,e.minVersion=null,"undefined"==typeof window||!window.navigator)return e.browser="Not a browser.",e;if(navigator.mozGetUserMedia)e.browser="firefox",e.version=this.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1),e.minVersion=31;else if(navigator.webkitGetUserMedia)if(window.webkitRTCPeerConnection)e.browser="chrome",e.version=this.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2),e.minVersion=38;else{if(!navigator.userAgent.match(/Version\/(\d+).(\d+)/))return e.browser="Unsupported webkit-based browser with GUM support but no WebRTC support.",e;e.browser="safari",e.version=this.extractVersion(navigator.userAgent,/AppleWebKit\/([0-9]+)\./,1),e.minVersion=602}else{if(!navigator.mediaDevices||!navigator.userAgent.match(/Edge\/(\d+).(\d+)$/))return e.browser="Not a supported browser.",e;e.browser="edge",e.version=this.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2),e.minVersion=10547}return e.version 0) { + return CONNECT_WITH_TIMEOUT + Math.log10(nbPeers); + } else { + return CONNECT_WITH_TIMEOUT; + } + } + }, { + key: 'reUseIntermediaryChannelIfPossible', + value: function reUseIntermediaryChannelIfPossible(webChannel, jpId, ids) { + var idToRemove = null; + var jp = void 0; + if (webChannel.isJoining()) { + jp = webChannel.getJoiningPeer(jpId); + if (ids.indexOf(jp.intermediaryId) !== -1) { + idToRemove = jp.intermediaryId; + } + } else { + if (ids.indexOf(jpId) !== -1) { + jp = webChannel.getJoiningPeer(jpId); + if (jp.intermediaryChannel !== null) { + idToRemove = jpId; + } + } + } + if (idToRemove !== null) { + jp.toAddList(jp.intermediaryChannel); + webChannel.sendSrvMsg(this.name, idToRemove, { + code: ADD_INTERMEDIARY_CHANNEL, jpId: jpId + }); + ids.splice(ids.indexOf(idToRemove), 1); + } + return ids; + } + + /** + * Adds a new peer into Web Channel. + * + * @abstract + * @param {Channel} ch - Channel to be added (it should has + * the `webChannel` property). + * @return {Promise} - Resolved once the channel has been succesfully added, + * rejected otherwise. + */ + + }, { + key: 'add', + value: function add(ch) { + throw new Error('Must be implemented by subclass!'); + } + + /** + * Send a message to all peers in Web Channel. + * + * @abstract + * @param {WebChannel} wc - Web Channel where the message will be propagated. + * @param {string} data - Data in stringified JSON format to be send. + */ + + }, { + key: 'broadcast', + value: function broadcast(wc, data) { + throw new Error('Must be implemented by subclass!'); + } + + /** + * Send a message to a particular peer in Web Channel. + * + * @abstract + * @param {string} id - Peer id. + * @param {WebChannel} wc - Web Channel where the message will be propagated. + * @param {string} data - Data in stringified JSON format to be send. + */ + + }, { + key: 'sendTo', + value: function sendTo(id, wc, data) { + throw new Error('Must be implemented by subclass!'); + } + + /** + * Leave Web Channel. + * + * @abstract + * @param {WebChannel} wc - Web Channel to leave. + */ + + }, { + key: 'leave', + value: function leave(wc) { + throw new Error('Must be implemented by subclass!'); + } + }]); + + return Interface; + }(service.Interface); + + exports. + /** @see module:webChannelManager~Interface */ + Interface = Interface; + +/***/ }, +/* 5 */ +/***/ function(module, exports) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /** + * Service module includes {@link module:channelBuilder}, + * {@link module:webChannelManager} and {@link module:channelProxy} modules. + * Services are substitutable stateless objects. Each service is identified by + * its class name and can receive messages via `WebChannel` sent by another + * service. + * + * @module service + * @see module:channelBuilder + * @see module:webChannelManager + * @see module:channelProxy + */ + + /** + * Each service must implement this interface. + * + * @interface + */ + + var Interface = function () { + function Interface() { + _classCallCheck(this, Interface); + } + + _createClass(Interface, [{ + key: "name", + + + /** + * Service name which corresponds to its class name. + * + * @return {string} - name + */ + get: function get() { + return this.constructor.name; + } + }]); + + return Interface; + }(); + + exports. + /** @see module:service~Interface */ + Interface = Interface; + +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /** + * This class represents a temporary state of a peer, while he is about to join + * the web channel. During the joining process every peer in the web channel + * and the joining peer have an instance of this class with the same `id` and + * `intermediaryId` attribute values. After the joining process has been finished + * regardless of success, these instances will be deleted. + */ + + var JoiningPeer = function () { + function JoiningPeer(id, intermediaryId) { + _classCallCheck(this, JoiningPeer); + + /** + * The joining peer id. + * + * @type {string} + */ + this.id = id; + + /** + * The id of the peer who invited the joining peer to the web channel. It is + * a member of the web channel and called an intermediary peer between the + * joining peer and the web channel. The same value for all instances. + * + * @type {string} + */ + this.intermediaryId = intermediaryId; + + /** + * The channel between the joining peer and intermediary peer. It is null + * for every peer, but the joining and intermediary peers. + * + * @type {Channel} + */ + this.intermediaryChannel = null; + + /** + * This attribute is proper to each peer. Array of channels which will be + * added to the current peer once the joining peer become the member of the + * web channel. + * + * @type {Array[Channel]} + */ + this.channelsToAdd = []; + + /** + * This attribute is proper to each peer. Array of channels which will be + * closed with the current peer once the joining peer become the member of the + * web channel. + * + * @type {Array[Channel]} + */ + this.channelsToRemove = []; + } + + /** + * Add channel to `channelsToAdd` array. + * + * @param {Channel} channel - Channel to add. + */ + + + _createClass(JoiningPeer, [{ + key: "toAddList", + value: function toAddList(channel) { + this.channelsToAdd[this.channelsToAdd.length] = channel; + } + + /** + * Add channel to `channelsToRemove` array + * + * @param {Channel} channel - Channel to add. + */ + + }, { + key: "toRemoveList", + value: function toRemoveList(channel) { + this.channelsToAdd[this.channelsToAdd.length] = channel; + } + }]); + + return JoiningPeer; + }(); + + exports.default = JoiningPeer; + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /** + * @external RTCPeerConnection + * @see {@link https://developer.mozilla.org/en/docs/Web/API/RTCPeerConnection} + */ + /** + * @external RTCSessionDescription + * @see {@link https://developer.mozilla.org/en/docs/Web/API/RTCSessionDescription} + */ + /** + * @external RTCDataChannel + * @see {@link https://developer.mozilla.org/en/docs/Web/API/RTCDataChannel} + */ + /** + * @external RTCIceCandidate + * @see {@link https://developer.mozilla.org/en/docs/Web/API/RTCIceCandidate} + */ + /** + * @external RTCPeerConnectionIceEvent + * @see {@link https://developer.mozilla.org/en/docs/Web/API/RTCPeerConnectionIceEvent} + */ + + var _channelBuilder = __webpack_require__(8); + + var channelBuilder = _interopRequireWildcard(_channelBuilder); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /** + * Ice candidate event handler. + * + * @callback WebRTCService~onCandidate + * @param {external:RTCPeerConnectionIceEvent} evt - Event. + */ + + /** + * Session description event handler. + * + * @callback WebRTCService~onSDP + * @param {external:RTCPeerConnectionIceEvent} evt - Event. + */ + + /** + * Data channel event handler. + * + * @callback WebRTCService~onChannel + * @param {external:RTCPeerConnectionIceEvent} evt - Event. + */ + + /** + * The goal of this class is to prevent the error when adding an ice candidate + * before the remote description has been set. + */ + + var RTCPendingConnections = function () { + function RTCPendingConnections() { + _classCallCheck(this, RTCPendingConnections); + + this.connections = new Map(); + } + + /** + * Prepares pending connection for the specified peer only if it has not been added already. + * + * @param {string} id - Peer id + */ + + + _createClass(RTCPendingConnections, [{ + key: 'add', + value: function add(id) { + var _this = this; + + if (!this.connections.has(id)) { + (function () { + var pc = null; + var obj = { promise: null }; + obj.promise = new Promise(function (resolve, reject) { + Object.defineProperty(obj, 'pc', { + get: function get() { + return pc; + }, + set: function set(value) { + pc = value; + resolve(); + } + }); + setTimeout(reject, CONNECT_TIMEOUT, 'timeout'); + }); + _this.connections.set(id, obj); + })(); + } + } + + /** + * Remove a pending connection from the Map. Usually when the connection has already + * been established and there is now interest to hold this reference. + * + * @param {string} id - Peer id. + */ + + }, { + key: 'remove', + value: function remove(id) { + this.connections.delete(id); + } + + /** + * Returns RTCPeerConnection object for the provided peer id. + * + * @param {string} id - Peer id. + * @return {external:RTCPeerConnection} - Peer connection. + */ + + }, { + key: 'getPC', + value: function getPC(id) { + return this.connections.get(id).pc; + } + + /** + * Updates RTCPeerConnection reference for the provided peer id. + * + * @param {string} id - Peer id. + * @param {external:RTCPeerConnection} pc - Peer connection. + */ + + }, { + key: 'setPC', + value: function setPC(id, pc) { + this.connections.get(id).pc = pc; + } + + /** + * When the remote description is set, it will add the ice candidate to the + * peer connection of specified peer. + * + * @param {string} id - Peer id. + * @param {external:RTCIceCandidate} candidate - Ice candidate. + * @return {Promise} - Resolved once the ice candidate has been succesfully added. + */ + + }, { + key: 'addIceCandidate', + value: function addIceCandidate(id, candidate) { + var obj = this.connections.get(id); + return obj.promise.then(function () { + return obj.pc.addIceCandidate(candidate); + }); + } + }]); + + return RTCPendingConnections; + }(); + + var CONNECT_TIMEOUT = 2000; + var connectionsByWC = new Map(); + + /** + * Service class responsible to establish connections between peers via + * `RTCDataChannel`. + * + * @see {@link external:RTCPeerConnection} + * @extends module:channelBuilder~Interface + */ + + var WebRTCService = function (_channelBuilder$Inter) { + _inherits(WebRTCService, _channelBuilder$Inter); + + /** + * WebRTCService constructor. + * + * @param {Object} [options] - This service options. + * @param {Object} [options.signaling='wws://sigver-coastteam.rhcloud.com:8000'] - + * Signaling server URL. + * @param {Object[]} [options.iceServers=[{urls: 'stun:23.21.150.121'},{urls: 'stun:stun.l.google.com:19302'},{urls: 'turn:numb.viagenie.ca', credential: 'webrtcdemo', username: 'louis%40mozilla.com'}]] - WebRTC options to setup which STUN + * and TURN servers to be used. + */ + + function WebRTCService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebRTCService); + + var _this2 = _possibleConstructorReturn(this, Object.getPrototypeOf(WebRTCService).call(this)); + + _this2.defaults = { + signaling: 'wws://sigver-coastteam.rhcloud.com:8000', + iceServers: [{ urls: 'stun:23.21.150.121' }, { urls: 'stun:stun.l.google.com:19302' }, { urls: 'turn:numb.viagenie.ca', credential: 'webrtcdemo', username: 'louis%40mozilla.com' }] + }; + _this2.settings = Object.assign({}, _this2.defaults, options); + return _this2; + } + + _createClass(WebRTCService, [{ + key: 'open', + value: function open(key, onChannel) { + var _this3 = this; + + var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + + var settings = Object.assign({}, this.settings, options); + return new Promise(function (resolve, reject) { + var connections = new RTCPendingConnections(); + var socket = void 0; + try { + socket = new window.WebSocket(settings.signaling); + } catch (err) { + reject(err.message); + } + // Send a message to signaling server: ready to receive offer + socket.onopen = function () { + try { + socket.send(JSON.stringify({ key: key })); + } catch (err) { + reject(err.message); + } + // TODO: find a better solution than setTimeout. This is for the case when the key already exists and thus the server will close the socket, but it will close it after this function resolves the Promise. + setTimeout(resolve, 100, { key: key, url: settings.signaling, socket: socket }); + }; + socket.onmessage = function (evt) { + var msg = JSON.parse(evt.data); + if (!Reflect.has(msg, 'id') || !Reflect.has(msg, 'data')) { + console.log('Unknown message from the signaling server: ' + evt.data); + socket.close(); + return; + } + connections.add(msg.id); + if (Reflect.has(msg.data, 'offer')) { + _this3.createPeerConnectionAndAnswer(function (candidate) { + return socket.send(JSON.stringify({ id: msg.id, data: { candidate: candidate } })); + }, function (answer) { + return socket.send(JSON.stringify({ id: msg.id, data: { answer: answer } })); + }, onChannel, msg.data.offer).then(function (pc) { + return connections.setPC(msg.id, pc); + }).catch(function (reason) { + console.error('Answer generation failed: ' + reason); + }); + } else if (Reflect.has(msg.data, 'candidate')) { + connections.addIceCandidate(msg.id, _this3.createIceCandidate(msg.data.candidate)).catch(function (reason) { + console.error('Adding ice candidate failed: ' + reason); + }); + } + }; + socket.onclose = function (closeEvt) { + if (closeEvt.code !== 1000) { + console.error('Socket with signaling server ' + settings.signaling + ' has been closed with code ' + closeEvt.code + ': ' + closeEvt.reason); + reject(closeEvt.reason); + } + }; + }); + } + }, { + key: 'join', + value: function join(key) { + var _this4 = this; + + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var settings = Object.assign({}, this.settings, options); + return new Promise(function (resolve, reject) { + var pc = void 0; + // Connect to the signaling server + var socket = new WebSocket(settings.signaling); + socket.onopen = function () { + // Prepare and send offer + _this4.createPeerConnectionAndOffer(function (candidate) { + return socket.send(JSON.stringify({ data: { candidate: candidate } })); + }, function (offer) { + return socket.send(JSON.stringify({ join: key, data: { offer: offer } })); + }, resolve).then(function (peerConnection) { + pc = peerConnection; + }).catch(reject); + }; + socket.onmessage = function (evt) { + try { + var msg = JSON.parse(evt.data); + // Check message format + if (!Reflect.has(msg, 'data')) { + reject('Unknown message from the signaling server: ' + evt.data); + } + + if (Reflect.has(msg.data, 'answer')) { + pc.setRemoteDescription(_this4.createSessionDescription(msg.data.answer)).catch(reject); + } else if (Reflect.has(msg.data, 'candidate')) { + pc.addIceCandidate(_this4.createIceCandidate(msg.data.candidate)).catch(function (evt) { + // This exception does not reject the current Promise, because + // still the connection may be established even without one or + // several candidates + console.error('Adding candidate failed: ', evt); + }); + } else { + reject('Unknown message from the signaling server: ' + evt.data); + } + } catch (err) { + reject(err.message); + } + }; + socket.onerror = function (evt) { + reject('WebSocket with signaling server error'); + }; + socket.onclose = function (closeEvt) { + if (closeEvt.code !== 1000) { + reject('Socket with signaling server ' + settings.signaling + ' has been closed with code ' + closeEvt.code + ': ' + closeEvt.reason); + } + }; + }); + } + }, { + key: 'connectMeTo', + value: function connectMeTo(wc, id) { + var _this5 = this; + + return new Promise(function (resolve, reject) { + var sender = wc.myId; + var connections = _this5.getPendingConnections(wc); + connections.add(id); + _this5.createPeerConnectionAndOffer(function (candidate) { + return wc.sendSrvMsg(_this5.name, id, { sender: sender, candidate: candidate }); + }, function (offer) { + return wc.sendSrvMsg(_this5.name, id, { sender: sender, offer: offer }); + }, function (channel) { + connections.remove(id); + resolve(channel); + }).then(function (pc) { + return connections.setPC(id, pc); + }); + setTimeout(reject, CONNECT_TIMEOUT, 'Timeout'); + }); + } + }, { + key: 'onMessage', + value: function onMessage(wc, channel, msg) { + var _this6 = this; + + var connections = this.getPendingConnections(wc); + connections.add(msg.sender); + if (Reflect.has(msg, 'offer')) { + this.createPeerConnectionAndAnswer(function (candidate) { + return wc.sendSrvMsg(_this6.name, msg.sender, { sender: wc.myId, candidate: candidate }); + }, function (answer) { + return wc.sendSrvMsg(_this6.name, msg.sender, { sender: wc.myId, answer: answer }); + }, function (channel) { + wc.initChannel(channel, false, msg.sender); + connections.remove(channel.peerId); + }, msg.offer).then(function (pc) { + connections.setPC(msg.sender, pc); + }); + }if (Reflect.has(msg, 'answer')) { + connections.getPC(msg.sender).setRemoteDescription(this.createSessionDescription(msg.answer)).catch(function (reason) { + console.error('Setting answer error: ' + reason); + }); + } else if (Reflect.has(msg, 'candidate')) { + connections.addIceCandidate(msg.sender, this.createIceCandidate(msg.candidate)).catch(function (reason) { + console.error('Setting candidate error: ' + reason); + }); + } + } + + /** + * Creates a peer connection and generates an SDP offer. + * + * @param {WebRTCService~onCandidate} onCandidate - Ice candidate event handler. + * @param {WebRTCService~onSDP} sendOffer - Session description event handler. + * @param {WebRTCService~onChannel} onChannel - Handler event when the data channel is ready. + * @return {Promise} - Resolved when the offer has been succesfully created, + * set as local description and sent to the peer. + */ + + }, { + key: 'createPeerConnectionAndOffer', + value: function createPeerConnectionAndOffer(onCandidate, sendOffer, onChannel) { + var pc = this.createPeerConnection(onCandidate); + var dc = pc.createDataChannel(null); + pc.oniceconnectionstatechange = function () { + if (pc.iceConnectionState === 'disconnected') { + dc.onclose(); + } + }; + dc.onopen = function (evt) { + return onChannel(dc); + }; + return pc.createOffer().then(function (offer) { + return pc.setLocalDescription(offer); + }).then(function () { + sendOffer(pc.localDescription.toJSON()); + return pc; + }); + } + + /** + * Creates a peer connection and generates an SDP answer. + * + * @param {WebRTCService~onCandidate} onCandidate - Ice candidate event handler. + * @param {WebRTCService~onSDP} sendOffer - Session description event handler. + * @param {WebRTCService~onChannel} onChannel - Handler event when the data channel is ready. + * @param {Object} offer - Offer received from a peer. + * @return {Promise} - Resolved when the offer has been succesfully created, + * set as local description and sent to the peer. + */ + + }, { + key: 'createPeerConnectionAndAnswer', + value: function createPeerConnectionAndAnswer(onCandidate, sendAnswer, onChannel, offer) { + var pc = this.createPeerConnection(onCandidate); + pc.ondatachannel = function (dcEvt) { + var dc = dcEvt.channel; + pc.oniceconnectionstatechange = function () { + if (pc.iceConnectionState === 'disconnected') { + dc.onclose(); + } + }; + dc.onopen = function (evt) { + return onChannel(dc); + }; + }; + return pc.setRemoteDescription(this.createSessionDescription(offer)).then(function () { + return pc.createAnswer(); + }).then(function (answer) { + return pc.setLocalDescription(answer); + }).then(function () { + sendAnswer(pc.localDescription.toJSON()); + return pc; + }); + } + + /** + * Creates an instance of `RTCPeerConnection` and sets `onicecandidate` event handler. + * + * @private + * @param {WebRTCService~onCandidate} onCandidate - Ice + * candidate event handler. + * @return {external:RTCPeerConnection} - Peer connection. + */ + + }, { + key: 'createPeerConnection', + value: function createPeerConnection(onCandidate) { + var pc = new RTCPeerConnection({ iceServers: this.settings.iceServers }); + pc.onicecandidate = function (evt) { + if (evt.candidate !== null) { + var candidate = { + candidate: evt.candidate.candidate, + sdpMLineIndex: evt.candidate.sdpMLineIndex + }; + onCandidate(candidate); + } + }; + return pc; + } + + /** + * Creates an instance of `RTCIceCandidate`. + * + * @private + * @param {Object} candidate - Candidate object created in + * {@link WebRTCService#createPeerConnection}. + * @param {} candidate.candidate + * @param {} candidate.sdpMLineIndex + * @return {external:RTCIceCandidate} - Ice candidate. + */ + + }, { + key: 'createIceCandidate', + value: function createIceCandidate(candidate) { + return new RTCIceCandidate(candidate); + } + + /** + * Creates an instance of `RTCSessionDescription`. + * + * @private + * @param {Object} sd - An offer or an answer created by WebRTC API. + * @param {} sd.type + * @param {} sd.sdp + * @return {external:RTCSessionDescription} - Session description. + */ + + }, { + key: 'createSessionDescription', + value: function createSessionDescription(sd) { + return Object.assign(new RTCSessionDescription(), sd); + } + }, { + key: 'getPendingConnections', + value: function getPendingConnections(wc) { + if (connectionsByWC.has(wc.id)) { + return connectionsByWC.get(wc.id); + } else { + var connections = new RTCPendingConnections(); + connectionsByWC.set(wc.id, connections); + return connections; + } + } + }]); + + return WebRTCService; + }(channelBuilder.Interface); + + exports.default = WebRTCService; + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.Interface = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _service = __webpack_require__(5); + + var service = _interopRequireWildcard(_service); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + /** + * Channel Builder module is responsible to create a connection between two + * peers. + * @module channelBuilder + * @see Channel + */ + + /** + * On channel callback for {@link module:channelBuilder~Interface#open} + * function. + * + * @callback module:channelBuilder~onChannelCallback + * @param {Channel} channel - A new channel. + */ + + /** + * Call back to initialize the channel. It should be executed on both peer + * sides during connection establishment to assure that both channels would be + * ready to be used in the web channel. + * + * @callback module:channelBuilder~initChannel + * @param {Channel} ch - Channel. + * @param {string} id - Unique channel identifier. + */ + + /** + * Interface to be implemented by each connection service. + * + * @interface + * @extends module:service~Interface + */ + + var Interface = function (_service$Interface) { + _inherits(Interface, _service$Interface); + + function Interface() { + _classCallCheck(this, Interface); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(Interface).apply(this, arguments)); + } + + _createClass(Interface, [{ + key: 'open', + + /** + * Enables other clients to establish a connection with you. + * + * @abstract + * @param {string} key - The unique identifier which has to be passed to the + * peers who need to connect to you. + * @param {module:channelBuilder~Interface~onChannelCallback} onChannel - Callback + * function to execute once the connection has been established. + * @param {Object} [options] - Any other options which depend on the service implementation. + * @return {Promise} - Once resolved, provide an Object with `key` and `url` + * attributes to be passed to {@link module:channelBuilder~Interface#join} function. + * It is rejected if an error occured. + */ + value: function open(key, onChannel, options) { + throw new Error('Must be implemented by subclass!'); + } + + /** + * Connects you with the peer who provided the `key`. + * + * @abstract + * @param {string} key - A key obtained from the peer who executed + * {@link module:channelBuilder~Interface#open} function. + * @param {Object} [options] Any other options which depend on the implementation. + * @return {Promise} It is resolved when the connection is established, otherwise it is rejected. + */ + + }, { + key: 'join', + value: function join(key, options) { + throw new Error('Must be implemented by subclass!'); + } + + /** + * Establish a connection between you and another peer (including joining peer) via web channel. + * + * @abstract + * @param {WebChannel} wc - Web Channel through which the connection will be established. + * @param {string} id - Peer id with whom you will be connected. + * @return {Promise} - Resolved once the connection has been established, rejected otherwise. + */ + + }, { + key: 'connectMeTo', + value: function connectMeTo(wc, id) { + throw new Error('Must be implemented by subclass!'); + } + }]); + + return Interface; + }(service.Interface); + + exports. + /** @see module:channelBuilder~Interface */ + Interface = Interface; + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(Buffer) {'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.HEADER_OFFSET = exports.USER_MSG_OFFSET = exports.MAX_USER_MSG_SIZE = exports.MAX_MSG_SIZE = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _service = __webpack_require__(5); + + var service = _interopRequireWildcard(_service); + + var _WebChannel = __webpack_require__(1); + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + // Max message size sent on Channel: 16kb + var MAX_MSG_SIZE = exports.MAX_MSG_SIZE = 16384; + + var MAX_USER_MSG_SIZE = exports.MAX_USER_MSG_SIZE = 16366; + + var USER_MSG_OFFSET = exports.USER_MSG_OFFSET = 18; + + var HEADER_OFFSET = exports.HEADER_OFFSET = 9; + + var MAX_MSG_ID_SIZE = 65535; + + var ARRAY_BUFFER_TYPE = 1; + var U_INT_8_ARRAY_TYPE = 2; + var STRING_TYPE = 3; + var INT_8_ARRAY_TYPE = 4; + var U_INT_8_CLAMPED_ARRAY_TYPE = 5; + var INT_16_ARRAY_TYPE = 6; + var U_INT_16_ARRAY_TYPE = 7; + var INT_32_ARRAY_TYPE = 8; + var U_INT_32_ARRAY_TYPE = 9; + var FLOAT_32_ARRAY_TYPE = 10; + var FLOAT_64_ARRAY_TYPE = 11; + var DATA_VIEW_TYPE = 12; + + var buffers = new Map(); + + var MessageBuilder = function (_service$Interface) { + _inherits(MessageBuilder, _service$Interface); + + function MessageBuilder() { + _classCallCheck(this, MessageBuilder); + + return _possibleConstructorReturn(this, Object.getPrototypeOf(MessageBuilder).apply(this, arguments)); + } + + _createClass(MessageBuilder, [{ + key: 'handleUserMessage', + value: function handleUserMessage(data, senderId, recipientId, action) { + var workingData = this.userDataToType(data); + var dataUint8Array = workingData.content; + if (dataUint8Array.byteLength <= MAX_USER_MSG_SIZE) { + var dataView = this.writeHeader(_WebChannel.USER_DATA, senderId, recipientId, dataUint8Array.byteLength + USER_MSG_OFFSET); + dataView.setUint32(HEADER_OFFSET, dataUint8Array.byteLength); + dataView.setUint8(13, workingData.type); + var resultUint8Array = new Uint8Array(dataView.buffer); + resultUint8Array.set(dataUint8Array, USER_MSG_OFFSET); + action(resultUint8Array.buffer); + } else { + var msgId = Math.ceil(Math.random() * MAX_MSG_ID_SIZE); + var totalChunksNb = Math.ceil(dataUint8Array.byteLength / MAX_USER_MSG_SIZE); + for (var chunkNb = 0; chunkNb < totalChunksNb; chunkNb++) { + var currentChunkMsgByteLength = Math.min(MAX_USER_MSG_SIZE, dataUint8Array.byteLength - MAX_USER_MSG_SIZE * chunkNb); + var _dataView = this.writeHeader(_WebChannel.USER_DATA, senderId, recipientId, USER_MSG_OFFSET + currentChunkMsgByteLength); + _dataView.setUint32(9, dataUint8Array.byteLength); + _dataView.setUint8(13, workingData.type); + _dataView.setUint16(14, msgId); + _dataView.setUint16(16, chunkNb); + var _resultUint8Array = new Uint8Array(_dataView.buffer); + var j = USER_MSG_OFFSET; + var startIndex = MAX_USER_MSG_SIZE * chunkNb; + var endIndex = startIndex + currentChunkMsgByteLength; + for (var i = startIndex; i < endIndex; i++) { + _resultUint8Array[j++] = dataUint8Array[i]; + } + action(_resultUint8Array.buffer); + } + } + } + }, { + key: 'msg', + value: function msg(code) { + var data = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var msgEncoded = new TextEncoder().encode(JSON.stringify(data)); + var msgSize = msgEncoded.byteLength + HEADER_OFFSET; + var dataView = this.writeHeader(code, null, null, msgSize); + var fullMsg = new Uint8Array(dataView.buffer); + fullMsg.set(msgEncoded, HEADER_OFFSET); + return fullMsg; + } + }, { + key: 'readUserMessage', + value: function readUserMessage(wcId, senderId, data, action) { + var _this2 = this; + + var dataView = new DataView(data); + var msgSize = dataView.getUint32(HEADER_OFFSET); + var dataType = dataView.getUint8(13); + if (msgSize > MAX_USER_MSG_SIZE) { + var msgId = dataView.getUint16(14); + var chunk = dataView.getUint16(16); + var buffer = this.getBuffer(wcId, senderId, msgId); + if (buffer === undefined) { + this.setBuffer(wcId, senderId, msgId, new Buffer(msgSize, data, chunk, function (fullData) { + action(_this2.extractUserData(fullData, dataType)); + })); + } else { + buffer.add(data, chunk); + } + } else { + var dataArray = new Uint8Array(data); + var userData = new Uint8Array(data.byteLength - USER_MSG_OFFSET); + var j = USER_MSG_OFFSET; + for (var i in userData) { + userData[i] = dataArray[j++]; + } + action(this.extractUserData(userData.buffer, dataType)); + } + } + }, { + key: 'readInternalMessage', + value: function readInternalMessage(data) { + var uInt8Array = new Uint8Array(data); + return JSON.parse(new TextDecoder().decode(uInt8Array.subarray(HEADER_OFFSET, uInt8Array.byteLength))); + } + }, { + key: 'readHeader', + value: function readHeader(data) { + var dataView = new DataView(data); + return { + code: dataView.getUint8(0), + senderId: dataView.getUint32(1), + recepientId: dataView.getUint32(5) + }; + } + }, { + key: 'writeHeader', + value: function writeHeader(code, senderId, recipientId, dataSize) { + var dataView = new DataView(new ArrayBuffer(dataSize)); + dataView.setUint8(0, code); + dataView.setUint32(1, senderId); + dataView.setUint32(5, recipientId); + return dataView; + } + }, { + key: 'extractUserData', + value: function extractUserData(buffer, type) { + switch (type) { + case ARRAY_BUFFER_TYPE: + return buffer; + case U_INT_8_ARRAY_TYPE: + return new Uint8Array(buffer); + case STRING_TYPE: + return new TextDecoder().decode(new Uint8Array(buffer)); + case INT_8_ARRAY_TYPE: + return new Int8Array(buffer); + case U_INT_8_CLAMPED_ARRAY_TYPE: + return new Uint8ClampedArray(buffer); + case INT_16_ARRAY_TYPE: + return new Int16Array(buffer); + case U_INT_16_ARRAY_TYPE: + return new Uint16Array(buffer); + case INT_32_ARRAY_TYPE: + return new Int32Array(buffer); + case U_INT_32_ARRAY_TYPE: + return new Uint32Array(buffer); + case FLOAT_32_ARRAY_TYPE: + return new Float32Array(buffer); + case FLOAT_64_ARRAY_TYPE: + return new Float64Array(buffer); + case DATA_VIEW_TYPE: + return new DataView(buffer); + } + } + }, { + key: 'userDataToType', + value: function userDataToType(data) { + var result = {}; + if (data instanceof ArrayBuffer) { + result.type = ARRAY_BUFFER_TYPE; + result.content = new Uint8Array(data); + } else if (data instanceof Uint8Array) { + result.type = U_INT_8_ARRAY_TYPE; + result.content = data; + } else if (typeof data === 'string' || data instanceof String) { + result.type = STRING_TYPE; + result.content = new TextEncoder().encode(data); + } else { + result.content = new Uint8Array(data.buffer); + if (data instanceof Int8Array) { + result.type = INT_8_ARRAY_TYPE; + } else if (data instanceof Uint8ClampedArray) { + result.type = U_INT_8_CLAMPED_ARRAY_TYPE; + } else if (data instanceof Int16Array) { + result.type = INT_16_ARRAY_TYPE; + } else if (data instanceof Uint16Array) { + result.type = U_INT_16_ARRAY_TYPE; + } else if (data instanceof Int32Array) { + result.type = INT_32_ARRAY_TYPE; + } else if (data instanceof Uint32Array) { + result.type = U_INT_32_ARRAY_TYPE; + } else if (data instanceof Float32Array) { + result.type = FLOAT_32_ARRAY_TYPE; + } else if (data instanceof Float64Array) { + result.type = FLOAT_64_ARRAY_TYPE; + } else if (data instanceof DataView) { + result.type = DATA_VIEW_TYPE; + } else { + throw new Error('Unknown data object'); + } + } + return result; + } + }, { + key: 'getBuffer', + value: function getBuffer(wcId, peerId, msgId) { + var wcBuffer = buffers.get(wcId); + if (wcBuffer !== undefined) { + var peerBuffer = wcBuffer.get(peerId); + if (peerBuffer !== undefined) { + return peerBuffer.get(msgId); + } + } + return undefined; + } + }, { + key: 'setBuffer', + value: function setBuffer(wcId, peerId, msgId, buffer) { + var wcBuffer = buffers.get(wcId); + if (wcBuffer === undefined) { + wcBuffer = new Map(); + buffers.set(wcId, wcBuffer); + } + var peerBuffer = wcBuffer.get(peerId); + if (peerBuffer === undefined) { + peerBuffer = new Map(); + wcBuffer.set(peerId, peerBuffer); + } + peerBuffer.set(msgId, buffer); + } + }]); + + return MessageBuilder; + }(service.Interface); + + var Buffer = function () { + function Buffer(fullDataSize, data, chunkNb, action) { + _classCallCheck(this, Buffer); + + this.fullData = new Uint8Array(fullDataSize); + this.currentSize = 0; + this.action = action; + this.add(data, chunkNb); + } + + _createClass(Buffer, [{ + key: 'add', + value: function add(data, chunkNb) { + var dataChunk = new Uint8Array(data); + var dataChunkSize = data.byteLength; + this.currentSize += dataChunkSize - USER_MSG_OFFSET; + var index = chunkNb * MAX_USER_MSG_SIZE; + for (var i = USER_MSG_OFFSET; i < dataChunkSize; i++) { + this.fullData[index++] = dataChunk[i]; + } + if (this.currentSize === this.fullData.byteLength) { + this.action(this.fullData.buffer); + } + } + }]); + + return Buffer; + }(); + + exports.default = MessageBuilder; + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(10).Buffer)) + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(Buffer, global) {/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ + /* eslint-disable no-proto */ + + 'use strict' + + var base64 = __webpack_require__(11) + var ieee754 = __webpack_require__(12) + var isArray = __webpack_require__(13) + + exports.Buffer = Buffer + exports.SlowBuffer = SlowBuffer + exports.INSPECT_MAX_BYTES = 50 + Buffer.poolSize = 8192 // not used by this implementation + + var rootParent = {} + + /** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Use Object implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * Due to various browser bugs, sometimes the Object implementation will be used even + * when the browser supports typed arrays. + * + * Note: + * + * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances, + * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438. + * + * - Safari 5-7 lacks support for changing the `Object.prototype.constructor` property + * on objects. + * + * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function. + * + * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of + * incorrect length in some situations. + + * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they + * get the Object implementation, which is slower but behaves correctly. + */ + Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined + ? global.TYPED_ARRAY_SUPPORT + : typedArraySupport() + + function typedArraySupport () { + function Bar () {} + try { + var arr = new Uint8Array(1) + arr.foo = function () { return 42 } + arr.constructor = Bar + return arr.foo() === 42 && // typed array instances can be augmented + arr.constructor === Bar && // constructor can be set + typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray` + arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray` + } catch (e) { + return false + } + } + + function kMaxLength () { + return Buffer.TYPED_ARRAY_SUPPORT + ? 0x7fffffff + : 0x3fffffff + } + + /** + * Class: Buffer + * ============= + * + * The Buffer constructor returns instances of `Uint8Array` that are augmented + * with function properties for all the node `Buffer` API functions. We use + * `Uint8Array` so that square bracket notation works as expected -- it returns + * a single octet. + * + * By augmenting the instances, we can avoid modifying the `Uint8Array` + * prototype. + */ + function Buffer (arg) { + if (!(this instanceof Buffer)) { + // Avoid going through an ArgumentsAdaptorTrampoline in the common case. + if (arguments.length > 1) return new Buffer(arg, arguments[1]) + return new Buffer(arg) + } + + if (!Buffer.TYPED_ARRAY_SUPPORT) { + this.length = 0 + this.parent = undefined + } + + // Common case. + if (typeof arg === 'number') { + return fromNumber(this, arg) + } + + // Slightly less common case. + if (typeof arg === 'string') { + return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8') + } + + // Unusual. + return fromObject(this, arg) + } + + function fromNumber (that, length) { + that = allocate(that, length < 0 ? 0 : checked(length) | 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) { + for (var i = 0; i < length; i++) { + that[i] = 0 + } + } + return that + } + + function fromString (that, string, encoding) { + if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8' + + // Assumption: byteLength() return value is always < kMaxLength. + var length = byteLength(string, encoding) | 0 + that = allocate(that, length) + + that.write(string, encoding) + return that + } + + function fromObject (that, object) { + if (Buffer.isBuffer(object)) return fromBuffer(that, object) + + if (isArray(object)) return fromArray(that, object) + + if (object == null) { + throw new TypeError('must start with number, buffer, array or string') + } + + if (typeof ArrayBuffer !== 'undefined') { + if (object.buffer instanceof ArrayBuffer) { + return fromTypedArray(that, object) + } + if (object instanceof ArrayBuffer) { + return fromArrayBuffer(that, object) + } + } + + if (object.length) return fromArrayLike(that, object) + + return fromJsonObject(that, object) + } + + function fromBuffer (that, buffer) { + var length = checked(buffer.length) | 0 + that = allocate(that, length) + buffer.copy(that, 0, 0, length) + return that + } + + function fromArray (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that + } + + // Duplicate of fromArray() to keep fromArray() monomorphic. + function fromTypedArray (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + // Truncating the elements is probably not what people expect from typed + // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior + // of the old Buffer constructor. + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that + } + + function fromArrayBuffer (that, array) { + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + array.byteLength + that = Buffer._augment(new Uint8Array(array)) + } else { + // Fallback: Return an object instance of the Buffer class + that = fromTypedArray(that, new Uint8Array(array)) + } + return that + } + + function fromArrayLike (that, array) { + var length = checked(array.length) | 0 + that = allocate(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that + } + + // Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object. + // Returns a zero-length buffer for inputs that don't conform to the spec. + function fromJsonObject (that, object) { + var array + var length = 0 + + if (object.type === 'Buffer' && isArray(object.data)) { + array = object.data + length = checked(array.length) | 0 + } + that = allocate(that, length) + + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that + } + + if (Buffer.TYPED_ARRAY_SUPPORT) { + Buffer.prototype.__proto__ = Uint8Array.prototype + Buffer.__proto__ = Uint8Array + } else { + // pre-set for values that may exist in the future + Buffer.prototype.length = undefined + Buffer.prototype.parent = undefined + } + + function allocate (that, length) { + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + that = Buffer._augment(new Uint8Array(length)) + that.__proto__ = Buffer.prototype + } else { + // Fallback: Return an object instance of the Buffer class + that.length = length + that._isBuffer = true + } + + var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1 + if (fromPool) that.parent = rootParent + + return that + } + + function checked (length) { + // Note: cannot use `length < kMaxLength` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= kMaxLength()) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + kMaxLength().toString(16) + ' bytes') + } + return length | 0 + } + + function SlowBuffer (subject, encoding) { + if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding) + + var buf = new Buffer(subject, encoding) + delete buf.parent + return buf + } + + Buffer.isBuffer = function isBuffer (b) { + return !!(b != null && b._isBuffer) + } + + Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + var i = 0 + var len = Math.min(x, y) + while (i < len) { + if (a[i] !== b[i]) break + + ++i + } + + if (i !== len) { + x = a[i] + y = b[i] + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 + } + + Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'binary': + case 'base64': + case 'raw': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } + } + + Buffer.concat = function concat (list, length) { + if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.') + + if (list.length === 0) { + return new Buffer(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; i++) { + length += list[i].length + } + } + + var buf = new Buffer(length) + var pos = 0 + for (i = 0; i < list.length; i++) { + var item = list[i] + item.copy(buf, pos) + pos += item.length + } + return buf + } + + function byteLength (string, encoding) { + if (typeof string !== 'string') string = '' + string + + var len = string.length + if (len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'binary': + // Deprecated + case 'raw': + case 'raws': + return len + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) return utf8ToBytes(string).length // assume utf8 + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } + } + Buffer.byteLength = byteLength + + function slowToString (encoding, start, end) { + var loweredCase = false + + start = start | 0 + end = end === undefined || end === Infinity ? this.length : end | 0 + + if (!encoding) encoding = 'utf8' + if (start < 0) start = 0 + if (end > this.length) end = this.length + if (end <= start) return '' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'binary': + return binarySlice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } + } + + Buffer.prototype.toString = function toString () { + var length = this.length | 0 + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) + } + + Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 + } + + Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + if (this.length > 0) { + str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') + if (this.length > max) str += ' ... ' + } + return '' + } + + Buffer.prototype.compare = function compare (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return 0 + return Buffer.compare(this, b) + } + + Buffer.prototype.indexOf = function indexOf (val, byteOffset) { + if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff + else if (byteOffset < -0x80000000) byteOffset = -0x80000000 + byteOffset >>= 0 + + if (this.length === 0) return -1 + if (byteOffset >= this.length) return -1 + + // Negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0) + + if (typeof val === 'string') { + if (val.length === 0) return -1 // special case: looking for empty string always fails + return String.prototype.indexOf.call(this, val, byteOffset) + } + if (Buffer.isBuffer(val)) { + return arrayIndexOf(this, val, byteOffset) + } + if (typeof val === 'number') { + if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') { + return Uint8Array.prototype.indexOf.call(this, val, byteOffset) + } + return arrayIndexOf(this, [ val ], byteOffset) + } + + function arrayIndexOf (arr, val, byteOffset) { + var foundIndex = -1 + for (var i = 0; byteOffset + i < arr.length; i++) { + if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex + } else { + foundIndex = -1 + } + } + return -1 + } + + throw new TypeError('val must be string, number or Buffer') + } + + // `get` is deprecated + Buffer.prototype.get = function get (offset) { + console.log('.get() is deprecated. Access using array indexes instead.') + return this.readUInt8(offset) + } + + // `set` is deprecated + Buffer.prototype.set = function set (v, offset) { + console.log('.set() is deprecated. Access using array indexes instead.') + return this.writeUInt8(v, offset) + } + + function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + // must be an even number of digits + var strLen = string.length + if (strLen % 2 !== 0) throw new Error('Invalid hex string') + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; i++) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (isNaN(parsed)) throw new Error('Invalid hex string') + buf[offset + i] = parsed + } + return i + } + + function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) + } + + function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) + } + + function binaryWrite (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) + } + + function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) + } + + function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) + } + + Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset | 0 + if (isFinite(length)) { + length = length | 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + // legacy write(string, encoding, offset, length) - remove in v0.13 + } else { + var swap = encoding + encoding = offset + offset = length | 0 + length = swap + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'binary': + return binaryWrite(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } + } + + Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } + } + + function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } + } + + function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) + } + + // Based on http://stackoverflow.com/a/22747272/680742, the browser with + // the lowest limit is Chrome, with 0x10000 args. + // We go 1 magnitude less, for safety + var MAX_ARGUMENTS_LENGTH = 0x1000 + + function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res + } + + function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret + } + + function binarySlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; i++) { + ret += String.fromCharCode(buf[i]) + } + return ret + } + + function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; i++) { + out += toHex(buf[i]) + } + return out + } + + function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) + } + return res + } + + Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf + if (Buffer.TYPED_ARRAY_SUPPORT) { + newBuf = Buffer._augment(this.subarray(start, end)) + } else { + var sliceLen = end - start + newBuf = new Buffer(sliceLen, undefined) + for (var i = 0; i < sliceLen; i++) { + newBuf[i] = this[i + start] + } + } + + if (newBuf.length) newBuf.parent = this.parent || this + + return newBuf + } + + /* + * Need to make sure that buffer isn't trying to write out of bounds. + */ + function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') + } + + Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val + } + + Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val + } + + Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] + } + + Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) + } + + Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] + } + + Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) + } + + Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) + } + + Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val + } + + Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val + } + + Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) + } + + Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val + } + + Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val + } + + Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) + } + + Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) + } + + Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) + } + + Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) + } + + Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) + } + + Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) + } + + function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance') + if (value > max || value < min) throw new RangeError('value is out of bounds') + if (offset + ext > buf.length) throw new RangeError('index out of range') + } + + Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength + } + + Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0) + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength + } + + Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + this[offset] = (value & 0xff) + return offset + 1 + } + + function objectWriteUInt16 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) { + buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>> + (littleEndian ? i : 1 - i) * 8 + } + } + + Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 + } + + Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 + } + + function objectWriteUInt32 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffffffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) { + buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff + } + } + + Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 + } + + Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 + } + + Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = value < 0 ? 1 : 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength + } + + Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = value < 0 ? 1 : 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength + } + + Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 + } + + Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 + } + + Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 + } + + Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 + } + + Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 + } + + function checkIEEE754 (buf, value, offset, ext, max, min) { + if (value > max || value < min) throw new RangeError('value is out of bounds') + if (offset + ext > buf.length) throw new RangeError('index out of range') + if (offset < 0) throw new RangeError('index out of range') + } + + function writeFloat (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 + } + + Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) + } + + Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) + } + + function writeDouble (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 + } + + Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) + } + + Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) + } + + // copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) + Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + var i + + if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (i = len - 1; i >= 0; i--) { + target[i + targetStart] = this[i + start] + } + } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) { + // ascending copy from start + for (i = 0; i < len; i++) { + target[i + targetStart] = this[i + start] + } + } else { + target._set(this.subarray(start, start + len), targetStart) + } + + return len + } + + // fill(value, start=0, end=buffer.length) + Buffer.prototype.fill = function fill (value, start, end) { + if (!value) value = 0 + if (!start) start = 0 + if (!end) end = this.length + + if (end < start) throw new RangeError('end < start') + + // Fill 0 bytes; we're done + if (end === start) return + if (this.length === 0) return + + if (start < 0 || start >= this.length) throw new RangeError('start out of bounds') + if (end < 0 || end > this.length) throw new RangeError('end out of bounds') + + var i + if (typeof value === 'number') { + for (i = start; i < end; i++) { + this[i] = value + } + } else { + var bytes = utf8ToBytes(value.toString()) + var len = bytes.length + for (i = start; i < end; i++) { + this[i] = bytes[i % len] + } + } + + return this + } + + /** + * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance. + * Added in Node 0.12. Only available in browsers that support ArrayBuffer. + */ + Buffer.prototype.toArrayBuffer = function toArrayBuffer () { + if (typeof Uint8Array !== 'undefined') { + if (Buffer.TYPED_ARRAY_SUPPORT) { + return (new Buffer(this)).buffer + } else { + var buf = new Uint8Array(this.length) + for (var i = 0, len = buf.length; i < len; i += 1) { + buf[i] = this[i] + } + return buf.buffer + } + } else { + throw new TypeError('Buffer.toArrayBuffer not supported in this browser') + } + } + + // HELPER FUNCTIONS + // ================ + + var BP = Buffer.prototype + + /** + * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods + */ + Buffer._augment = function _augment (arr) { + arr.constructor = Buffer + arr._isBuffer = true + + // save reference to original Uint8Array set method before overwriting + arr._set = arr.set + + // deprecated + arr.get = BP.get + arr.set = BP.set + + arr.write = BP.write + arr.toString = BP.toString + arr.toLocaleString = BP.toString + arr.toJSON = BP.toJSON + arr.equals = BP.equals + arr.compare = BP.compare + arr.indexOf = BP.indexOf + arr.copy = BP.copy + arr.slice = BP.slice + arr.readUIntLE = BP.readUIntLE + arr.readUIntBE = BP.readUIntBE + arr.readUInt8 = BP.readUInt8 + arr.readUInt16LE = BP.readUInt16LE + arr.readUInt16BE = BP.readUInt16BE + arr.readUInt32LE = BP.readUInt32LE + arr.readUInt32BE = BP.readUInt32BE + arr.readIntLE = BP.readIntLE + arr.readIntBE = BP.readIntBE + arr.readInt8 = BP.readInt8 + arr.readInt16LE = BP.readInt16LE + arr.readInt16BE = BP.readInt16BE + arr.readInt32LE = BP.readInt32LE + arr.readInt32BE = BP.readInt32BE + arr.readFloatLE = BP.readFloatLE + arr.readFloatBE = BP.readFloatBE + arr.readDoubleLE = BP.readDoubleLE + arr.readDoubleBE = BP.readDoubleBE + arr.writeUInt8 = BP.writeUInt8 + arr.writeUIntLE = BP.writeUIntLE + arr.writeUIntBE = BP.writeUIntBE + arr.writeUInt16LE = BP.writeUInt16LE + arr.writeUInt16BE = BP.writeUInt16BE + arr.writeUInt32LE = BP.writeUInt32LE + arr.writeUInt32BE = BP.writeUInt32BE + arr.writeIntLE = BP.writeIntLE + arr.writeIntBE = BP.writeIntBE + arr.writeInt8 = BP.writeInt8 + arr.writeInt16LE = BP.writeInt16LE + arr.writeInt16BE = BP.writeInt16BE + arr.writeInt32LE = BP.writeInt32LE + arr.writeInt32BE = BP.writeInt32BE + arr.writeFloatLE = BP.writeFloatLE + arr.writeFloatBE = BP.writeFloatBE + arr.writeDoubleLE = BP.writeDoubleLE + arr.writeDoubleBE = BP.writeDoubleBE + arr.fill = BP.fill + arr.inspect = BP.inspect + arr.toArrayBuffer = BP.toArrayBuffer + + return arr + } + + var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g + + function base64clean (str) { + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = stringtrim(str).replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str + } + + function stringtrim (str) { + if (str.trim) return str.trim() + return str.replace(/^\s+|\s+$/g, '') + } + + function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) + } + + function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; i++) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes + } + + function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; i++) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray + } + + function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; i++) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray + } + + function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) + } + + function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; i++) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i + } + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(10).Buffer, (function() { return this; }()))) + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + ;(function (exports) { + 'use strict'; + + var Arr = (typeof Uint8Array !== 'undefined') + ? Uint8Array + : Array + + var PLUS = '+'.charCodeAt(0) + var SLASH = '/'.charCodeAt(0) + var NUMBER = '0'.charCodeAt(0) + var LOWER = 'a'.charCodeAt(0) + var UPPER = 'A'.charCodeAt(0) + var PLUS_URL_SAFE = '-'.charCodeAt(0) + var SLASH_URL_SAFE = '_'.charCodeAt(0) + + function decode (elt) { + var code = elt.charCodeAt(0) + if (code === PLUS || + code === PLUS_URL_SAFE) + return 62 // '+' + if (code === SLASH || + code === SLASH_URL_SAFE) + return 63 // '/' + if (code < NUMBER) + return -1 //no match + if (code < NUMBER + 10) + return code - NUMBER + 26 + 26 + if (code < UPPER + 26) + return code - UPPER + if (code < LOWER + 26) + return code - LOWER + 26 + } + + function b64ToByteArray (b64) { + var i, j, l, tmp, placeHolders, arr + + if (b64.length % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + var len = b64.length + placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0 + + // base64 is 4/3 + up to two characters of the original data + arr = new Arr(b64.length * 3 / 4 - placeHolders) + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? b64.length - 4 : b64.length + + var L = 0 + + function push (v) { + arr[L++] = v + } + + for (i = 0, j = 0; i < l; i += 4, j += 3) { + tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3)) + push((tmp & 0xFF0000) >> 16) + push((tmp & 0xFF00) >> 8) + push(tmp & 0xFF) + } + + if (placeHolders === 2) { + tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4) + push(tmp & 0xFF) + } else if (placeHolders === 1) { + tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2) + push((tmp >> 8) & 0xFF) + push(tmp & 0xFF) + } + + return arr + } + + function uint8ToBase64 (uint8) { + var i, + extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes + output = "", + temp, length + + function encode (num) { + return lookup.charAt(num) + } + + function tripletToBase64 (num) { + return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F) + } + + // go through the array every three bytes, we'll deal with trailing stuff later + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) + output += tripletToBase64(temp) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + switch (extraBytes) { + case 1: + temp = uint8[uint8.length - 1] + output += encode(temp >> 2) + output += encode((temp << 4) & 0x3F) + output += '==' + break + case 2: + temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]) + output += encode(temp >> 10) + output += encode((temp >> 4) & 0x3F) + output += encode((temp << 2) & 0x3F) + output += '=' + break + } + + return output + } + + exports.toByteArray = b64ToByteArray + exports.fromByteArray = uint8ToBase64 + }( false ? (this.base64js = {}) : exports)) + + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) + } + + exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 + } + + +/***/ }, +/* 13 */ +/***/ function(module, exports) { + + var toString = {}.toString; + + module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; + }; + + +/***/ }, +/* 14 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /** + * Channel interface. + * [RTCDataChannel]{@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel} + * and + * [WebSocket]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket} + * implement it implicitly. Any other channel must implement this interface. + * + * @interface + */ + + var Channel = function () { + function Channel(channel, webChannel, peerId) { + _classCallCheck(this, Channel); + + channel.binaryType = 'arraybuffer'; + this.channel = channel; + this.webChannel = webChannel; + this.peerId = peerId; + } + + _createClass(Channel, [{ + key: 'config', + value: function config() { + var _this = this; + + this.channel.onmessage = function (msgEvt) { + _this.webChannel.onChannelMessage(_this, msgEvt.data); + }; + this.channel.onerror = function (evt) { + _this.webChannel.onChannelError(evt); + }; + this.channel.onclose = function (evt) { + _this.webChannel.onChannelClose(evt); + }; + } + + /** + * send - description. + * + * @abstract + * @param {string} msg - Message in stringified JSON format. + */ + + }, { + key: 'send', + value: function send(data) { + if (this.channel.readyState !== 'closed') { + this.channel.send(data); + } + } + + /** + * Close channel. + * + * @abstract + */ + + }, { + key: 'close', + value: function close() { + this.channel.close(); + } + }]); + + return Channel; + }(); + + exports.default = Channel; + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + var require;var require;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return require(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && typeof selector === 'function') { + return origGetStats(selector, successCallback); + } + + var fixChromeStats_ = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + // shim getStats with maplike support + var makeMapStats = function(stats, legacyStats) { + var map = new Map(Object.keys(stats).map(function(key) { + return[key, stats[key]]; + })); + legacyStats = legacyStats || stats; + Object.keys(legacyStats).forEach(function(key) { + map[key] = legacyStats[key]; + }); + return map; + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function(response) { + args[1](makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, + arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + if (args.length === 1 && typeof selector === 'object') { + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + } else { + // Preserve legacy chrome stats only on legacy access of stats obj + origGetStats.apply(self, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response), + response.result())); + }, reject]); + } + }).then(successCallback, errorCallback); + }; + + return pc; + }; + window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (webkitRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return webkitRTCPeerConnection.generateCertificate; + } + }); + } + + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var self = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; + }); + } + + // shim implicit creation of RTCSessionDescription/RTCIceCandidate + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + RTCIceCandidate : RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + }, + + // Attach a media stream to an element. + attachMediaStream: function(element, stream) { + logging('DEPRECATED, attachMediaStream will soon be removed.'); + if (browserDetails.version >= 43) { + element.srcObject = stream; + } else if (typeof element.src !== 'undefined') { + element.src = URL.createObjectURL(stream); + } else { + logging('Error attaching stream to element.'); + } + }, + + reattachMediaStream: function(to, from) { + logging('DEPRECATED, reattachMediaStream will soon be removed.'); + if (browserDetails.version >= 43) { + to.srcObject = from.srcObject; + } else { + to.src = from.src; + } + } + }; + + + // Expose public methods. + module.exports = { + shimMediaStream: chromeShim.shimMediaStream, + shimOnTrack: chromeShim.shimOnTrack, + shimSourceObject: chromeShim.shimSourceObject, + shimPeerConnection: chromeShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia'), + attachMediaStream: chromeShim.attachMediaStream, + reattachMediaStream: chromeShim.reattachMediaStream + }; + + },{"../utils.js":8,"./getusermedia":4}],4:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + var logging = require('../utils.js').log; + + // Expose public methods. + module.exports = function() { + var constraintsToChrome_ = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname_ = function(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return (name === 'deviceId') ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function(mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + var shimConstraints_ = function(constraints, func) { + constraints = JSON.parse(JSON.stringify(constraints)); + if (constraints && constraints.audio) { + constraints.audio = constraintsToChrome_(constraints.audio); + } + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile, where it defaults to "user". + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode)) { + delete constraints.video.facingMode; + if (face.exact === 'environment' || face.ideal === 'environment') { + // Look for "back" in label, or use last cam (typically back cam). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var back = devices.find(function(d) { + return d.label.toLowerCase().indexOf('back') !== -1; + }) || (devices.length && devices[devices.length - 1]); + if (back) { + constraints.video.deviceId = face.exact ? {exact: back.deviceId} : + {ideal: back.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + constraints.video = constraintsToChrome_(constraints.video); + } + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError', + ConstraintNotSatisfiedError: 'OverconstrainedError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + onError(shimError_(e)); + }); + }); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: getUserMediaPromise_, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: ''}; + })); + }); + }); + } + }; + } + + // A shim for getUserMedia method on the mediaDevices object. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (!navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia = function(constraints) { + return getUserMediaPromise_(constraints); + }; + } else { + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + logging('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + logging('Dummy mediaDevices.removeEventListener called.'); + }; + } + }; + + },{"../utils.js":8}],5:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var logging = require('../utils').log; + var browserDetails = require('../utils').browserDetails; + + var firefoxShim = { + shimOnTrack: function() { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + } + }); + } + }, + + shimSourceObject: function() { + // Firefox has supported mozSrcObject since FF22, unprefixed in 42. + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this.mozSrcObject; + }, + set: function(stream) { + this.mozSrcObject = stream; + } + }); + } + } + }, + + shimPeerConnection: function() { + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + // The RTCPeerConnection object. + if (!window.RTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (browserDetails.version < 38) { + // .urls is not supported in FF < 38. + // create RTCIceServers with a single url. + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = { + url: server.urls[j] + }; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new mozRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (mozRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return mozRTCPeerConnection.generateCertificate; + } + }); + } + + window.RTCSessionDescription = mozRTCSessionDescription; + window.RTCIceCandidate = mozRTCIceCandidate; + } + + // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + RTCIceCandidate : RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var nativeGetStats = RTCPeerConnection.prototype.getStats; + RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + return makeMapStats(stats); + }) + .then(onSucc, onErr); + }; + }, + + shimGetUserMedia: function() { + // getUserMedia constraints shim. + var getUserMedia_ = function(constraints, onSuccess, onError) { + var constraintsToFF37_ = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || + key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r. min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + constraints = JSON.parse(JSON.stringify(constraints)); + if (browserDetails.version < 38) { + logging('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37_(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37_(constraints.video); + } + logging('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, onError); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (browserDetails.version < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + }, + + // Attach a media stream to an element. + attachMediaStream: function(element, stream) { + logging('DEPRECATED, attachMediaStream will soon be removed.'); + element.srcObject = stream; + }, + + reattachMediaStream: function(to, from) { + logging('DEPRECATED, reattachMediaStream will soon be removed.'); + to.srcObject = from.srcObject; + } + }; + + // Expose public methods. + module.exports = { + shimOnTrack: firefoxShim.shimOnTrack, + shimSourceObject: firefoxShim.shimSourceObject, + shimPeerConnection: firefoxShim.shimPeerConnection, + shimGetUserMedia: require('./getusermedia'), + attachMediaStream: firefoxShim.attachMediaStream, + reattachMediaStream: firefoxShim.reattachMediaStream + }; + + },{"../utils":8,"./getusermedia":6}],6:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var logging = require('../utils').log; + var browserDetails = require('../utils').browserDetails; + + // Expose public methods. + module.exports = function() { + var shimError_ = e => ({ + name: { + SecurityError: 'NotAllowedError', + PermissionDeniedError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the user ' + + 'agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }); + + + // getUserMedia constraints shim. + var getUserMedia_ = function(constraints, onSuccess, onError) { + var constraintsToFF37_ = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r. min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + constraints = JSON.parse(JSON.stringify(constraints)); + if (browserDetails.version < 38) { + logging('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37_(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37_(constraints.video); + } + logging('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, + e => onError(shimError_(e))); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (browserDetails.version < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = c => + origGetUserMedia(c).catch(e => Promise.reject(shimError_(e))); + } + }; + + },{"../utils":8}],7:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + 'use strict'; + var safariShim = { + // TODO: DrAlex, should be here, double check against LayoutTests + // shimOnTrack: function() { }, + + // TODO: DrAlex + // attachMediaStream: function(element, stream) { }, + // reattachMediaStream: function(to, from) { }, + + // TODO: once the back-end for the mac port is done, add. + // TODO: check for webkitGTK+ + // shimPeerConnection: function() { }, + + shimGetUserMedia: function() { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + }; + + // Expose public methods. + module.exports = { + shimGetUserMedia: safariShim.shimGetUserMedia + // TODO + // shimOnTrack: safariShim.shimOnTrack, + // shimPeerConnection: safariShim.shimPeerConnection, + // attachMediaStream: safariShim.attachMediaStream, + // reattachMediaStream: safariShim.reattachMediaStream + }; + + },{}],8:[function(require,module,exports){ + /* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + 'use strict'; + + var logDisabled_ = false; + + // Utility methods. + var utils = { + disableLog: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + logDisabled_ = bool; + return (bool) ? 'adapter.js logging disabled' : + 'adapter.js logging enabled'; + }, + + log: function() { + if (typeof window === 'object') { + if (logDisabled_) { + return; + } + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } + }, + + /** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ + extractVersion: function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); + }, + + /** + * Browser detector. + * + * @return {object} result containing browser, version and minVersion + * properties. + */ + detectBrowser: function() { + // Returned result object. + var result = {}; + result.browser = null; + result.version = null; + result.minVersion = null; + + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + // Firefox. + if (navigator.mozGetUserMedia) { + result.browser = 'firefox'; + result.version = this.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); + result.minVersion = 31; + + // all webkit-based browsers + } else if (navigator.webkitGetUserMedia) { + // Chrome, Chromium, Webview, Opera, all use the chrome shim for now + if (window.webkitRTCPeerConnection) { + result.browser = 'chrome'; + result.version = this.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); + result.minVersion = 38; + + // Safari or unknown webkit-based + // for the time being Safari has support for MediaStreams but not webRTC + } else { + // Safari UA substrings of interest for reference: + // - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr) + // - safari UI version: Version/9.0.3 (unique to Safari) + // - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr) + // + // if the webkit version and safari UI webkit versions are equals, + // ... this is a stable version. + // + // only the internal webkit version is important today to know if + // media streams are supported + // + if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) { + result.browser = 'safari'; + result.version = this.extractVersion(navigator.userAgent, + /AppleWebKit\/([0-9]+)\./, 1); + result.minVersion = 602; + + // unknown webkit-based browser + } else { + result.browser = 'Unsupported webkit-based browser ' + + 'with GUM support but no WebRTC support.'; + return result; + } + } + + // Edge. + } else if (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + result.browser = 'edge'; + result.version = this.extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + result.minVersion = 10547; + + // Default fallthrough: not supported. + } else { + result.browser = 'Not a supported browser.'; + return result; + } + + // Warn if version is less than minVersion. + if (result.version < result.minVersion) { + utils.log('Browser: ' + result.browser + ' Version: ' + result.version + + ' < minimum supported version: ' + result.minVersion + + '\n some things might not work!'); + } + + return result; + } + }; + + // Export. + module.exports = { + log: utils.log, + disableLog: utils.disableLog, + browserDetails: utils.detectBrowser(), + extractVersion: utils.extractVersion + }; + + },{}]},{},[2]); + + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/src/Buffer.js b/src/Buffer.js deleted file mode 100644 index 7b5f729b..00000000 --- a/src/Buffer.js +++ /dev/null @@ -1,28 +0,0 @@ -import provide, {MESSAGE_FORMATTER} from './serviceProvider' -import {USER_MSG_BYTE_OFFSET} from './service/MessageFormatterService' - -const formatter = provide(MESSAGE_FORMATTER) - -class Buffer { - constructor (totalByteLength, action) { - this.totalByteLength = totalByteLength - this.currentByteLength = 0 - this.i8array = new Uint8Array(this.totalByteLength) - this.action = action - } - - add (data, chunkNb) { - const maxSize = formatter.getMaxMsgByteLength() - let intU8Array = new Uint8Array(data) - this.currentByteLength += data.byteLength - USER_MSG_BYTE_OFFSET - let index = chunkNb * maxSize - for (let i = USER_MSG_BYTE_OFFSET; i < data.byteLength; i++) { - this.i8array[index++] = intU8Array[i] - } - if (this.currentByteLength === this.totalByteLength) { - this.action(this.i8array) - } - } -} - -export default Buffer diff --git a/src/WebChannel.js b/src/WebChannel.js index cc299556..8b37feb8 100644 --- a/src/WebChannel.js +++ b/src/WebChannel.js @@ -1,16 +1,16 @@ import provide, {FULLY_CONNECTED, WEBRTC, MESSAGE_FORMATTER} from './serviceProvider' -import {USER_MSG_BYTE_OFFSET} from './service/MessageFormatterService' import Channel from './Channel' -import Buffer from './Buffer' import JoiningPeer from './JoiningPeer' const formatter = provide(MESSAGE_FORMATTER) +const MAX_ID = 4294967295 + /** * Constant used to build a message designated to API user. * @type {int} */ -const USER_DATA = 0 +export const USER_DATA = 0 /** * Constant used to build a message designated to a specific service. @@ -128,8 +128,6 @@ class WebChannel { */ this.myId = this.generateId() - this.buffers = new Map() - this.onJoining = (id) => {} this.onMessage = (id, msg) => {} this.onLeaving = (id) => {} @@ -147,7 +145,7 @@ class WebChannel { * @param {string} data Message */ send (data) { - formatter.splitUserMessage(data, USER_DATA, this.myId, null, (dataChunk) => { + formatter.handleUserMessage(data, this.myId, null, (dataChunk) => { this.manager.broadcast(this, dataChunk) }) } @@ -159,7 +157,7 @@ class WebChannel { * @param {type} data Message */ sendTo (id, data) { - formatter.splitUserMessage(data, USER_DATA, this.myId, id, (dataChunk) => { + formatter.handleUserMessage(data, this.myId, id, (dataChunk) => { this.manager.sendTo(id, this, dataChunk) }) } @@ -301,48 +299,14 @@ class WebChannel { } onChannelMessage (channel, data) { - let decoder = new TextDecoder() - let dataView = new DataView(data) - let code = dataView.getUint8(0) - if (code === USER_DATA) { - let totalMsgByteLength = dataView.getUint32(12) - let senderId = dataView.getUint32(2) - if (totalMsgByteLength > formatter.getMaxMsgByteLength()) { - let msgId = dataView.getUint32(10) - let msgMap - if (this.buffers.has(senderId)) { - msgMap = this.buffers.get(senderId) - } else { - msgMap = new Map() - this.buffers.set(senderId, msgMap) - } - let chunkNb = dataView.getUint16(16) - if (msgMap.has(msgId)) { - msgMap.get(msgId).add(dataView.buffer, chunkNb) - } else { - let buf = new Buffer(totalMsgByteLength, (i8array) => { - this.onMessage(senderId, decoder.decode(i8array)) - msgMap.delete(msgId) - }) - buf.add(dataView.buffer, chunkNb) - msgMap.set(msgId, buf) - } - } else { - let uInt8Array = new Uint8Array(data) - let str = decoder.decode(uInt8Array.subarray(USER_MSG_BYTE_OFFSET, uInt8Array.byteLength)) - this.onMessage(senderId, str) - } - return + let header = formatter.readHeader(data) + if (header.code === USER_DATA) { + formatter.readUserMessage(this.id, header.senderId, data, (fullData) => { + this.onMessage(header.senderId, fullData) + }) } else { - let msg = {} - let uInt8Array = new Uint8Array(data) - let str = decoder.decode(uInt8Array.subarray(1, uInt8Array.byteLength)) - msg = JSON.parse(str) - let jp - switch (code) { - // case USER_DATA: - // this.webChannel.onMessage(msg.id, msg.data) - // break + let msg = formatter.readInternalMessage(data) + switch (header.code) { case LEAVE: this.onLeaving(msg.id) for (let c of this.channels) { @@ -362,7 +326,7 @@ class WebChannel { this.topology = msg.manager this.myId = msg.id channel.peerId = msg.intermediaryId - jp = new JoiningPeer(this.myId, channel.peerId) + let jp = new JoiningPeer(this.myId, channel.peerId) jp.intermediaryChannel = channel this.addJoiningPeer(jp) break @@ -536,18 +500,14 @@ class WebChannel { } generateId () { - const MAX = 16777215 let id do { - id = Math.floor(Math.random() * MAX) + id = Math.ceil(Math.random() * MAX_ID) for (let c of this.channels) { - if (c.peerId === id) { - continue - } - } - if (this.myId === id) { - continue + if (id === c.peerId) continue } + if (this.hasJoiningPeer(id)) continue + if (id === this.myId) continue break } while (true) return id diff --git a/src/service/MessageBuilderService.js b/src/service/MessageBuilderService.js new file mode 100644 index 00000000..e00d2f40 --- /dev/null +++ b/src/service/MessageBuilderService.js @@ -0,0 +1,249 @@ +import * as service from './service' +import {USER_DATA} from '../WebChannel' + +// Max message size sent on Channel: 16kb +export const MAX_MSG_SIZE = 16384 + +export const MAX_USER_MSG_SIZE = 16366 + +export const USER_MSG_OFFSET = 18 + +export const HEADER_OFFSET = 9 + +const MAX_MSG_ID_SIZE = 65535 + +const ARRAY_BUFFER_TYPE = 1 +const U_INT_8_ARRAY_TYPE = 2 +const STRING_TYPE = 3 +const INT_8_ARRAY_TYPE = 4 +const U_INT_8_CLAMPED_ARRAY_TYPE = 5 +const INT_16_ARRAY_TYPE = 6 +const U_INT_16_ARRAY_TYPE = 7 +const INT_32_ARRAY_TYPE = 8 +const U_INT_32_ARRAY_TYPE = 9 +const FLOAT_32_ARRAY_TYPE = 10 +const FLOAT_64_ARRAY_TYPE = 11 +const DATA_VIEW_TYPE = 12 + +const buffers = new Map() + +class MessageBuilder extends service.Interface { + handleUserMessage (data, senderId, recipientId, action) { + let workingData = this.userDataToType(data) + let dataUint8Array = workingData.content + if (dataUint8Array.byteLength <= MAX_USER_MSG_SIZE) { + let dataView = this.writeHeader(USER_DATA, senderId, recipientId, + dataUint8Array.byteLength + USER_MSG_OFFSET + ) + dataView.setUint32(HEADER_OFFSET, dataUint8Array.byteLength) + dataView.setUint8(13, workingData.type) + let resultUint8Array = new Uint8Array(dataView.buffer) + resultUint8Array.set(dataUint8Array, USER_MSG_OFFSET) + action(resultUint8Array.buffer) + } else { + const msgId = Math.ceil(Math.random() * MAX_MSG_ID_SIZE) + const totalChunksNb = Math.ceil(dataUint8Array.byteLength / MAX_USER_MSG_SIZE) + for (let chunkNb = 0; chunkNb < totalChunksNb; chunkNb++) { + let currentChunkMsgByteLength = Math.min( + MAX_USER_MSG_SIZE, + dataUint8Array.byteLength - MAX_USER_MSG_SIZE * chunkNb + ) + let dataView = this.writeHeader( + USER_DATA, + senderId, + recipientId, + USER_MSG_OFFSET + currentChunkMsgByteLength + ) + dataView.setUint32(9, dataUint8Array.byteLength) + dataView.setUint8(13, workingData.type) + dataView.setUint16(14, msgId) + dataView.setUint16(16, chunkNb) + let resultUint8Array = new Uint8Array(dataView.buffer) + let j = USER_MSG_OFFSET + let startIndex = MAX_USER_MSG_SIZE * chunkNb + let endIndex = startIndex + currentChunkMsgByteLength + for (let i = startIndex; i < endIndex; i++) { + resultUint8Array[j++] = dataUint8Array[i] + } + action(resultUint8Array.buffer) + } + } + } + + msg (code, data = {}) { + let msgEncoded = (new TextEncoder()).encode(JSON.stringify(data)) + let msgSize = msgEncoded.byteLength + HEADER_OFFSET + let dataView = this.writeHeader(code, null, null, msgSize) + let fullMsg = new Uint8Array(dataView.buffer) + fullMsg.set(msgEncoded, HEADER_OFFSET) + return fullMsg + } + + readUserMessage (wcId, senderId, data, action) { + let dataView = new DataView(data) + let msgSize = dataView.getUint32(HEADER_OFFSET) + let dataType = dataView.getUint8(13) + if (msgSize > MAX_USER_MSG_SIZE) { + let msgId = dataView.getUint16(14) + let chunk = dataView.getUint16(16) + let buffer = this.getBuffer(wcId, senderId, msgId) + if (buffer === undefined) { + this.setBuffer(wcId, senderId, msgId, + new Buffer(msgSize, data, chunk, (fullData) => { + action(this.extractUserData(fullData, dataType)) + }) + ) + } else { + buffer.add(data, chunk) + } + } else { + let dataArray = new Uint8Array(data) + let userData = new Uint8Array(data.byteLength - USER_MSG_OFFSET) + let j = USER_MSG_OFFSET + for (let i in userData) { + userData[i] = dataArray[j++] + } + action(this.extractUserData(userData.buffer, dataType)) + } + } + + readInternalMessage (data) { + let uInt8Array = new Uint8Array(data) + return JSON.parse((new TextDecoder()) + .decode(uInt8Array.subarray(HEADER_OFFSET, uInt8Array.byteLength)) + ) + } + + readHeader (data) { + let dataView = new DataView(data) + return { + code: dataView.getUint8(0), + senderId: dataView.getUint32(1), + recepientId: dataView.getUint32(5) + } + } + + writeHeader (code, senderId, recipientId, dataSize) { + let dataView = new DataView(new ArrayBuffer(dataSize)) + dataView.setUint8(0, code) + dataView.setUint32(1, senderId) + dataView.setUint32(5, recipientId) + return dataView + } + + extractUserData (buffer, type) { + switch (type) { + case ARRAY_BUFFER_TYPE: + return buffer + case U_INT_8_ARRAY_TYPE: + return new Uint8Array(buffer) + case STRING_TYPE: + return new TextDecoder().decode(new Uint8Array(buffer)) + case INT_8_ARRAY_TYPE: + return new Int8Array(buffer) + case U_INT_8_CLAMPED_ARRAY_TYPE: + return new Uint8ClampedArray(buffer) + case INT_16_ARRAY_TYPE: + return new Int16Array(buffer) + case U_INT_16_ARRAY_TYPE: + return new Uint16Array(buffer) + case INT_32_ARRAY_TYPE: + return new Int32Array(buffer) + case U_INT_32_ARRAY_TYPE: + return new Uint32Array(buffer) + case FLOAT_32_ARRAY_TYPE: + return new Float32Array(buffer) + case FLOAT_64_ARRAY_TYPE: + return new Float64Array(buffer) + case DATA_VIEW_TYPE: + return new DataView(buffer) + } + } + + userDataToType (data) { + let result = {} + if (data instanceof ArrayBuffer) { + result.type = ARRAY_BUFFER_TYPE + result.content = new Uint8Array(data) + } else if (data instanceof Uint8Array) { + result.type = U_INT_8_ARRAY_TYPE + result.content = data + } else if (typeof data === 'string' || data instanceof String) { + result.type = STRING_TYPE + result.content = new TextEncoder().encode(data) + } else { + result.content = new Uint8Array(data.buffer) + if (data instanceof Int8Array) { + result.type = INT_8_ARRAY_TYPE + } else if (data instanceof Uint8ClampedArray) { + result.type = U_INT_8_CLAMPED_ARRAY_TYPE + } else if (data instanceof Int16Array) { + result.type = INT_16_ARRAY_TYPE + } else if (data instanceof Uint16Array) { + result.type = U_INT_16_ARRAY_TYPE + } else if (data instanceof Int32Array) { + result.type = INT_32_ARRAY_TYPE + } else if (data instanceof Uint32Array) { + result.type = U_INT_32_ARRAY_TYPE + } else if (data instanceof Float32Array) { + result.type = FLOAT_32_ARRAY_TYPE + } else if (data instanceof Float64Array) { + result.type = FLOAT_64_ARRAY_TYPE + } else if (data instanceof DataView) { + result.type = DATA_VIEW_TYPE + } else { + throw new Error('Unknown data object') + } + } + return result + } + + getBuffer (wcId, peerId, msgId) { + let wcBuffer = buffers.get(wcId) + if (wcBuffer !== undefined) { + let peerBuffer = wcBuffer.get(peerId) + if (peerBuffer !== undefined) { + return peerBuffer.get(msgId) + } + } + return undefined + } + + setBuffer (wcId, peerId, msgId, buffer) { + let wcBuffer = buffers.get(wcId) + if (wcBuffer === undefined) { + wcBuffer = new Map() + buffers.set(wcId, wcBuffer) + } + let peerBuffer = wcBuffer.get(peerId) + if (peerBuffer === undefined) { + peerBuffer = new Map() + wcBuffer.set(peerId, peerBuffer) + } + peerBuffer.set(msgId, buffer) + } +} + +class Buffer { + constructor (fullDataSize, data, chunkNb, action) { + this.fullData = new Uint8Array(fullDataSize) + this.currentSize = 0 + this.action = action + this.add(data, chunkNb) + } + + add (data, chunkNb) { + let dataChunk = new Uint8Array(data) + let dataChunkSize = data.byteLength + this.currentSize += dataChunkSize - USER_MSG_OFFSET + let index = chunkNb * MAX_USER_MSG_SIZE + for (let i = USER_MSG_OFFSET; i < dataChunkSize; i++) { + this.fullData[index++] = dataChunk[i] + } + if (this.currentSize === this.fullData.byteLength) { + this.action(this.fullData.buffer) + } + } +} + +export default MessageBuilder diff --git a/src/service/MessageFormatterService.js b/src/service/MessageFormatterService.js deleted file mode 100644 index 40872983..00000000 --- a/src/service/MessageFormatterService.js +++ /dev/null @@ -1,88 +0,0 @@ -import * as service from './service' - -// Max message size sent on Channel: 16kb -export const MAX_CHANNEL_MSG_BYTE_SIZE = 16384 - -export const USER_MSG_BYTE_OFFSET = 18 - -export const STRING_TYPE = 100 - -export const UINT8ARRAY_TYPE = 101 - -export const ARRAYBUFFER_TYPE = 102 - -class MessageFormatter extends service.Interface { - splitUserMessage (data, code, senderId, recipientId, action) { - const dataType = this.getDataType(data) - let uInt8Array - switch (dataType) { - case STRING_TYPE: - uInt8Array = new TextEncoder().encode(data) - break - case UINT8ARRAY_TYPE: - uInt8Array = data - break - case ARRAYBUFFER_TYPE: - uInt8Array = new Uint8Array(data) - break - default: - return - } - - const maxUserDataLength = this.getMaxMsgByteLength() - const msgId = this.generateMsgId() - const totalChunksNb = Math.ceil(uInt8Array.byteLength / maxUserDataLength) - for (let chunkNb = 0; chunkNb < totalChunksNb; chunkNb++) { - let chunkMsgByteLength = Math.min(maxUserDataLength, uInt8Array.byteLength - maxUserDataLength * chunkNb) - let index = maxUserDataLength * chunkNb - let totalChunkByteLength = USER_MSG_BYTE_OFFSET + chunkMsgByteLength - let dataView = new DataView(new ArrayBuffer(totalChunkByteLength)) - dataView.setUint8(0, code) - dataView.setUint8(1, dataType) - dataView.setUint32(2, senderId) - dataView.setUint32(6, recipientId) - dataView.setUint16(10, msgId) - dataView.setUint32(12, uInt8Array.byteLength) - dataView.setUint16(16, chunkNb) - let resultUint8Array = new Uint8Array(dataView.buffer) - let j = USER_MSG_BYTE_OFFSET - for (let i = index; i < index + chunkMsgByteLength; i++) { - resultUint8Array[j++] = uInt8Array[i] - } - action(resultUint8Array) - } - } - - msg (code, data = {}) { - let msgEncoded = (new TextEncoder()).encode(JSON.stringify(data)) - let i8array = new Uint8Array(1 + msgEncoded.length) - i8array[0] = code - let index = 1 - for (let i in msgEncoded) { - i8array[index++] = msgEncoded[i] - } - return i8array - } - - getMaxMsgByteLength () { - return MAX_CHANNEL_MSG_BYTE_SIZE - USER_MSG_BYTE_OFFSET - } - - generateMsgId () { - const MAX = 16777215 - return Math.round(Math.random() * MAX) - } - - getDataType (data) { - if (typeof data === 'string' || data instanceof String) { - return STRING_TYPE - } else if (data instanceof Uint8Array) { - return UINT8ARRAY_TYPE - } else if (data instanceof ArrayBuffer) { - return ARRAYBUFFER_TYPE - } - return 0 - } -} - -export default MessageFormatter diff --git a/src/serviceProvider.js b/src/serviceProvider.js index 939d63f2..a0b7bdf3 100644 --- a/src/serviceProvider.js +++ b/src/serviceProvider.js @@ -1,6 +1,6 @@ import FullyConnectedService from './service/webChannelManager/FullyConnectedService' import WebRTCService from './service/channelBuilder/WebRTCService' -import MessageFormatterService from './service/MessageFormatterService' +import MessageBuilderService from './service/MessageBuilderService' /** * Service Provider module is a helper module for {@link module:service}. It is * responsible to instantiate all services. This module must be used to get @@ -20,7 +20,7 @@ export const WEBRTC = 'WebRTCService' */ export const FULLY_CONNECTED = 'FullyConnectedService' -export const MESSAGE_FORMATTER = 'MessageFormatterService' +export const MESSAGE_FORMATTER = 'MessageBuilderService' const services = new Map() @@ -46,7 +46,7 @@ export default function provide (name, options = {}) { services.set(name, service) return service case MESSAGE_FORMATTER: - service = new MessageFormatterService() + service = new MessageBuilderService() services.set(name, service) return service default: diff --git a/test/config.js b/test/config.js index 3deb1cb3..a41c8729 100644 --- a/test/config.js +++ b/test/config.js @@ -1,2 +1,26 @@ export const signaling = 'ws://localhost:8000' export const MSG_NUMBER = 100 + +export function randString () { + const MIN_LENGTH = 1 + const MAX_LENGTH = 3700 // To limit message size to less than 16kb (4 bytes per character) + const MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + let result = '' + const length = MIN_LENGTH + Math.ceil(Math.random() * (MAX_LENGTH - MIN_LENGTH)) + + for (let i = 0; i < length; i++) { + result += MASK[Math.round(Math.random() * (MASK.length - 1))] + } + return result +} + +export function randArrayBuffer (minLength = 8, maxLength = 16000) { + const length = minLength + 8 * Math.ceil(Math.random() * ((maxLength - minLength) / 8)) + let buffer = new ArrayBuffer(length) + let bufferUint8 = new Uint8Array(buffer) + + for (let i = 0; i < length; i++) { + bufferUint8[i] = Math.round(Math.random() * 255) + } + return buffer +} diff --git a/test/scenarios/fullyConnected/2peers.test.js b/test/functional/fullyConnected/2peers.test.js similarity index 100% rename from test/scenarios/fullyConnected/2peers.test.js rename to test/functional/fullyConnected/2peers.test.js diff --git a/test/scenarios/fullyConnected/3peers.test.js b/test/functional/fullyConnected/3peers.test.js similarity index 100% rename from test/scenarios/fullyConnected/3peers.test.js rename to test/functional/fullyConnected/3peers.test.js diff --git a/test/functional/fullyConnected/dataType.test.js b/test/functional/fullyConnected/dataType.test.js new file mode 100644 index 00000000..64355f3c --- /dev/null +++ b/test/functional/fullyConnected/dataType.test.js @@ -0,0 +1,147 @@ +import {signaling, randArrayBuffer, randString} from '../../config' +import {WebChannel} from '../../../src/WebChannel' + +let wc1, wc2 +let buffer +let str + +function isEqual (ta1, ta2) { + return ta1.every((value, index) => { + return value === ta2[index] + }) +} + +beforeAll((done) => { + buffer = randArrayBuffer(8, 200) + str = randString() + // Peer #1 + wc1 = new WebChannel({signaling}) + wc1.openForJoining().then((data) => { + // Peer #2 + wc2 = new WebChannel({signaling}) + wc2.join(data.key).then(() => { + done() + }) + .catch(done.fail) + }).catch(done.fail) +}) + +describe('Should send & receive (between 2 peers) -> ', () => { + it('ArrayBuffer', (done) => { + wc2.onMessage = (id, msg) => { + expect(msg instanceof ArrayBuffer).toBeTruthy() + expect(isEqual(new Uint8Array(buffer), new Uint8Array(msg))).toBeTruthy() + done() + } + wc1.send(buffer) + }) + + it('Uint8Array', (done) => { + let original = new Uint8Array(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Uint8Array).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('String', (done) => { + wc2.onMessage = (id, msg) => { + expect(msg instanceof Uint8Array || typeof msg === 'string').toBeTruthy() + expect(msg).toEqual(str) + done() + } + wc1.send(str) + }) + + it('Int8Array', (done) => { + let original = new Int8Array(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Int8Array).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('Uint8ClampedArray', (done) => { + let original = new Uint8ClampedArray(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Uint8ClampedArray).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('Int16Array', (done) => { + let original = new Int16Array(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Int16Array).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('Uint16Array', (done) => { + let original = new Uint16Array(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Uint16Array).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('Int32Array', (done) => { + let original = new Int32Array(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Int32Array).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('Uint32Array', (done) => { + let original = new Uint32Array(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Uint32Array).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('Float32Array', (done) => { + let original = new Float32Array(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Float32Array).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('Float64Array', (done) => { + let original = new Float64Array(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof Float64Array).toBeTruthy() + expect(isEqual(original, msg)).toBeTruthy() + done() + } + wc1.send(original) + }) + + it('DataView', (done) => { + let original = new DataView(buffer) + wc2.onMessage = (id, msg) => { + expect(msg instanceof DataView).toBeTruthy() + expect(isEqual(new Uint8Array(buffer), new Uint8Array(msg.buffer))).toBeTruthy() + done() + } + wc1.send(original) + }) +}) diff --git a/test/scenarios/fullyConnected/largeData/string8192kb.test.js b/test/functional/fullyConnected/largeData.test.js similarity index 87% rename from test/scenarios/fullyConnected/largeData/string8192kb.test.js rename to test/functional/fullyConnected/largeData.test.js index e86a8951..a47aa631 100644 --- a/test/scenarios/fullyConnected/largeData/string8192kb.test.js +++ b/test/functional/fullyConnected/largeData.test.js @@ -1,35 +1,128 @@ -import {signaling} from '../../../config' -import {WebChannel} from '../../../../src/WebChannel' +import {signaling, randArrayBuffer} from '../../config' +import {WebChannel} from '../../../src/WebChannel' let wc1, wc2 const MULTIPLIER_FOR_128KB_STRING = 64 -it('Send/receive 2048kb String between 2 peers', (done) => { - let fullStr = '' - let startTime - for (let i = 0; i < MULTIPLIER_FOR_128KB_STRING; i++) { - fullStr += str - } +beforeAll((done) => { // Peer #1 wc1 = new WebChannel({signaling}) - wc1.onMessage = (id, msg) => { - console.info('~' + (128 * MULTIPLIER_FOR_128KB_STRING) + 'kb String sent between 2 peers in: ' + (Date.now() - startTime) + ' ms') - expect(msg).toEqual(fullStr) - done() - } wc1.openForJoining().then((data) => { // Peer #2 wc2 = new WebChannel({signaling}) wc2.join(data.key).then(() => { - startTime = Date.now() - wc2.send(fullStr) + done() }) .catch(done.fail) }).catch(done.fail) }) -const str = +describe('Should send & receive String between 2 peers -> ', () => { + it('~17kb', (done) => { + wc1.onMessage = (id, msg) => { + expect(msg).toEqual(smallStr) + done() + } + wc2.send(smallStr) + }) + + it('~8192kb', (done) => { + let fullStr = '' + let startTime + for (let i = 0; i < MULTIPLIER_FOR_128KB_STRING; i++) { + fullStr += bigStr + } + wc1.onMessage = (id, msg) => { + console.info('~' + (128 * MULTIPLIER_FOR_128KB_STRING) + 'kb String sent between 2 peers in: ' + (Date.now() - startTime) + ' ms') + expect(msg).toEqual(fullStr) + done() + } + startTime = Date.now() + wc2.send(fullStr) + }) +}) + +function isEqual (buffer1, buffer2) { + let array1 = new Uint8Array(buffer1) + let array2 = new Uint8Array(buffer2) + + return array1.every((value, index) => { + return value === array2[index] + }) +} + +describe('Should send & receive ArrayBuffer between 2 peers -> ', () => { + let smallBuffer = randArrayBuffer(10000, 10000) + let bigBuffer = randArrayBuffer(8388608, 8400000) + + it('between 16kb & 20kb', (done) => { + wc1.onMessage = (id, msg) => { + expect(isEqual(msg, smallBuffer)).toBeTruthy() + done() + } + wc2.send(smallBuffer) + }, 10000) + + it('~8192kb', (done) => { + let startTime + wc1.onMessage = (id, msg) => { + console.info(bigBuffer.byteLength + ' bytes sent between 2 peers in: ' + (Date.now() - startTime) + ' ms') + expect(isEqual(msg, bigBuffer)).toBeTruthy() + done() + } + startTime = Date.now() + wc2.send(bigBuffer) + }) +}) + +const smallStr = +`Clint Eastwood (prononcé en anglais : /klɪnt istwʊd/), né le 31 mai 1930 à San Francisco (Californie), est un acteur, réalisateur, compositeur et producteur de cinéma américain. +Autodidacte, il entre grâce à des amis au studio Universal où il interprète d’abord de petits rôles dans des séries B, puis l’un des rôles phares d'une longue série, Rawhide. Il se fait alors remarquer par Sergio Leone qui l’embauche pour la Trilogie du dollar (Pour une poignée de dollars, Et pour quelques dollars de plus et Le Bon, la Brute et le Truand). Devenu célèbre, il interprète de nombreux rôles, d’abord pour Universal, puis pour Warner Bros., notamment ceux de L'Inspecteur Harry. En 1968, il devient producteur avec la création de la société Malpaso et réalise son premier film en 1971, avec Un frisson dans la nuit. Aujourd'hui, avec plus de quatre-vingt films à son actif, parmi lesquels Impitoyable, Sur la route de Madison ou encore Mystic River et plus récemment Million Dollar Baby, Gran Torino, Invictus et American Sniper, Clint Eastwood figure parmi les cinéastes les plus reconnus au monde. +D'abord connu pour ses rôles d'antihéros volontiers redresseur de torts et tragiques, dans des films d'action violents ou des westerns tels que L'Homme des Hautes Plaines ou encore Pale Rider, il a ensuite endossé des rôles plus touchants dans des films empreints d'un certain classicisme, influencés par le cinéma de John Ford et de Howard Hawks. Il est également connu pour ses comédies telles que Doux, dur et dingue et Ça va cogner. Il a ainsi été récompensé à de nombreuses reprises, remportant notamment quatre Oscars, cinq Golden Globes, trois Césars et la Palme d'honneur au Festival de Cannes en 2009. +Étymologie du nom +Selon Le Robert des noms propres, le nom Eastwood, vient du vieil anglais qui signifie « bois (wudu) de l'Est (ēast) » ou du vieux scandinave °veit « prairie ». +Biographie +Origines + +Hôpital Saint Francis où naît Clint Eastwood +Clint Eastwood a toujours été mystérieux sur ses origines, sa vie privée et son passé. Eastwood est sélectif car il veut être celui qui sait sans divulguer. Lors d'interviews, il dévoile seulement la partie de son arbre généalogique qui met en valeur son image,. Pourtant, les origines d'Eastwood suivent de près l'histoire américaine. Ses ancêtres arrivent en Amérique du Nord au milieu du xviie siècle. Ils font partie des premiers colons à se lancer dans la conquête de l'Ouest. Sa famille se partage donc entre des membres installés à New York, dans l'Ohio, dans le Michigan, en Virginie, dans l'Illinois, en Louisiane, au Kansas, dans le Colorado, le Nevada, en Californie et enfin en Alaska. Bien avant que Clint Eastwood naisse, sa famille est marquée par le monde du spectacle. Le premier Eastwood né en Amérique est Lewis Eastwood. Ses parents sont venus d'Angleterre ; ils sont toutefois d'origine irlandaise,. Bien qu'il ait déclaré à la presse être « le premier de la famille à avoir réussi », Clint Eastwood est bien loin de la vérité : à la fin du xviiie siècle, Lewis Eastwood est devenu un entrepreneur renommé, classé cent troisième parmi les mille deux cents commissionnaires de la ville de New York. L'un des petits-fils de Lewis, Asa Bedesco Eastwood, l'arrière-grand-père de Clint, quitte la ville pour devenir mineur. C'est à cause de lui que le réalisateur a souvent montré, à travers ses films, une tendresse particulière à l'égard des mineurs, comme dans La Kermesse de l'Ouest (1969), L'Homme des Hautes Plaines (1973) ou encore dans Pale Rider, le cavalier solitaire (1985). Habitué du commerce, Burr, l'un des fils d'Asa Bedesco, quitte sa famille pour travailler comme magasinier, emploi dans lequel il monte rapidement l'échelle sociale. Il épouse Jessie Anderson, une immigrante d'Écosse, qui lui donne deux fils, dont Clinton. Clinton se marie en 1927 avec Margaret Ruth Runner, une femme de la haute société. Ils donnent naissance à un garçon qui leur dédie plus tard l'Oscar du meilleur film qu'il remporte pour Impitoyable,, : +« Cette victoire est simplement merveilleuse, je la décerne à toutes les personnes auxquelles je pourrais penser. […] Durant cette année de la femme, la plus grande femme sur la planète est ici ce soir — ma mère, Ruth. » +Clinton Eastwood Jr. est donc né le 31 mai 1930, à l'hôpital Saint Francis de San Francisco. À cette époque, le nourrisson était déjà célèbre. Sa mère déclare au journal anglais News of the World : « C'était le plus gros bébé de la maternité. Il pesait 5,2 kg. Les infirmières s'amusaient beaucoup à le montrer aux autres mamans. Elles l'appelaient « Samson ». Il était tellement grand,. » Il fut surnommé Clinton Jr. en hommage à son père, bien que son nom complet soit Clinton Elias Eastwood Jr. Toutefois, il fut surnommé par ses parents « Sonny ». Les liens familiaux sont forts chez les Eastwood, comme l'exprime Ruth lors de la naissance de son fils : « Je suis tombée amoureuse de lui dès qu'il est né ! ». Et cet amour est largement restitué dans tous les films dans lesquels Clint Jr. est impliqué par la suite. +Enfance +La Grande Dépression + +Oakland, où Eastwood aurait passé son enfance. +Ses différents attachés de presse ont, durant quarante ans, clamé que Clint Eastwood était originaire d'Oakland, ville ouvrière qui mettrait en valeur la réussite d'Eastwood. Ce dernier a même déclaré dans une interview que s'il traitait si souvent les gens de « trous du cul » dans ses films, c'était probablement à cause de son enfance passée à Oakland. Mais cette information n'est pas vraiment exacte. Dans la biographie écrite par Schickel publiée en 1996, on découvre qu'Eastwood a, en fait, grandi à Piedmont,. Schickel y déclare que les Eastwood ont grandi dans une « modeste maison au toit couvert de bardeaux », mais il précise que « cette maison était [toutefois] située à la limite d'Oakland ». L'enfance de Clint Eastwood est marquée par la Grande Dépression et le passage au cinéma sonore. Les journaux locaux ne traitent guère de la crise. Toutefois le chômage ne cesse d'augmenter. Il atteint un taux de 28 % en Californie. Si Oakland, d'origine ouvrière, est très touchée par la Grande Dépression, Piedmont fait figure de banlieue chic où la crise n'a pas de réel impact sur la vie de tous les jours. Toutefois, les parents du jeune Eastwood quittent la région : Clinton Sr. vient de perdre son emploi de commercial chez East Bay Refrigeration Products,. + +Spokane, ville dans laquelle Clinton Sr. trouve un travail temporaire. +Selon les divers témoignages, Clinton Sr. se met en quête de travail partout où il y en a. Il déclare ainsi à son fils : « dans la vie, on n’a rien pour rien », ce que Clint Jr. n’a jamais contesté. C'est d'ailleurs peut-être de ce nomadisme que naît la future passion d'Eastwood pour les westerns. Il n'a ni diplôme universitaire ni qualification professionnelle. Les voyant découragés, le frère de Ruth, la mère de Clint Jr., les dépanne financièrement comme il peut. Il aide par ailleurs Clinton Sr. à trouver un emploi dans une usine de réfrigérateurs à Spokane. Ce dernier enchaîne avec un travail de pompiste sur Sunset Boulevard qu'il obtient grâce à des amis. La famille s'installe alors à Pacific Palisades, un district de Los Angeles. C'est durant cette période que Clint Jr. manque de mourir noyé à l'âge de quatre ans et qu'il assiste à la naissance de sa sœur, Jeanne. L'enfance de Clint Jr. est ainsi marquée par des déménagements incessants dus aux changements de travail de son père : ils vont notamment à Sacramento, et Redding. Ces voyages vont durer près de six ans. Cependant, Schickel déclare dans son livre sur Clint Eastwood qu'« il n'y avait jamais ni panique ni désespoir dans ces déménagements. […] Quand la famille faisait ses paquets, M. Eastwood avait toujours retrouvé un emploi. Et à aucun moment Clint ne s'est senti délaissé ou abandonné durant cette période. ». Au milieu des années 1930, la mère de Clint Eastwood achète la maison de sa tante à Piedmont pour une somme dérisoire. En évoquant cette période, l'acteur déclare au Village Voice, en 1976, que « c'était une époque merdique ». Il ajoute « on n'était pas itinérants. […] C'était pas Les Raisins de la colère, mais c'était pas le luxe non plus » au Rolling Stone. +De retour dans sa ville natale, Clint Eastwood rend souvent visite à sa grand-mère, Virginia May Runner, jusqu'en 1937, date à laquelle cette dernière déménage vers la région rurale, derrière les faubourgs Est d'Oakland. Malgré son départ, Clint Eastwood ne la perd pas de vue pour autant, il va chez elle de temps en temps. C'est durant ces quelques séjours que Clint Eastwood apprend à monter à cheval. Il y apprend également les valeurs du sacrifice et du devoir : +« Grand-mère a eu plus d'impact sur ce que je suis devenu que n'importe quelle théorie de l'éducation. Elle vivait seule et était très autonome. » +Premiers pas dans le monde artistique +Il est âgé de dix ans lorsque son père trouve enfin un emploi lucratif en tant qu'assureur à la Connecticut Mutual Life Insurance Co. Mais la Guerre éclate. Clinton Sr. étant mobilisable, il devient tuyauteur sur des chantiers navals. Peu de temps après, l'économie prend un nouvel essor grâce à la Guerre, et les Eastwood en profitent. La famille achète une résidence sur la Hillside Avenue, à quelques pas de l'école de Clint Jr. L'époque de sa vie qu'il qualifiait de « merdique » est terminée. + +Piedmont, ville natale de Clint Eastwood +Bien qu'appartenant à une famille tournée vers la religion, Clint Jr. n'est inscrit sur aucun registre de baptême et ne va jamais à la messe. Ce manque est certainement dû aux déménagements de son enfance. Lorsque David Frost lui demande si la religion est importante pour lui, Clint Eastwood répond : « Je ne souscris à aucune religion officielle. Mais j'ai toujours accordé beaucoup d'importance à ce genre de choses […]. Surtout quand je suis dans la nature. Je crois que c'est pour ça que j'ai tourné autant de films […] dans la nature. […] Je n'ai jamais vraiment réfléchi là-dessus à haute voix. » +Clint trouve son premier travail comme caddy sur un terrain de golf. Il distribue aussi le journal Oakland Tribune, tond des pelouses et emballe les courses des clients d'une épicerie locale pour se faire de l'argent de poche. En parallèle, sa vie scolaire n'est pas très épanouie : il change près de dix fois d'établissement. Il fréquente notamment les écoles Glenview, Crocket Highlands et Frank Havens School, toutes à proximité de Piedmont. À la deuxième d'entre elles, Eastwood suit un cours de photographie, ce qui se révèle être son premier contact avec le monde artistique. Plus tard, au collège, Clint Eastwood découvre la comédie. Bien qu'il soit introverti, il est choisi parmi tous les élèves de sa classe pour interpréter le rôle principal d'une pièce par son professeur d'anglais, Gertrude Falk. Désastreux au début, il prend peu à peu confiance en lui et termine la pièce avec plusieurs rires appréciateurs. +Malgré la présence de sa sœur, Clint joue seul. Il s'invente des amis imaginaires et mime des scènes avec ses jouets. Voici ce que Clint Eastwood déclara à ce sujet au McCall's en 1987 : +« Comme j'étais presque toujours le petit nouveau, je jouais souvent tout seul, et dans ces cas-là votre imagination devient très vite active. Vous vous inventez plein de petites histoires dans votre tête… » +Il découvre le jazz grâce à sa mère qui collectionne des disques. De son côté, son père joue de la guitare et chante dans un groupe improvisé. Clint grandit ainsi en écoutant des morceaux de jazz et de rhythm and blues. Il commence lui-même à jouer de la clarinette, puis du piano. Il finit même par prendre des cours. Cela deviendra par la suite une de ses passions. +La période « rebelle » +Clint entre à l'école secondaire en 1945. Il est indifférent à l'éducation et doit suivre les cours de rattrapage pour pouvoir passer en deuxième année. Bien élevé et socialement avantagé, Clint Eastwood devient de plus en plus un « marginal » qui cherche à se montrer rebelle. Le personnage solitaire du collège est désormais entouré de plusieurs amis. Malgré son physique sportif, Eastwood n’est pas un bon athlète, il ne s'investit pas dans les équipes sportives de l'école. Il déclare à ce sujet qu'il ne s'est « jamais vraiment impliqué dans les sports d'équipe, à cause de tous les déménagements ». Ce n'est pas exactement la vraie raison puisqu'il ne déménagea plus à partir de 1940. Les seuls sports que le futur acteur pratique sont le golf et le tennis. Il n'est pas intéressé par les sports collectifs. C'est pourtant un travail collectif qu'il effectue en créant la Malpaso des années plus tard, en embauchant de nombreuses personnes qui travailleront à ses côtés. + +L'école technique d'Oakland dans lequel Eastwood termine ses études +Après avoir validé sa première année à l'école secondaire de Piedmont, Clint Eastwood la quitte pour l'école technique. Les raisons de ce départ sont assez floues. Certains affirment que c'est à cause des cours de théâtre que dispensait l'école technique que l'acteur changea d'établissement. D'autres avancent que c'est l'absence de familles noires ou asiatiques qui poussèrent Clint Eastwood à partir et d'autres déclarent qu'il a quitté l'école sur la demande de ce dernier — Clint aurait inscrit sur le panneau d'affichage du stade de l'école des propos obscènes sur l'une de ses employées. +Il finit son cursus dans cette école technique. Durant cette période, il obtient sa première voiture, alors qu'il n'avait pas l'âge légal pour la conduire. L'acteur avait deux priorités dans la vie : les voitures et les filles. Il assouvit sa passion avec ses copains, entre balades en voiture et flirt à l'arrière. On remarque d'ailleurs qu'une fois sa société de production créée, il enchaîne les films sur ces thèmes : Le Canardeur (1974), L'Épreuve de force (1977), Honkytonk Man (1982), Pink Cadillac (1989) ou encore La Relève (1990). À l'école secondaire, plutôt que de suivre des cours de théâtre, Clint assiste à des cours de mécanique et d'aéronautique. Il ne pense alors pas à son avenir, préférant vivre aux côtés de ses amis plutôt que de travailler ses leçons. +En 1948, la famille Eastwood doit à nouveau déménager, à la suite d'une promotion de Clinton Sr. Il est nommé directeur de l'une des usines de la société, à Seattle. Ses parents laissent derrière eux Clint Jr., qui termine son semestre à l'école hébergé par Harry Pendleton, l'un de ses camarades. Ainsi, à dix-neuf ans, il obtient son baccalauréat américain, malgré une scolarité dissipée,. Après ceci, Clint Eastwood demeure encore chez son camarade quelque temps. Entouré de son groupe d'amis, il est persuadé que la vie étudiante n'a aucun attrait. Il ne voit qu'un côté positif : faire la fête. Dans cette optique, il côtoie nombre de discothèques chaque fin de semaine. Un soir, alors qu'il rentre chez lui en voiture, accompagné de quelques amis, ils sont contraints de s'arrêter pour ne pas percuter des chevaux qui traversent la route. L'un d'entre eux reconnaît les chevaux : « Stop ! Je sais à qui ces chevaux appartiennent. » Tous descendent alors de la voiture, et ramènent les chevaux à leur propriétaire qui n'était autre que Howard Hawks. Eastwood croisa pour la première fois Hawks, réalisateur et producteur notamment des westerns de John Wayne. « Ce fut la seule rencontre d'Eastwood avec Howard Hawks, qui était l'un de ses réalisateurs préférés […]. Il dit considérer Hawks, de même que John Ford et Anthony Mann, comme des hommes qui ont beaucoup influencé son propre travail » écrit Janet Maslin dans un article du New York Times en 1993. Cependant, Clint n'échange aucune parole avec Hawks lors de leur rencontre. +Le début de l'âge adulte +Les prémices de la collaboration avec Universal + +La Seattle University dans laquelle Clint souhaite poursuivre ses études +Au début de l'été 1949, Clint Eastwood part rejoindre sa famille à Seattle. Malgré son manque de qualifications, il se fait embaucher dans une usine de Weyerhaeuser Company à Springfield, dans laquelle il reste un an. Il enchaîne ensuite plusieurs petits travaux : il fait l'inventaire des pièces chez Boeing, conduit un camion pour Color Shake, puis est veilleur de nuit chez Bethlehem Steel. En parallèle, il suit une formation et obtient de la Croix-Rouge le diplôme de maître-nageur. Il reçoit en même temps sa convocation au service militaire, où ce diplôme se révéla précieux. Il décide alors de poursuivre des études supérieures de musique à la Seattle University. Les étudiants ne sont pas repris, à cause de l'engagement du général Lewis B. Hershey d'envoyer 30 000 hommes en quatre-vingt-dix jours en Corée. Clint fait appel auprès du conseil de révision pour obtenir un délai, mais on le lui refuse. +` + +const bigStr = `Clint Eastwood (prononcé en anglais : /klɪnt istwʊd/), né le 31 mai 1930 à San Francisco (Californie), est un acteur, réalisateur, compositeur et producteur de cinéma américain. Autodidacte, il entre grâce à des amis au studio Universal où il interprète d’abord de petits rôles dans des séries B, puis l’un des rôles phares d'une longue série, Rawhide. Il se fait alors remarquer par Sergio Leone qui l’embauche pour la Trilogie du dollar (Pour une poignée de dollars, Et pour quelques dollars de plus et Le Bon, la Brute et le Truand). Devenu célèbre, il interprète de nombreux rôles, d’abord pour Universal, puis pour Warner Bros., notamment ceux de L'Inspecteur Harry. En 1968, il devient producteur avec la création de la société Malpaso et réalise son premier film en 1971, avec Un frisson dans la nuit. Aujourd'hui, avec plus de quatre-vingt films à son actif, parmi lesquels Impitoyable, Sur la route de Madison ou encore Mystic River et plus récemment Million Dollar Baby, Gran Torino, Invictus et American Sniper, Clint Eastwood figure parmi les cinéastes les plus reconnus au monde. D'abord connu pour ses rôles d'antihéros volontiers redresseur de torts et tragiques, dans des films d'action violents ou des westerns tels que L'Homme des Hautes Plaines ou encore Pale Rider, il a ensuite endossé des rôles plus touchants dans des films empreints d'un certain classicisme, influencés par le cinéma de John Ford et de Howard Hawks. Il est également connu pour ses comédies telles que Doux, dur et dingue et Ça va cogner. Il a ainsi été récompensé à de nombreuses reprises, remportant notamment quatre Oscars, cinq Golden Globes, trois Césars et la Palme d'honneur au Festival de Cannes en 2009. diff --git a/test/scenarios/fullyConnected/scadsLittleData/string200msg.test.js b/test/functional/fullyConnected/scadsOfLittleData.test.js similarity index 70% rename from test/scenarios/fullyConnected/scadsLittleData/string200msg.test.js rename to test/functional/fullyConnected/scadsOfLittleData.test.js index af760745..fd60071d 100644 --- a/test/scenarios/fullyConnected/scadsLittleData/string200msg.test.js +++ b/test/functional/fullyConnected/scadsOfLittleData.test.js @@ -1,5 +1,5 @@ -import {signaling, MSG_NUMBER} from '../../../config' -import {WebChannel} from '../../../../src/WebChannel' +import {signaling, MSG_NUMBER, randString} from '../../config' +import {WebChannel} from '../../../src/WebChannel' it('Should send/receive STRING messages', (done) => { let msg1Array = [] @@ -49,16 +49,3 @@ it('Should send/receive STRING messages', (done) => { .catch(done.fail) }).catch(done.fail) }, 15000) - -function randString () { - const MIN_LENGTH = 0 - const DELTA_LENGTH = 3700 // To limit message size to less than 16kb (4 bytes per character) - const MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' - let result = '' - const length = MIN_LENGTH + Math.round(Math.random() * DELTA_LENGTH) - - for (let i = 0; i < length; i++) { - result += MASK[Math.round(Math.random() * (MASK.length - 1))] - } - return result -} diff --git a/test/scenarios/s01.test.js b/test/scenarios/s01.test.js deleted file mode 100644 index 6c0321b0..00000000 --- a/test/scenarios/s01.test.js +++ /dev/null @@ -1,27 +0,0 @@ -xdescribe('One peer creates a network (webRTC), invites another peer which joins it.', () => { - it('Peer#1: initialize/configure API', () => { - nf1.onPeerJoining = (peer, net) => { - // TODO - } - }) - it('Peer#1: create network', () => { - net1 = nf1.create('fullyconnected') - }) - it('Peer#1: start inviting', () => { - invitingURL = wc1.startInviting() - }) - // Peer#1: give URL to Peer#2 by email for example - it('Peer#2: join network', () => { - nf2.join(invitingURL) - .then((net) => { - // TODO - }) - .catch((reason) => { - // TODO - }) - }) - // Peer#1: receive peer join notification (execute NF1.onPeerJoining) - it('Peer#1: stop inviting', () => { - wc1.stopInviting() - }) -}) diff --git a/test/scenarios/s02.test.js b/test/scenarios/s02.test.js deleted file mode 100644 index 965d0d23..00000000 --- a/test/scenarios/s02.test.js +++ /dev/null @@ -1,58 +0,0 @@ -xdescribe('2 peers in the network send/receive broadcast messages', () => { - let net1, net2, invitingURL - it('Peer#1: initialize/configure API', () => { - NF1.onBroadcastMessage = (peer, net, data) => { - // TODO - } - }) - it('Peer#1: create network', () => { - netP1 = NF.create('fullyconnected') - }) - it('Peer#1: start inviting', () => { - invitingURL = net.startInviting() - }) - // Peer#1 gives URL to Peer#2 by email for example - it('Peer#2: initialize/configure API', () => { - NF2.onPeerJoining = (peer, net) => { - // TODO - } - NF2.onBroadcastMessage = (peer, net, data) => { - // TODO - } - }) - it('Peer#2: join network', () => { - NF2.onBroadcastMessage = (peer, net, data) => { - // TODO - } - NF.join(invitingURL) - .then((net) => { - netP2 = net - // TODO - }) - .catch((reason) => { - // TODO - }) - }) - it('Peer#1: get join peer notification', () => { - }) - it('Peer#1: stop inviting', () => { - netP1.stopInviting() - }) - it('Peer#1: send broadcast message to the network', () => { - netP1.broadcast("Hello network! I'm Peer#1.") - }) - it('Peer#2: get broadcast message', () => { - }) - it('Peer#2: send broadcast message to the network', () => { - }) - it('Peer#1: get broadcast message', () => { - }) - it('Peer#1: send message to Peer#2', () => { - }) - it('Peer#2: get Peer1\'s message', () => { - }) - it('Peer#2: send message to Peer#1', () => { - }) - it('Peer#1: get Peer2\'s message', () => { - }) -}) diff --git a/test/serviceProvider.test.js b/test/serviceProvider.test.js index 750cf3ac..60857fba 100644 --- a/test/serviceProvider.test.js +++ b/test/serviceProvider.test.js @@ -13,11 +13,11 @@ it('serviceProvider -> should provide a service', () => { it('serviceProvider -> FullyConnectedService should be a singleton', () => { let fullyConnected1 = provide(FULLY_CONNECTED) let fullyConnected2 = provide(FULLY_CONNECTED) - expect(fullyConnected1 == fullyConnected2).toBeTruthy() + expect(fullyConnected1).toBe(fullyConnected2) }) it('serviceProvider -> WebRTCService should NOT be a singleton', () => { - expect(provide(WEBRTC) == provide(WEBRTC)).toBeFalsy() + expect(provide(WEBRTC)).not.toBe(provide(WEBRTC)) }) it('serviceProvider -> should return null', () => {