diff --git a/dist/pear-player.js b/dist/pear-player.js index 1d8a9d0321295381e06385ff013cccc5c0273149..8b11947be59d10d59afecfc57e4b3601184a0670 100644 --- a/dist/pear-player.js +++ b/dist/pear-player.js @@ -1,28560 +1,34163 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.PearPlayer = f()}})(function(){var define,module,exports;return (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 a(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) { + throw new Error('Invalid string. Length must be a multiple of 4') + } - var client = new WebTorrent(); + // Trim off extra bytes after placeholder bytes are found + // See: https://github.com/beatgammit/base64-js/issues/42 + var validLen = b64.indexOf('=') + if (validLen === -1) validLen = len - return client.add(opts.magnetURI, function (torrent) { - // Got torrent metadata! - // debug('Client is downloading:', torrent.infoHash) + var placeHoldersLen = validLen === len + ? 0 + : 4 - (validLen % 4) - torrent.files.forEach(function (file) { + return [validLen, placeHoldersLen] +} - render.render(file, opts.selector, {autoplay: opts.autoplay}); - }) - }) +// base64 is 4/3 + up to two characters of the original data +function byteLength (b64) { + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} - } +function _byteLength (b64, validLen, placeHoldersLen) { + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} - PearDownloader.call(self, opts.src || self.video.src, token, opts); +function toByteArray (b64) { + var tmp + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] - self.setupListeners(); -} + var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)) -PearPlayer.prototype.setupListeners = function () { - var self = this; + var curByte = 0 - self.video.addEventListener('canplay', function () { + // if there are placeholders, only get up to the last complete 4 chars + var len = placeHoldersLen > 0 + ? validLen - 4 + : validLen - self.canPlayDelayEnd = (new Date()).getTime(); - var canPlayDelay = (self.canPlayDelayEnd - self.canPlayDelayStart); - self.emit('canplay', canPlayDelay); + for (var i = 0; i < len; i += 4) { + tmp = + (revLookup[b64.charCodeAt(i)] << 18) | + (revLookup[b64.charCodeAt(i + 1)] << 12) | + (revLookup[b64.charCodeAt(i + 2)] << 6) | + revLookup[b64.charCodeAt(i + 3)] + arr[curByte++] = (tmp >> 16) & 0xFF + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + if (placeHoldersLen === 2) { + tmp = + (revLookup[b64.charCodeAt(i)] << 2) | + (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[curByte++] = tmp & 0xFF + } - }); + if (placeHoldersLen === 1) { + tmp = + (revLookup[b64.charCodeAt(i)] << 10) | + (revLookup[b64.charCodeAt(i + 1)] << 4) | + (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } - self.video.addEventListener('loadedmetadata', function () { + return arr +} - var dispatcher = self.dispatcher; +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + + lookup[num >> 12 & 0x3F] + + lookup[num >> 6 & 0x3F] + + lookup[num & 0x3F] +} - if (dispatcher) { - var bitrate = Math.ceil(dispatcher.fileSize/self.video.duration); - var windowLength = Math.ceil(bitrate * 15 / dispatcher.pieceLength); //根据码率和时间间隔来计算窗口长度 - // console.warn('dispatcher._windowLength:'+dispatcher._windowLength); - // self.normalWindowLength = self._windowLength; - if (windowLength < 3) { - windowLength = 3; - } else if (self._windowLength > 15) { - windowLength = 15; - } - dispatcher._windowLength = windowLength; - dispatcher.interval = 5000; - // console.warn('dispatcher._windowLength:'+dispatcher._windowLength); - // self._colddown = 5/self._slideInterval*self._interval2BufPos + 5; //窗口滑动的冷却时间 - // self._colddown = self._windowLength*2; - // self._colddown = 5; - self.emit('metadata', {'bitrate': bitrate, 'duration': self.video.duration}); - } +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = + ((uint8[i] << 16) & 0xFF0000) + + ((uint8[i + 1] << 8) & 0xFF00) + + (uint8[i + 2] & 0xFF) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 - }); + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk( + uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength) + )) + } -} + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + parts.push( + lookup[tmp >> 2] + + lookup[(tmp << 4) & 0x3F] + + '==' + ) + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + uint8[len - 1] + parts.push( + lookup[tmp >> 10] + + lookup[(tmp >> 4) & 0x3F] + + lookup[(tmp << 2) & 0x3F] + + '=' + ) + } -PearPlayer.isWebRTCSupported = function () { - return PearDownloader.isWebRTCSupported(); -}; + return parts.join('') +} -PearPlayer.isMSESupported = function () { - return isMSESupported(); -}; +},{}],3:[function(require,module,exports){ +arguments[4][1][0].apply(exports,arguments) +},{"dup":1}],4:[function(require,module,exports){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ -function isMSESupported() { +'use strict' - return !!(window['MediaSource'] || window['WebKitMediaSource']); +var base64 = require('base64-js') +var ieee754 = require('ieee754') -} -},{"./src/index.downloader":139,"debug":27,"inherits":58,"render-media":93,"webtorrent":122}],2:[function(require,module,exports){ -module.exports = require('./lib/axios'); -},{"./lib/axios":4}],3:[function(require,module,exports){ -(function (process){ -'use strict'; +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 -var utils = require('./../utils'); -var settle = require('./../core/settle'); -var buildURL = require('./../helpers/buildURL'); -var parseHeaders = require('./../helpers/parseHeaders'); -var isURLSameOrigin = require('./../helpers/isURLSameOrigin'); -var createError = require('../core/createError'); -var btoa = (typeof window !== 'undefined' && window.btoa && window.btoa.bind(window)) || require('./../helpers/btoa'); +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH -module.exports = function xhrAdapter(config) { - return new Promise(function dispatchXhrRequest(resolve, reject) { - var requestData = config.data; - var requestHeaders = config.headers; +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an 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+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() - if (utils.isFormData(requestData)) { - delete requestHeaders['Content-Type']; // Let the browser set it - } +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && + typeof console.error === 'function') { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' + ) +} - var request = new XMLHttpRequest(); - var loadEvent = 'onreadystatechange'; - var xDomain = false; +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }} + return arr.foo() === 42 + } catch (e) { + return false + } +} - // For IE 8/9 CORS support - // Only supports POST and GET calls and doesn't returns the response headers. - // DON'T do this for testing b/c XMLHttpRequest is mocked, not XDomainRequest. - if (process.env.NODE_ENV !== 'test' && - typeof window !== 'undefined' && - window.XDomainRequest && !('withCredentials' in request) && - !isURLSameOrigin(config.url)) { - request = new window.XDomainRequest(); - loadEvent = 'onload'; - xDomain = true; - request.onprogress = function handleProgress() {}; - request.ontimeout = function handleTimeout() {}; +Object.defineProperty(Buffer.prototype, 'parent', { + get: function () { + if (!(this instanceof Buffer)) { + return undefined } + return this.buffer + } +}) - // HTTP basic authentication - if (config.auth) { - var username = config.auth.username || ''; - var password = config.auth.password || ''; - requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); +Object.defineProperty(Buffer.prototype, 'offset', { + get: function () { + if (!(this instanceof Buffer)) { + return undefined } + return this.byteOffset + } +}) - request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true); +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('Invalid typed array length') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} - // Set the request timeout in MS - request.timeout = config.timeout; +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ - // Listen for ready state - request[loadEvent] = function handleLoad() { - if (!request || (request.readyState !== 4 && !xDomain)) { - return; - } +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new Error( + 'If encoding is specified then the first argument must be a string' + ) + } + return allocUnsafe(arg) + } + return from(arg, encodingOrOffset, length) +} - // The request errored out and we didn't get a response, this will be - // handled by onerror instead - // With one exception: request that using file: protocol, most browsers - // will return status as 0 even though it's a successful request - if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { - return; - } +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} - // Prepare the response - var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; - var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; - var response = { - data: responseData, - // IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201) - status: request.status === 1223 ? 204 : request.status, - statusText: request.status === 1223 ? 'No Content' : request.statusText, - headers: responseHeaders, - config: config, - request: request - }; +Buffer.poolSize = 8192 // not used by this implementation - settle(resolve, reject, response); +function from (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') + } - // Clean up request - request = null; - }; + if (isArrayBuffer(value) || (value && isArrayBuffer(value.buffer))) { + return fromArrayBuffer(value, encodingOrOffset, length) + } - // Handle low level network errors - request.onerror = function handleError() { - // Real errors are hidden from us by the browser - // onerror should only fire if it's a network error - reject(createError('Network Error', config, null, request)); + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } - // Clean up request - request = null; - }; + return fromObject(value) +} - // Handle timeout - request.ontimeout = function handleTimeout() { - reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', - request)); - - // Clean up request - request = null; - }; - - // Add xsrf header - // This is only done if running in a standard browser environment. - // Specifically not if we're in a web worker, or react-native. - if (utils.isStandardBrowserEnv()) { - var cookies = require('./../helpers/cookies'); +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} - // Add xsrf header - var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ? - cookies.read(config.xsrfCookieName) : - undefined; +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array - if (xsrfValue) { - requestHeaders[config.xsrfHeaderName] = xsrfValue; - } - } +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be of type number') + } else if (size < 0) { + throw new RangeError('"size" argument must not be negative') + } +} - // Add headers to the request - if ('setRequestHeader' in request) { - utils.forEach(requestHeaders, function setRequestHeader(val, key) { - if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { - // Remove Content-Type if data is undefined - delete requestHeaders[key]; - } else { - // Otherwise add header to the request - request.setRequestHeader(key, val); - } - }); - } +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) + } + return createBuffer(size) +} - // Add withCredentials to request if needed - if (config.withCredentials) { - request.withCredentials = true; - } +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) +} - // Add responseType to request if needed - if (config.responseType) { - try { - request.responseType = config.responseType; - } catch (e) { - // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2. - // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function. - if (config.responseType !== 'json') { - throw e; - } - } - } +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} - // Handle progress if needed - if (typeof config.onDownloadProgress === 'function') { - request.addEventListener('progress', config.onDownloadProgress); - } +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} - // Not all browsers support upload events - if (typeof config.onUploadProgress === 'function' && request.upload) { - request.upload.addEventListener('progress', config.onUploadProgress); - } +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } - if (config.cancelToken) { - // Handle cancellation - config.cancelToken.promise.then(function onCanceled(cancel) { - if (!request) { - return; - } + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } - request.abort(); - reject(cancel); - // Clean up request - request = null; - }); - } + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) - if (requestData === undefined) { - requestData = null; - } + var actual = buf.write(string, encoding) - // Send the request - request.send(requestData); - }); -}; + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } -}).call(this,require('_process')) -},{"../core/createError":10,"./../core/settle":13,"./../helpers/btoa":17,"./../helpers/buildURL":18,"./../helpers/cookies":20,"./../helpers/isURLSameOrigin":22,"./../helpers/parseHeaders":24,"./../utils":26,"_process":170}],4:[function(require,module,exports){ -'use strict'; + return buf +} -var utils = require('./utils'); -var bind = require('./helpers/bind'); -var Axios = require('./core/Axios'); -var defaults = require('./defaults'); +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} -/** - * Create an instance of Axios - * - * @param {Object} defaultConfig The default config for the instance - * @return {Axios} A new instance of Axios - */ -function createInstance(defaultConfig) { - var context = new Axios(defaultConfig); - var instance = bind(Axios.prototype.request, context); +function fromArrayBuffer (array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('"offset" is outside of buffer bounds') + } - // Copy axios.prototype to instance - utils.extend(instance, Axios.prototype, context); + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('"length" is outside of buffer bounds') + } - // Copy context to instance - utils.extend(instance, context); + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } - return instance; + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf } -// Create the default instance to be exported -var axios = createInstance(defaults); +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) -// Expose Axios class to allow class inheritance -axios.Axios = Axios; + if (buf.length === 0) { + return buf + } -// Factory for creating new instances -axios.create = function create(instanceConfig) { - return createInstance(utils.merge(defaults, instanceConfig)); -}; + obj.copy(buf, 0, 0, len) + return buf + } -// Expose Cancel & CancelToken -axios.Cancel = require('./cancel/Cancel'); -axios.CancelToken = require('./cancel/CancelToken'); -axios.isCancel = require('./cancel/isCancel'); + if (obj) { + if (ArrayBuffer.isView(obj) || 'length' in obj) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } -// Expose all/spread -axios.all = function all(promises) { - return Promise.all(promises); -}; -axios.spread = require('./helpers/spread'); + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } + } -module.exports = axios; + throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.') +} -// Allow use of default import syntax in TypeScript -module.exports.default = axios; +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') + } + return length | 0 +} -},{"./cancel/Cancel":5,"./cancel/CancelToken":6,"./cancel/isCancel":7,"./core/Axios":8,"./defaults":15,"./helpers/bind":16,"./helpers/spread":25,"./utils":26}],5:[function(require,module,exports){ -'use strict'; +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} -/** - * A `Cancel` is an object that is thrown when an operation is canceled. - * - * @class - * @param {string=} message The message. - */ -function Cancel(message) { - this.message = message; +Buffer.isBuffer = function isBuffer (b) { + return b != null && b._isBuffer === true } -Cancel.prototype.toString = function toString() { - return 'Cancel' + (this.message ? ': ' + this.message : ''); -}; +Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } -Cancel.prototype.__CANCEL__ = true; + if (a === b) return 0 -module.exports = Cancel; + var x = a.length + var y = b.length -},{}],6:[function(require,module,exports){ -'use strict'; + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } -var Cancel = require('./Cancel'); + if (x < y) return -1 + if (y < x) return 1 + return 0 +} -/** - * A `CancelToken` is an object that can be used to request cancellation of an operation. - * - * @class - * @param {Function} executor The executor function. - */ -function CancelToken(executor) { - if (typeof executor !== 'function') { - throw new TypeError('executor must be a function.'); +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false } +} - var resolvePromise; - this.promise = new Promise(function promiseExecutor(resolve) { - resolvePromise = resolve; - }); +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } - var token = this; - executor(function cancel(message) { - if (token.reason) { - // Cancellation has already been requested - return; + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length } + } - token.reason = new Cancel(message); - resolvePromise(token.reason); - }); + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (ArrayBuffer.isView(buf)) { + buf = Buffer.from(buf) + } + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer } -/** - * Throws a `Cancel` if cancellation has been requested. - */ -CancelToken.prototype.throwIfRequested = function throwIfRequested() { - if (this.reason) { - throw this.reason; +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (ArrayBuffer.isView(string) || isArrayBuffer(string)) { + return string.byteLength + } + if (typeof string !== 'string') { + string = '' + string } -}; -/** - * Returns an object that contains a new `CancelToken` and a function that, when called, - * cancels the `CancelToken`. - */ -CancelToken.source = function source() { - var cancel; - var token = new CancelToken(function executor(c) { - cancel = c; - }); - return { - token: token, - cancel: cancel - }; -}; + var len = string.length + if (len === 0) return 0 -module.exports = CancelToken; + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + case undefined: + 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 -},{"./Cancel":5}],7:[function(require,module,exports){ -'use strict'; +function slowToString (encoding, start, end) { + var loweredCase = false -module.exports = function isCancel(value) { - return !!(value && value.__CANCEL__); -}; + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. -},{}],8:[function(require,module,exports){ -'use strict'; + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } -var defaults = require('./../defaults'); -var utils = require('./../utils'); -var InterceptorManager = require('./InterceptorManager'); -var dispatchRequest = require('./dispatchRequest'); + if (end === undefined || end > this.length) { + end = this.length + } -/** - * Create a new instance of Axios - * - * @param {Object} instanceConfig The default config for the instance - */ -function Axios(instanceConfig) { - this.defaults = instanceConfig; - this.interceptors = { - request: new InterceptorManager(), - response: new InterceptorManager() - }; -} + if (end <= 0) { + return '' + } -/** - * Dispatch a request - * - * @param {Object} config The config specific for this request (merged with this.defaults) - */ -Axios.prototype.request = function request(config) { - /*eslint no-param-reassign:0*/ - // Allow for axios('example/url'[, config]) a la fetch API - if (typeof config === 'string') { - config = utils.merge({ - url: arguments[0] - }, arguments[1]); + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' } - config = utils.merge(defaults, this.defaults, { method: 'get' }, config); - config.method = config.method.toLowerCase(); + if (!encoding) encoding = 'utf8' - // Hook up interceptors middleware - var chain = [dispatchRequest, undefined]; - var promise = Promise.resolve(config); + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) - this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { - chain.unshift(interceptor.fulfilled, interceptor.rejected); - }); + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) - this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { - chain.push(interceptor.fulfilled, interceptor.rejected); - }); + case 'ascii': + return asciiSlice(this, start, end) - while (chain.length) { - promise = promise.then(chain.shift(), chain.shift()); - } + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) - return promise; -}; + case 'base64': + return base64Slice(this, start, end) -// Provide aliases for supported request methods -utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { - /*eslint func-names:0*/ - Axios.prototype[method] = function(url, config) { - return this.request(utils.merge(config || {}, { - method: method, - url: url - })); - }; -}); - -utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { - /*eslint func-names:0*/ - Axios.prototype[method] = function(url, data, config) { - return this.request(utils.merge(config || {}, { - method: method, - url: url, - data: data - })); - }; -}); - -module.exports = Axios; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) -},{"./../defaults":15,"./../utils":26,"./InterceptorManager":9,"./dispatchRequest":11}],9:[function(require,module,exports){ -'use strict'; + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} -var utils = require('./../utils'); +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true -function InterceptorManager() { - this.handlers = []; +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i } -/** - * Add a new interceptor to the stack - * - * @param {Function} fulfilled The function to handle `then` for a `Promise` - * @param {Function} rejected The function to handle `reject` for a `Promise` - * - * @return {Number} An ID used to remove interceptor later - */ -InterceptorManager.prototype.use = function use(fulfilled, rejected) { - this.handlers.push({ - fulfilled: fulfilled, - rejected: rejected - }); - return this.handlers.length - 1; -}; - -/** - * Remove an interceptor from the stack - * - * @param {Number} id The ID that was returned by `use` - */ -InterceptorManager.prototype.eject = function eject(id) { - if (this.handlers[id]) { - this.handlers[id] = null; +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') } -}; - -/** - * Iterate over all the registered interceptors - * - * This method is particularly useful for skipping over any - * interceptors that may have become `null` calling `eject`. - * - * @param {Function} fn The function to call for each interceptor - */ -InterceptorManager.prototype.forEach = function forEach(fn) { - utils.forEach(this.handlers, function forEachHandler(h) { - if (h !== null) { - fn(h); - } - }); -}; - -module.exports = InterceptorManager; + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} -},{"./../utils":26}],10:[function(require,module,exports){ -'use strict'; +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} -var enhanceError = require('./enhanceError'); +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} -/** - * Create an Error with the specified message, config, error code, request and response. - * - * @param {string} message The error message. - * @param {Object} config The config. - * @param {string} [code] The error code (for example, 'ECONNABORTED'). - * @param {Object} [request] The request. - * @param {Object} [response] The response. - * @returns {Error} The created error. - */ -module.exports = function createError(message, config, code, request, response) { - var error = new Error(message); - return enhanceError(error, config, code, request, response); -}; +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} -},{"./enhanceError":12}],11:[function(require,module,exports){ -'use strict'; +Buffer.prototype.toLocaleString = Buffer.prototype.toString -var utils = require('./../utils'); -var transformData = require('./transformData'); -var isCancel = require('../cancel/isCancel'); -var defaults = require('../defaults'); -var isAbsoluteURL = require('./../helpers/isAbsoluteURL'); -var combineURLs = require('./../helpers/combineURLs'); +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 +} -/** - * Throws a `Cancel` if cancellation has been requested. - */ -function throwIfCancellationRequested(config) { - if (config.cancelToken) { - config.cancelToken.throwIfRequested(); +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 '' } -/** - * Dispatch a request to the server using the configured adapter. - * - * @param {object} config The config that is to be used for the request - * @returns {Promise} The Promise to be fulfilled - */ -module.exports = function dispatchRequest(config) { - throwIfCancellationRequested(config); +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (!Buffer.isBuffer(target)) { + throw new TypeError('Argument must be a Buffer') + } - // Support baseURL config - if (config.baseURL && !isAbsoluteURL(config.url)) { - config.url = combineURLs(config.baseURL, config.url); + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length } - // Ensure headers exist - config.headers = config.headers || {}; + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } - // Transform request data - config.data = transformData( - config.data, - config.headers, - config.transformRequest - ); + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } - // Flatten headers - config.headers = utils.merge( - config.headers.common || {}, - config.headers[config.method] || {}, - config.headers || {} - ); + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 - utils.forEach( - ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], - function cleanHeaderConfig(method) { - delete config.headers[method]; - } - ); + if (this === target) return 0 - var adapter = config.adapter || defaults.adapter; + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) - return adapter(config).then(function onAdapterResolution(response) { - throwIfCancellationRequested(config); + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) - // Transform response data - response.data = transformData( - response.data, - response.headers, - config.transformResponse - ); + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } - return response; - }, function onAdapterRejection(reason) { - if (!isCancel(reason)) { - throwIfCancellationRequested(config); + if (x < y) return -1 + if (y < x) return 1 + return 0 +} - // Transform response data - if (reason && reason.response) { - reason.response.data = transformData( - reason.response.data, - reason.response.headers, - config.transformResponse - ); +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) } } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } - return Promise.reject(reason); - }); -}; + throw new TypeError('val must be string, number or Buffer') +} -},{"../cancel/isCancel":7,"../defaults":15,"./../helpers/combineURLs":19,"./../helpers/isAbsoluteURL":21,"./../utils":26,"./transformData":14}],12:[function(require,module,exports){ -'use strict'; +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length -/** - * Update an Error with the specified config, error code, and response. - * - * @param {Error} error The error to update. - * @param {Object} config The config. - * @param {string} [code] The error code (for example, 'ECONNABORTED'). - * @param {Object} [request] The request. - * @param {Object} [response] The response. - * @returns {Error} The error. - */ -module.exports = function enhanceError(error, config, code, request, response) { - error.config = config; - if (code) { - error.code = code; + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } } - error.request = request; - error.response = response; - return error; -}; - -},{}],13:[function(require,module,exports){ -'use strict'; -var createError = require('./createError'); + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } -/** - * Resolve or reject a Promise based on response status. - * - * @param {Function} resolve A function that resolves the promise. - * @param {Function} reject A function that rejects the promise. - * @param {object} response The response. - */ -module.exports = function settle(resolve, reject, response) { - var validateStatus = response.config.validateStatus; - // Note: status is not exposed by XDomainRequest - if (!response.status || !validateStatus || validateStatus(response.status)) { - resolve(response); + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } } else { - reject(createError( - 'Request failed with status code ' + response.status, - response.config, - null, - response.request, - response - )); + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } } -}; - -},{"./createError":10}],14:[function(require,module,exports){ -'use strict'; -var utils = require('./../utils'); + return -1 +} -/** - * Transform the data for a request or a response - * - * @param {Object|String} data The data to be transformed - * @param {Array} headers The headers for the request or response - * @param {Array|Function} fns A single function or Array of functions - * @returns {*} The resulting transformed data - */ -module.exports = function transformData(data, headers, fns) { - /*eslint no-param-reassign:0*/ - utils.forEach(fns, function transform(fn) { - data = fn(data, headers); - }); +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} - return data; -}; +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} -},{"./../utils":26}],15:[function(require,module,exports){ -(function (process){ -'use strict'; +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} -var utils = require('./utils'); -var normalizeHeaderName = require('./helpers/normalizeHeaderName'); +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 + } + } -var DEFAULT_CONTENT_TYPE = { - 'Content-Type': 'application/x-www-form-urlencoded' -}; + var strLen = string.length -function setContentTypeIfUnset(headers, value) { - if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { - headers['Content-Type'] = value; + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (numberIsNaN(parsed)) return i + buf[offset + i] = parsed } + return i } -function getDefaultAdapter() { - var adapter; - if (typeof XMLHttpRequest !== 'undefined') { - // For browsers use XHR adapter - adapter = require('./adapters/xhr'); - } else if (typeof process !== 'undefined') { - // For node use HTTP adapter - adapter = require('./adapters/http'); - } - return adapter; +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) } -var defaults = { - adapter: getDefaultAdapter(), +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} - transformRequest: [function transformRequest(data, headers) { - normalizeHeaderName(headers, 'Content-Type'); - if (utils.isFormData(data) || - utils.isArrayBuffer(data) || - utils.isBuffer(data) || - utils.isStream(data) || - utils.isFile(data) || - utils.isBlob(data) - ) { - return data; - } - if (utils.isArrayBufferView(data)) { - return data.buffer; - } - if (utils.isURLSearchParams(data)) { - setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); - return data.toString(); - } - if (utils.isObject(data)) { - setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); - return JSON.stringify(data); - } - return data; - }], - - transformResponse: [function transformResponse(data) { - /*eslint no-param-reassign:0*/ - if (typeof data === 'string') { - try { - data = JSON.parse(data); - } catch (e) { /* Ignore */ } - } - return data; - }], - - timeout: 0, - - xsrfCookieName: 'XSRF-TOKEN', - xsrfHeaderName: 'X-XSRF-TOKEN', +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} - maxContentLength: -1, +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} - validateStatus: function validateStatus(status) { - return status >= 200 && status < 300; - } -}; +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} -defaults.headers = { - common: { - 'Accept': 'application/json, text/plain, */*' +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 + } + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) } -}; -utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { - defaults.headers[method] = {}; -}); + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining -utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { - defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); -}); + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } -module.exports = defaults; + if (!encoding) encoding = 'utf8' -}).call(this,require('_process')) -},{"./adapters/http":3,"./adapters/xhr":3,"./helpers/normalizeHeaderName":23,"./utils":26,"_process":170}],16:[function(require,module,exports){ -'use strict'; + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) -module.exports = function bind(fn, thisArg) { - return function wrap() { - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } - return fn.apply(thisArg, args); - }; -}; + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) -},{}],17:[function(require,module,exports){ -'use strict'; + case 'ascii': + return asciiWrite(this, string, offset, length) -// btoa polyfill for IE<10 courtesy https://github.com/davidchambers/Base64.js + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) -var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) -function E() { - this.message = 'String contains an invalid character'; -} -E.prototype = new Error; -E.prototype.code = 5; -E.prototype.name = 'InvalidCharacterError'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) -function btoa(input) { - var str = String(input); - var output = ''; - for ( - // initialize result and counter - var block, charCode, idx = 0, map = chars; - // if the next str index does not exist: - // change the mapping table to "=" - // check if d has no fractional digits - str.charAt(idx | 0) || (map = '=', idx % 1); - // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 - output += map.charAt(63 & block >> 8 - idx % 1 * 8) - ) { - charCode = str.charCodeAt(idx += 3 / 4); - if (charCode > 0xFF) { - throw new E(); + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true } - block = block << 8 | charCode; } - return output; } -module.exports = btoa; - -},{}],18:[function(require,module,exports){ -'use strict'; - -var utils = require('./../utils'); - -function encode(val) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%20/g, '+'). - replace(/%5B/gi, '['). - replace(/%5D/gi, ']'); +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } } -/** - * Build a URL by appending params to the end - * - * @param {string} url The base of the url (e.g., http://www.google.com) - * @param {object} [params] The params to be appended - * @returns {string} The formatted url - */ -module.exports = function buildURL(url, params, paramsSerializer) { - /*eslint no-param-reassign:0*/ - if (!params) { - return url; +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) } +} - var serializedParams; - if (paramsSerializer) { - serializedParams = paramsSerializer(params); - } else if (utils.isURLSearchParams(params)) { - serializedParams = params.toString(); - } else { - var parts = []; +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] - utils.forEach(params, function serialize(val, key) { - if (val === null || typeof val === 'undefined') { - return; - } + 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 (utils.isArray(val)) { - key = key + '[]'; - } + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint - if (!utils.isArray(val)) { - val = [val]; + 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 + } + } } + } - utils.forEach(val, function parseValue(v) { - if (utils.isDate(v)) { - v = v.toISOString(); - } else if (utils.isObject(v)) { - v = JSON.stringify(v); - } - parts.push(encode(key) + '=' + encode(v)); - }); - }); + 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 + } - serializedParams = parts.join('&'); + res.push(codePoint) + i += bytesPerSequence } - if (serializedParams) { - url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; - } + return decodeCodePointsArray(res) +} - return url; -}; +// 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 -},{"./../utils":26}],19:[function(require,module,exports){ -'use strict'; +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } -/** - * Creates a new URL by combining the specified URLs - * - * @param {string} baseURL The base URL - * @param {string} relativeURL The relative URL - * @returns {string} The combined URL - */ -module.exports = function combineURLs(baseURL, relativeURL) { - return relativeURL - ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') - : baseURL; -}; + // 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 +} -},{}],20:[function(require,module,exports){ -'use strict'; +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) -var utils = require('./../utils'); + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} -module.exports = ( - utils.isStandardBrowserEnv() ? +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) - // Standard browser envs support document.cookie - (function standardBrowserEnv() { - return { - write: function write(name, value, expires, path, domain, secure) { - var cookie = []; - cookie.push(name + '=' + encodeURIComponent(value)); + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} - if (utils.isNumber(expires)) { - cookie.push('expires=' + new Date(expires).toGMTString()); - } +function hexSlice (buf, start, end) { + var len = buf.length - if (utils.isString(path)) { - cookie.push('path=' + path); - } + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len - if (utils.isString(domain)) { - cookie.push('domain=' + domain); - } + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} - if (secure === true) { - cookie.push('secure'); - } +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 +} - document.cookie = cookie.join('; '); - }, +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end - read: function read(name) { - var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); - return (match ? decodeURIComponent(match[3]) : null); - }, + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } - remove: function remove(name) { - this.write(name, '', Date.now() - 86400000); - } - }; - })() : + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } - // Non standard browser env (web workers, react-native) lack needed support. - (function nonStandardBrowserEnv() { - return { - write: function write() {}, - read: function read() { return null; }, - remove: function remove() {} - }; - })() -); + if (end < start) end = start -},{"./../utils":26}],21:[function(require,module,exports){ -'use strict'; + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf +} -/** - * Determines whether the specified URL is absolute - * - * @param {string} url The URL to test - * @returns {boolean} True if the specified URL is absolute, otherwise false +/* + * Need to make sure that buffer isn't trying to write out of bounds. */ -module.exports = function isAbsoluteURL(url) { - // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). - // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed - // by any combination of letters, digits, plus, period, or hyphen. - return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); -}; +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') +} -},{}],22:[function(require,module,exports){ -'use strict'; +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) -var utils = require('./../utils'); + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } -module.exports = ( - utils.isStandardBrowserEnv() ? + return val +} - // Standard browser envs have full support of the APIs needed to test - // whether the request URL is of the same origin as current location. - (function standardBrowserEnv() { - var msie = /(msie|trident)/i.test(navigator.userAgent); - var urlParsingNode = document.createElement('a'); - var originURL; +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } - /** - * Parse a URL to discover it's components - * - * @param {String} url The URL to be parsed - * @returns {Object} - */ - function resolveURL(url) { - var href = url; + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } - if (msie) { - // IE needs attribute set twice to normalize properties - urlParsingNode.setAttribute('href', href); - href = urlParsingNode.href; - } + return val +} - urlParsingNode.setAttribute('href', href); +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} - // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils - return { - href: urlParsingNode.href, - protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', - host: urlParsingNode.host, - search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', - hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', - hostname: urlParsingNode.hostname, - port: urlParsingNode.port, - pathname: (urlParsingNode.pathname.charAt(0) === '/') ? - urlParsingNode.pathname : - '/' + urlParsingNode.pathname - }; - } +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} - originURL = resolveURL(window.location.href); +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} - /** - * Determine if a URL shares the same origin as the current location - * - * @param {String} requestURL The URL to test - * @returns {boolean} True if URL shares the same origin, otherwise false - */ - return function isURLSameOrigin(requestURL) { - var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; - return (parsed.protocol === originURL.protocol && - parsed.host === originURL.host); - }; - })() : - - // Non standard browser envs (web workers, react-native) lack needed support. - (function nonStandardBrowserEnv() { - return function isURLSameOrigin() { - return true; - }; - })() -); - -},{"./../utils":26}],23:[function(require,module,exports){ -'use strict'; +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) -var utils = require('../utils'); + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} -module.exports = function normalizeHeaderName(headers, normalizedName) { - utils.forEach(headers, function processHeader(value, name) { - if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { - headers[normalizedName] = value; - delete headers[name]; - } - }); -}; +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) -},{"../utils":26}],24:[function(require,module,exports){ -'use strict'; + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} -var utils = require('./../utils'); +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) -// Headers whose duplicates are ignored by node -// c.f. https://nodejs.org/api/http.html#http_message_headers -var ignoreDuplicateOf = [ - 'age', 'authorization', 'content-length', 'content-type', 'etag', - 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', - 'last-modified', 'location', 'max-forwards', 'proxy-authorization', - 'referer', 'retry-after', 'user-agent' -]; + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 -/** - * Parse headers into an object - * - * ``` - * Date: Wed, 27 Aug 2014 08:58:49 GMT - * Content-Type: application/json - * Connection: keep-alive - * Transfer-Encoding: chunked - * ``` - * - * @param {String} headers Headers needing to be parsed - * @returns {Object} Headers parsed into an object - */ -module.exports = function parseHeaders(headers) { - var parsed = {}; - var key; - var val; - var i; + if (val >= mul) val -= Math.pow(2, 8 * byteLength) - if (!headers) { return parsed; } + return val +} - utils.forEach(headers.split('\n'), function parser(line) { - i = line.indexOf(':'); - key = utils.trim(line.substr(0, i)).toLowerCase(); - val = utils.trim(line.substr(i + 1)); +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) - if (key) { - if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { - return; - } - if (key === 'set-cookie') { - parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); - } else { - parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; - } - } - }); + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 - return parsed; -}; + if (val >= mul) val -= Math.pow(2, 8 * byteLength) -},{"./../utils":26}],25:[function(require,module,exports){ -'use strict'; + return val +} -/** - * Syntactic sugar for invoking a function and expanding an array for arguments. - * - * Common use case would be to use `Function.prototype.apply`. - * - * ```js - * function f(x, y, z) {} - * var args = [1, 2, 3]; - * f.apply(null, args); - * ``` - * - * With `spread` this example can be re-written. - * - * ```js - * spread(function(x, y, z) {})([1, 2, 3]); - * ``` - * - * @param {Function} callback - * @returns {Function} - */ -module.exports = function spread(callback) { - return function wrap(arr) { - return callback.apply(null, arr); - }; -}; +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} -},{}],26:[function(require,module,exports){ -'use strict'; +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} -var bind = require('./helpers/bind'); -var isBuffer = require('is-buffer'); +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} -/*global toString:true*/ +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) -// utils is a library of generic helper functions non-specific to axios + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} -var toString = Object.prototype.toString; +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) -/** - * Determine if a value is an Array - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an Array, otherwise false - */ -function isArray(val) { - return toString.call(val) === '[object Array]'; + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) } -/** - * Determine if a value is an ArrayBuffer - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an ArrayBuffer, otherwise false - */ -function isArrayBuffer(val) { - return toString.call(val) === '[object ArrayBuffer]'; +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) } -/** - * Determine if a value is a FormData - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an FormData, otherwise false - */ -function isFormData(val) { - return (typeof FormData !== 'undefined') && (val instanceof FormData); +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) } -/** - * Determine if a value is a view on an ArrayBuffer - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false - */ -function isArrayBufferView(val) { - var result; - if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { - result = ArrayBuffer.isView(val); - } else { - result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); - } - return result; +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) } -/** - * Determine if a value is a String - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a String, otherwise false - */ -function isString(val) { - return typeof val === 'string'; +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) } -/** - * Determine if a value is a Number - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a Number, otherwise false - */ -function isNumber(val) { - return typeof val === 'number'; +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') } -/** - * Determine if a value is undefined - * - * @param {Object} val The value to test - * @returns {boolean} True if the value is undefined, otherwise false - */ -function isUndefined(val) { - return typeof val === 'undefined'; -} +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } -/** - * Determine if a value is an Object - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an Object, otherwise false - */ -function isObject(val) { - return val !== null && typeof val === 'object'; -} + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } -/** - * Determine if a value is a Date - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a Date, otherwise false - */ -function isDate(val) { - return toString.call(val) === '[object Date]'; + return offset + byteLength } -/** - * Determine if a value is a File - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a File, otherwise false - */ -function isFile(val) { - return toString.call(val) === '[object File]'; -} +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } -/** - * Determine if a value is a Blob - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a Blob, otherwise false - */ -function isBlob(val) { - return toString.call(val) === '[object Blob]'; + 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 } -/** - * Determine if a value is a Function - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a Function, otherwise false - */ -function isFunction(val) { - return toString.call(val) === '[object Function]'; +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 } -/** - * Determine if a value is a Stream - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a Stream, otherwise false - */ -function isStream(val) { - return isObject(val) && isFunction(val.pipe); +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 } -/** - * Determine if a value is a URLSearchParams object - * - * @param {Object} val The value to test - * @returns {boolean} True if value is a URLSearchParams object, otherwise false - */ -function isURLSearchParams(val) { - return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 } -/** - * Trim excess whitespace off the beginning and end of a string - * - * @param {String} str The String to trim - * @returns {String} The String freed of excess whitespace - */ -function trim(str) { - return str.replace(/^\s*/, '').replace(/\s*$/, ''); +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 } -/** - * Determine if we're running in a standard browser environment - * - * This allows axios to run in a web worker, and react-native. - * Both environments support XMLHttpRequest, but not fully standard globals. - * - * web workers: - * typeof window -> undefined - * typeof document -> undefined - * - * react-native: - * navigator.product -> 'ReactNative' - */ -function isStandardBrowserEnv() { - if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { - return false; - } - return ( - typeof window !== 'undefined' && - typeof document !== 'undefined' - ); +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 } -/** - * Iterate over an Array or an Object invoking a function for each item. - * - * If `obj` is an Array callback will be called passing - * the value, index, and complete array for each item. - * - * If 'obj' is an Object callback will be called passing - * the value, key, and complete object for each property. - * - * @param {Object|Array} obj The object to iterate - * @param {Function} fn The callback to invoke for each item - */ -function forEach(obj, fn) { - // Don't bother if no value provided - if (obj === null || typeof obj === 'undefined') { - return; - } +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) - // Force an array if not already something iterable - if (typeof obj !== 'object') { - /*eslint no-param-reassign:0*/ - obj = [obj]; + checkInt(this, value, offset, byteLength, limit - 1, -limit) } - if (isArray(obj)) { - // Iterate over array values - for (var i = 0, l = obj.length; i < l; i++) { - fn.call(null, obj[i], i, obj); - } - } else { - // Iterate over object keys - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - fn.call(null, obj[key], key, obj); - } + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF } + + return offset + byteLength } -/** - * Accepts varargs expecting each argument to be an object, then - * immutably merges the properties of each object and returns result. - * - * When multiple objects contain the same key the later object in - * the arguments list will take precedence. - * - * Example: - * - * ```js - * var result = merge({foo: 123}, {foo: 456}); - * console.log(result.foo); // outputs 456 - * ``` - * - * @param {Object} obj1 Object to merge - * @returns {Object} Result of all merge properties - */ -function merge(/* obj1, obj2, obj3, ... */) { - var result = {}; - function assignValue(val, key) { - if (typeof result[key] === 'object' && typeof val === 'object') { - result[key] = merge(result[key], val); - } else { - result[key] = val; - } +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) } - for (var i = 0, l = arguments.length; i < l; i++) { - forEach(arguments[i], assignValue); + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF } - return result; + + return offset + byteLength } -/** - * Extends object a by mutably adding to it the properties of object b. - * - * @param {Object} a The object to be extended - * @param {Object} b The object to copy properties from - * @param {Object} thisArg The object to bind function to - * @return {Object} The resulting value of object a - */ -function extend(a, b, thisArg) { - forEach(b, function assignValue(val, key) { - if (thisArg && typeof val === 'function') { - a[key] = bind(val, thisArg); - } else { - a[key] = val; - } - }); - return a; +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 } -module.exports = { - isArray: isArray, - isArrayBuffer: isArrayBuffer, - isBuffer: isBuffer, - isFormData: isFormData, - isArrayBufferView: isArrayBufferView, - isString: isString, - isNumber: isNumber, - isObject: isObject, - isUndefined: isUndefined, - isDate: isDate, - isFile: isFile, - isBlob: isBlob, - isFunction: isFunction, - isStream: isStream, - isURLSearchParams: isURLSearchParams, - isStandardBrowserEnv: isStandardBrowserEnv, - forEach: forEach, - merge: merge, - extend: extend, - trim: trim -}; - -},{"./helpers/bind":16,"is-buffer":29}],27:[function(require,module,exports){ -(function (process){ -/** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = require('./debug'); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = 'undefined' != typeof chrome - && 'undefined' != typeof chrome.storage - ? chrome.storage.local - : localstorage(); +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} -/** - * Colors. - */ +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} -exports.colors = [ - '#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', - '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', - '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', - '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', - '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', - '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', - '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', - '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', - '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', - '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', - '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33' -]; +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ +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 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { - return true; - } +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} - // Internet Explorer and Edge do not support colors. - if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { - return false; +function writeFloat (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} - // is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) } -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} -exports.formatters.j = function(v) { - try { - return JSON.stringify(v); - } catch (err) { - return '[UnexpectedJSONParseError]: ' + err.message; +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) } -}; - + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} -/** - * Colorize log arguments if enabled. - * - * @api public - */ +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} -function formatArgs(args) { - var useColors = this.useColors; +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer') + 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 - if (!useColors) return; + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 - var c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit') + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('Index out of range') + if (end < 0) throw new RangeError('sourceEnd out of bounds') - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } - args.splice(lastC, 0, c); -} + var len = end - start -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ + if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { + // Use built-in when available, missing from IE11 + this.copyWithin(targetStart, start, end) + } else if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (var i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, end), + targetStart + ) + } -function log() { - // this hackery is required for IE8/9, where - // the `console.log` function doesn't have 'apply' - return 'object' === typeof console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); + return len } -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - try { - if (null == namespaces) { - exports.storage.removeItem('debug'); - } else { - exports.storage.debug = namespaces; +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length } - } catch(e) {} -} + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if ((encoding === 'utf8' && code < 128) || + encoding === 'latin1') { + // Fast path: If `val` fits into a single byte, use that numeric value. + val = code + } + } + } else if (typeof val === 'number') { + val = val & 255 + } -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } -function load() { - var r; - try { - r = exports.storage.debug; - } catch(e) {} + if (end <= start) { + return this + } - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : new Buffer(val, encoding) + var len = bytes.length + if (len === 0) { + throw new TypeError('The value "' + val + + '" is invalid for argument "value"') + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } } - return r; + return this } -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ - -exports.enable(load()); +// HELPER FUNCTIONS +// ================ -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ +var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g -function localstorage() { - try { - return window.localStorage; - } catch (e) {} +function base64clean (str) { + // Node takes equal signs as end of the Base64 encoding + str = str.split('=')[0] + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = str.trim().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 } -}).call(this,require('_process')) -},{"./debug":28,"_process":170}],28:[function(require,module,exports){ - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. - */ +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} -exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = require('ms'); +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] -/** - * Active `debug` instances. - */ -exports.instances = []; + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) -/** - * The currently active debug mode names, and names to skip. - */ + // 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 + } -exports.names = []; -exports.skips = []; + // valid lead + leadSurrogate = codePoint -/** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ + continue + } -exports.formatters = {}; + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } -/** - * Select a color. - * @param {String} namespace - * @return {Number} - * @api private - */ + // 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) + } -function selectColor(namespace) { - var hash = 0, i; + leadSurrogate = null - for (i in namespace) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer + // 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 exports.colors[Math.abs(hash) % exports.colors.length]; + return bytes } -/** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ +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 createDebug(namespace) { +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break - var prevTime; + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } - function debug() { - // disabled? - if (!debug.enabled) return; + return byteArray +} - var self = debug; +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // turn the `arguments` into a proper Array - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } +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 +} - args[0] = exports.coerce(args[0]); +// ArrayBuffers from another context (i.e. an iframe) do not pass the `instanceof` check +// but they should be treated as valid. See: https://github.com/feross/buffer/issues/166 +function isArrayBuffer (obj) { + return obj instanceof ArrayBuffer || + (obj != null && obj.constructor != null && obj.constructor.name === 'ArrayBuffer' && + typeof obj.byteLength === 'number') +} - if ('string' !== typeof args[0]) { - // anything else let's inspect with %O - args.unshift('%O'); - } +function numberIsNaN (obj) { + return obj !== obj // eslint-disable-line no-self-compare +} - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); +},{"base64-js":2,"ieee754":9}],5:[function(require,module,exports){ +module.exports = { + "100": "Continue", + "101": "Switching Protocols", + "102": "Processing", + "200": "OK", + "201": "Created", + "202": "Accepted", + "203": "Non-Authoritative Information", + "204": "No Content", + "205": "Reset Content", + "206": "Partial Content", + "207": "Multi-Status", + "208": "Already Reported", + "226": "IM Used", + "300": "Multiple Choices", + "301": "Moved Permanently", + "302": "Found", + "303": "See Other", + "304": "Not Modified", + "305": "Use Proxy", + "307": "Temporary Redirect", + "308": "Permanent Redirect", + "400": "Bad Request", + "401": "Unauthorized", + "402": "Payment Required", + "403": "Forbidden", + "404": "Not Found", + "405": "Method Not Allowed", + "406": "Not Acceptable", + "407": "Proxy Authentication Required", + "408": "Request Timeout", + "409": "Conflict", + "410": "Gone", + "411": "Length Required", + "412": "Precondition Failed", + "413": "Payload Too Large", + "414": "URI Too Long", + "415": "Unsupported Media Type", + "416": "Range Not Satisfiable", + "417": "Expectation Failed", + "418": "I'm a teapot", + "421": "Misdirected Request", + "422": "Unprocessable Entity", + "423": "Locked", + "424": "Failed Dependency", + "425": "Unordered Collection", + "426": "Upgrade Required", + "428": "Precondition Required", + "429": "Too Many Requests", + "431": "Request Header Fields Too Large", + "451": "Unavailable For Legal Reasons", + "500": "Internal Server Error", + "501": "Not Implemented", + "502": "Bad Gateway", + "503": "Service Unavailable", + "504": "Gateway Timeout", + "505": "HTTP Version Not Supported", + "506": "Variant Also Negotiates", + "507": "Insufficient Storage", + "508": "Loop Detected", + "509": "Bandwidth Limit Exceeded", + "510": "Not Extended", + "511": "Network Authentication Required" +} - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); +},{}],6:[function(require,module,exports){ +(function (Buffer){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. - // apply env-specific formatting (colors, etc.) - exports.formatArgs.call(self, args); +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. - var logFn = debug.log || exports.log || console.log.bind(console); - logFn.apply(self, args); +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); } + return objectToString(arg) === '[object Array]'; +} +exports.isArray = isArray; - debug.namespace = namespace; - debug.enabled = exports.enabled(namespace); - debug.useColors = exports.useColors(); - debug.color = selectColor(namespace); - debug.destroy = destroy; +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; - // env-specific initialization logic for debug instances - if ('function' === typeof exports.init) { - exports.init(debug); - } +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; - exports.instances.push(debug); +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; - return debug; +function isNumber(arg) { + return typeof arg === 'number'; } +exports.isNumber = isNumber; -function destroy () { - var index = exports.instances.indexOf(this); - if (index !== -1) { - exports.instances.splice(index, 1); - return true; - } else { - return false; - } +function isString(arg) { + return typeof arg === 'string'; } +exports.isString = isString; -/** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; -function enable(namespaces) { - exports.save(namespaces); +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; - exports.names = []; - exports.skips = []; +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; - var i; - var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - var len = split.length; +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; - for (i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); - } - } +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; - for (i = 0; i < exports.instances.length; i++) { - var instance = exports.instances[i]; - instance.enabled = exports.enabled(instance.namespace); - } +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); } +exports.isError = isError; -/** - * Disable debug output. - * - * @api public - */ +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; -function disable() { - exports.enable(''); +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; } +exports.isPrimitive = isPrimitive; -/** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ +exports.isBuffer = Buffer.isBuffer; -function enabled(name) { - if (name[name.length - 1] === '*') { - return true; - } - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; +function objectToString(o) { + return Object.prototype.toString.call(o); } -/** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} +}).call(this,{"isBuffer":require("../../is-buffer/index.js")}) +},{"../../is-buffer/index.js":11}],7:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -},{"ms":30}],29:[function(require,module,exports){ -/*! - * Determine if an object is a Buffer - * - * @author Feross Aboukhadijeh - * @license MIT - */ +var objectCreate = Object.create || objectCreatePolyfill +var objectKeys = Object.keys || objectKeysPolyfill +var bind = Function.prototype.bind || functionBindPolyfill -// The _isBuffer check is for Safari 5-7 support, because it's missing -// Object.prototype.constructor. Remove this eventually -module.exports = function (obj) { - return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) -} +function EventEmitter() { + if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) { + this._events = objectCreate(null); + this._eventsCount = 0; + } -function isBuffer (obj) { - return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) + this._maxListeners = this._maxListeners || undefined; } +module.exports = EventEmitter; -// For Node v0.10 support. Remove this eventually. -function isSlowBuffer (obj) { - return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) -} +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; -},{}],30:[function(require,module,exports){ -/** - * Helpers. - */ +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public - */ +var hasDefineProperty; +try { + var o = {}; + if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 }); + hasDefineProperty = o.x === 0; +} catch (err) { hasDefineProperty = false } +if (hasDefineProperty) { + Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + // check whether the input is a positive number (whose value is zero or + // greater and not a NaN). + if (typeof arg !== 'number' || arg < 0 || arg !== arg) + throw new TypeError('"defaultMaxListeners" must be a positive number'); + defaultMaxListeners = arg; + } + }); +} else { + EventEmitter.defaultMaxListeners = defaultMaxListeners; +} -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isNaN(val) === false) { - return options.long ? fmtLong(val) : fmtShort(val); - } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || isNaN(n)) + throw new TypeError('"n" argument must be a positive number'); + this._maxListeners = n; + return this; }; -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - str = String(str); - if (str.length > 100) { - return; - } - var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; - } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; - } +function $getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; } -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return $getMaxListeners(this); +}; -function fmtShort(ms) { - if (ms >= d) { - return Math.round(ms / d) + 'd'; - } - if (ms >= h) { - return Math.round(ms / h) + 'h'; +// These standalone emit* functions are used to optimize calling of event +// handlers for fast cases because emit() itself often has a variable number of +// arguments and can be deoptimized because of that. These functions always have +// the same number of arguments and thus do not get deoptimized, so the code +// inside them can execute faster. +function emitNone(handler, isFn, self) { + if (isFn) + handler.call(self); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self); } - if (ms >= m) { - return Math.round(ms / m) + 'm'; +} +function emitOne(handler, isFn, self, arg1) { + if (isFn) + handler.call(self, arg1); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1); } - if (ms >= s) { - return Math.round(ms / s) + 's'; +} +function emitTwo(handler, isFn, self, arg1, arg2) { + if (isFn) + handler.call(self, arg1, arg2); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1, arg2); } - return ms + 'ms'; } - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtLong(ms) { - return plural(ms, d, 'day') || - plural(ms, h, 'hour') || - plural(ms, m, 'minute') || - plural(ms, s, 'second') || - ms + ' ms'; +function emitThree(handler, isFn, self, arg1, arg2, arg3) { + if (isFn) + handler.call(self, arg1, arg2, arg3); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1, arg2, arg3); + } } -/** - * Pluralization helper. - */ - -function plural(ms, n, name) { - if (ms < n) { - return; - } - if (ms < n * 1.5) { - return Math.floor(ms / n) + ' ' + name; +function emitMany(handler, isFn, self, args) { + if (isFn) + handler.apply(self, args); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].apply(self, args); } - return Math.ceil(ms / n) + ' ' + name + 's'; } -},{}],31:[function(require,module,exports){ -var ADDR_RE = /^\[?([^\]]+)\]?:(\d+)$/ // ipv4/ipv6/hostname + port - -var cache = {} +EventEmitter.prototype.emit = function emit(type) { + var er, handler, len, args, i, events; + var doError = (type === 'error'); -// reset cache when it gets to 100,000 elements (~ 600KB of ipv4 addresses) -// so it will not grow to consume all memory in long-running processes -var size = 0 + events = this._events; + if (events) + doError = (doError && events.error == null); + else if (!doError) + return false; -module.exports = function addrToIPPort (addr) { - if (size === 100000) module.exports.reset() - if (!cache[addr]) { - var m = ADDR_RE.exec(addr) - if (!m) throw new Error('invalid addr: ' + addr) - cache[addr] = [ m[1], Number(m[2]) ] - size += 1 + // If there is no 'error' event listener then throw. + if (doError) { + if (arguments.length > 1) + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Unhandled "error" event. (' + er + ')'); + err.context = er; + throw err; + } + return false; } - return cache[addr] -} - -module.exports.reset = function reset () { - cache = {} - size = 0 -} -},{}],32:[function(require,module,exports){ -'use strict' + handler = events[type]; -exports.byteLength = byteLength -exports.toByteArray = toByteArray -exports.fromByteArray = fromByteArray + if (!handler) + return false; -var lookup = [] -var revLookup = [] -var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array + var isFn = typeof handler === 'function'; + len = arguments.length; + switch (len) { + // fast cases + case 1: + emitNone(handler, isFn, this); + break; + case 2: + emitOne(handler, isFn, this, arguments[1]); + break; + case 3: + emitTwo(handler, isFn, this, arguments[1], arguments[2]); + break; + case 4: + emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]); + break; + // slower + default: + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + emitMany(handler, isFn, this, args); + } -var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -for (var i = 0, len = code.length; i < len; ++i) { - lookup[i] = code[i] - revLookup[code.charCodeAt(i)] = i -} + return true; +}; -revLookup['-'.charCodeAt(0)] = 62 -revLookup['_'.charCodeAt(0)] = 63 +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; -function placeHoldersCount (b64) { - var len = b64.length - if (len % 4 > 0) { - throw new Error('Invalid string. Length must be a multiple of 4') - } + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); - // 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 - return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0 -} + events = target._events; + if (!events) { + events = target._events = objectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); -function byteLength (b64) { - // base64 is 4/3 + up to two characters of the original data - return (b64.length * 3 / 4) - placeHoldersCount(b64) -} + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } -function toByteArray (b64) { - var i, l, tmp, placeHolders, arr - var len = b64.length - placeHolders = placeHoldersCount(b64) + if (!existing) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + } else { + // If we've already got an array, just append. + if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + } - arr = new Arr((len * 3 / 4) - placeHolders) + // Check for listener leak + if (!existing.warned) { + m = $getMaxListeners(target); + if (m && m > 0 && existing.length > m) { + existing.warned = true; + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' "' + String(type) + '" listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit.'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + if (typeof console === 'object' && console.warn) { + console.warn('%s: %s', w.name, w.message); + } + } + } + } - // if there are placeholders, only get up to the last complete 4 chars - l = placeHolders > 0 ? len - 4 : len + return target; +} - var L = 0 +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; - for (i = 0; i < l; i += 4) { - tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)] - arr[L++] = (tmp >> 16) & 0xFF - arr[L++] = (tmp >> 8) & 0xFF - arr[L++] = tmp & 0xFF - } +EventEmitter.prototype.on = EventEmitter.prototype.addListener; - if (placeHolders === 2) { - tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4) - arr[L++] = tmp & 0xFF - } else if (placeHolders === 1) { - tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2) - arr[L++] = (tmp >> 8) & 0xFF - arr[L++] = tmp & 0xFF - } +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; - return arr +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + switch (arguments.length) { + case 0: + return this.listener.call(this.target); + case 1: + return this.listener.call(this.target, arguments[0]); + case 2: + return this.listener.call(this.target, arguments[0], arguments[1]); + case 3: + return this.listener.call(this.target, arguments[0], arguments[1], + arguments[2]); + default: + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) + args[i] = arguments[i]; + this.listener.apply(this.target, args); + } + } } -function tripletToBase64 (num) { - return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F] +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = bind.call(onceWrapper, state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; } -function encodeChunk (uint8, start, end) { - var tmp - var output = [] - for (var i = start; i < end; i += 3) { - tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) - output.push(tripletToBase64(tmp)) - } - return output.join('') -} +EventEmitter.prototype.once = function once(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; -function fromByteArray (uint8) { - var tmp - var len = uint8.length - var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes - var output = '' - var parts = [] - var maxChunkLength = 16383 // must be multiple of 3 +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; - // go through the array every three bytes, we'll deal with trailing stuff later - for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { - parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) - } +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = this._events; + if (!events) + return this; + + list = events[type]; + if (!list) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; - // pad the end with zeros, but make sure to not forget the extra bytes - if (extraBytes === 1) { - tmp = uint8[len - 1] - output += lookup[tmp >> 2] - output += lookup[(tmp << 4) & 0x3F] - output += '==' - } else if (extraBytes === 2) { - tmp = (uint8[len - 2] << 8) + (uint8[len - 1]) - output += lookup[tmp >> 10] - output += lookup[(tmp >> 4) & 0x3F] - output += lookup[(tmp << 2) & 0x3F] - output += '=' - } + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } - parts.push(output) + if (position < 0) + return this; - return parts.join('') -} + if (position === 0) + list.shift(); + else + spliceOne(list, position); -},{}],33:[function(require,module,exports){ -(function (Buffer){ -const INTEGER_START = 0x69 // 'i' -const STRING_DELIM = 0x3A // ':' -const DICTIONARY_START = 0x64 // 'd' -const LIST_START = 0x6C // 'l' -const END_OF_TYPE = 0x65 // 'e' + if (list.length === 1) + events[type] = list[0]; -/** - * replaces parseInt(buffer.toString('ascii', start, end)). - * For strings with less then ~30 charachters, this is actually a lot faster. - * - * @param {Buffer} data - * @param {Number} start - * @param {Number} end - * @return {Number} calculated number - */ -function getIntFromBuffer (buffer, start, end) { - var sum = 0 - var sign = 1 + if (events.removeListener) + this.emit('removeListener', type, originalListener || listener); + } - for (var i = start; i < end; i++) { - var num = buffer[i] + return this; + }; - if (num < 58 && num >= 48) { - sum = sum * 10 + (num - 48) - continue - } +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (!events) + return this; + + // not listening for removeListener, no need to emit + if (!events.removeListener) { + if (arguments.length === 0) { + this._events = objectCreate(null); + this._eventsCount = 0; + } else if (events[type]) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else + delete events[type]; + } + return this; + } - if (i === start && num === 43) { // + - continue - } + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = objectKeys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = objectCreate(null); + this._eventsCount = 0; + return this; + } - if (i === start && num === 45) { // - - sign = -1 - continue - } + listeners = events[type]; - if (num === 46) { // . - // its a float. break here. - break - } + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } - throw new Error('not a number: buffer[' + i + '] = ' + num) - } + return this; + }; - return sum * sign +function _listeners(target, type, unwrap) { + var events = target._events; + + if (!events) + return []; + + var evlistener = events[type]; + if (!evlistener) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); } -/** - * Decodes bencoded data. - * - * @param {Buffer} data - * @param {Number} start (optional) - * @param {Number} end (optional) - * @param {String} encoding (optional) - * @return {Object|Array|Buffer|String|Number} - */ -function decode (data, start, end, encoding) { - if (data == null || data.length === 0) { - return null - } +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; - if (typeof start !== 'number' && encoding == null) { - encoding = start - start = undefined - } +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; - if (typeof end !== 'number' && encoding == null) { - encoding = end - end = undefined +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); } +}; - decode.position = 0 - decode.encoding = encoding || null +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; - decode.data = !(Buffer.isBuffer(data)) - ? new Buffer(data) - : data.slice(start, end) + if (events) { + var evlistener = events[type]; - decode.bytes = decode.data.length + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener) { + return evlistener.length; + } + } - return decode.next() + return 0; } -decode.bytes = 0 -decode.position = 0 -decode.data = null -decode.encoding = null +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; +}; -decode.next = function () { - switch (decode.data[decode.position]) { - case DICTIONARY_START: - return decode.dictionary() - case LIST_START: - return decode.list() - case INTEGER_START: - return decode.integer() - default: - return decode.buffer() - } +// About 1.5x faster than the two-arg version of Array#splice(). +function spliceOne(list, index) { + for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) + list[i] = list[k]; + list.pop(); } -decode.find = function (chr) { - var i = decode.position - var c = decode.data.length - var d = decode.data +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} - while (i < c) { - if (d[i] === chr) return i - i++ +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; } - - throw new Error( - 'Invalid data: Missing delimiter "' + - String.fromCharCode(chr) + '" [0x' + - chr.toString(16) + ']' - ) + return ret; } -decode.dictionary = function () { - decode.position++ - - var dict = {} - - while (decode.data[decode.position] !== END_OF_TYPE) { - dict[decode.buffer()] = decode.next() - } - - decode.position++ - - return dict +function objectCreatePolyfill(proto) { + var F = function() {}; + F.prototype = proto; + return new F; } - -decode.list = function () { - decode.position++ - - var lst = [] - - while (decode.data[decode.position] !== END_OF_TYPE) { - lst.push(decode.next()) +function objectKeysPolyfill(obj) { + var keys = []; + for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) { + keys.push(k); } - - decode.position++ - - return lst + return k; +} +function functionBindPolyfill(context) { + var fn = this; + return function () { + return fn.apply(context, arguments); + }; } -decode.integer = function () { - var end = decode.find(END_OF_TYPE) - var number = getIntFromBuffer(decode.data, decode.position + 1, end) +},{}],8:[function(require,module,exports){ +var http = require('http') +var url = require('url') - decode.position += end + 1 - decode.position +var https = module.exports - return number +for (var key in http) { + if (http.hasOwnProperty(key)) https[key] = http[key] } -decode.buffer = function () { - var sep = decode.find(STRING_DELIM) - var length = getIntFromBuffer(decode.data, decode.position, sep) - var end = ++sep + length +https.request = function (params, cb) { + params = validateParams(params) + return http.request.call(this, params, cb) +} - decode.position = end +https.get = function (params, cb) { + params = validateParams(params) + return http.get.call(this, params, cb) +} - return decode.encoding - ? decode.data.toString(decode.encoding, sep, end) - : decode.data.slice(sep, end) +function validateParams (params) { + if (typeof params === 'string') { + params = url.parse(params) + } + if (!params.protocol) { + params.protocol = 'https:' + } + if (params.protocol !== 'https:') { + throw new Error('Protocol "' + params.protocol + '" not supported. Expected "https:"') + } + return params } -module.exports = decode +},{"http":30,"url":37}],9:[function(require,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] -}).call(this,require("buffer").Buffer) -},{"buffer":159}],34:[function(require,module,exports){ -var Buffer = require('safe-buffer').Buffer + i += d -/** - * Encodes data in bencode. - * - * @param {Buffer|Array|String|Object|Number|Boolean} data - * @return {Buffer} - */ -function encode (data, buffer, offset) { - var buffers = [] - var result = null + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} - encode._encode(buffers, data) - result = Buffer.concat(buffers) - encode.bytes = result.length + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} - if (Buffer.isBuffer(buffer)) { - result.copy(buffer, offset) - return buffer + 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 result + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) } -encode.bytes = -1 -encode._floatConversionDetected = false +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 -encode._encode = function (buffers, data) { - if (Buffer.isBuffer(data)) { - buffers.push(Buffer.from(data.length + ':')) - buffers.push(data) - return - } + value = Math.abs(value) - if (data == null) { return } + 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 + } - switch (typeof data) { - case 'string': - encode.buffer(buffers, data) - break - case 'number': - encode.number(buffers, data) - break - case 'object': - data.constructor === Array - ? encode.list(buffers, data) - : encode.dict(buffers, data) - break - case 'boolean': - encode.number(buffers, data ? 1 : 0) - break + 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 + } } -} - -var buffE = Buffer.from('e') -var buffD = Buffer.from('d') -var buffL = Buffer.from('l') -encode.buffer = function (buffers, data) { - buffers.push(Buffer.from(Buffer.byteLength(data) + ':' + data)) -} + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} -encode.number = function (buffers, data) { - var maxLo = 0x80000000 - var hi = (data / maxLo) << 0 - var lo = (data % maxLo) << 0 - var val = hi * maxLo + lo + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} - buffers.push(Buffer.from('i' + val + 'e')) + buffer[offset + i - d] |= s * 128 +} - if (val !== data && !encode._floatConversionDetected) { - encode._floatConversionDetected = true - console.warn( - 'WARNING: Possible data corruption detected with value "' + data + '":', - 'Bencoding only defines support for integers, value was converted to "' + val + '"' - ) - console.trace() +},{}],10:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor } } -encode.dict = function (buffers, data) { - buffers.push(buffD) +},{}],11:[function(require,module,exports){ +/*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ - var j = 0 - var k - // fix for issue #13 - sorted dicts - var keys = Object.keys(data).sort() - var kl = keys.length +// The _isBuffer check is for Safari 5-7 support, because it's missing +// Object.prototype.constructor. Remove this eventually +module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) +} - for (; j < kl; j++) { - k = keys[j] - if (data[k] == null) continue - encode.buffer(buffers, k) - encode._encode(buffers, data[k]) - } +function isBuffer (obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} - buffers.push(buffE) +// For Node v0.10 support. Remove this eventually. +function isSlowBuffer (obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) } -encode.list = function (buffers, data) { - var i = 0 - var c = data.length - buffers.push(buffL) +},{}],12:[function(require,module,exports){ +var toString = {}.toString; - for (; i < c; i++) { - if (data[i] == null) continue - encode._encode(buffers, data[i]) - } +module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; +}; - buffers.push(buffE) -} +},{}],13:[function(require,module,exports){ +(function (process){ +// .dirname, .basename, and .extname methods are extracted from Node.js v8.11.1, +// backported and transplited with Babel, with backwards-compat fixes -module.exports = encode +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -},{"safe-buffer":98}],35:[function(require,module,exports){ -var bencode = module.exports +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } -bencode.encode = require('./encode') -bencode.decode = require('./decode') + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } -/** - * Determines the amount of bytes - * needed to encode the given value - * @param {Object|Array|Buffer|String|Number|Boolean} value - * @return {Number} byteCount - */ -bencode.byteLength = bencode.encodingLength = function (value) { - return bencode.encode(value).length + return parts; } -},{"./decode":33,"./encode":34}],36:[function(require,module,exports){ -module.exports = function(haystack, needle, comparator, low, high) { - var mid, cmp; +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; - if(low === undefined) - low = 0; + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); - else { - low = low|0; - if(low < 0 || low >= haystack.length) - throw new RangeError("invalid lower bound"); + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; } - if(high === undefined) - high = haystack.length - 1; + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) - else { - high = high|0; - if(high < low || high >= haystack.length) - throw new RangeError("invalid upper bound"); - } + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); - while(low <= high) { - /* Note that "(low + high) >>> 1" may overflow, and results in a typecast - * to double (which gives the wrong results). */ - mid = low + (high - low >> 1); - cmp = +comparator(haystack[mid], needle, mid, haystack); + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; - /* Too low. */ - if(cmp < 0.0) - low = mid + 1; +// path.normalize(path) +// posix version +exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = substr(path, -1) === '/'; - /* Too high. */ - else if(cmp > 0.0) - high = mid - 1; + // Normalize the path + path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); - /* Key found. */ - else - return mid; + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; } - /* Key not found. */ - return ~low; -} + return (isAbsolute ? '/' : '') + path; +}; -},{}],37:[function(require,module,exports){ -(function (Buffer){ -var Container = typeof Buffer !== "undefined" ? Buffer //in node, use buffers - : typeof Int8Array !== "undefined" ? Int8Array //in newer browsers, use webgl int8arrays - : function(l){ var a = new Array(l); for(var i = 0; i < l; i++) a[i]=0; }; //else, do something similar - -function BitField(data, opts){ - if(!(this instanceof BitField)) { - return new BitField(data, opts); - } - - if(arguments.length === 0){ - data = 0; - } - - this.grow = opts && (isFinite(opts.grow) && getByteSize(opts.grow) || opts.grow) || 0; - - if(typeof data === "number" || data === undefined){ - data = new Container(getByteSize(data)); - if(data.fill && !data._isBuffer) data.fill(0); // clear node buffers of garbage - } - this.buffer = data; -} - -function getByteSize(num){ - var out = num >> 3; - if(num % 8 !== 0) out++; - return out; -} - -BitField.prototype.get = function(i){ - var j = i >> 3; - return (j < this.buffer.length) && - !!(this.buffer[j] & (128 >> (i % 8))); -}; - -BitField.prototype.set = function(i, b){ - var j = i >> 3; - if (b || arguments.length === 1){ - if (this.buffer.length < j + 1) this._grow(Math.max(j + 1, Math.min(2 * this.buffer.length, this.grow))); - // Set - this.buffer[j] |= 128 >> (i % 8); - } else if (j < this.buffer.length) { - /// Clear - this.buffer[j] &= ~(128 >> (i % 8)); - } -}; - -BitField.prototype._grow = function(length) { - if (this.buffer.length < length && length <= this.grow) { - var newBuffer = new Container(length); - if (newBuffer.fill) newBuffer.fill(0); - if (this.buffer.copy) this.buffer.copy(newBuffer, 0); - else { - for(var i = 0; i < this.buffer.length; i++) { - newBuffer[i] = this.buffer[i]; - } - } - this.buffer = newBuffer; - } -}; - -if(typeof module !== "undefined") module.exports = BitField; +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; -}).call(this,require("buffer").Buffer) -},{"buffer":159}],38:[function(require,module,exports){ -module.exports = Wire +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; -var arrayRemove = require('unordered-array-remove') -var bencode = require('bencode') -var BitField = require('bitfield') -var Buffer = require('safe-buffer').Buffer -var debug = require('debug')('bittorrent-protocol') -var extend = require('xtend') -var inherits = require('inherits') -var randombytes = require('randombytes') -var speedometer = require('speedometer') -var stream = require('readable-stream') -var BITFIELD_GROW = 400000 -var KEEP_ALIVE_TIMEOUT = 55000 +// path.relative(from, to) +// posix version +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); -var MESSAGE_PROTOCOL = Buffer.from('\u0013BitTorrent protocol') -var MESSAGE_KEEP_ALIVE = Buffer.from([0x00, 0x00, 0x00, 0x00]) -var MESSAGE_CHOKE = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x00]) -var MESSAGE_UNCHOKE = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x01]) -var MESSAGE_INTERESTED = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x02]) -var MESSAGE_UNINTERESTED = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x03]) + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } -var MESSAGE_RESERVED = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] -var MESSAGE_PORT = [0x00, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00] + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } -function Request (piece, offset, length, callback) { - this.piece = piece - this.offset = offset - this.length = length - this.callback = callback -} - -inherits(Wire, stream.Duplex) - -function Wire () { - if (!(this instanceof Wire)) return new Wire() - stream.Duplex.call(this) - - this._debugId = randombytes(4).toString('hex') - this._debug('new wire') - - this.peerId = null // remote peer id (hex string) - this.peerIdBuffer = null // remote peer id (buffer) - this.type = null // connection type ('webrtc', 'tcpIncoming', 'tcpOutgoing', 'webSeed') - - this.amChoking = true // are we choking the peer? - this.amInterested = false // are we interested in the peer? - - this.peerChoking = true // is the peer choking us? - this.peerInterested = false // is the peer interested in us? - - // The largest torrent that I know of (the Geocities archive) is ~641 GB and has - // ~41,000 pieces. Therefore, cap bitfield to 10x larger (400,000 bits) to support all - // possible torrents but prevent malicious peers from growing bitfield to fill memory. - this.peerPieces = new BitField(0, { grow: BITFIELD_GROW }) - - this.peerExtensions = {} - - this.requests = [] // outgoing - this.peerRequests = [] // incoming + if (start > end) return []; + return arr.slice(start, end - start + 1); + } - this.extendedMapping = {} // number -> string, ex: 1 -> 'ut_metadata' - this.peerExtendedMapping = {} // string -> number, ex: 9 -> 'ut_metadata' + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); - // The extended handshake to send, minus the "m" field, which gets automatically - // filled from `this.extendedMapping` - this.extendedHandshake = {} + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } - this.peerExtendedHandshake = {} // remote peer's extended handshake + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } - this._ext = {} // string -> function, ex 'ut_metadata' -> ut_metadata() - this._nextExt = 1 + outputParts = outputParts.concat(toParts.slice(samePartsLength)); - this.uploaded = 0 - this.downloaded = 0 - this.uploadSpeed = speedometer() - this.downloadSpeed = speedometer() + return outputParts.join('/'); +}; - this._keepAliveInterval = null - this._timeout = null - this._timeoutMs = 0 +exports.sep = '/'; +exports.delimiter = ':'; - this.destroyed = false // was the wire ended by calling `destroy`? - this._finished = false +exports.dirname = function (path) { + if (typeof path !== 'string') path = path + ''; + if (path.length === 0) return '.'; + var code = path.charCodeAt(0); + var hasRoot = code === 47 /*/*/; + var end = -1; + var matchedSlash = true; + for (var i = path.length - 1; i >= 1; --i) { + code = path.charCodeAt(i); + if (code === 47 /*/*/) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } - this._parserSize = 0 // number of needed bytes to parse next message from remote peer - this._parser = null // function to call once `this._parserSize` bytes are available + if (end === -1) return hasRoot ? '/' : '.'; + if (hasRoot && end === 1) { + // return '//'; + // Backwards-compat fix: + return '/'; + } + return path.slice(0, end); +}; - this._buffer = [] // incomplete message data - this._bufferSize = 0 // cached total length of buffers in `this._buffer` +function basename(path) { + if (typeof path !== 'string') path = path + ''; - this.on('finish', this._onFinish) + var start = 0; + var end = -1; + var matchedSlash = true; + var i; - this._parseHandshake() -} + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } -/** - * Set whether to send a "keep-alive" ping (sent every 55s) - * @param {boolean} enable - */ -Wire.prototype.setKeepAlive = function (enable) { - var self = this - self._debug('setKeepAlive %s', enable) - clearInterval(self._keepAliveInterval) - if (enable === false) return - self._keepAliveInterval = setInterval(function () { - self.keepAlive() - }, KEEP_ALIVE_TIMEOUT) + if (end === -1) return ''; + return path.slice(start, end); } -/** - * Set the amount of time to wait before considering a request to be "timed out" - * @param {number} ms - * @param {boolean=} unref (should the timer be unref'd? default: false) - */ -Wire.prototype.setTimeout = function (ms, unref) { - this._debug('setTimeout ms=%d unref=%s', ms, unref) - this._clearTimeout() - this._timeoutMs = ms - this._timeoutUnref = !!unref - this._updateTimeout() -} +// Uses a mixed approach for backwards-compatibility, as ext behavior changed +// in new Node.js versions, so only basename() above is backported here +exports.basename = function (path, ext) { + var f = basename(path); + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; -Wire.prototype.destroy = function () { - if (this.destroyed) return - this.destroyed = true - this._debug('destroy') - this.emit('close') - this.end() -} +exports.extname = function (path) { + if (typeof path !== 'string') path = path + ''; + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + for (var i = path.length - 1; i >= 0; --i) { + var code = path.charCodeAt(i); + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46 /*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { + return ''; + } + return path.slice(startDot, end); +}; -Wire.prototype.end = function () { - this._debug('end') - this._onUninterested() - this._onChoke() - stream.Duplex.prototype.end.apply(this, arguments) +function filter (xs, f) { + if (xs.filter) return xs.filter(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (f(xs[i], i, xs)) res.push(xs[i]); + } + return res; } -/** - * Use the specified protocol extension. - * @param {function} Extension - */ -Wire.prototype.use = function (Extension) { - var name = Extension.prototype.name - if (!name) { - throw new Error('Extension class requires a "name" property on the prototype') - } - this._debug('use extension.name=%s', name) +// String.prototype.substr - negative index don't work in IE8 +var substr = 'ab'.substr(-1) === 'b' + ? function (str, start, len) { return str.substr(start, len) } + : function (str, start, len) { + if (start < 0) start = str.length + start; + return str.substr(start, len); + } +; - var ext = this._nextExt - var handler = new Extension(this) +}).call(this,require('_process')) +},{"_process":15}],14:[function(require,module,exports){ +(function (process){ +'use strict'; - function noop () {} +if (!process.version || + process.version.indexOf('v0.') === 0 || + process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { + module.exports = { nextTick: nextTick }; +} else { + module.exports = process +} - if (typeof handler.onHandshake !== 'function') { - handler.onHandshake = noop - } - if (typeof handler.onExtendedHandshake !== 'function') { - handler.onExtendedHandshake = noop +function nextTick(fn, arg1, arg2, arg3) { + if (typeof fn !== 'function') { + throw new TypeError('"callback" argument must be a function'); } - if (typeof handler.onMessage !== 'function') { - handler.onMessage = noop + var len = arguments.length; + var args, i; + switch (len) { + case 0: + case 1: + return process.nextTick(fn); + case 2: + return process.nextTick(function afterTickOne() { + fn.call(null, arg1); + }); + case 3: + return process.nextTick(function afterTickTwo() { + fn.call(null, arg1, arg2); + }); + case 4: + return process.nextTick(function afterTickThree() { + fn.call(null, arg1, arg2, arg3); + }); + default: + args = new Array(len - 1); + i = 0; + while (i < args.length) { + args[i++] = arguments[i]; + } + return process.nextTick(function afterTick() { + fn.apply(null, args); + }); } - - this.extendedMapping[ext] = name - this._ext[name] = handler - this[name] = handler - - this._nextExt += 1 -} - -// -// OUTGOING MESSAGES -// - -/** - * Message "keep-alive": - */ -Wire.prototype.keepAlive = function () { - this._debug('keep-alive') - this._push(MESSAGE_KEEP_ALIVE) } -/** - * Message: "handshake" - * @param {Buffer|string} infoHash (as Buffer or *hex* string) - * @param {Buffer|string} peerId - * @param {Object} extensions - */ -Wire.prototype.handshake = function (infoHash, peerId, extensions) { - var infoHashBuffer, peerIdBuffer - if (typeof infoHash === 'string') { - infoHashBuffer = Buffer.from(infoHash, 'hex') - } else { - infoHashBuffer = infoHash - infoHash = infoHashBuffer.toString('hex') - } - if (typeof peerId === 'string') { - peerIdBuffer = Buffer.from(peerId, 'hex') - } else { - peerIdBuffer = peerId - peerId = peerIdBuffer.toString('hex') - } - if (infoHashBuffer.length !== 20 || peerIdBuffer.length !== 20) { - throw new Error('infoHash and peerId MUST have length 20') - } +}).call(this,require('_process')) +},{"_process":15}],15:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; - this._debug('handshake i=%s p=%s exts=%o', infoHash, peerId, extensions) +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. - var reserved = Buffer.from(MESSAGE_RESERVED) +var cachedSetTimeout; +var cachedClearTimeout; - // enable extended message - reserved[5] |= 0x10 - - if (extensions && extensions.dht) reserved[7] |= 1 +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } - this._push(Buffer.concat([MESSAGE_PROTOCOL, reserved, infoHashBuffer, peerIdBuffer])) - this._handshakeSent = true - if (this.peerExtensions.extended && !this._extendedHandshakeSent) { - // Peer's handshake indicated support already - // (incoming connection) - this._sendExtendedHandshake() - } } +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } -/* Peer supports BEP-0010, send extended handshake. - * - * This comes after the 'handshake' event to give the user a chance to populate - * `this.extendedHandshake` and `this.extendedMapping` before the extended handshake - * is sent to the remote peer. - */ -Wire.prototype._sendExtendedHandshake = function () { - // Create extended message object from registered extensions - var msg = extend(this.extendedHandshake) - msg.m = {} - for (var ext in this.extendedMapping) { - var name = this.extendedMapping[ext] - msg.m[name] = Number(ext) - } - // Send extended handshake - this.extended(0, bencode.encode(msg)) - this._extendedHandshakeSent = true -} -/** - * Message "choke": - */ -Wire.prototype.choke = function () { - if (this.amChoking) return - this.amChoking = true - this._debug('choke') - while (this.peerRequests.length) { - this.peerRequests.pop() - } - this._push(MESSAGE_CHOKE) } +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; -/** - * Message "unchoke": - */ -Wire.prototype.unchoke = function () { - if (!this.amChoking) return - this.amChoking = false - this._debug('unchoke') - this._push(MESSAGE_UNCHOKE) +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } } -/** - * Message "interested": - */ -Wire.prototype.interested = function () { - if (this.amInterested) return - this.amInterested = true - this._debug('interested') - this._push(MESSAGE_INTERESTED) -} +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; -/** - * Message "uninterested": - */ -Wire.prototype.uninterested = function () { - if (!this.amInterested) return - this.amInterested = false - this._debug('uninterested') - this._push(MESSAGE_UNINTERESTED) + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); } -/** - * Message "have": - * @param {number} index - */ -Wire.prototype.have = function (index) { - this._debug('have %d', index) - this._message(4, [index], null) -} +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; -/** - * Message "bitfield": - * @param {BitField|Buffer} bitfield - */ -Wire.prototype.bitfield = function (bitfield) { - this._debug('bitfield') - if (!Buffer.isBuffer(bitfield)) bitfield = bitfield.buffer - this._message(5, [], bitfield) +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; } +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; -/** - * Message "request": - * @param {number} index - * @param {number} offset - * @param {number} length - * @param {function} cb - */ -Wire.prototype.request = function (index, offset, length, cb) { - if (!cb) cb = function () {} - if (this._finished) return cb(new Error('wire is closed')) - if (this.peerChoking) return cb(new Error('peer is choking')) - - this._debug('request index=%d offset=%d length=%d', index, offset, length) +function noop() {} - this.requests.push(new Request(index, offset, length, cb)) - this._updateTimeout() - this._message(6, [index, offset, length], null) -} +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; -/** - * Message "piece": - * @param {number} index - * @param {number} offset - * @param {Buffer} buffer - */ -Wire.prototype.piece = function (index, offset, buffer) { - this._debug('piece index=%d offset=%d', index, offset) - this.uploaded += buffer.length - this.uploadSpeed(buffer.length) - this.emit('upload', buffer.length) - this._message(7, [index, offset], buffer) -} +process.listeners = function (name) { return [] } -/** - * Message "cancel": - * @param {number} index - * @param {number} offset - * @param {number} length - */ -Wire.prototype.cancel = function (index, offset, length) { - this._debug('cancel index=%d offset=%d length=%d', index, offset, length) - this._callback( - pull(this.requests, index, offset, length), - new Error('request was cancelled'), - null - ) - this._message(8, [index, offset, length], null) -} +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; -/** - * Message: "port" - * @param {Number} port - */ -Wire.prototype.port = function (port) { - this._debug('port %d', port) - var message = Buffer.from(MESSAGE_PORT) - message.writeUInt16BE(port, 5) - this._push(message) -} +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; -/** - * Message: "extended" - * @param {number|string} ext - * @param {Object} obj - */ -Wire.prototype.extended = function (ext, obj) { - this._debug('extended ext=%s', ext) - if (typeof ext === 'string' && this.peerExtendedMapping[ext]) { - ext = this.peerExtendedMapping[ext] - } - if (typeof ext === 'number') { - var extId = Buffer.from([ext]) - var buf = Buffer.isBuffer(obj) ? obj : bencode.encode(obj) +},{}],16:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/punycode v1.4.1 by @mathias */ +;(function(root) { - this._message(20, [], Buffer.concat([extId, buf])) - } else { - throw new Error('Unrecognized extension: ' + ext) - } -} + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && + !exports.nodeType && exports; + var freeModule = typeof module == 'object' && module && + !module.nodeType && module; + var freeGlobal = typeof global == 'object' && global; + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal; + } -/** - * Duplex stream method. Called whenever the remote peer stream wants data. No-op - * since we'll just push data whenever we get it. - */ -Wire.prototype._read = function () {} + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, -/** - * Send a message to the remote peer. - */ -Wire.prototype._message = function (id, numbers, data) { - var dataLength = data ? data.length : 0 - var buffer = Buffer.allocUnsafe(5 + (4 * numbers.length)) + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 - buffer.writeUInt32BE(buffer.length + dataLength - 4, 0) - buffer[4] = id - for (var i = 0; i < numbers.length; i++) { - buffer.writeUInt32BE(numbers[i], 5 + (4 * i)) - } + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' - this._push(buffer) - if (data) this._push(data) -} + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators -Wire.prototype._push = function (data) { - if (this._finished) return - return this.push(data) -} + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, -// -// INCOMING MESSAGES -// + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, -Wire.prototype._onKeepAlive = function () { - this._debug('got keep-alive') - this.emit('keep-alive') -} + /** Temporary variable */ + key; -Wire.prototype._onHandshake = function (infoHashBuffer, peerIdBuffer, extensions) { - var infoHash = infoHashBuffer.toString('hex') - var peerId = peerIdBuffer.toString('hex') + /*--------------------------------------------------------------------------*/ - this._debug('got handshake i=%s p=%s exts=%o', infoHash, peerId, extensions) + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]); + } - this.peerId = peerId - this.peerIdBuffer = peerIdBuffer - this.peerExtensions = extensions + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } - this.emit('handshake', infoHash, peerId, extensions) + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } - var name - for (name in this._ext) { - this._ext[name].onHandshake(infoHash, peerId, extensions) - } + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } - if (extensions.extended && this._handshakeSent && - !this._extendedHandshakeSent) { - // outgoing connection - this._sendExtendedHandshake() - } -} + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } -Wire.prototype._onChoke = function () { - this.peerChoking = true - this._debug('got choke') - this.emit('choke') - while (this.requests.length) { - this._callback(this.requests.pop(), new Error('peer is choking'), null) - } -} - -Wire.prototype._onUnchoke = function () { - this.peerChoking = false - this._debug('got unchoke') - this.emit('unchoke') -} + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } -Wire.prototype._onInterested = function () { - this.peerInterested = true - this._debug('got interested') - this.emit('interested') -} + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } -Wire.prototype._onUninterested = function () { - this.peerInterested = false - this._debug('got uninterested') - this.emit('uninterested') -} + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } -Wire.prototype._onHave = function (index) { - if (this.peerPieces.get(index)) return - this._debug('got have %d', index) + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; - this.peerPieces.set(index, true) - this.emit('have', index) -} + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. -Wire.prototype._onBitField = function (buffer) { - this.peerPieces = new BitField(buffer) - this._debug('got bitfield') - this.emit('bitfield', this.peerPieces) -} + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } -Wire.prototype._onRequest = function (index, offset, length) { - var self = this - if (self.amChoking) return - self._debug('got request index=%d offset=%d length=%d', index, offset, length) + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } - var respond = function (err, buffer) { - if (request !== pull(self.peerRequests, index, offset, length)) return - if (err) return self._debug('error satisfying request index=%d offset=%d length=%d (%s)', index, offset, length, err.message) - self.piece(index, offset, buffer) - } + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. - var request = new Request(index, offset, length, respond) - self.peerRequests.push(request) - self.emit('request', index, offset, length, respond) -} + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { -Wire.prototype._onPiece = function (index, offset, buffer) { - this._debug('got piece index=%d offset=%d', index, offset) - this._callback(pull(this.requests, index, offset, buffer.length), null, buffer) - this.downloaded += buffer.length - this.downloadSpeed(buffer.length) - this.emit('download', buffer.length) - this.emit('piece', index, offset, buffer) -} + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { -Wire.prototype._onCancel = function (index, offset, length) { - this._debug('got cancel index=%d offset=%d length=%d', index, offset, length) - pull(this.peerRequests, index, offset, length) - this.emit('cancel', index, offset, length) -} + if (index >= inputLength) { + error('invalid-input'); + } -Wire.prototype._onPort = function (port) { - this._debug('got port %d', port) - this.emit('port', port) -} + digit = basicToDigit(input.charCodeAt(index++)); -Wire.prototype._onExtended = function (ext, buf) { - if (ext === 0) { - var info - try { - info = bencode.decode(buf) - } catch (err) { - this._debug('ignoring invalid extended handshake: %s', err.message || err) - } + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } - if (!info) return - this.peerExtendedHandshake = info + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - var name - if (typeof info.m === 'object') { - for (name in info.m) { - this.peerExtendedMapping[name] = Number(info.m[name].toString()) - } - } - for (name in this._ext) { - if (this.peerExtendedMapping[name]) { - this._ext[name].onExtendedHandshake(this.peerExtendedHandshake) - } - } - this._debug('got extended handshake') - this.emit('extended', 'handshake', this.peerExtendedHandshake) - } else { - if (this.extendedMapping[ext]) { - ext = this.extendedMapping[ext] // friendly name for extension - if (this._ext[ext]) { - // there is an registered extension handler, so call it - this._ext[ext].onMessage(buf) - } - } - this._debug('got extended message ext=%s', ext) - this.emit('extended', ext, buf) - } -} + if (digit < t) { + break; + } -Wire.prototype._onTimeout = function () { - this._debug('request timed out') - this._callback(this.requests.shift(), new Error('request has timed out'), null) - this.emit('timeout') -} + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } -/** - * Duplex stream method. Called whenever the remote peer has data for us. Data that the - * remote peer sends gets buffered (i.e. not actually processed) until the right number - * of bytes have arrived, determined by the last call to `this._parse(number, callback)`. - * Once enough bytes have arrived to process the message, the callback function - * (i.e. `this._parser`) gets called with the full buffer of data. - * @param {Buffer} data - * @param {string} encoding - * @param {function} cb - */ -Wire.prototype._write = function (data, encoding, cb) { - this._bufferSize += data.length - this._buffer.push(data) + w *= baseMinusT; - while (this._bufferSize >= this._parserSize) { - var buffer = (this._buffer.length === 1) - ? this._buffer[0] - : Buffer.concat(this._buffer) - this._bufferSize -= this._parserSize - this._buffer = this._bufferSize - ? [buffer.slice(this._parserSize)] - : [] - this._parser(buffer.slice(0, this._parserSize)) - } + } - cb(null) // Signal that we're ready for more data -} + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); -Wire.prototype._callback = function (request, err, buffer) { - if (!request) return + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } - this._clearTimeout() + n += floor(i / out); + i %= out; - if (!this.peerChoking && !this._finished) this._updateTimeout() - request.callback(err, buffer) -} + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); -Wire.prototype._clearTimeout = function () { - if (!this._timeout) return + } - clearTimeout(this._timeout) - this._timeout = null -} + return ucs2encode(output); + } -Wire.prototype._updateTimeout = function () { - var self = this - if (!self._timeoutMs || !self.requests.length || self._timeout) return + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; - self._timeout = setTimeout(function () { - self._onTimeout() - }, self._timeoutMs) - if (self._timeoutUnref && self._timeout.unref) self._timeout.unref() -} + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); -/** - * Takes a number of bytes that the local peer is waiting to receive from the remote peer - * in order to parse a complete message, and a callback function to be called once enough - * bytes have arrived. - * @param {number} size - * @param {function} parser - */ -Wire.prototype._parse = function (size, parser) { - this._parserSize = size - this._parser = parser -} + // Cache the length + inputLength = input.length; -/** - * Handle the first 4 bytes of a message, to determine the length of bytes that must be - * waited for in order to have the whole message. - * @param {Buffer} buffer - */ -Wire.prototype._onMessageLength = function (buffer) { - var length = buffer.readUInt32BE(0) - if (length > 0) { - this._parse(length, this._onMessage) - } else { - this._onKeepAlive() - this._parse(4, this._onMessageLength) - } -} + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; -/** - * Handle a message from the remote peer. - * @param {Buffer} buffer - */ -Wire.prototype._onMessage = function (buffer) { - this._parse(4, this._onMessageLength) - switch (buffer[0]) { - case 0: - return this._onChoke() - case 1: - return this._onUnchoke() - case 2: - return this._onInterested() - case 3: - return this._onUninterested() - case 4: - return this._onHave(buffer.readUInt32BE(1)) - case 5: - return this._onBitField(buffer.slice(1)) - case 6: - return this._onRequest(buffer.readUInt32BE(1), - buffer.readUInt32BE(5), buffer.readUInt32BE(9)) - case 7: - return this._onPiece(buffer.readUInt32BE(1), - buffer.readUInt32BE(5), buffer.slice(9)) - case 8: - return this._onCancel(buffer.readUInt32BE(1), - buffer.readUInt32BE(5), buffer.readUInt32BE(9)) - case 9: - return this._onPort(buffer.readUInt16BE(1)) - case 20: - return this._onExtended(buffer.readUInt8(1), buffer.slice(2)) - default: - this._debug('got unknown message') - return this.emit('unknownmessage', buffer) - } -} + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } -Wire.prototype._parseHandshake = function () { - var self = this - self._parse(1, function (buffer) { - var pstrlen = buffer.readUInt8(0) - self._parse(pstrlen + 48, function (handshake) { - var protocol = handshake.slice(0, pstrlen) - if (protocol.toString() !== 'BitTorrent protocol') { - self._debug('Error: wire not speaking BitTorrent protocol (%s)', protocol.toString()) - self.end() - return - } - handshake = handshake.slice(pstrlen) - self._onHandshake(handshake.slice(8, 28), handshake.slice(28, 48), { - dht: !!(handshake[7] & 0x01), // see bep_0005 - extended: !!(handshake[5] & 0x10) // see bep_0010 - }) - self._parse(4, self._onMessageLength) - }) - }) -} + handledCPCount = basicLength = output.length; -Wire.prototype._onFinish = function () { - this._finished = true + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. - this.push(null) // stream cannot be half open, so signal the end of it - while (this.read()) {} // consume and discard the rest of the stream data + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } - clearInterval(this._keepAliveInterval) - this._parse(Number.MAX_VALUE, function () {}) - while (this.peerRequests.length) { - this.peerRequests.pop() - } - while (this.requests.length) { - this._callback(this.requests.pop(), new Error('wire was closed'), null) - } -} + // Main encoding loop: + while (handledCPCount < inputLength) { -Wire.prototype._debug = function () { - var args = [].slice.call(arguments) - args[0] = '[' + this._debugId + '] ' + args[0] - debug.apply(null, args) -} + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } -function pull (requests, piece, offset, length) { - for (var i = 0; i < requests.length; i++) { - var req = requests[i] - if (req.piece === piece && req.offset === offset && req.length === length) { - arrayRemove(requests, i) - return req - } - } - return null -} + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } -},{"bencode":35,"bitfield":37,"debug":27,"inherits":58,"randombytes":82,"readable-stream":92,"safe-buffer":98,"speedometer":104,"unordered-array-remove":117,"xtend":131}],39:[function(require,module,exports){ -(function (process){ -module.exports = Client + delta += (m - n) * handledCPCountPlusOne; + n = m; -var Buffer = require('safe-buffer').Buffer -var debug = require('debug')('bittorrent-tracker:client') -var EventEmitter = require('events').EventEmitter -var extend = require('xtend') -var inherits = require('inherits') -var once = require('once') -var parallel = require('run-parallel') -var Peer = require('simple-peer') -var uniq = require('uniq') -var url = require('url') + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; -var common = require('./lib/common') -var HTTPTracker = require('./lib/client/http-tracker') // empty object in browser -var UDPTracker = require('./lib/client/udp-tracker') // empty object in browser -var WebSocketTracker = require('./lib/client/websocket-tracker') + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } -inherits(Client, EventEmitter) + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } -/** - * BitTorrent tracker client. - * - * Find torrent peers, to help a torrent client participate in a torrent swarm. - * - * @param {Object} opts options object - * @param {string|Buffer} opts.infoHash torrent info hash - * @param {string|Buffer} opts.peerId peer id - * @param {string|Array.} opts.announce announce - * @param {number} opts.port torrent client listening port - * @param {function} opts.getAnnounceOpts callback to provide data to tracker - * @param {number} opts.rtcConfig RTCPeerConnection configuration object - * @param {number} opts.userAgent User-Agent header for http requests - * @param {number} opts.wrtc custom webrtc impl (useful in node.js) - */ -function Client (opts) { - var self = this - if (!(self instanceof Client)) return new Client(opts) - EventEmitter.call(self) - if (!opts) opts = {} + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } - if (!opts.peerId) throw new Error('Option `peerId` is required') - if (!opts.infoHash) throw new Error('Option `infoHash` is required') - if (!opts.announce) throw new Error('Option `announce` is required') - if (!process.browser && !opts.port) throw new Error('Option `port` is required') + ++delta; + ++n; - self.peerId = typeof opts.peerId === 'string' - ? opts.peerId - : opts.peerId.toString('hex') - self._peerIdBuffer = Buffer.from(self.peerId, 'hex') - self._peerIdBinary = self._peerIdBuffer.toString('binary') + } + return output.join(''); + } - self.infoHash = typeof opts.infoHash === 'string' - ? opts.infoHash - : opts.infoHash.toString('hex') - self._infoHashBuffer = Buffer.from(self.infoHash, 'hex') - self._infoHashBinary = self._infoHashBuffer.toString('binary') + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } - debug('new client %s', self.infoHash) + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } - self.destroyed = false + /*--------------------------------------------------------------------------*/ - self._port = opts.port - self._getAnnounceOpts = opts.getAnnounceOpts - self._rtcConfig = opts.rtcConfig - self._userAgent = opts.userAgent + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.4.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; - // Support lazy 'wrtc' module initialization - // See: https://github.com/webtorrent/webtorrent-hybrid/issues/46 - self._wrtc = typeof opts.wrtc === 'function' ? opts.wrtc() : opts.wrtc + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { + // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { + // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { + // in Rhino or a web browser + root.punycode = punycode; + } - var announce = typeof opts.announce === 'string' - ? [ opts.announce ] - : opts.announce == null ? [] : opts.announce +}(this)); - // Remove trailing slash from trackers to catch duplicates - announce = announce.map(function (announceUrl) { - announceUrl = announceUrl.toString() - if (announceUrl[announceUrl.length - 1] === '/') { - announceUrl = announceUrl.substring(0, announceUrl.length - 1) - } - return announceUrl - }) - announce = uniq(announce) +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],17:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. - var webrtcSupport = self._wrtc !== false && (!!self._wrtc || Peer.WEBRTC_SUPPORT) +'use strict'; - self._trackers = announce - .map(function (announceUrl) { - var protocol = url.parse(announceUrl).protocol - if ((protocol === 'http:' || protocol === 'https:') && - typeof HTTPTracker === 'function') { - return new HTTPTracker(self, announceUrl) - } else if (protocol === 'udp:' && typeof UDPTracker === 'function') { - return new UDPTracker(self, announceUrl) - } else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) { - // Skip ws:// trackers on https:// sites because they throw SecurityError - if (protocol === 'ws:' && typeof window !== 'undefined' && - window.location.protocol === 'https:') { - nextTickWarn(new Error('Unsupported tracker protocol: ' + announceUrl)) - return null - } - return new WebSocketTracker(self, announceUrl) - } else { - nextTickWarn(new Error('Unsupported tracker protocol: ' + announceUrl)) - return null - } - }) - .filter(Boolean) +// If obj.hasOwnProperty has been overridden, then calling +// obj.hasOwnProperty(prop) will break. +// See: https://github.com/joyent/node/issues/1707 +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} - function nextTickWarn (err) { - process.nextTick(function () { - self.emit('warning', err) - }) +module.exports = function(qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; + + if (typeof qs !== 'string' || qs.length === 0) { + return obj; } -} -/** - * Simple convenience function to scrape a tracker for an info hash without needing to - * create a Client, pass it a parsed torrent, etc. Support scraping a tracker for multiple - * torrents at the same time. - * @params {Object} opts - * @param {string|Array.} opts.infoHash - * @param {string} opts.announce - * @param {function} cb - */ -Client.scrape = function (opts, cb) { - cb = once(cb) + var regexp = /\+/g; + qs = qs.split(sep); - if (!opts.infoHash) throw new Error('Option `infoHash` is required') - if (!opts.announce) throw new Error('Option `announce` is required') + var maxKeys = 1000; + if (options && typeof options.maxKeys === 'number') { + maxKeys = options.maxKeys; + } - var clientOpts = extend(opts, { - infoHash: Array.isArray(opts.infoHash) ? opts.infoHash[0] : opts.infoHash, - peerId: Buffer.from('01234567890123456789'), // dummy value - port: 6881 // dummy value - }) + var len = qs.length; + // maxKeys <= 0 means that we should not limit keys count + if (maxKeys > 0 && len > maxKeys) { + len = maxKeys; + } - var client = new Client(clientOpts) - client.once('error', cb) - client.once('warning', cb) + for (var i = 0; i < len; ++i) { + var x = qs[i].replace(regexp, '%20'), + idx = x.indexOf(eq), + kstr, vstr, k, v; - var len = Array.isArray(opts.infoHash) ? opts.infoHash.length : 1 - var results = {} - client.on('scrape', function (data) { - len -= 1 - results[data.infoHash] = data - if (len === 0) { - client.destroy() - var keys = Object.keys(results) - if (keys.length === 1) { - cb(null, results[keys[0]]) - } else { - cb(null, results) - } + if (idx >= 0) { + kstr = x.substr(0, idx); + vstr = x.substr(idx + 1); + } else { + kstr = x; + vstr = ''; } - }) - opts.infoHash = Array.isArray(opts.infoHash) - ? opts.infoHash.map(function (infoHash) { - return Buffer.from(infoHash, 'hex') - }) - : Buffer.from(opts.infoHash, 'hex') - client.scrape({ infoHash: opts.infoHash }) - return client -} - -/** - * Send a `start` announce to the trackers. - * @param {Object} opts - * @param {number=} opts.uploaded - * @param {number=} opts.downloaded - * @param {number=} opts.left (if not set, calculated automatically) - */ -Client.prototype.start = function (opts) { - var self = this - debug('send `start`') - opts = self._defaultAnnounceOpts(opts) - opts.event = 'started' - self._announce(opts) + k = decodeURIComponent(kstr); + v = decodeURIComponent(vstr); - // start announcing on intervals - self._trackers.forEach(function (tracker) { - tracker.setInterval() - }) -} + if (!hasOwnProperty(obj, k)) { + obj[k] = v; + } else if (isArray(obj[k])) { + obj[k].push(v); + } else { + obj[k] = [obj[k], v]; + } + } -/** - * Send a `stop` announce to the trackers. - * @param {Object} opts - * @param {number=} opts.uploaded - * @param {number=} opts.downloaded - * @param {number=} opts.numwant - * @param {number=} opts.left (if not set, calculated automatically) - */ -Client.prototype.stop = function (opts) { - var self = this - debug('send `stop`') - opts = self._defaultAnnounceOpts(opts) - opts.event = 'stopped' - self._announce(opts) -} + return obj; +}; -/** - * Send a `complete` announce to the trackers. - * @param {Object} opts - * @param {number=} opts.uploaded - * @param {number=} opts.downloaded - * @param {number=} opts.numwant - * @param {number=} opts.left (if not set, calculated automatically) - */ -Client.prototype.complete = function (opts) { - var self = this - debug('send `complete`') - if (!opts) opts = {} - opts = self._defaultAnnounceOpts(opts) - opts.event = 'completed' - self._announce(opts) -} +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; -/** - * Send a `update` announce to the trackers. - * @param {Object} opts - * @param {number=} opts.uploaded - * @param {number=} opts.downloaded - * @param {number=} opts.numwant - * @param {number=} opts.left (if not set, calculated automatically) - */ -Client.prototype.update = function (opts) { - var self = this - debug('send `update`') - opts = self._defaultAnnounceOpts(opts) - if (opts.event) delete opts.event - self._announce(opts) -} +},{}],18:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -Client.prototype._announce = function (opts) { - var self = this - self._trackers.forEach(function (tracker) { - // tracker should not modify `opts` object, it's passed to all trackers - tracker.announce(opts) - }) -} +'use strict'; -/** - * Send a scrape request to the trackers. - * @param {Object} opts - */ -Client.prototype.scrape = function (opts) { - var self = this - debug('send `scrape`') - if (!opts) opts = {} - self._trackers.forEach(function (tracker) { - // tracker should not modify `opts` object, it's passed to all trackers - tracker.scrape(opts) - }) -} +var stringifyPrimitive = function(v) { + switch (typeof v) { + case 'string': + return v; -Client.prototype.setInterval = function (intervalMs) { - var self = this - debug('setInterval %d', intervalMs) - self._trackers.forEach(function (tracker) { - tracker.setInterval(intervalMs) - }) -} + case 'boolean': + return v ? 'true' : 'false'; -Client.prototype.destroy = function (cb) { - var self = this - if (self.destroyed) return - self.destroyed = true - debug('destroy') + case 'number': + return isFinite(v) ? v : ''; - var tasks = self._trackers.map(function (tracker) { - return function (cb) { - tracker.destroy(cb) - } - }) + default: + return ''; + } +}; - parallel(tasks, cb) +module.exports = function(obj, sep, eq, name) { + sep = sep || '&'; + eq = eq || '='; + if (obj === null) { + obj = undefined; + } - self._trackers = [] - self._getAnnounceOpts = null -} + if (typeof obj === 'object') { + return map(objectKeys(obj), function(k) { + var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; + if (isArray(obj[k])) { + return map(obj[k], function(v) { + return ks + encodeURIComponent(stringifyPrimitive(v)); + }).join(sep); + } else { + return ks + encodeURIComponent(stringifyPrimitive(obj[k])); + } + }).join(sep); -Client.prototype._defaultAnnounceOpts = function (opts) { - var self = this - if (!opts) opts = {} + } - if (opts.numwant == null) opts.numwant = common.DEFAULT_ANNOUNCE_PEERS + if (!name) return ''; + return encodeURIComponent(stringifyPrimitive(name)) + eq + + encodeURIComponent(stringifyPrimitive(obj)); +}; - if (opts.uploaded == null) opts.uploaded = 0 - if (opts.downloaded == null) opts.downloaded = 0 +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; - if (self._getAnnounceOpts) opts = extend(opts, self._getAnnounceOpts()) - return opts +function map (xs, f) { + if (xs.map) return xs.map(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + res.push(f(xs[i], i)); + } + return res; } -}).call(this,require('_process')) -},{"./lib/client/http-tracker":158,"./lib/client/udp-tracker":158,"./lib/client/websocket-tracker":41,"./lib/common":42,"_process":170,"debug":27,"events":162,"inherits":58,"once":75,"run-parallel":96,"safe-buffer":98,"simple-peer":101,"uniq":116,"url":191,"xtend":131}],40:[function(require,module,exports){ -module.exports = Tracker +var objectKeys = Object.keys || function (obj) { + var res = []; + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key); + } + return res; +}; -var EventEmitter = require('events').EventEmitter -var inherits = require('inherits') +},{}],19:[function(require,module,exports){ +'use strict'; -inherits(Tracker, EventEmitter) +exports.decode = exports.parse = require('./decode'); +exports.encode = exports.stringify = require('./encode'); -function Tracker (client, announceUrl) { - var self = this - EventEmitter.call(self) - self.client = client - self.announceUrl = announceUrl +},{"./decode":17,"./encode":18}],20:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. - self.interval = null - self.destroyed = false -} +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. -Tracker.prototype.setInterval = function (intervalMs) { - var self = this - if (intervalMs == null) intervalMs = self.DEFAULT_ANNOUNCE_INTERVAL +'use strict'; - clearInterval(self.interval) +/**/ - if (intervalMs) { - self.interval = setInterval(function () { - self.announce(self.client._defaultAnnounceOpts()) - }, intervalMs) - if (self.interval.unref) self.interval.unref() - } -} +var pna = require('process-nextick-args'); +/**/ -},{"events":162,"inherits":58}],41:[function(require,module,exports){ -module.exports = WebSocketTracker +/**/ +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ -var debug = require('debug')('bittorrent-tracker:websocket-tracker') -var extend = require('xtend') -var inherits = require('inherits') -var Peer = require('simple-peer') -var randombytes = require('randombytes') -var Socket = require('simple-websocket') +module.exports = Duplex; -var common = require('../common') -var Tracker = require('./tracker') +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ -// Use a socket pool, so tracker clients share WebSocket objects for the same server. -// In practice, WebSockets are pretty slow to establish, so this gives a nice performance -// boost, and saves browser resources. -var socketPool = {} +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); -var RECONNECT_MINIMUM = 15 * 1000 -var RECONNECT_MAXIMUM = 30 * 60 * 1000 -var RECONNECT_VARIANCE = 30 * 1000 -var OFFER_TIMEOUT = 50 * 1000 +util.inherits(Duplex, Readable); -inherits(WebSocketTracker, Tracker) +{ + // avoid scope creep, the keys array can then be collected + var keys = objectKeys(Writable.prototype); + for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; + } +} -function WebSocketTracker (client, announceUrl, opts) { - var self = this - Tracker.call(self, client, announceUrl) - debug('new websocket tracker %s', announceUrl) +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); - self.peers = {} // peers (offer id -> peer) - self.socket = null + Readable.call(this, options); + Writable.call(this, options); - self.reconnecting = false - self.retries = 0 - self.reconnectTimer = null + if (options && options.readable === false) this.readable = false; - // Simple boolean flag to track whether the socket has received data from - // the websocket server since the last time socket.send() was called. - self.expectingResponse = false + if (options && options.writable === false) this.writable = false; - self._openSocket() -} + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; -WebSocketTracker.prototype.DEFAULT_ANNOUNCE_INTERVAL = 30 * 1000 // 30 seconds + this.once('end', onend); +} -WebSocketTracker.prototype.announce = function (opts) { - var self = this - if (self.destroyed || self.reconnecting) return - if (!self.socket.connected) { - self.socket.once('connect', function () { - self.announce(opts) - }) - return +Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; } +}); - var params = extend(opts, { - action: 'announce', - info_hash: self.client._infoHashBinary, - peer_id: self.client._peerIdBinary - }) - if (self._trackerId) params.trackerid = self._trackerId +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) return; - if (opts.event === 'stopped' || opts.event === 'completed') { - // Don't include offers with 'stopped' or 'completed' event - self._send(params) - } else { - // Limit the number of offers that are generated, since it can be slow - var numwant = Math.min(opts.numwant, 10) + // no more data can be written. + // But allow more writes to happen in this tick. + pna.nextTick(onEndNT, this); +} - self._generateOffers(numwant, function (offers) { - params.numwant = numwant - params.offers = offers - self._send(params) - }) - } +function onEndNT(self) { + self.end(); } -WebSocketTracker.prototype.scrape = function (opts) { - var self = this - if (self.destroyed || self.reconnecting) return - if (!self.socket.connected) { - self.socket.once('connect', function () { - self.scrape(opts) - }) - return - } +Object.defineProperty(Duplex.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } - var infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0) - ? opts.infoHash.map(function (infoHash) { - return infoHash.toString('binary') - }) - : (opts.infoHash && opts.infoHash.toString('binary')) || self.client._infoHashBinary - var params = { - action: 'scrape', - info_hash: infoHashes + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + this._writableState.destroyed = value; } +}); - self._send(params) -} +Duplex.prototype._destroy = function (err, cb) { + this.push(null); + this.end(); -WebSocketTracker.prototype.destroy = function (cb) { - var self = this - if (!cb) cb = noop - if (self.destroyed) return cb(null) + pna.nextTick(cb, err); +}; +},{"./_stream_readable":22,"./_stream_writable":24,"core-util-is":6,"inherits":10,"process-nextick-args":14}],21:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. - self.destroyed = true +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. - clearInterval(self.interval) - clearTimeout(self.reconnectTimer) +'use strict'; - // Destroy peers - for (var peerId in self.peers) { - var peer = self.peers[peerId] - clearTimeout(peer.trackerTimeout) - peer.destroy() - } - self.peers = null +module.exports = PassThrough; - if (self.socket) { - self.socket.removeListener('connect', self._onSocketConnectBound) - self.socket.removeListener('data', self._onSocketDataBound) - self.socket.removeListener('close', self._onSocketCloseBound) - self.socket.removeListener('error', self._onSocketErrorBound) - self.socket = null - } +var Transform = require('./_stream_transform'); - self._onSocketConnectBound = null - self._onSocketErrorBound = null - self._onSocketDataBound = null - self._onSocketCloseBound = null +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ - if (socketPool[self.announceUrl]) { - socketPool[self.announceUrl].consumers -= 1 - } +util.inherits(PassThrough, Transform); - // Other instances are using the socket, so there's nothing left to do here - if (socketPool[self.announceUrl].consumers > 0) return cb() +function PassThrough(options) { + if (!(this instanceof PassThrough)) return new PassThrough(options); - var socket = socketPool[self.announceUrl] - delete socketPool[self.announceUrl] - socket.on('error', noop) // ignore all future errors - socket.once('close', cb) + Transform.call(this, options); +} - // If there is no data response expected, destroy immediately. - if (!self.expectingResponse) return destroyCleanup() +PassThrough.prototype._transform = function (chunk, encoding, cb) { + cb(null, chunk); +}; +},{"./_stream_transform":23,"core-util-is":6,"inherits":10}],22:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. - // Otherwise, wait a short time for potential responses to come in from the - // server, then force close the socket. - var timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT) +'use strict'; - // But, if a response comes from the server before the timeout fires, do cleanup - // right away. - socket.once('data', destroyCleanup) +/**/ - function destroyCleanup () { - if (timeout) { - clearTimeout(timeout) - timeout = null - } - socket.removeListener('data', destroyCleanup) - socket.destroy() - socket = null - } -} +var pna = require('process-nextick-args'); +/**/ -WebSocketTracker.prototype._openSocket = function () { - var self = this - self.destroyed = false +module.exports = Readable; - if (!self.peers) self.peers = {} +/**/ +var isArray = require('isarray'); +/**/ - self._onSocketConnectBound = function () { - self._onSocketConnect() - } - self._onSocketErrorBound = function (err) { - self._onSocketError(err) - } - self._onSocketDataBound = function (data) { - self._onSocketData(data) - } - self._onSocketCloseBound = function () { - self._onSocketClose() - } +/**/ +var Duplex; +/**/ - self.socket = socketPool[self.announceUrl] - if (self.socket) { - socketPool[self.announceUrl].consumers += 1 - } else { - self.socket = socketPool[self.announceUrl] = new Socket(self.announceUrl) - self.socket.consumers = 1 - self.socket.once('connect', self._onSocketConnectBound) - } +Readable.ReadableState = ReadableState; - self.socket.on('data', self._onSocketDataBound) - self.socket.once('close', self._onSocketCloseBound) - self.socket.once('error', self._onSocketErrorBound) +/**/ +var EE = require('events').EventEmitter; + +var EElistenerCount = function (emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; } -WebSocketTracker.prototype._onSocketConnect = function () { - var self = this - if (self.destroyed) return +/**/ - if (self.reconnecting) { - self.reconnecting = false - self.retries = 0 - self.announce(self.client._defaultAnnounceOpts()) - } +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var debugUtil = require('util'); +var debug = void 0; +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function () {}; } +/**/ -WebSocketTracker.prototype._onSocketData = function (data) { - var self = this - if (self.destroyed) return +var BufferList = require('./internal/streams/BufferList'); +var destroyImpl = require('./internal/streams/destroy'); +var StringDecoder; - self.expectingResponse = false +util.inherits(Readable, Stream); - try { - data = JSON.parse(data) - } catch (err) { - self.client.emit('warning', new Error('Invalid tracker response')) - return - } +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; - if (data.action === 'announce') { - self._onAnnounceResponse(data) - } else if (data.action === 'scrape') { - self._onScrapeResponse(data) - } else { - self._onSocketError(new Error('invalid action in WS response: ' + data.action)) - } +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn); + + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; } -WebSocketTracker.prototype._onAnnounceResponse = function (data) { - var self = this +function ReadableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); - if (data.info_hash !== self.client._infoHashBinary) { - debug( - 'ignoring websocket data from %s for %s (looking for %s: reused socket)', - self.announceUrl, common.binaryToHex(data.info_hash), self.client.infoHash - ) - return - } + options = options || {}; - if (data.peer_id && data.peer_id === self.client._peerIdBinary) { - // ignore offers/answers from this client - return - } + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; - debug( - 'received %s from %s for %s', - JSON.stringify(data), self.announceUrl, self.client.infoHash - ) + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; - var failure = data['failure reason'] - if (failure) return self.client.emit('warning', new Error(failure)) + if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode; - var warning = data['warning message'] - if (warning) self.client.emit('warning', new Error(warning)) + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var readableHwm = options.readableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; - var interval = data.interval || data['min interval'] - if (interval) self.setInterval(interval * 1000) + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (readableHwm || readableHwm === 0)) this.highWaterMark = readableHwm;else this.highWaterMark = defaultHwm; - var trackerId = data['tracker id'] - if (trackerId) { - // If absent, do not discard previous trackerId value - self._trackerId = trackerId - } + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); - if (data.complete != null) { - var response = Object.assign({}, data, { - announce: self.announceUrl, - infoHash: common.binaryToHex(data.info_hash) - }) - self.client.emit('update', response) - } + // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; - var peer - if (data.offer && data.peer_id) { - debug('creating peer (from remote offer)') - peer = self._createPeer() - peer.id = common.binaryToHex(data.peer_id) - peer.once('signal', function (answer) { - var params = { - action: 'announce', - info_hash: self.client._infoHashBinary, - peer_id: self.client._peerIdBinary, - to_peer_id: data.peer_id, - answer: answer, - offer_id: data.offer_id - } - if (self._trackerId) params.trackerid = self._trackerId - self._send(params) - }) - peer.signal(data.offer) - self.client.emit('peer', peer) - } + // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + this.sync = true; - if (data.answer && data.peer_id) { - var offerId = common.binaryToHex(data.offer_id) - peer = self.peers[offerId] - if (peer) { - peer.id = common.binaryToHex(data.peer_id) - peer.signal(data.answer) - self.client.emit('peer', peer) + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; - clearTimeout(peer.trackerTimeout) - peer.trackerTimeout = null - delete self.peers[offerId] - } else { - debug('got unexpected answer: ' + JSON.stringify(data.answer)) - } - } -} + // has it been destroyed + this.destroyed = false; -WebSocketTracker.prototype._onScrapeResponse = function (data) { - var self = this - data = data.files || {} + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; - var keys = Object.keys(data) - if (keys.length === 0) { - self.client.emit('warning', new Error('invalid scrape response')) - return - } + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; - keys.forEach(function (infoHash) { - // TODO: optionally handle data.flags.min_request_interval - // (separate from announce interval) - var response = Object.assign(data[infoHash], { - announce: self.announceUrl, - infoHash: common.binaryToHex(infoHash) - }) - self.client.emit('scrape', response) - }) -} + // if true, a maybeReadMore has been scheduled + this.readingMore = false; -WebSocketTracker.prototype._onSocketClose = function () { - var self = this - if (self.destroyed) return - self.destroy() - self._startReconnectTimer() + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } } -WebSocketTracker.prototype._onSocketError = function (err) { - var self = this - if (self.destroyed) return - self.destroy() - // errors will often happen if a tracker is offline, so don't treat it as fatal - self.client.emit('warning', err) - self._startReconnectTimer() -} - -WebSocketTracker.prototype._startReconnectTimer = function () { - var self = this - var ms = Math.floor(Math.random() * RECONNECT_VARIANCE) + Math.min(Math.pow(2, self.retries) * RECONNECT_MINIMUM, RECONNECT_MAXIMUM) +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); - self.reconnecting = true - clearTimeout(self.reconnectTimer) - self.reconnectTimer = setTimeout(function () { - self.retries++ - self._openSocket() - }, ms) - if (self.reconnectTimer.unref) self.reconnectTimer.unref() + if (!(this instanceof Readable)) return new Readable(options); - debug('reconnecting socket in %s ms', ms) -} + this._readableState = new ReadableState(options, this); -WebSocketTracker.prototype._send = function (params) { - var self = this - if (self.destroyed) return - self.expectingResponse = true - var message = JSON.stringify(params) - debug('send %s', message) - self.socket.send(message) -} + // legacy + this.readable = true; -WebSocketTracker.prototype._generateOffers = function (numwant, cb) { - var self = this - var offers = [] - debug('generating %s offers', numwant) + if (options) { + if (typeof options.read === 'function') this._read = options.read; - for (var i = 0; i < numwant; ++i) { - generateOffer() + if (typeof options.destroy === 'function') this._destroy = options.destroy; } - checkDone() - function generateOffer () { - var offerId = randombytes(20).toString('hex') - debug('creating peer (from _generateOffers)') - var peer = self.peers[offerId] = self._createPeer({ initiator: true }) - peer.once('signal', function (offer) { - offers.push({ - offer: offer, - offer_id: common.hexToBinary(offerId) - }) - checkDone() - }) - peer.trackerTimeout = setTimeout(function () { - debug('tracker timeout: destroying peer') - peer.trackerTimeout = null - delete self.peers[offerId] - peer.destroy() - }, OFFER_TIMEOUT) - if (peer.trackerTimeout.unref) peer.trackerTimeout.unref() - } + Stream.call(this); +} - function checkDone () { - if (offers.length === numwant) { - debug('generated %s offers', numwant) - cb(offers) +Object.defineProperty(Readable.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined) { + return false; + } + return this._readableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; } -} +}); -WebSocketTracker.prototype._createPeer = function (opts) { - var self = this +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; +Readable.prototype._destroy = function (err, cb) { + this.push(null); + cb(err); +}; - opts = Object.assign({ - trickle: false, - config: self.client._rtcConfig, - wrtc: self.client._wrtc - }, opts) +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; - var peer = new Peer(opts) + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } - peer.once('error', onError) - peer.once('connect', onConnect) + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; - return peer +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; - // Handle peer 'error' events that are fired *before* the peer is emitted in - // a 'peer' event. - function onError (err) { - self.client.emit('warning', new Error('Connection error: ' + err.message)) - peer.destroy() - } +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + var state = stream._readableState; + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } - // Once the peer is emitted in a 'peer' event, then it's the consumer's - // responsibility to listen for errors, so the listeners are removed here. - function onConnect () { - peer.removeListener('error', onError) - peer.removeListener('connect', onConnect) + if (addToFront) { + if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); + } else if (state.ended) { + stream.emit('error', new Error('stream.push() after EOF')); + } else { + state.reading = false; + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + } } -} - -function noop () {} - -},{"../common":42,"./tracker":40,"debug":27,"inherits":58,"randombytes":82,"simple-peer":101,"simple-websocket":103,"xtend":131}],42:[function(require,module,exports){ -/** - * Functions/constants needed by both the client and server. - */ -var Buffer = require('safe-buffer').Buffer -var extend = require('xtend/mutable') + return needMoreData(state); +} -exports.DEFAULT_ANNOUNCE_PEERS = 50 -exports.MAX_ANNOUNCE_PEERS = 82 +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); -exports.binaryToHex = function (str) { - if (typeof str !== 'string') { - str = String(str) + if (state.needReadable) emitReadable(stream); } - return Buffer.from(str, 'binary').toString('hex') + maybeReadMore(stream, state); } -exports.hexToBinary = function (str) { - if (typeof str !== 'string') { - str = String(str) +function chunkInvalid(state, chunk) { + var er; + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); } - return Buffer.from(str, 'hex').toString('binary') + return er; } -var config = require('./common-node') -extend(exports, config) - -},{"./common-node":158,"safe-buffer":98,"xtend/mutable":132}],43:[function(require,module,exports){ -(function (Buffer){ -/* global Blob, FileReader */ +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); +} -module.exports = function blobToBuffer (blob, cb) { - if (typeof Blob === 'undefined' || !(blob instanceof Blob)) { - throw new Error('first argument must be a Blob') - } - if (typeof cb !== 'function') { - throw new Error('second argument must be a function') - } +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; - var reader = new FileReader() +// backwards compatibility. +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; - function onLoadEnd (e) { - reader.removeEventListener('loadend', onLoadEnd, false) - if (e.error) cb(e.error) - else cb(null, new Buffer(reader.result)) +// Don't raise the hwm > 8MB +var MAX_HWM = 0x800000; +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; } + return n; +} - reader.addEventListener('loadend', onLoadEnd, false) - reader.readAsArrayBuffer(blob) +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } + // If we're asking for more than the current hwm, then raise the hwm. + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; + // Don't have enough + if (!state.ended) { + state.needReadable = true; + return 0; + } + return state.length; } -}).call(this,require("buffer").Buffer) -},{"buffer":159}],44:[function(require,module,exports){ -(function (Buffer){ -var inherits = require('inherits'); -var Transform = require('readable-stream').Transform; -var defined = require('defined'); - -module.exports = Block; -inherits(Block, Transform); +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; -function Block (size, opts) { - if (!(this instanceof Block)) return new Block(size, opts); - Transform.call(this); - if (!opts) opts = {}; - if (typeof size === 'object') { - opts = size; - size = opts.size; - } - this.size = size || 512; - - if (opts.nopad) this._zeroPadding = false; - else this._zeroPadding = defined(opts.zeroPadding, true); - - this._buffered = []; - this._bufferedBytes = 0; -} + if (n !== 0) state.emittedReadable = false; -Block.prototype._transform = function (buf, enc, next) { - this._bufferedBytes += buf.length; - this._buffered.push(buf); - - while (this._bufferedBytes >= this.size) { - var b = Buffer.concat(this._buffered); - this._bufferedBytes -= this.size; - this.push(b.slice(0, this.size)); - this._buffered = [ b.slice(this.size, b.length) ]; - } - next(); -}; + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } -Block.prototype._flush = function () { - if (this._bufferedBytes && this._zeroPadding) { - var zeroes = new Buffer(this.size - this._bufferedBytes); - zeroes.fill(0); - this._buffered.push(zeroes); - this.push(Buffer.concat(this._buffered)); - this._buffered = null; - } - else if (this._bufferedBytes) { - this.push(Buffer.concat(this._buffered)); - this._buffered = null; - } - this.push(null); -}; + n = howMuchToRead(n, state); -}).call(this,require("buffer").Buffer) -},{"buffer":159,"defined":51,"inherits":58,"readable-stream":92}],45:[function(require,module,exports){ -/* - * JavaScript MD5 - * https://github.com/blueimp/JavaScript-MD5 - * - * Copyright 2011, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * https://opensource.org/licenses/MIT - * - * Based on - * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message - * Digest Algorithm, as defined in RFC 1321. - * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for more info. - */ + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } -/* global define */ + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. -;(function ($) { - 'use strict' + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); - /* - * Add integers, wrapping at 2^32. This uses 16-bit operations internally - * to work around bugs in some JS interpreters. - */ - function safeAdd (x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF) - var msw = (x >> 16) + (y >> 16) + (lsw >> 16) - return (msw << 16) | (lsw & 0xFFFF) + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); } - /* - * Bitwise rotate a 32-bit number to the left. - */ - function bitRotateLeft (num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)) + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (!state.reading) n = howMuchToRead(nOrig, state); } - /* - * These functions implement the four basic operations the algorithm uses. - */ - function md5cmn (q, a, b, x, s, t) { - return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b) - } - function md5ff (a, b, c, d, x, s, t) { - return md5cmn((b & c) | ((~b) & d), a, b, x, s, t) - } - function md5gg (a, b, c, d, x, s, t) { - return md5cmn((b & d) | (c & (~d)), a, b, x, s, t) - } - function md5hh (a, b, c, d, x, s, t) { - return md5cmn(b ^ c ^ d, a, b, x, s, t) + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = true; + n = 0; + } else { + state.length -= n; } - function md5ii (a, b, c, d, x, s, t) { - return md5cmn(c ^ (b | (~d)), a, b, x, s, t) + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended) endReadable(this); } - /* - * Calculate the MD5 of an array of little-endian words, and a bit length. - */ - function binlMD5 (x, len) { - /* append padding */ - x[len >> 5] |= 0x80 << (len % 32) - x[(((len + 64) >>> 9) << 4) + 14] = len + if (ret !== null) this.emit('data', ret); - var i - var olda - var oldb - var oldc - var oldd - var a = 1732584193 - var b = -271733879 - var c = -1732584194 - var d = 271733878 + return ret; +}; - for (i = 0; i < x.length; i += 16) { - olda = a - oldb = b - oldc = c - oldd = d +function onEofChunk(stream, state) { + if (state.ended) return; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; - a = md5ff(a, b, c, d, x[i], 7, -680876936) - d = md5ff(d, a, b, c, x[i + 1], 12, -389564586) - c = md5ff(c, d, a, b, x[i + 2], 17, 606105819) - b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330) - a = md5ff(a, b, c, d, x[i + 4], 7, -176418897) - d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426) - c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341) - b = md5ff(b, c, d, a, x[i + 7], 22, -45705983) - a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416) - d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417) - c = md5ff(c, d, a, b, x[i + 10], 17, -42063) - b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162) - a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682) - d = md5ff(d, a, b, c, x[i + 13], 12, -40341101) - c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290) - b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329) + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); +} - a = md5gg(a, b, c, d, x[i + 1], 5, -165796510) - d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632) - c = md5gg(c, d, a, b, x[i + 11], 14, 643717713) - b = md5gg(b, c, d, a, x[i], 20, -373897302) - a = md5gg(a, b, c, d, x[i + 5], 5, -701558691) - d = md5gg(d, a, b, c, x[i + 10], 9, 38016083) - c = md5gg(c, d, a, b, x[i + 15], 14, -660478335) - b = md5gg(b, c, d, a, x[i + 4], 20, -405537848) - a = md5gg(a, b, c, d, x[i + 9], 5, 568446438) - d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690) - c = md5gg(c, d, a, b, x[i + 3], 14, -187363961) - b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501) - a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467) - d = md5gg(d, a, b, c, x[i + 2], 9, -51403784) - c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473) - b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734) - - a = md5hh(a, b, c, d, x[i + 5], 4, -378558) - d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463) - c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562) - b = md5hh(b, c, d, a, x[i + 14], 23, -35309556) - a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060) - d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353) - c = md5hh(c, d, a, b, x[i + 7], 16, -155497632) - b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640) - a = md5hh(a, b, c, d, x[i + 13], 4, 681279174) - d = md5hh(d, a, b, c, x[i], 11, -358537222) - c = md5hh(c, d, a, b, x[i + 3], 16, -722521979) - b = md5hh(b, c, d, a, x[i + 6], 23, 76029189) - a = md5hh(a, b, c, d, x[i + 9], 4, -640364487) - d = md5hh(d, a, b, c, x[i + 12], 11, -421815835) - c = md5hh(c, d, a, b, x[i + 15], 16, 530742520) - b = md5hh(b, c, d, a, x[i + 2], 23, -995338651) - - a = md5ii(a, b, c, d, x[i], 6, -198630844) - d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415) - c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905) - b = md5ii(b, c, d, a, x[i + 5], 21, -57434055) - a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571) - d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606) - c = md5ii(c, d, a, b, x[i + 10], 15, -1051523) - b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799) - a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359) - d = md5ii(d, a, b, c, x[i + 15], 10, -30611744) - c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380) - b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649) - a = md5ii(a, b, c, d, x[i + 4], 6, -145523070) - d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379) - c = md5ii(c, d, a, b, x[i + 2], 15, 718787259) - b = md5ii(b, c, d, a, x[i + 9], 21, -343485551) - - a = safeAdd(a, olda) - b = safeAdd(b, oldb) - c = safeAdd(c, oldc) - d = safeAdd(d, oldd) - } - return [a, b, c, d] +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) pna.nextTick(emitReadable_, stream);else emitReadable_(stream); } +} - /* - * Convert an array of little-endian words to a string - */ - function binl2rstr (input) { - var i - var output = '' - var length32 = input.length * 32 - for (i = 0; i < length32; i += 8) { - output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF) - } - return output - } +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} - /* - * Convert a raw string to an array of little-endian words - * Characters >255 have their high-byte silently ignored. - */ - function rstr2binl (input) { - var i - var output = [] - output[(input.length >> 2) - 1] = undefined - for (i = 0; i < output.length; i += 1) { - output[i] = 0 - } - var length8 = input.length * 8 - for (i = 0; i < length8; i += 8) { - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32) - } - return output +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + pna.nextTick(maybeReadMore_, stream, state); } +} - /* - * Calculate the MD5 of a raw string - */ - function rstrMD5 (s) { - return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)) +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break;else len = state.length; } + state.readingMore = false; +} - /* - * Calculate the HMAC-MD5, of a key and some data (raw strings) - */ - function rstrHMACMD5 (key, data) { - var i - var bkey = rstr2binl(key) - var ipad = [] - var opad = [] - var hash - ipad[15] = opad[15] = undefined - if (bkey.length > 16) { - bkey = binlMD5(bkey, key.length * 8) - } - for (i = 0; i < 16; i += 1) { - ipad[i] = bkey[i] ^ 0x36363636 - opad[i] = bkey[i] ^ 0x5C5C5C5C - } - hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8) - return binl2rstr(binlMD5(opad.concat(hash), 512 + 128)) - } +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function (n) { + this.emit('error', new Error('_read() is not implemented')); +}; - /* - * Convert a raw string to a hex string - */ - function rstr2hex (input) { - var hexTab = '0123456789abcdef' - var output = '' - var x - var i - for (i = 0; i < input.length; i += 1) { - x = input.charCodeAt(i) - output += hexTab.charAt((x >>> 4) & 0x0F) + - hexTab.charAt(x & 0x0F) - } - return output - } +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; - /* - * Encode a string as utf-8 - */ - function str2rstrUTF8 (input) { - return unescape(encodeURIComponent(input)) + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); - /* - * Take string arguments and return either raw or hex encoded strings - */ - function rawMD5 (s) { - return rstrMD5(str2rstrUTF8(s)) - } - function hexMD5 (s) { - return rstr2hex(rawMD5(s)) - } - function rawHMACMD5 (k, d) { - return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d)) - } - function hexHMACMD5 (k, d) { - return rstr2hex(rawHMACMD5(k, d)) - } + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; - function md5 (string, key, raw) { - if (!key) { - if (!raw) { - return hexMD5(string) + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) pna.nextTick(endFn);else src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); } - return rawMD5(string) - } - if (!raw) { - return hexHMACMD5(key, string) } - return rawHMACMD5(key, string) } - if (typeof define === 'function' && define.amd) { - define(function () { - return md5 - }) - } else if (typeof module === 'object' && module.exports) { - module.exports = md5 - } else { - $.md5 = md5 + function onend() { + debug('onend'); + dest.end(); } -}(this)) - -},{}],46:[function(require,module,exports){ -/*! - * The buffer module from node.js, for the browser. - * - * @author Feross Aboukhadijeh - * @license MIT - */ -/* eslint-disable no-proto */ - -'use strict' - -var base64 = require('base64-js') -var ieee754 = require('ieee754') -exports.Buffer = Buffer -exports.SlowBuffer = SlowBuffer -exports.INSPECT_MAX_BYTES = 50 + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); -var K_MAX_LENGTH = 0x7fffffff -exports.kMaxLength = K_MAX_LENGTH - -/** - * If `Buffer.TYPED_ARRAY_SUPPORT`: - * === true Use Uint8Array implementation (fastest) - * === false Print warning and recommend using `buffer` v4.x which has an 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+. - * - * We report that the browser does not support typed arrays if the are not subclassable - * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` - * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support - * for __proto__ and has a buggy typed array implementation. - */ -Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() - -if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && - typeof console.error === 'function') { - console.error( - 'This browser lacks typed array (Uint8Array) support which is required by ' + - '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' - ) -} + var cleanedUp = false; + function cleanup() { + debug('cleanup'); + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); -function typedArraySupport () { - // Can typed array instances can be augmented? - try { - var arr = new Uint8Array(1) - arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }} - return arr.foo() === 42 - } catch (e) { - return false - } -} + cleanedUp = true; -function createBuffer (length) { - if (length > K_MAX_LENGTH) { - throw new RangeError('Invalid typed array length') + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); } - // Return an augmented `Uint8Array` instance - var buf = new Uint8Array(length) - buf.__proto__ = Buffer.prototype - return buf -} - -/** - * The Buffer constructor returns instances of `Uint8Array` that have their - * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of - * `Uint8Array`, so the returned instances will have all the node `Buffer` methods - * and the `Uint8Array` methods. Square bracket notation works as expected -- it - * returns a single octet. - * - * The `Uint8Array` prototype remains unmodified. - */ -function Buffer (arg, encodingOrOffset, length) { - // Common case. - if (typeof arg === 'number') { - if (typeof encodingOrOffset === 'string') { - throw new Error( - 'If encoding is specified then the first argument must be a string' - ) + // If the user pushes more data while we're writing to dest then we'll end up + // in ondata again. However, we only want to increase awaitDrain once because + // dest will only emit one 'drain' event for the multiple writes. + // => Introduce a guard on increasing awaitDrain. + var increasedAwaitDrain = false; + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + increasedAwaitDrain = false; + var ret = dest.write(chunk); + if (false === ret && !increasedAwaitDrain) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', src._readableState.awaitDrain); + src._readableState.awaitDrain++; + increasedAwaitDrain = true; + } + src.pause(); } - return allocUnsafe(arg) } - return from(arg, encodingOrOffset, length) -} -// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 -if (typeof Symbol !== 'undefined' && Symbol.species && - Buffer[Symbol.species] === Buffer) { - Object.defineProperty(Buffer, Symbol.species, { - value: null, - configurable: true, - enumerable: false, - writable: false - }) -} + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); + } -Buffer.poolSize = 8192 // not used by this implementation + // Make sure our error handler is attached before userland ones. + prependListener(dest, 'error', onerror); -function from (value, encodingOrOffset, length) { - if (typeof value === 'number') { - throw new TypeError('"value" argument must not be a number') + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); } - - if (value instanceof ArrayBuffer) { - return fromArrayBuffer(value, encodingOrOffset, length) + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); } + dest.once('finish', onfinish); - if (typeof value === 'string') { - return fromString(value, encodingOrOffset) + function unpipe() { + debug('unpipe'); + src.unpipe(dest); } - return fromObject(value) -} - -/** - * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError - * if value is a number. - * Buffer.from(str[, encoding]) - * Buffer.from(array) - * Buffer.from(buffer) - * Buffer.from(arrayBuffer[, byteOffset[, length]]) - **/ -Buffer.from = function (value, encodingOrOffset, length) { - return from(value, encodingOrOffset, length) -} - -// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: -// https://github.com/feross/buffer/pull/148 -Buffer.prototype.__proto__ = Uint8Array.prototype -Buffer.__proto__ = Uint8Array + // tell the dest that it's being piped to + dest.emit('pipe', src); -function assertSize (size) { - if (typeof size !== 'number') { - throw new TypeError('"size" argument must be a number') - } else if (size < 0) { - throw new RangeError('"size" argument must not be negative') + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); } -} -function alloc (size, fill, encoding) { - assertSize(size) - if (size <= 0) { - return createBuffer(size) - } - if (fill !== undefined) { - // Only pay attention to encoding if it's a string. This - // prevents accidentally sending in a number that would - // be interpretted as a start offset. - return typeof encoding === 'string' - ? createBuffer(size).fill(fill, encoding) - : createBuffer(size).fill(fill) - } - return createBuffer(size) -} + return dest; +}; -/** - * Creates a new filled Buffer instance. - * alloc(size[, fill[, encoding]]) - **/ -Buffer.alloc = function (size, fill, encoding) { - return alloc(size, fill, encoding) +function pipeOnDrain(src) { + return function () { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; } -function allocUnsafe (size) { - assertSize(size) - return createBuffer(size < 0 ? 0 : checked(size) | 0) -} +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { hasUnpiped: false }; -/** - * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. - * */ -Buffer.allocUnsafe = function (size) { - return allocUnsafe(size) -} -/** - * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. - */ -Buffer.allocUnsafeSlow = function (size) { - return allocUnsafe(size) -} + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) return this; -function fromString (string, encoding) { - if (typeof encoding !== 'string' || encoding === '') { - encoding = 'utf8' - } + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; - if (!Buffer.isEncoding(encoding)) { - throw new TypeError('"encoding" must be a valid string encoding') - } + if (!dest) dest = state.pipes; - var length = byteLength(string, encoding) | 0 - var buf = createBuffer(length) + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } - var actual = buf.write(string, encoding) + // slow case. multiple pipe destinations. - if (actual !== length) { - // Writing a hex string, for example, that contains invalid characters will - // cause everything after the first invalid character to be ignored. (e.g. - // 'abxxcd' will be treated as 'ab') - buf = buf.slice(0, actual) - } - - return buf -} - -function fromArrayLike (array) { - var length = array.length < 0 ? 0 : checked(array.length) | 0 - var buf = createBuffer(length) - for (var i = 0; i < length; i += 1) { - buf[i] = array[i] & 255 - } - return buf -} - -function fromArrayBuffer (array, byteOffset, length) { - if (byteOffset < 0 || array.byteLength < byteOffset) { - throw new RangeError('\'offset\' is out of bounds') - } + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; - if (array.byteLength < byteOffset + (length || 0)) { - throw new RangeError('\'length\' is out of bounds') + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, unpipeInfo); + }return this; } - var buf - if (byteOffset === undefined && length === undefined) { - buf = new Uint8Array(array) - } else if (length === undefined) { - buf = new Uint8Array(array, byteOffset) - } else { - buf = new Uint8Array(array, byteOffset, length) - } + // try to find the right one. + var index = indexOf(state.pipes, dest); + if (index === -1) return this; - // Return an augmented `Uint8Array` instance - buf.__proto__ = Buffer.prototype - return buf -} + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; -function fromObject (obj) { - if (Buffer.isBuffer(obj)) { - var len = checked(obj.length) | 0 - var buf = createBuffer(len) + dest.emit('unpipe', this, unpipeInfo); - if (buf.length === 0) { - return buf - } + return this; +}; - obj.copy(buf, 0, 0, len) - return buf - } +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); - if (obj) { - if (isArrayBufferView(obj) || 'length' in obj) { - if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { - return createBuffer(0) + if (ev === 'data') { + // Start flowing on next tick if stream isn't explicitly paused + if (this._readableState.flowing !== false) this.resume(); + } else if (ev === 'readable') { + var state = this._readableState; + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.emittedReadable = false; + if (!state.reading) { + pna.nextTick(nReadingNextTick, this); + } else if (state.length) { + emitReadable(this); } - return fromArrayLike(obj) - } - - if (obj.type === 'Buffer' && Array.isArray(obj.data)) { - return fromArrayLike(obj.data) } } - throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); } -function checked (length) { - // Note: cannot use `length < K_MAX_LENGTH` here because that fails when - // length is NaN (which is otherwise coerced to zero.) - if (length >= K_MAX_LENGTH) { - throw new RangeError('Attempt to allocate Buffer larger than maximum ' + - 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function () { + var state = this._readableState; + if (!state.flowing) { + debug('resume'); + state.flowing = true; + resume(this, state); } - return length | 0 -} + return this; +}; -function SlowBuffer (length) { - if (+length != length) { // eslint-disable-line eqeqeq - length = 0 +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + pna.nextTick(resume_, stream, state); } - return Buffer.alloc(+length) } -Buffer.isBuffer = function isBuffer (b) { - return b != null && b._isBuffer === true +function resume_(stream, state) { + if (!state.reading) { + debug('resume read 0'); + stream.read(0); + } + + state.resumeScheduled = false; + state.awaitDrain = 0; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); } -Buffer.compare = function compare (a, b) { - if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { - throw new TypeError('Arguments must be Buffers') +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + if (false !== this._readableState.flowing) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); } + return this; +}; - if (a === b) return 0 +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + while (state.flowing && stream.read() !== null) {} +} - var x = a.length - var y = b.length +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function (stream) { + var _this = this; - for (var i = 0, len = Math.min(x, y); i < len; ++i) { - if (a[i] !== b[i]) { - x = a[i] - y = b[i] - break - } - } + var state = this._readableState; + var paused = false; - if (x < y) return -1 - if (y < x) return 1 - return 0 -} + stream.on('end', function () { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) _this.push(chunk); + } -Buffer.isEncoding = function isEncoding (encoding) { - switch (String(encoding).toLowerCase()) { - case 'hex': - case 'utf8': - case 'utf-8': - case 'ascii': - case 'latin1': - case 'binary': - case 'base64': - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return true - default: - return false - } -} + _this.push(null); + }); -Buffer.concat = function concat (list, length) { - if (!Array.isArray(list)) { - throw new TypeError('"list" argument must be an Array of Buffers') - } + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); - if (list.length === 0) { - return Buffer.alloc(0) - } + // don't skip over falsy values in objectMode + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; - var i - if (length === undefined) { - length = 0 - for (i = 0; i < list.length; ++i) { - length += list[i].length + var ret = _this.push(chunk); + if (!ret) { + paused = true; + stream.pause(); } - } + }); - var buffer = Buffer.allocUnsafe(length) - var pos = 0 - for (i = 0; i < list.length; ++i) { - var buf = list[i] - if (!Buffer.isBuffer(buf)) { - throw new TypeError('"list" argument must be an Array of Buffers') + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function (method) { + return function () { + return stream[method].apply(stream, arguments); + }; + }(i); } - buf.copy(buffer, pos) - pos += buf.length } - return buffer -} -function byteLength (string, encoding) { - if (Buffer.isBuffer(string)) { - return string.length - } - if (isArrayBufferView(string) || string instanceof ArrayBuffer) { - return string.byteLength - } - if (typeof string !== 'string') { - string = '' + string + // proxy certain important events. + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n])); } - var len = string.length - if (len === 0) return 0 + // when we try to consume some more bytes, simply unpause the + // underlying stream. + this._read = function (n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); + } + }; - // Use a for loop to avoid recursion - var loweredCase = false - for (;;) { - switch (encoding) { - case 'ascii': - case 'latin1': - case 'binary': - return len - case 'utf8': - case 'utf-8': - case undefined: - 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 - } + return this; +}; + +Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._readableState.highWaterMark; } -} -Buffer.byteLength = byteLength +}); -function slowToString (encoding, start, end) { - var loweredCase = false +// exposed for testing purposes only. +Readable._fromList = fromList; - // No need to verify that "this.length <= MAX_UINT32" since it's a read-only - // property of a typed array. +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; - // This behaves neither like String nor Uint8Array in that we set start/end - // to their upper/lower bounds if the value passed is out of range. - // undefined is handled specially as per ECMA-262 6th Edition, - // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. - if (start === undefined || start < 0) { - start = 0 - } - // Return early if start > this.length. Done here to prevent potential uint32 - // coercion fail below. - if (start > this.length) { - return '' + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = fromListPartial(n, state.buffer, state.decoder); } - if (end === undefined || end > this.length) { - end = this.length - } + return ret; +} - if (end <= 0) { - return '' +// Extracts only enough buffered data to satisfy the amount requested. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromListPartial(n, list, hasStrings) { + var ret; + if (n < list.head.data.length) { + // slice is the same for buffers and strings + ret = list.head.data.slice(0, n); + list.head.data = list.head.data.slice(n); + } else if (n === list.head.data.length) { + // first chunk is a perfect match + ret = list.shift(); + } else { + // result spans more than one buffer + ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); } + return ret; +} - // Force coersion to uint32. This will also coerce falsey/NaN values to 0. - end >>>= 0 - start >>>= 0 - - if (end <= start) { - return '' +// Copies a specified amount of characters from the list of buffered data +// chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBufferString(n, list) { + var p = list.head; + var c = 1; + var ret = p.data; + n -= ret.length; + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = str.slice(nb); + } + break; + } + ++c; } + list.length -= c; + return ret; +} - if (!encoding) encoding = 'utf8' - - 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 'latin1': - case 'binary': - return latin1Slice(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 +// Copies a specified amount of bytes from the list of buffered data chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBuffer(n, list) { + var ret = Buffer.allocUnsafe(n); + var p = list.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = buf.slice(nb); + } + break; } + ++c; } + list.length -= c; + return ret; } -// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) -// to detect a Buffer instance. It's not possible to use `instanceof Buffer` -// reliably in a browserify context because there could be multiple different -// copies of the 'buffer' package in use. This method works even for Buffer -// instances that were created from another copy of the `buffer` package. -// See: https://github.com/feross/buffer/issues/154 -Buffer.prototype._isBuffer = true +function endReadable(stream) { + var state = stream._readableState; -function swap (b, n, m) { - var i = b[n] - b[n] = b[m] - b[m] = i -} + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); -Buffer.prototype.swap16 = function swap16 () { - var len = this.length - if (len % 2 !== 0) { - throw new RangeError('Buffer size must be a multiple of 16-bits') - } - for (var i = 0; i < len; i += 2) { - swap(this, i, i + 1) + if (!state.endEmitted) { + state.ended = true; + pna.nextTick(endReadableNT, state, stream); } - return this } -Buffer.prototype.swap32 = function swap32 () { - var len = this.length - if (len % 4 !== 0) { - throw new RangeError('Buffer size must be a multiple of 32-bits') - } - for (var i = 0; i < len; i += 4) { - swap(this, i, i + 3) - swap(this, i + 1, i + 2) +function endReadableNT(state, stream) { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); } - return this } -Buffer.prototype.swap64 = function swap64 () { - var len = this.length - if (len % 8 !== 0) { - throw new RangeError('Buffer size must be a multiple of 64-bits') - } - for (var i = 0; i < len; i += 8) { - swap(this, i, i + 7) - swap(this, i + 1, i + 6) - swap(this, i + 2, i + 5) - swap(this, i + 3, i + 4) +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; } - return this -} - -Buffer.prototype.toString = function toString () { - var length = this.length - 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 '' + return -1; } +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./_stream_duplex":20,"./internal/streams/BufferList":25,"./internal/streams/destroy":26,"./internal/streams/stream":27,"_process":15,"core-util-is":6,"events":7,"inherits":10,"isarray":12,"process-nextick-args":14,"safe-buffer":29,"string_decoder/":34,"util":3}],23:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { - if (!Buffer.isBuffer(target)) { - throw new TypeError('Argument must be a Buffer') - } +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. - if (start === undefined) { - start = 0 - } - if (end === undefined) { - end = target ? target.length : 0 - } - if (thisStart === undefined) { - thisStart = 0 - } - if (thisEnd === undefined) { - thisEnd = this.length - } +'use strict'; - if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { - throw new RangeError('out of range index') - } +module.exports = Transform; - if (thisStart >= thisEnd && start >= end) { - return 0 - } - if (thisStart >= thisEnd) { - return -1 - } - if (start >= end) { - return 1 - } +var Duplex = require('./_stream_duplex'); - start >>>= 0 - end >>>= 0 - thisStart >>>= 0 - thisEnd >>>= 0 +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ - if (this === target) return 0 +util.inherits(Transform, Duplex); - var x = thisEnd - thisStart - var y = end - start - var len = Math.min(x, y) +function afterTransform(er, data) { + var ts = this._transformState; + ts.transforming = false; - var thisCopy = this.slice(thisStart, thisEnd) - var targetCopy = target.slice(start, end) + var cb = ts.writecb; - for (var i = 0; i < len; ++i) { - if (thisCopy[i] !== targetCopy[i]) { - x = thisCopy[i] - y = targetCopy[i] - break - } + if (!cb) { + return this.emit('error', new Error('write callback called multiple times')); } - if (x < y) return -1 - if (y < x) return 1 - return 0 -} + ts.writechunk = null; + ts.writecb = null; -// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, -// OR the last index of `val` in `buffer` at offset <= `byteOffset`. -// -// Arguments: -// - buffer - a Buffer to search -// - val - a string, Buffer, or number -// - byteOffset - an index into `buffer`; will be clamped to an int32 -// - encoding - an optional encoding, relevant is val is a string -// - dir - true for indexOf, false for lastIndexOf -function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { - // Empty buffer means no match - if (buffer.length === 0) return -1 + if (data != null) // single equals check for both `null` and `undefined` + this.push(data); - // Normalize byteOffset - if (typeof byteOffset === 'string') { - encoding = byteOffset - byteOffset = 0 - } else if (byteOffset > 0x7fffffff) { - byteOffset = 0x7fffffff - } else if (byteOffset < -0x80000000) { - byteOffset = -0x80000000 - } - byteOffset = +byteOffset // Coerce to Number. - if (numberIsNaN(byteOffset)) { - // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer - byteOffset = dir ? 0 : (buffer.length - 1) - } + cb(er); - // Normalize byteOffset: negative offsets start from the end of the buffer - if (byteOffset < 0) byteOffset = buffer.length + byteOffset - if (byteOffset >= buffer.length) { - if (dir) return -1 - else byteOffset = buffer.length - 1 - } else if (byteOffset < 0) { - if (dir) byteOffset = 0 - else return -1 + var rs = this._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + this._read(rs.highWaterMark); } +} - // Normalize val - if (typeof val === 'string') { - val = Buffer.from(val, encoding) - } +function Transform(options) { + if (!(this instanceof Transform)) return new Transform(options); - // Finally, search either indexOf (if dir is true) or lastIndexOf - if (Buffer.isBuffer(val)) { - // Special case: looking for empty string/buffer always fails - if (val.length === 0) { - return -1 - } - return arrayIndexOf(buffer, val, byteOffset, encoding, dir) - } else if (typeof val === 'number') { - val = val & 0xFF // Search for a byte value [0-255] - if (typeof Uint8Array.prototype.indexOf === 'function') { - if (dir) { - return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) - } else { - return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) - } - } - return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) - } + Duplex.call(this, options); - throw new TypeError('val must be string, number or Buffer') -} + this._transformState = { + afterTransform: afterTransform.bind(this), + needTransform: false, + transforming: false, + writecb: null, + writechunk: null, + writeencoding: null + }; -function arrayIndexOf (arr, val, byteOffset, encoding, dir) { - var indexSize = 1 - var arrLength = arr.length - var valLength = val.length + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; - if (encoding !== undefined) { - encoding = String(encoding).toLowerCase() - if (encoding === 'ucs2' || encoding === 'ucs-2' || - encoding === 'utf16le' || encoding === 'utf-16le') { - if (arr.length < 2 || val.length < 2) { - return -1 - } - indexSize = 2 - arrLength /= 2 - valLength /= 2 - byteOffset /= 2 - } - } + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; - function read (buf, i) { - if (indexSize === 1) { - return buf[i] - } else { - return buf.readUInt16BE(i * indexSize) - } - } + if (options) { + if (typeof options.transform === 'function') this._transform = options.transform; - var i - if (dir) { - var foundIndex = -1 - for (i = byteOffset; i < arrLength; i++) { - if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { - if (foundIndex === -1) foundIndex = i - if (i - foundIndex + 1 === valLength) return foundIndex * indexSize - } else { - if (foundIndex !== -1) i -= i - foundIndex - foundIndex = -1 - } - } - } else { - if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength - for (i = byteOffset; i >= 0; i--) { - var found = true - for (var j = 0; j < valLength; j++) { - if (read(arr, i + j) !== read(val, j)) { - found = false - break - } - } - if (found) return i - } + if (typeof options.flush === 'function') this._flush = options.flush; } - return -1 + // When the writable side finishes, then flush out anything remaining. + this.on('prefinish', prefinish); } -Buffer.prototype.includes = function includes (val, byteOffset, encoding) { - return this.indexOf(val, byteOffset, encoding) !== -1 -} +function prefinish() { + var _this = this; -Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { - return bidirectionalIndexOf(this, val, byteOffset, encoding, true) + if (typeof this._flush === 'function') { + this._flush(function (er, data) { + done(_this, er, data); + }); + } else { + done(this, null, null); + } } -Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { - return bidirectionalIndexOf(this, val, byteOffset, encoding, false) -} +Transform.prototype.push = function (chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; -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 - } +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function (chunk, encoding, cb) { + throw new Error('_transform() is not implemented'); +}; + +Transform.prototype._write = function (chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); } +}; - // must be an even number of digits - var strLen = string.length - if (strLen % 2 !== 0) throw new TypeError('Invalid hex string') +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function (n) { + var ts = this._transformState; - if (length > strLen / 2) { - length = strLen / 2 - } - for (var i = 0; i < length; ++i) { - var parsed = parseInt(string.substr(i * 2, 2), 16) - if (numberIsNaN(parsed)) return i - buf[offset + i] = parsed + if (ts.writechunk !== null && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; } - return i -} +}; -function utf8Write (buf, string, offset, length) { - return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) -} +Transform.prototype._destroy = function (err, cb) { + var _this2 = this; -function asciiWrite (buf, string, offset, length) { - return blitBuffer(asciiToBytes(string), buf, offset, length) -} + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + _this2.emit('close'); + }); +}; -function latin1Write (buf, string, offset, length) { - return asciiWrite(buf, string, offset, length) -} +function done(stream, er, data) { + if (er) return stream.emit('error', er); -function base64Write (buf, string, offset, length) { - return blitBuffer(base64ToBytes(string), buf, offset, length) -} + if (data != null) // single equals check for both `null` and `undefined` + stream.push(data); -function ucs2Write (buf, string, offset, length) { - return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + if (stream._writableState.length) throw new Error('Calling transform done when ws.length != 0'); + + if (stream._transformState.transforming) throw new Error('Calling transform done when still transforming'); + + return stream.push(null); } +},{"./_stream_duplex":20,"core-util-is":6,"inherits":10}],24:[function(require,module,exports){ +(function (process,global,setImmediate){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -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 - } - } else { - throw new Error( - 'Buffer.write(string, encoding, offset[, length]) is no longer supported' - ) - } +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. - var remaining = this.length - offset - if (length === undefined || length > remaining) length = remaining +'use strict'; - 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 pna = require('process-nextick-args'); +/**/ - var loweredCase = false - for (;;) { - switch (encoding) { - case 'hex': - return hexWrite(this, string, offset, length) +module.exports = Writable; - case 'utf8': - case 'utf-8': - return utf8Write(this, string, offset, length) +/* */ +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} - case 'ascii': - return asciiWrite(this, string, offset, length) +// It seems a linked list but it is not +// there will be only 2 of these for each stream +function CorkedRequest(state) { + var _this = this; - case 'latin1': - case 'binary': - return latin1Write(this, string, offset, length) + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ - case 'base64': - // Warning: maxLength not taken into account in base64Write - return base64Write(this, string, offset, length) +/**/ +var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : pna.nextTick; +/**/ - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return ucs2Write(this, string, offset, length) +/**/ +var Duplex; +/**/ - default: - if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) - encoding = ('' + encoding).toLowerCase() - loweredCase = true - } - } -} +Writable.WritableState = WritableState; -Buffer.prototype.toJSON = function toJSON () { - return { - type: 'Buffer', - data: Array.prototype.slice.call(this._arr || this, 0) - } -} +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ -function base64Slice (buf, start, end) { - if (start === 0 && end === buf.length) { - return base64.fromByteArray(buf) - } else { - return base64.fromByteArray(buf.slice(start, end)) - } -} +/**/ +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ -function utf8Slice (buf, start, end) { - end = Math.min(buf.length, end) - var res = [] +/**/ +var Stream = require('./internal/streams/stream'); +/**/ - 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 +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} - 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 - } +var destroyImpl = require('./internal/streams/destroy'); - res.push(codePoint) - i += bytesPerSequence - } +util.inherits(Writable, Stream); - return decodeCodePointsArray(res) -} +function nop() {} -// 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 WritableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); -function decodeCodePointsArray (codePoints) { - var len = codePoints.length - if (len <= MAX_ARGUMENTS_LENGTH) { - return String.fromCharCode.apply(String, codePoints) // avoid extra slice() - } + options = options || {}; - // 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 -} + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; -function asciiSlice (buf, start, end) { - var ret = '' - end = Math.min(buf.length, end) + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; - for (var i = start; i < end; ++i) { - ret += String.fromCharCode(buf[i] & 0x7F) - } - return ret -} + if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode; -function latin1Slice (buf, start, end) { - var ret = '' - end = Math.min(buf.length, end) + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var writableHwm = options.writableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; - for (var i = start; i < end; ++i) { - ret += String.fromCharCode(buf[i]) - } - return ret -} + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (writableHwm || writableHwm === 0)) this.highWaterMark = writableHwm;else this.highWaterMark = defaultHwm; -function hexSlice (buf, start, end) { - var len = buf.length + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); - if (!start || start < 0) start = 0 - if (!end || end < 0 || end > len) end = len + // if _final has been called + this.finalCalled = false; - var out = '' - for (var i = start; i < end; ++i) { - out += toHex(buf[i]) - } - return out -} + // drain event flag. + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; -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 -} + // has it been destroyed + this.destroyed = false; -Buffer.prototype.slice = function slice (start, end) { - var len = this.length - start = ~~start - end = end === undefined ? len : ~~end + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; - if (start < 0) { - start += len - if (start < 0) start = 0 - } else if (start > len) { - start = len - } + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; - if (end < 0) { - end += len - if (end < 0) end = 0 - } else if (end > len) { - end = len - } + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; - if (end < start) end = start + // a flag to see when we're in the middle of a write. + this.writing = false; - var newBuf = this.subarray(start, end) - // Return an augmented `Uint8Array` instance - newBuf.__proto__ = Buffer.prototype - return newBuf -} + // when true all writes will be buffered until .uncork() call + this.corked = 0; -/* - * 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') -} + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; -Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { - offset = offset >>> 0 - byteLength = byteLength >>> 0 - if (!noAssert) checkOffset(offset, byteLength, this.length) + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; - var val = this[offset] - var mul = 1 - var i = 0 - while (++i < byteLength && (mul *= 0x100)) { - val += this[offset + i] * mul - } + // the callback that's passed to _write(chunk,cb) + this.onwrite = function (er) { + onwrite(stream, er); + }; - return val -} + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; -Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { - offset = offset >>> 0 - byteLength = byteLength >>> 0 - if (!noAssert) { - checkOffset(offset, byteLength, this.length) - } + // the amount that is being written when _write is called. + this.writelen = 0; - var val = this[offset + --byteLength] - var mul = 1 - while (byteLength > 0 && (mul *= 0x100)) { - val += this[offset + --byteLength] * mul - } + this.bufferedRequest = null; + this.lastBufferedRequest = null; - return val -} + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; -Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 1, this.length) - return this[offset] -} + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; -Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 2, this.length) - return this[offset] | (this[offset + 1] << 8) -} + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; -Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 2, this.length) - return (this[offset] << 8) | this[offset + 1] + // count buffered requests + this.bufferedRequestCount = 0; + + // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + this.corkedRequestsFree = new CorkedRequest(this); } -Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 4, this.length) +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + while (current) { + out.push(current); + current = current.next; + } + return out; +}; - return ((this[offset]) | - (this[offset + 1] << 8) | - (this[offset + 2] << 16)) + - (this[offset + 3] * 0x1000000) -} +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function () { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); -Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 4, this.length) +// Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. +var realHasInstance; +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function (object) { + if (realHasInstance.call(this, object)) return true; + if (this !== Writable) return false; - return (this[offset] * 0x1000000) + - ((this[offset + 1] << 16) | - (this[offset + 2] << 8) | - this[offset + 3]) + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function (object) { + return object instanceof this; + }; } -Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { - offset = offset >>> 0 - byteLength = byteLength >>> 0 - if (!noAssert) checkOffset(offset, byteLength, this.length) +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); - var val = this[offset] - var mul = 1 - var i = 0 - while (++i < byteLength && (mul *= 0x100)) { - val += this[offset + i] * mul + // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { + return new Writable(options); } - mul *= 0x80 - if (val >= mul) val -= Math.pow(2, 8 * byteLength) + this._writableState = new WritableState(options, this); - return val -} + // legacy. + this.writable = true; -Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { - offset = offset >>> 0 - byteLength = byteLength >>> 0 - if (!noAssert) checkOffset(offset, byteLength, this.length) + if (options) { + if (typeof options.write === 'function') this._write = options.write; - var i = byteLength - var mul = 1 - var val = this[offset + --i] - while (i > 0 && (mul *= 0x100)) { - val += this[offset + --i] * mul - } - mul *= 0x80 + if (typeof options.writev === 'function') this._writev = options.writev; - if (val >= mul) val -= Math.pow(2, 8 * byteLength) + if (typeof options.destroy === 'function') this._destroy = options.destroy; - return val -} + if (typeof options.final === 'function') this._final = options.final; + } -Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 1, this.length) - if (!(this[offset] & 0x80)) return (this[offset]) - return ((0xff - this[offset] + 1) * -1) + Stream.call(this); } -Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 2, this.length) - var val = this[offset] | (this[offset + 1] << 8) - return (val & 0x8000) ? val | 0xFFFF0000 : val -} +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function () { + this.emit('error', new Error('Cannot pipe, not readable')); +}; -Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 2, this.length) - var val = this[offset + 1] | (this[offset] << 8) - return (val & 0x8000) ? val | 0xFFFF0000 : val +function writeAfterEnd(stream, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + pna.nextTick(cb, er); } -Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 4, this.length) +// Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. +function validChunk(stream, state, chunk, cb) { + var valid = true; + var er = false; - return (this[offset]) | - (this[offset + 1] << 8) | - (this[offset + 2] << 16) | - (this[offset + 3] << 24) + if (chunk === null) { + er = new TypeError('May not write null values to stream'); + } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + if (er) { + stream.emit('error', er); + pna.nextTick(cb, er); + valid = false; + } + return valid; } -Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 4, this.length) +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + var isBuf = !state.objectMode && _isUint8Array(chunk); - return (this[offset] << 24) | - (this[offset + 1] << 16) | - (this[offset + 2] << 8) | - (this[offset + 3]) -} + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } -Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 4, this.length) - return ieee754.read(this, offset, true, 23, 4) -} + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } -Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 4, this.length) - return ieee754.read(this, offset, false, 23, 4) -} + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; -Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 8, this.length) - return ieee754.read(this, offset, true, 52, 8) -} + if (typeof cb !== 'function') cb = nop; -Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { - offset = offset >>> 0 - if (!noAssert) checkOffset(offset, 8, this.length) - return ieee754.read(this, offset, false, 52, 8) -} + if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } -function checkInt (buf, value, offset, ext, max, min) { - if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') - if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') - if (offset + ext > buf.length) throw new RangeError('Index out of range') -} + return ret; +}; -Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { - value = +value - offset = offset >>> 0 - byteLength = byteLength >>> 0 - if (!noAssert) { - var maxBytes = Math.pow(2, 8 * byteLength) - 1 - checkInt(this, value, offset, byteLength, maxBytes, 0) - } +Writable.prototype.cork = function () { + var state = this._writableState; - var mul = 1 - var i = 0 - this[offset] = value & 0xFF - while (++i < byteLength && (mul *= 0x100)) { - this[offset + i] = (value / mul) & 0xFF - } + state.corked++; +}; - return offset + byteLength -} +Writable.prototype.uncork = function () { + var state = this._writableState; -Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { - value = +value - offset = offset >>> 0 - byteLength = byteLength >>> 0 - if (!noAssert) { - var maxBytes = Math.pow(2, 8 * byteLength) - 1 - checkInt(this, value, offset, byteLength, maxBytes, 0) - } + if (state.corked) { + state.corked--; - var i = byteLength - 1 - var mul = 1 - this[offset + i] = value & 0xFF - while (--i >= 0 && (mul *= 0x100)) { - this[offset + i] = (value / mul) & 0xFF + if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); } +}; - return offset + byteLength -} +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; -Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { - value = +value - offset = offset >>> 0 - if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) - this[offset] = (value & 0xff) - return offset + 1 +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + return chunk; } -Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { - value = +value - offset = offset >>> 0 - if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) - this[offset] = (value & 0xff) - this[offset + 1] = (value >>> 8) - 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) - this[offset] = (value >>> 8) - this[offset + 1] = (value & 0xff) - return offset + 2 -} - -Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { - value = +value - offset = offset >>> 0 - if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) - this[offset + 3] = (value >>> 24) - this[offset + 2] = (value >>> 16) - this[offset + 1] = (value >>> 8) - this[offset] = (value & 0xff) - 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) - this[offset] = (value >>> 24) - this[offset + 1] = (value >>> 16) - this[offset + 2] = (value >>> 8) - this[offset + 3] = (value & 0xff) - 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) +Object.defineProperty(Writable.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; } +}); - var i = 0 - var mul = 1 - var sub = 0 - this[offset] = value & 0xFF - while (++i < byteLength && (mul *= 0x100)) { - if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { - sub = 1 +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; } - this[offset + i] = ((value / mul) >> 0) - sub & 0xFF } + var len = state.objectMode ? 1 : chunk.length; - 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) + state.length += len; - checkInt(this, value, offset, byteLength, limit - 1, -limit) - } + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) state.needDrain = true; - var i = byteLength - 1 - var mul = 1 - var sub = 0 - this[offset + i] = value & 0xFF - while (--i >= 0 && (mul *= 0x100)) { - if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { - sub = 1 + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; } - this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); } - 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 (value < 0) value = 0xff + value + 1 - this[offset] = (value & 0xff) - return offset + 1 + return ret; } -Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { - value = +value - offset = offset >>> 0 - if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) - this[offset] = (value & 0xff) - this[offset + 1] = (value >>> 8) - return offset + 2 +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; } -Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { - value = +value - offset = offset >>> 0 - if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) - this[offset] = (value >>> 8) - this[offset + 1] = (value & 0xff) - return offset + 2 -} +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; -Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { - value = +value - offset = offset >>> 0 - if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) - this[offset] = (value & 0xff) - this[offset + 1] = (value >>> 8) - this[offset + 2] = (value >>> 16) - this[offset + 3] = (value >>> 24) - return offset + 4 + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + pna.nextTick(cb, er); + // this can emit finish, and it will always happen + // after error + pna.nextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + // this can emit finish, but finish must + // always follow error + finishMaybe(stream, state); + } } -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 - this[offset] = (value >>> 24) - this[offset + 1] = (value >>> 16) - this[offset + 2] = (value >>> 8) - this[offset + 3] = (value & 0xff) - return offset + 4 +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; } -function checkIEEE754 (buf, value, offset, ext, max, min) { - if (offset + ext > buf.length) throw new RangeError('Index out of range') - if (offset < 0) throw new RangeError('Index out of range') -} +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; -function writeFloat (buf, value, offset, littleEndian, noAssert) { - value = +value - offset = offset >>> 0 - if (!noAssert) { - checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) - } - ieee754.write(buf, value, offset, littleEndian, 23, 4) - return offset + 4 -} + onwriteStateUpdate(state); -Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { - return writeFloat(this, value, offset, true, noAssert) -} + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state); -Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { - return writeFloat(this, value, offset, false, noAssert) -} + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } -function writeDouble (buf, value, offset, littleEndian, noAssert) { - value = +value - offset = offset >>> 0 - if (!noAssert) { - checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + if (sync) { + /**/ + asyncWrite(afterWrite, stream, state, finished, cb); + /**/ + } else { + afterWrite(stream, state, finished, cb); + } } - 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) +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); } -Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { - return writeDouble(this, value, offset, false, noAssert) +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } } -// 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 +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; - // 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') + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; - // Are we oob? - if (end > this.length) end = this.length - if (target.length - targetStart < end - start) { - end = target.length - targetStart + start - } + var count = 0; + var allBuffers = true; + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + buffer.allBuffers = allBuffers; - var len = end - start - var i + doWrite(stream, state, true, state.length, buffer, '', holder.finish); - 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) { - // ascending copy from start - for (i = 0; i < len; ++i) { - target[i + targetStart] = this[i + start] + // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + state.pendingcb++; + state.lastBufferedRequest = null; + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); } + state.bufferedRequestCount = 0; } else { - Uint8Array.prototype.set.call( - target, - this.subarray(start, start + len), - targetStart - ) - } - - return len -} + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; -// Usage: -// buffer.fill(number[, offset[, end]]) -// buffer.fill(buffer[, offset[, end]]) -// buffer.fill(string[, offset[, end]][, encoding]) -Buffer.prototype.fill = function fill (val, start, end, encoding) { - // Handle string cases: - if (typeof val === 'string') { - if (typeof start === 'string') { - encoding = start - start = 0 - end = this.length - } else if (typeof end === 'string') { - encoding = end - end = this.length - } - if (val.length === 1) { - var code = val.charCodeAt(0) - if (code < 256) { - val = code + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + state.bufferedRequestCount--; + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + break; } } - if (encoding !== undefined && typeof encoding !== 'string') { - throw new TypeError('encoding must be a string') - } - if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { - throw new TypeError('Unknown encoding: ' + encoding) - } - } else if (typeof val === 'number') { - val = val & 255 - } - // Invalid ranges are not set to a default, so can range check early. - if (start < 0 || this.length < start || this.length < end) { - throw new RangeError('Out of range index') + if (entry === null) state.lastBufferedRequest = null; } - if (end <= start) { - return this + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new Error('_write() is not implemented')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; } - start = start >>> 0 - end = end === undefined ? this.length : end >>> 0 + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); - if (!val) val = 0 + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } - var i - if (typeof val === 'number') { - for (i = start; i < end; ++i) { - this[i] = val + // ignore unnecessary end() calls. + if (!state.ending && !state.finished) endWritable(this, state, cb); +}; + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + if (err) { + stream.emit('error', err); } - } else { - var bytes = Buffer.isBuffer(val) - ? val - : new Buffer(val, encoding) - var len = bytes.length - for (i = 0; i < end - start; ++i) { - this[i + start] = bytes[i % len] + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function') { + state.pendingcb++; + state.finalCalled = true; + pna.nextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); } } - - return this } -// HELPER FUNCTIONS -// ================ - -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 = str.trim().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 + '=' +function finishMaybe(stream, state) { + var need = needFinish(state); + if (need) { + prefinish(stream, state); + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + } } - return str + return need; } -function toHex (n) { - if (n < 16) return '0' + n.toString(16) - return n.toString(16) +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) pna.nextTick(cb);else stream.once('finish', cb); + } + state.ended = true; + stream.writable = false; } -function utf8ToBytes (string, units) { - units = units || Infinity - var codePoint - var length = string.length - var leadSurrogate = null - var bytes = [] +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + if (state.corkedRequestsFree) { + state.corkedRequestsFree.next = corkReq; + } else { + state.corkedRequestsFree = corkReq; + } +} - for (var i = 0; i < length; ++i) { - codePoint = string.charCodeAt(i) +Object.defineProperty(Writable.prototype, 'destroyed', { + get: function () { + if (this._writableState === undefined) { + return false; + } + return this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } - // 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 - } + // backward compatibility, the user is explicitly + // managing destroyed + this._writableState.destroyed = value; + } +}); - // valid lead - leadSurrogate = codePoint +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; +Writable.prototype._destroy = function (err, cb) { + this.end(); + cb(err); +}; +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate) +},{"./_stream_duplex":20,"./internal/streams/destroy":26,"./internal/streams/stream":27,"_process":15,"core-util-is":6,"inherits":10,"process-nextick-args":14,"safe-buffer":29,"timers":35,"util-deprecate":39}],25:[function(require,module,exports){ +'use strict'; - continue - } +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - // 2 leads in a row - if (codePoint < 0xDC00) { - if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) - leadSurrogate = codePoint - continue - } +var Buffer = require('safe-buffer').Buffer; +var util = require('util'); - // 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) - } +function copyBuffer(src, target, offset) { + src.copy(target, offset); +} - leadSurrogate = null +module.exports = function () { + function BufferList() { + _classCallCheck(this, BufferList); - // 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') - } + this.head = null; + this.tail = null; + this.length = 0; } - return bytes -} + BufferList.prototype.push = function push(v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + }; -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 -} + BufferList.prototype.unshift = function unshift(v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + }; -function utf16leToBytes (str, units) { - var c, hi, lo - var byteArray = [] - for (var i = 0; i < str.length; ++i) { - if ((units -= 2) < 0) break + BufferList.prototype.shift = function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + }; - c = str.charCodeAt(i) - hi = c >> 8 - lo = c % 256 - byteArray.push(lo) - byteArray.push(hi) - } + BufferList.prototype.clear = function clear() { + this.head = this.tail = null; + this.length = 0; + }; - return byteArray -} + BufferList.prototype.join = function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; + }; -function base64ToBytes (str) { - return base64.toByteArray(base64clean(str)) -} + BufferList.prototype.concat = function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + if (this.length === 1) return this.head.data; + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + return ret; + }; -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 -} + return BufferList; +}(); -// Node 0.10 supports `ArrayBuffer` but lacks `ArrayBuffer.isView` -function isArrayBufferView (obj) { - return (typeof ArrayBuffer.isView === 'function') && ArrayBuffer.isView(obj) +if (util && util.inspect && util.inspect.custom) { + module.exports.prototype[util.inspect.custom] = function () { + var obj = util.inspect({ length: this.length }); + return this.constructor.name + ' ' + obj; + }; } +},{"safe-buffer":29,"util":3}],26:[function(require,module,exports){ +'use strict'; -function numberIsNaN (obj) { - return obj !== obj // eslint-disable-line no-self-compare -} +/**/ -},{"base64-js":32,"ieee754":56}],47:[function(require,module,exports){ -module.exports = ChunkStoreWriteStream +var pna = require('process-nextick-args'); +/**/ -var BlockStream = require('block-stream2') -var inherits = require('inherits') -var stream = require('readable-stream') +// undocumented cb() API, needed for core, not for public API +function destroy(err, cb) { + var _this = this; -inherits(ChunkStoreWriteStream, stream.Writable) + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; -function ChunkStoreWriteStream (store, chunkLength, opts) { - var self = this - if (!(self instanceof ChunkStoreWriteStream)) { - return new ChunkStoreWriteStream(store, chunkLength, opts) + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err && (!this._writableState || !this._writableState.errorEmitted)) { + pna.nextTick(emitErrorNT, this, err); + } + return this; } - stream.Writable.call(self, opts) - if (!opts) opts = {} - if (!store || !store.put || !store.get) { - throw new Error('First argument must be an abstract-chunk-store compliant store') + // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + if (this._readableState) { + this._readableState.destroyed = true; } - chunkLength = Number(chunkLength) - if (!chunkLength) throw new Error('Second argument must be a chunk length') - self._blockstream = new BlockStream(chunkLength, { zeroPadding: false }) + // if this is a duplex stream mark the writable part as destroyed as well + if (this._writableState) { + this._writableState.destroyed = true; + } - self._blockstream - .on('data', onData) - .on('error', function (err) { self.destroy(err) }) + this._destroy(err || null, function (err) { + if (!cb && err) { + pna.nextTick(emitErrorNT, _this, err); + if (_this._writableState) { + _this._writableState.errorEmitted = true; + } + } else if (cb) { + cb(err); + } + }); - var index = 0 - function onData (chunk) { - if (self.destroyed) return - store.put(index, chunk) - index += 1 + return this; +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; } - self.on('finish', function () { this._blockstream.end() }) + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } } -ChunkStoreWriteStream.prototype._write = function (chunk, encoding, callback) { - this._blockstream.write(chunk, encoding, callback) +function emitErrorNT(self, err) { + self.emit('error', err); } -ChunkStoreWriteStream.prototype.destroy = function (err) { - if (this.destroyed) return - this.destroyed = true - - if (err) this.emit('error', err) - this.emit('close') -} +module.exports = { + destroy: destroy, + undestroy: undestroy +}; +},{"process-nextick-args":14}],27:[function(require,module,exports){ +module.exports = require('events').EventEmitter; -},{"block-stream2":44,"inherits":58,"readable-stream":92}],48:[function(require,module,exports){ -var abs = Math.abs +},{"events":7}],28:[function(require,module,exports){ +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = exports; +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); -module.exports = closest +},{"./lib/_stream_duplex.js":20,"./lib/_stream_passthrough.js":21,"./lib/_stream_readable.js":22,"./lib/_stream_transform.js":23,"./lib/_stream_writable.js":24}],29:[function(require,module,exports){ +/* eslint-disable node/no-deprecated-api */ +var buffer = require('buffer') +var Buffer = buffer.Buffer -function closest (n, arr, rndx) { - var i, ndx, diff, best = Infinity - var low = 0, high = arr.length - 1 - while (low <= high) { - i = low + (high - low >> 1) - diff = arr[i] - n - diff < 0 ? low = i + 1 : - diff > 0 ? high = i - 1 : void 0 - diff = abs(diff) - if (diff < best) best = diff, ndx = i - if (arr[i] === n) break +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] } - return rndx ? ndx : arr[ndx] +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer } -},{}],49:[function(require,module,exports){ -(function (Buffer){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) +} -// NOTE: These type checking functions intentionally don't use `instanceof` -// because it is fragile and can be easily faked with `Object.create()`. +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) -function isArray(arg) { - if (Array.isArray) { - return Array.isArray(arg); +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') } - return objectToString(arg) === '[object Array]'; + return Buffer(arg, encodingOrOffset, length) } -exports.isArray = isArray; -function isBoolean(arg) { - return typeof arg === 'boolean'; +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) + } + return buf } -exports.isBoolean = isBoolean; -function isNull(arg) { - return arg === null; +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) } -exports.isNull = isNull; -function isNullOrUndefined(arg) { - return arg == null; +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return buffer.SlowBuffer(size) } -exports.isNullOrUndefined = isNullOrUndefined; -function isNumber(arg) { - return typeof arg === 'number'; -} -exports.isNumber = isNumber; +},{"buffer":4}],30:[function(require,module,exports){ +(function (global){ +var ClientRequest = require('./lib/request') +var response = require('./lib/response') +var extend = require('xtend') +var statusCodes = require('builtin-status-codes') +var url = require('url') -function isString(arg) { - return typeof arg === 'string'; -} -exports.isString = isString; +var http = exports -function isSymbol(arg) { - return typeof arg === 'symbol'; -} -exports.isSymbol = isSymbol; +http.request = function (opts, cb) { + if (typeof opts === 'string') + opts = url.parse(opts) + else + opts = extend(opts) -function isUndefined(arg) { - return arg === void 0; -} -exports.isUndefined = isUndefined; + // Normally, the page is loaded from http or https, so not specifying a protocol + // will result in a (valid) protocol-relative url. However, this won't work if + // the protocol is something else, like 'file:' + var defaultProtocol = global.location.protocol.search(/^https?:$/) === -1 ? 'http:' : '' -function isRegExp(re) { - return objectToString(re) === '[object RegExp]'; -} -exports.isRegExp = isRegExp; + var protocol = opts.protocol || defaultProtocol + var host = opts.hostname || opts.host + var port = opts.port + var path = opts.path || '/' -function isObject(arg) { - return typeof arg === 'object' && arg !== null; -} -exports.isObject = isObject; + // Necessary for IPv6 addresses + if (host && host.indexOf(':') !== -1) + host = '[' + host + ']' -function isDate(d) { - return objectToString(d) === '[object Date]'; -} -exports.isDate = isDate; + // This may be a relative url. The browser should always be able to interpret it correctly. + opts.url = (host ? (protocol + '//' + host) : '') + (port ? ':' + port : '') + path + opts.method = (opts.method || 'GET').toUpperCase() + opts.headers = opts.headers || {} -function isError(e) { - return (objectToString(e) === '[object Error]' || e instanceof Error); -} -exports.isError = isError; + // Also valid opts.auth, opts.mode -function isFunction(arg) { - return typeof arg === 'function'; + var req = new ClientRequest(opts) + if (cb) + req.on('response', cb) + return req } -exports.isFunction = isFunction; -function isPrimitive(arg) { - return arg === null || - typeof arg === 'boolean' || - typeof arg === 'number' || - typeof arg === 'string' || - typeof arg === 'symbol' || // ES6 symbol - typeof arg === 'undefined'; +http.get = function get (opts, cb) { + var req = http.request(opts, cb) + req.end() + return req } -exports.isPrimitive = isPrimitive; -exports.isBuffer = Buffer.isBuffer; +http.ClientRequest = ClientRequest +http.IncomingMessage = response.IncomingMessage -function objectToString(o) { - return Object.prototype.toString.call(o); -} +http.Agent = function () {} +http.Agent.defaultMaxSockets = 4 -}).call(this,{"isBuffer":require("../../../../../../../../../usr/local/lib/node_modules/browserify/node_modules/is-buffer/index.js")}) -},{"../../../../../../../../../usr/local/lib/node_modules/browserify/node_modules/is-buffer/index.js":166}],50:[function(require,module,exports){ -(function (process,global,Buffer){ -module.exports = createTorrent -module.exports.parseInput = parseInput +http.globalAgent = new http.Agent() -module.exports.announceList = [ - [ 'udp://tracker.leechers-paradise.org:6969' ], - [ 'udp://tracker.coppersurfer.tk:6969' ], - [ 'udp://tracker.opentrackr.org:1337' ], - [ 'udp://explodie.org:6969' ], - [ 'udp://tracker.empire-js.us:1337' ], - [ 'wss://tracker.btorrent.xyz' ], - [ 'wss://tracker.openwebtorrent.com' ], - [ 'wss://tracker.fastcast.nz' ] -] +http.STATUS_CODES = statusCodes -var bencode = require('bencode') -var BlockStream = require('block-stream2') -var calcPieceLength = require('piece-length') -var corePath = require('path') -var extend = require('xtend') -var FileReadStream = require('filestream/read') -var flatten = require('flatten') -var fs = require('fs') -var isFile = require('is-file') -var junk = require('junk') -var MultiStream = require('multistream') -var once = require('once') -var parallel = require('run-parallel') -var sha1 = require('simple-sha1') -var stream = require('readable-stream') +http.METHODS = [ + 'CHECKOUT', + 'CONNECT', + 'COPY', + 'DELETE', + 'GET', + 'HEAD', + 'LOCK', + 'M-SEARCH', + 'MERGE', + 'MKACTIVITY', + 'MKCOL', + 'MOVE', + 'NOTIFY', + 'OPTIONS', + 'PATCH', + 'POST', + 'PROPFIND', + 'PROPPATCH', + 'PURGE', + 'PUT', + 'REPORT', + 'SEARCH', + 'SUBSCRIBE', + 'TRACE', + 'UNLOCK', + 'UNSUBSCRIBE' +] +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./lib/request":32,"./lib/response":33,"builtin-status-codes":5,"url":37,"xtend":40}],31:[function(require,module,exports){ +(function (global){ +exports.fetch = isFunction(global.fetch) && isFunction(global.ReadableStream) -/** - * Create a torrent. - * @param {string|File|FileList|Buffer|Stream|Array.} input - * @param {Object} opts - * @param {string=} opts.name - * @param {Date=} opts.creationDate - * @param {string=} opts.comment - * @param {string=} opts.createdBy - * @param {boolean|number=} opts.private - * @param {number=} opts.pieceLength - * @param {Array.>=} opts.announceList - * @param {Array.=} opts.urlList - * @param {function} cb - * @return {Buffer} buffer of .torrent file data - */ -function createTorrent (input, opts, cb) { - if (typeof opts === 'function') return createTorrent(input, null, opts) - opts = opts ? extend(opts) : {} +exports.writableStream = isFunction(global.WritableStream) - _parseInput(input, opts, function (err, files, singleFileTorrent) { - if (err) return cb(err) - opts.singleFileTorrent = singleFileTorrent - onFiles(files, opts, cb) - }) -} +exports.abortController = isFunction(global.AbortController) -function parseInput (input, opts, cb) { - if (typeof opts === 'function') return parseInput(input, null, opts) - opts = opts ? extend(opts) : {} - _parseInput(input, opts, cb) -} +exports.blobConstructor = false +try { + new Blob([new ArrayBuffer(1)]) + exports.blobConstructor = true +} catch (e) {} -/** - * Parse input file and return file information. - */ -function _parseInput (input, opts, cb) { - if (Array.isArray(input) && input.length === 0) throw new Error('invalid input type') +// The xhr request to example.com may violate some restrictive CSP configurations, +// so if we're running in a browser that supports `fetch`, avoid calling getXHR() +// and assume support for certain features below. +var xhr +function getXHR () { + // Cache the xhr value + if (xhr !== undefined) return xhr - if (isFileList(input)) input = Array.prototype.slice.call(input) - if (!Array.isArray(input)) input = [ input ] + if (global.XMLHttpRequest) { + xhr = new global.XMLHttpRequest() + // If XDomainRequest is available (ie only, where xhr might not work + // cross domain), use the page location. Otherwise use example.com + // Note: this doesn't actually make an http request. + try { + xhr.open('GET', global.XDomainRequest ? '/' : 'https://example.com') + } catch(e) { + xhr = null + } + } else { + // Service workers don't have XHR + xhr = null + } + return xhr +} - // In Electron, use the true file path - input = input.map(function (item) { - if (isBlob(item) && typeof item.path === 'string' && typeof fs.stat === 'function') return item.path - return item - }) +function checkTypeSupport (type) { + var xhr = getXHR() + if (!xhr) return false + try { + xhr.responseType = type + return xhr.responseType === type + } catch (e) {} + return false +} - // If there's just one file, allow the name to be set by `opts.name` - if (input.length === 1 && typeof input[0] !== 'string' && !input[0].name) input[0].name = opts.name +// For some strange reason, Safari 7.0 reports typeof global.ArrayBuffer === 'object'. +// Safari 7.1 appears to have fixed this bug. +var haveArrayBuffer = typeof global.ArrayBuffer !== 'undefined' +var haveSlice = haveArrayBuffer && isFunction(global.ArrayBuffer.prototype.slice) - var commonPrefix = null - input.forEach(function (item, i) { - if (typeof item === 'string') { - return - } +// If fetch is supported, then arraybuffer will be supported too. Skip calling +// checkTypeSupport(), since that calls getXHR(). +exports.arraybuffer = exports.fetch || (haveArrayBuffer && checkTypeSupport('arraybuffer')) - var path = item.fullPath || item.name - if (!path) { - path = 'Unknown File ' + (i + 1) - item.unknownName = true - } +// These next two tests unavoidably show warnings in Chrome. Since fetch will always +// be used if it's available, just return false for these to avoid the warnings. +exports.msstream = !exports.fetch && haveSlice && checkTypeSupport('ms-stream') +exports.mozchunkedarraybuffer = !exports.fetch && haveArrayBuffer && + checkTypeSupport('moz-chunked-arraybuffer') - item.path = path.split('/') +// If fetch is supported, then overrideMimeType will be supported too. Skip calling +// getXHR(). +exports.overrideMimeType = exports.fetch || (getXHR() ? isFunction(getXHR().overrideMimeType) : false) - // Remove initial slash - if (!item.path[0]) { - item.path.shift() - } +exports.vbArray = isFunction(global.VBArray) - if (item.path.length < 2) { // No real prefix - commonPrefix = null - } else if (i === 0 && input.length > 1) { // The first file has a prefix - commonPrefix = item.path[0] - } else if (item.path[0] !== commonPrefix) { // The prefix doesn't match - commonPrefix = null - } - }) +function isFunction (value) { + return typeof value === 'function' +} - // remove junk files - input = input.filter(function (item) { - if (typeof item === 'string') { - return true - } - var filename = item.path[item.path.length - 1] - return notHidden(filename) && junk.not(filename) - }) +xhr = null // Help gc - if (commonPrefix) { - input.forEach(function (item) { - var pathless = (Buffer.isBuffer(item) || isReadable(item)) && !item.path - if (typeof item === 'string' || pathless) return - item.path.shift() - }) - } +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],32:[function(require,module,exports){ +(function (process,global,Buffer){ +var capability = require('./capability') +var inherits = require('inherits') +var response = require('./response') +var stream = require('readable-stream') +var toArrayBuffer = require('to-arraybuffer') - if (!opts.name && commonPrefix) { - opts.name = commonPrefix - } +var IncomingMessage = response.IncomingMessage +var rStates = response.readyStates - if (!opts.name) { - // use first user-set file name - input.some(function (item) { - if (typeof item === 'string') { - opts.name = corePath.basename(item) - return true - } else if (!item.unknownName) { - opts.name = item.path[item.path.length - 1] - return true - } - }) - } +function decideMode (preferBinary, useFetch) { + if (capability.fetch && useFetch) { + return 'fetch' + } else if (capability.mozchunkedarraybuffer) { + return 'moz-chunked-arraybuffer' + } else if (capability.msstream) { + return 'ms-stream' + } else if (capability.arraybuffer && preferBinary) { + return 'arraybuffer' + } else if (capability.vbArray && preferBinary) { + return 'text:vbarray' + } else { + return 'text' + } +} - if (!opts.name) { - opts.name = 'Unnamed Torrent ' + Date.now() - } +var ClientRequest = module.exports = function (opts) { + var self = this + stream.Writable.call(self) - var numPaths = input.reduce(function (sum, item) { - return sum + Number(typeof item === 'string') - }, 0) + self._opts = opts + self._body = [] + self._headers = {} + if (opts.auth) + self.setHeader('Authorization', 'Basic ' + new Buffer(opts.auth).toString('base64')) + Object.keys(opts.headers).forEach(function (name) { + self.setHeader(name, opts.headers[name]) + }) - var isSingleFileTorrent = (input.length === 1) + var preferBinary + var useFetch = true + if (opts.mode === 'disable-fetch' || ('requestTimeout' in opts && !capability.abortController)) { + // If the use of XHR should be preferred. Not typically needed. + useFetch = false + preferBinary = true + } else if (opts.mode === 'prefer-streaming') { + // If streaming is a high priority but binary compatibility and + // the accuracy of the 'content-type' header aren't + preferBinary = false + } else if (opts.mode === 'allow-wrong-content-type') { + // If streaming is more important than preserving the 'content-type' header + preferBinary = !capability.overrideMimeType + } else if (!opts.mode || opts.mode === 'default' || opts.mode === 'prefer-fast') { + // Use binary if text streaming may corrupt data or the content-type header, or for speed + preferBinary = true + } else { + throw new Error('Invalid value for opts.mode') + } + self._mode = decideMode(preferBinary, useFetch) + self._fetchTimer = null - if (input.length === 1 && typeof input[0] === 'string') { - if (typeof fs.stat !== 'function') { - throw new Error('filesystem paths do not work in the browser') - } - // If there's a single path, verify it's a file before deciding this is a single - // file torrent - isFile(input[0], function (err, pathIsFile) { - if (err) return cb(err) - isSingleFileTorrent = pathIsFile - processInput() - }) - } else { - process.nextTick(function () { - processInput() - }) - } + self.on('finish', function () { + self._onFinish() + }) +} - function processInput () { - parallel(input.map(function (item) { - return function (cb) { - var file = {} +inherits(ClientRequest, stream.Writable) - if (isBlob(item)) { - file.getStream = getBlobStream(item) - file.length = item.size - } else if (Buffer.isBuffer(item)) { - file.getStream = getBufferStream(item) - file.length = item.length - } else if (isReadable(item)) { - file.getStream = getStreamStream(item, file) - file.length = 0 - } else if (typeof item === 'string') { - if (typeof fs.stat !== 'function') { - throw new Error('filesystem paths do not work in the browser') - } - var keepRoot = numPaths > 1 || isSingleFileTorrent - getFiles(item, keepRoot, cb) - return // early return! - } else { - throw new Error('invalid input type') - } - file.path = item.path - cb(null, file) - } - }), function (err, files) { - if (err) return cb(err) - files = flatten(files) - cb(null, files, isSingleFileTorrent) - }) - } +ClientRequest.prototype.setHeader = function (name, value) { + var self = this + var lowerName = name.toLowerCase() + // This check is not necessary, but it prevents warnings from browsers about setting unsafe + // headers. To be honest I'm not entirely sure hiding these warnings is a good thing, but + // http-browserify did it, so I will too. + if (unsafeHeaders.indexOf(lowerName) !== -1) + return + + self._headers[lowerName] = { + name: name, + value: value + } } -function getFiles (path, keepRoot, cb) { - traversePath(path, getFileInfo, function (err, files) { - if (err) return cb(err) - - if (Array.isArray(files)) files = flatten(files) - else files = [ files ] - - path = corePath.normalize(path) - if (keepRoot) { - path = path.slice(0, path.lastIndexOf(corePath.sep) + 1) - } - if (path[path.length - 1] !== corePath.sep) path += corePath.sep - - files.forEach(function (file) { - file.getStream = getFilePathStream(file.path) - file.path = file.path.replace(path, '').split(corePath.sep) - }) - cb(null, files) - }) +ClientRequest.prototype.getHeader = function (name) { + var header = this._headers[name.toLowerCase()] + if (header) + return header.value + return null } -function getFileInfo (path, cb) { - cb = once(cb) - fs.stat(path, function (err, stat) { - if (err) return cb(err) - var info = { - length: stat.size, - path: path - } - cb(null, info) - }) +ClientRequest.prototype.removeHeader = function (name) { + var self = this + delete self._headers[name.toLowerCase()] } -function traversePath (path, fn, cb) { - fs.stat(path, function (err, stats) { - if (err) return cb(err) - if (stats.isDirectory()) { - fs.readdir(path, function (err, entries) { - if (err) return cb(err) - parallel(entries.filter(notHidden).filter(junk.not).map(function (entry) { - return function (cb) { - traversePath(corePath.join(path, entry), fn, cb) - } - }), cb) - }) - } else if (stats.isFile()) { - fn(path, cb) - } - // Ignore other types (not a file or directory) - }) -} +ClientRequest.prototype._onFinish = function () { + var self = this -function notHidden (file) { - return file[0] !== '.' -} + if (self._destroyed) + return + var opts = self._opts -function getPieceList (files, pieceLength, cb) { - cb = once(cb) - var pieces = [] - var length = 0 + var headersObj = self._headers + var body = null + if (opts.method !== 'GET' && opts.method !== 'HEAD') { + if (capability.arraybuffer) { + body = toArrayBuffer(Buffer.concat(self._body)) + } else if (capability.blobConstructor) { + body = new global.Blob(self._body.map(function (buffer) { + return toArrayBuffer(buffer) + }), { + type: (headersObj['content-type'] || {}).value || '' + }) + } else { + // get utf8 string + body = Buffer.concat(self._body).toString() + } + } - var streams = files.map(function (file) { - return file.getStream - }) + // create flattened list of headers + var headersList = [] + Object.keys(headersObj).forEach(function (keyName) { + var name = headersObj[keyName].name + var value = headersObj[keyName].value + if (Array.isArray(value)) { + value.forEach(function (v) { + headersList.push([name, v]) + }) + } else { + headersList.push([name, value]) + } + }) - var remainingHashes = 0 - var pieceNum = 0 - var ended = false + if (self._mode === 'fetch') { + var signal = null + var fetchTimer = null + if (capability.abortController) { + var controller = new AbortController() + signal = controller.signal + self._fetchAbortController = controller + + if ('requestTimeout' in opts && opts.requestTimeout !== 0) { + self._fetchTimer = global.setTimeout(function () { + self.emit('requestTimeout') + if (self._fetchAbortController) + self._fetchAbortController.abort() + }, opts.requestTimeout) + } + } - var multistream = new MultiStream(streams) - var blockstream = new BlockStream(pieceLength, { zeroPadding: false }) + global.fetch(self._opts.url, { + method: self._opts.method, + headers: headersList, + body: body || undefined, + mode: 'cors', + credentials: opts.withCredentials ? 'include' : 'same-origin', + signal: signal + }).then(function (response) { + self._fetchResponse = response + self._connect() + }, function (reason) { + global.clearTimeout(self._fetchTimer) + if (!self._destroyed) + self.emit('error', reason) + }) + } else { + var xhr = self._xhr = new global.XMLHttpRequest() + try { + xhr.open(self._opts.method, self._opts.url, true) + } catch (err) { + process.nextTick(function () { + self.emit('error', err) + }) + return + } - multistream.on('error', onError) + // Can't set responseType on really old browsers + if ('responseType' in xhr) + xhr.responseType = self._mode.split(':')[0] - multistream - .pipe(blockstream) - .on('data', onData) - .on('end', onEnd) - .on('error', onError) + if ('withCredentials' in xhr) + xhr.withCredentials = !!opts.withCredentials - function onData (chunk) { - length += chunk.length + if (self._mode === 'text' && 'overrideMimeType' in xhr) + xhr.overrideMimeType('text/plain; charset=x-user-defined') - var i = pieceNum - sha1(chunk, function (hash) { - pieces[i] = hash - remainingHashes -= 1 - maybeDone() - }) - remainingHashes += 1 - pieceNum += 1 - } + if ('requestTimeout' in opts) { + xhr.timeout = opts.requestTimeout + xhr.ontimeout = function () { + self.emit('requestTimeout') + } + } - function onEnd () { - ended = true - maybeDone() - } + headersList.forEach(function (header) { + xhr.setRequestHeader(header[0], header[1]) + }) - function onError (err) { - cleanup() - cb(err) - } + self._response = null + xhr.onreadystatechange = function () { + switch (xhr.readyState) { + case rStates.LOADING: + case rStates.DONE: + self._onXHRProgress() + break + } + } + // Necessary for streaming in Firefox, since xhr.response is ONLY defined + // in onprogress, not in onreadystatechange with xhr.readyState = 3 + if (self._mode === 'moz-chunked-arraybuffer') { + xhr.onprogress = function () { + self._onXHRProgress() + } + } - function cleanup () { - multistream.removeListener('error', onError) - blockstream.removeListener('data', onData) - blockstream.removeListener('end', onEnd) - blockstream.removeListener('error', onError) - } + xhr.onerror = function () { + if (self._destroyed) + return + self.emit('error', new Error('XHR error')) + } - function maybeDone () { - if (ended && remainingHashes === 0) { - cleanup() - cb(null, Buffer.from(pieces.join(''), 'hex'), length) - } - } + try { + xhr.send(body) + } catch (err) { + process.nextTick(function () { + self.emit('error', err) + }) + return + } + } } -function onFiles (files, opts, cb) { - var announceList = opts.announceList +/** + * Checks if xhr.status is readable and non-zero, indicating no error. + * Even though the spec says it should be available in readyState 3, + * accessing it throws an exception in IE8 + */ +function statusValid (xhr) { + try { + var status = xhr.status + return (status !== null && status !== 0) + } catch (e) { + return false + } +} - if (!announceList) { - if (typeof opts.announce === 'string') announceList = [ [ opts.announce ] ] - else if (Array.isArray(opts.announce)) { - announceList = opts.announce.map(function (u) { return [ u ] }) - } - } +ClientRequest.prototype._onXHRProgress = function () { + var self = this - if (!announceList) announceList = [] + if (!statusValid(self._xhr) || self._destroyed) + return - if (global.WEBTORRENT_ANNOUNCE) { - if (typeof global.WEBTORRENT_ANNOUNCE === 'string') { - announceList.push([ [ global.WEBTORRENT_ANNOUNCE ] ]) - } else if (Array.isArray(global.WEBTORRENT_ANNOUNCE)) { - announceList = announceList.concat(global.WEBTORRENT_ANNOUNCE.map(function (u) { - return [ u ] - })) - } - } + if (!self._response) + self._connect() - // When no trackers specified, use some reasonable defaults - if (opts.announce === undefined && opts.announceList === undefined) { - announceList = announceList.concat(module.exports.announceList) - } + self._response._onXHRProgress() +} - if (typeof opts.urlList === 'string') opts.urlList = [ opts.urlList ] +ClientRequest.prototype._connect = function () { + var self = this - var torrent = { - info: { - name: opts.name - }, - 'creation date': Math.ceil((Number(opts.creationDate) || Date.now()) / 1000), - encoding: 'UTF-8' - } - - if (announceList.length !== 0) { - torrent.announce = announceList[0][0] - torrent['announce-list'] = announceList - } - - if (opts.comment !== undefined) torrent.comment = opts.comment - - if (opts.createdBy !== undefined) torrent['created by'] = opts.createdBy - - if (opts.private !== undefined) torrent.info.private = Number(opts.private) - - // "ssl-cert" key is for SSL torrents, see: - // - http://blog.libtorrent.org/2012/01/bittorrent-over-ssl/ - // - http://www.libtorrent.org/manual-ref.html#ssl-torrents - // - http://www.libtorrent.org/reference-Create_Torrents.html - if (opts.sslCert !== undefined) torrent.info['ssl-cert'] = opts.sslCert - - if (opts.urlList !== undefined) torrent['url-list'] = opts.urlList - - var pieceLength = opts.pieceLength || calcPieceLength(files.reduce(sumLength, 0)) - torrent.info['piece length'] = pieceLength - - getPieceList(files, pieceLength, function (err, pieces, torrentLength) { - if (err) return cb(err) - torrent.info.pieces = pieces - - files.forEach(function (file) { - delete file.getStream - }) - - if (opts.singleFileTorrent) { - torrent.info.length = torrentLength - } else { - torrent.info.files = files - } + if (self._destroyed) + return - cb(null, bencode.encode(torrent)) - }) -} + self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode, self._fetchTimer) + self._response.on('error', function(err) { + self.emit('error', err) + }) -/** - * Accumulator to sum file lengths - * @param {number} sum - * @param {Object} file - * @return {number} - */ -function sumLength (sum, file) { - return sum + file.length + self.emit('response', self._response) } -/** - * Check if `obj` is a W3C `Blob` object (which `File` inherits from) - * @param {*} obj - * @return {boolean} - */ -function isBlob (obj) { - return typeof Blob !== 'undefined' && obj instanceof Blob -} +ClientRequest.prototype._write = function (chunk, encoding, cb) { + var self = this -/** - * Check if `obj` is a W3C `FileList` object - * @param {*} obj - * @return {boolean} - */ -function isFileList (obj) { - return typeof FileList !== 'undefined' && obj instanceof FileList + self._body.push(chunk) + cb() } -/** - * Check if `obj` is a node Readable stream - * @param {*} obj - * @return {boolean} - */ -function isReadable (obj) { - return typeof obj === 'object' && obj != null && typeof obj.pipe === 'function' +ClientRequest.prototype.abort = ClientRequest.prototype.destroy = function () { + var self = this + self._destroyed = true + global.clearTimeout(self._fetchTimer) + if (self._response) + self._response._destroyed = true + if (self._xhr) + self._xhr.abort() + else if (self._fetchAbortController) + self._fetchAbortController.abort() } -/** - * Convert a `File` to a lazy readable stream. - * @param {File|Blob} file - * @return {function} - */ -function getBlobStream (file) { - return function () { - return new FileReadStream(file) - } -} +ClientRequest.prototype.end = function (data, encoding, cb) { + var self = this + if (typeof data === 'function') { + cb = data + data = undefined + } -/** - * Convert a `Buffer` to a lazy readable stream. - * @param {Buffer} buffer - * @return {function} - */ -function getBufferStream (buffer) { - return function () { - var s = new stream.PassThrough() - s.end(buffer) - return s - } + stream.Writable.prototype.end.call(self, data, encoding, cb) } -/** - * Convert a file path to a lazy readable stream. - * @param {string} path - * @return {function} - */ -function getFilePathStream (path) { - return function () { - return fs.createReadStream(path) - } -} +ClientRequest.prototype.flushHeaders = function () {} +ClientRequest.prototype.setTimeout = function () {} +ClientRequest.prototype.setNoDelay = function () {} +ClientRequest.prototype.setSocketKeepAlive = function () {} -/** - * Convert a readable stream to a lazy readable stream. Adds instrumentation to track - * the number of bytes in the stream and set `file.length`. - * - * @param {Stream} stream - * @param {Object} file - * @return {function} - */ -function getStreamStream (readable, file) { - return function () { - var counter = new stream.Transform() - counter._transform = function (buf, enc, done) { - file.length += buf.length - this.push(buf) - done() - } - readable.pipe(counter) - return counter - } -} +// Taken from http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader%28%29-method +var unsafeHeaders = [ + 'accept-charset', + 'accept-encoding', + 'access-control-request-headers', + 'access-control-request-method', + 'connection', + 'content-length', + 'cookie', + 'cookie2', + 'date', + 'dnt', + 'expect', + 'host', + 'keep-alive', + 'origin', + 'referer', + 'te', + 'trailer', + 'transfer-encoding', + 'upgrade', + 'via' +] }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) -},{"_process":170,"bencode":35,"block-stream2":44,"buffer":159,"filestream/read":53,"flatten":54,"fs":156,"is-file":60,"junk":63,"multistream":73,"once":75,"path":168,"piece-length":78,"readable-stream":92,"run-parallel":96,"simple-sha1":102,"xtend":131}],51:[function(require,module,exports){ -module.exports = function () { - for (var i = 0; i < arguments.length; i++) { - if (arguments[i] !== undefined) return arguments[i]; - } -}; - -},{}],52:[function(require,module,exports){ -var once = require('once'); - -var noop = function() {}; - -var isRequest = function(stream) { - return stream.setHeader && typeof stream.abort === 'function'; -}; - -var isChildProcess = function(stream) { - return stream.stdio && Array.isArray(stream.stdio) && stream.stdio.length === 3 -}; - -var eos = function(stream, opts, callback) { - if (typeof opts === 'function') return eos(stream, null, opts); - if (!opts) opts = {}; +},{"./capability":31,"./response":33,"_process":15,"buffer":4,"inherits":10,"readable-stream":28,"to-arraybuffer":36}],33:[function(require,module,exports){ +(function (process,global,Buffer){ +var capability = require('./capability') +var inherits = require('inherits') +var stream = require('readable-stream') - callback = once(callback || noop); +var rStates = exports.readyStates = { + UNSENT: 0, + OPENED: 1, + HEADERS_RECEIVED: 2, + LOADING: 3, + DONE: 4 +} - var ws = stream._writableState; - var rs = stream._readableState; - var readable = opts.readable || (opts.readable !== false && stream.readable); - var writable = opts.writable || (opts.writable !== false && stream.writable); +var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, fetchTimer) { + var self = this + stream.Readable.call(self) - var onlegacyfinish = function() { - if (!stream.writable) onfinish(); - }; + self._mode = mode + self.headers = {} + self.rawHeaders = [] + self.trailers = {} + self.rawTrailers = [] - var onfinish = function() { - writable = false; - if (!readable) callback.call(stream); - }; + // Fake the 'close' event, but only once 'end' fires + self.on('end', function () { + // The nextTick is necessary to prevent the 'request' module from causing an infinite loop + process.nextTick(function () { + self.emit('close') + }) + }) - var onend = function() { - readable = false; - if (!writable) callback.call(stream); - }; + if (mode === 'fetch') { + self._fetchResponse = response - var onexit = function(exitCode) { - callback.call(stream, exitCode ? new Error('exited with error code: ' + exitCode) : null); - }; + self.url = response.url + self.statusCode = response.status + self.statusMessage = response.statusText + + response.headers.forEach(function (header, key){ + self.headers[key.toLowerCase()] = header + self.rawHeaders.push(key, header) + }) - var onclose = function() { - if (readable && !(rs && rs.ended)) return callback.call(stream, new Error('premature close')); - if (writable && !(ws && ws.ended)) return callback.call(stream, new Error('premature close')); - }; + if (capability.writableStream) { + var writable = new WritableStream({ + write: function (chunk) { + return new Promise(function (resolve, reject) { + if (self._destroyed) { + reject() + } else if(self.push(new Buffer(chunk))) { + resolve() + } else { + self._resumeFetch = resolve + } + }) + }, + close: function () { + global.clearTimeout(fetchTimer) + if (!self._destroyed) + self.push(null) + }, + abort: function (err) { + if (!self._destroyed) + self.emit('error', err) + } + }) - var onrequest = function() { - stream.req.on('finish', onfinish); - }; - - if (isRequest(stream)) { - stream.on('complete', onfinish); - stream.on('abort', onclose); - if (stream.req) onrequest(); - else stream.on('request', onrequest); - } else if (writable && !ws) { // legacy streams - stream.on('end', onlegacyfinish); - stream.on('close', onlegacyfinish); - } - - if (isChildProcess(stream)) stream.on('exit', onexit); - - stream.on('end', onend); - stream.on('finish', onfinish); - if (opts.error !== false) stream.on('error', callback); - stream.on('close', onclose); + try { + response.body.pipeTo(writable).catch(function (err) { + global.clearTimeout(fetchTimer) + if (!self._destroyed) + self.emit('error', err) + }) + return + } catch (e) {} // pipeTo method isn't defined. Can't find a better way to feature test this + } + // fallback for when writableStream or pipeTo aren't available + var reader = response.body.getReader() + function read () { + reader.read().then(function (result) { + if (self._destroyed) + return + if (result.done) { + global.clearTimeout(fetchTimer) + self.push(null) + return + } + self.push(new Buffer(result.value)) + read() + }).catch(function (err) { + global.clearTimeout(fetchTimer) + if (!self._destroyed) + self.emit('error', err) + }) + } + read() + } else { + self._xhr = xhr + self._pos = 0 - return function() { - stream.removeListener('complete', onfinish); - stream.removeListener('abort', onclose); - stream.removeListener('request', onrequest); - if (stream.req) stream.req.removeListener('finish', onfinish); - stream.removeListener('end', onlegacyfinish); - stream.removeListener('close', onlegacyfinish); - stream.removeListener('finish', onfinish); - stream.removeListener('exit', onexit); - stream.removeListener('end', onend); - stream.removeListener('error', callback); - stream.removeListener('close', onclose); - }; -}; + self.url = xhr.responseURL + self.statusCode = xhr.status + self.statusMessage = xhr.statusText + var headers = xhr.getAllResponseHeaders().split(/\r?\n/) + headers.forEach(function (header) { + var matches = header.match(/^([^:]+):\s*(.*)/) + if (matches) { + var key = matches[1].toLowerCase() + if (key === 'set-cookie') { + if (self.headers[key] === undefined) { + self.headers[key] = [] + } + self.headers[key].push(matches[2]) + } else if (self.headers[key] !== undefined) { + self.headers[key] += ', ' + matches[2] + } else { + self.headers[key] = matches[2] + } + self.rawHeaders.push(matches[1], matches[2]) + } + }) -module.exports = eos; + self._charset = 'x-user-defined' + if (!capability.overrideMimeType) { + var mimeType = self.rawHeaders['mime-type'] + if (mimeType) { + var charsetMatch = mimeType.match(/;\s*charset=([^;])(;|$)/) + if (charsetMatch) { + self._charset = charsetMatch[1].toLowerCase() + } + } + if (!self._charset) + self._charset = 'utf-8' // best guess + } + } +} -},{"once":75}],53:[function(require,module,exports){ -var Readable = require('readable-stream').Readable; -var inherits = require('inherits'); -var reExtension = /^.*\.(\w+)$/; -var toBuffer = require('typedarray-to-buffer'); +inherits(IncomingMessage, stream.Readable) -function FileReadStream(file, opts) { - var readStream = this; - if (! (this instanceof FileReadStream)) { - return new FileReadStream(file, opts); - } - opts = opts || {}; +IncomingMessage.prototype._read = function () { + var self = this - // inherit readable - Readable.call(this, opts); + var resolve = self._resumeFetch + if (resolve) { + self._resumeFetch = null + resolve() + } +} - // save the read offset - this._offset = 0; - this._ready = false; - this._file = file; - this._size = file.size; - this._chunkSize = opts.chunkSize || Math.max(this._size / 1000, 200 * 1024); +IncomingMessage.prototype._onXHRProgress = function () { + var self = this - // create the reader - this.reader = new FileReader(); + var xhr = self._xhr - // generate the header blocks that we will send as part of the initial payload - this._generateHeaderBlocks(file, opts, function(err, blocks) { - // if we encountered an error, emit it - if (err) { - return readStream.emit('error', err); - } + var response = null + switch (self._mode) { + case 'text:vbarray': // For IE9 + if (xhr.readyState !== rStates.DONE) + break + try { + // This fails in IE8 + response = new global.VBArray(xhr.responseBody).toArray() + } catch (e) {} + if (response !== null) { + self.push(new Buffer(response)) + break + } + // Falls through in IE8 + case 'text': + try { // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4 + response = xhr.responseText + } catch (e) { + self._mode = 'text:vbarray' + break + } + if (response.length > self._pos) { + var newData = response.substr(self._pos) + if (self._charset === 'x-user-defined') { + var buffer = new Buffer(newData.length) + for (var i = 0; i < newData.length; i++) + buffer[i] = newData.charCodeAt(i) & 0xff - // push the header blocks out to the stream - if (Array.isArray(blocks)) { - blocks.forEach(function (block) { - readStream.push(block); - }); - } + self.push(buffer) + } else { + self.push(newData, self._charset) + } + self._pos = response.length + } + break + case 'arraybuffer': + if (xhr.readyState !== rStates.DONE || !xhr.response) + break + response = xhr.response + self.push(new Buffer(new Uint8Array(response))) + break + case 'moz-chunked-arraybuffer': // take whole + response = xhr.response + if (xhr.readyState !== rStates.LOADING || !response) + break + self.push(new Buffer(new Uint8Array(response))) + break + case 'ms-stream': + response = xhr.response + if (xhr.readyState !== rStates.LOADING) + break + var reader = new global.MSStreamReader() + reader.onprogress = function () { + if (reader.result.byteLength > self._pos) { + self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos)))) + self._pos = reader.result.byteLength + } + } + reader.onload = function () { + self.push(null) + } + // reader.onerror = ??? // TODO: this + reader.readAsArrayBuffer(response) + break + } - readStream._ready = true; - readStream.emit('_ready'); - }); + // The ms-stream case handles end separately in reader.onload() + if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') { + self.push(null) + } } -inherits(FileReadStream, Readable); -module.exports = FileReadStream; +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) +},{"./capability":31,"_process":15,"buffer":4,"inherits":10,"readable-stream":28}],34:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -FileReadStream.prototype._generateHeaderBlocks = function(file, opts, callback) { - callback(null, []); -}; +'use strict'; -FileReadStream.prototype._read = function() { - if (!this._ready) { - this.once('_ready', this._read.bind(this)); - return; - } - var readStream = this; - var reader = this.reader; +/**/ - var startOffset = this._offset; - var endOffset = this._offset + this._chunkSize; - if (endOffset > this._size) endOffset = this._size; +var Buffer = require('safe-buffer').Buffer; +/**/ - if (startOffset === this._size) { - this.destroy(); - this.push(null); - return; +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; } +}; - reader.onload = function() { - // update the stream offset - readStream._offset = endOffset; - - // get the data chunk - readStream.push(toBuffer(reader.result)); - } - reader.onerror = function() { - readStream.emit('error', reader.error); +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } } - - reader.readAsArrayBuffer(this._file.slice(startOffset, endOffset)); }; -FileReadStream.prototype.destroy = function() { - this._file = null; - if (this.reader) { - this.reader.onload = null; - this.reader.onerror = null; - try { this.reader.abort(); } catch (e) {}; - } - this.reader = null; +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; } -},{"inherits":58,"readable-stream":92,"typedarray-to-buffer":114}],54:[function(require,module,exports){ -module.exports = function flatten(list, depth) { - depth = (typeof depth == 'number') ? depth : Infinity; +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +exports.StringDecoder = StringDecoder; +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} - if (!depth) { - if (Array.isArray(list)) { - return list.map(function(i) { return i; }); - } - return list; +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; - return _flatten(list, 1); +StringDecoder.prototype.end = utf8End; - function _flatten(list, d) { - return list.reduce(function (acc, item) { - if (Array.isArray(item) && d < depth) { - return acc.concat(_flatten(item, d + 1)); - } - else { - return acc.concat(item); - } - }, []); +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; + +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; }; -},{}],55:[function(require,module,exports){ -// originally pulled out of simple-peer +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. If an invalid byte is detected, -2 is returned. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return byte >> 6 === 0x02 ? -1 : -2; +} -module.exports = function getBrowserRTC () { - if (typeof window === 'undefined') return null - var wrtc = { - RTCPeerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection || - window.webkitRTCPeerConnection, - RTCSessionDescription: window.RTCSessionDescription || - window.mozRTCSessionDescription || window.webkitRTCSessionDescription, - RTCIceCandidate: window.RTCIceCandidate || window.mozRTCIceCandidate || - window.webkitRTCIceCandidate +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; } - if (!wrtc.RTCPeerConnection) return null - return wrtc + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + } + return nb; + } + return 0; } -},{}],56:[function(require,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] +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'; + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'; + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'; + } + } + } +} - i += d +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} - e = s & ((1 << (-nBits)) - 1) - s >>= (-nBits) - nBits += eLen - for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} - m = e & ((1 << (-nBits)) - 1) - e >>= (-nBits) - nBits += mLen - for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} +// For UTF-8, a replacement character is added when ending on a partial +// character. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'; + return r; +} - 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 +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); + } + } + return r; } - return (s ? -1 : 1) * m * Math.pow(2, e - mLen) + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); } -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 UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); } - - 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 + return r; } -},{}],57:[function(require,module,exports){ -(function (process){ -module.exports = ImmediateStore - -function ImmediateStore (store) { - if (!(this instanceof ImmediateStore)) return new ImmediateStore(store) - - this.store = store - this.chunkLength = store.chunkLength - - if (!this.store || !this.store.get || !this.store.put) { - throw new Error('First argument must be abstract-chunk-store compliant') +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; } - - this.mem = [] + return buf.toString('base64', i, buf.length - n); } -ImmediateStore.prototype.put = function (index, buf, cb) { - var self = this - self.mem[index] = buf - self.store.put(index, buf, function (err) { - self.mem[index] = null - if (cb) cb(err) - }) +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; } -ImmediateStore.prototype.get = function (index, opts, cb) { - if (typeof opts === 'function') return this.get(index, null, opts) - - var start = (opts && opts.offset) || 0 - var end = opts && opts.length && (start + opts.length) - - var buf = this.mem[index] - if (buf) return nextTick(cb, null, opts ? buf.slice(start, end) : buf) - - this.store.get(index, opts, cb) +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); } -ImmediateStore.prototype.close = function (cb) { - this.store.close(cb) +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; } +},{"safe-buffer":29}],35:[function(require,module,exports){ +(function (setImmediate,clearImmediate){ +var nextTick = require('process/browser.js').nextTick; +var apply = Function.prototype.apply; +var slice = Array.prototype.slice; +var immediateIds = {}; +var nextImmediateId = 0; -ImmediateStore.prototype.destroy = function (cb) { - this.store.destroy(cb) -} +// DOM APIs, for completeness -function nextTick (cb, err, val) { - process.nextTick(function () { - if (cb) cb(err, val) - }) -} +exports.setTimeout = function() { + return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); +}; +exports.setInterval = function() { + return new Timeout(apply.call(setInterval, window, arguments), clearInterval); +}; +exports.clearTimeout = +exports.clearInterval = function(timeout) { timeout.close(); }; -}).call(this,require('_process')) -},{"_process":170}],58:[function(require,module,exports){ -if (typeof Object.create === 'function') { - // implementation from standard node.js 'util' module - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }; -} else { - // old school shim for old browsers - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - var TempCtor = function () {} - TempCtor.prototype = superCtor.prototype - ctor.prototype = new TempCtor() - ctor.prototype.constructor = ctor - } +function Timeout(id, clearFn) { + this._id = id; + this._clearFn = clearFn; } +Timeout.prototype.unref = Timeout.prototype.ref = function() {}; +Timeout.prototype.close = function() { + this._clearFn.call(window, this._id); +}; -},{}],59:[function(require,module,exports){ -/* (c) 2016 Ari Porad (@ariporad) . License: ariporad.mit-license.org */ +// Does not start the time, just sets up the members needed. +exports.enroll = function(item, msecs) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = msecs; +}; -// Partially from http://stackoverflow.com/a/94049/1928484, and from another SO answer, which told me that the highest -// char code that's ascii is 127, but I can't find the link for. Sorry. +exports.unenroll = function(item) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = -1; +}; -var MAX_ASCII_CHAR_CODE = 127; +exports._unrefActive = exports.active = function(item) { + clearTimeout(item._idleTimeoutId); -module.exports = function isAscii(str) { - for (var i = 0, strLen = str.length; i < strLen; ++i) { - if (str.charCodeAt(i) > MAX_ASCII_CHAR_CODE) return false; + var msecs = item._idleTimeout; + if (msecs >= 0) { + item._idleTimeoutId = setTimeout(function onTimeout() { + if (item._onTimeout) + item._onTimeout(); + }, msecs); } - return true; }; -},{}],60:[function(require,module,exports){ -'use strict'; - -var fs = require('fs'); +// That's not how node.js implements it but the exposed api is the same. +exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { + var id = nextImmediateId++; + var args = arguments.length < 2 ? false : slice.call(arguments, 1); -module.exports = function isFile(path, cb){ - if(!cb)return isFileSync(path); + immediateIds[id] = true; - fs.stat(path, function(err, stats){ - if(err)return cb(err); - return cb(null, stats.isFile()); + nextTick(function onNextTick() { + if (immediateIds[id]) { + // fn.call() is faster so we optimize for the common use-case + // @see http://jsperf.com/call-apply-segu + if (args) { + fn.apply(null, args); + } else { + fn.call(null); + } + // Prevent ids from leaking + exports.clearImmediate(id); + } }); -}; -module.exports.sync = isFileSync; + return id; +}; -function isFileSync(path){ - return fs.existsSync(path) && fs.statSync(path).isFile(); -} +exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { + delete immediateIds[id]; +}; +}).call(this,require("timers").setImmediate,require("timers").clearImmediate) +},{"process/browser.js":15,"timers":35}],36:[function(require,module,exports){ +var Buffer = require('buffer').Buffer -},{"fs":156}],61:[function(require,module,exports){ -module.exports = isTypedArray -isTypedArray.strict = isStrictTypedArray -isTypedArray.loose = isLooseTypedArray +module.exports = function (buf) { + // If the buffer is backed by a Uint8Array, a faster version will work + if (buf instanceof Uint8Array) { + // If the buffer isn't a subarray, return the underlying ArrayBuffer + if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) { + return buf.buffer + } else if (typeof buf.buffer.slice === 'function') { + // Otherwise we need to get a proper copy + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) + } + } -var toString = Object.prototype.toString -var names = { - '[object Int8Array]': true - , '[object Int16Array]': true - , '[object Int32Array]': true - , '[object Uint8Array]': true - , '[object Uint8ClampedArray]': true - , '[object Uint16Array]': true - , '[object Uint32Array]': true - , '[object Float32Array]': true - , '[object Float64Array]': true + if (Buffer.isBuffer(buf)) { + // This is the slow version that will work with any Buffer + // implementation (even in old browsers) + var arrayCopy = new Uint8Array(buf.length) + var len = buf.length + for (var i = 0; i < len; i++) { + arrayCopy[i] = buf[i] + } + return arrayCopy.buffer + } else { + throw new Error('Argument must be a Buffer') + } } -function isTypedArray(arr) { - return ( - isStrictTypedArray(arr) - || isLooseTypedArray(arr) - ) -} +},{"buffer":4}],37:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -function isStrictTypedArray(arr) { - return ( - arr instanceof Int8Array - || arr instanceof Int16Array - || arr instanceof Int32Array - || arr instanceof Uint8Array - || arr instanceof Uint8ClampedArray - || arr instanceof Uint16Array - || arr instanceof Uint32Array - || arr instanceof Float32Array - || arr instanceof Float64Array - ) -} +'use strict'; -function isLooseTypedArray(arr) { - return names[toString.call(arr)] -} +var punycode = require('punycode'); +var util = require('./util'); -},{}],62:[function(require,module,exports){ -var toString = {}.toString; +exports.parse = urlParse; +exports.resolve = urlResolve; +exports.resolveObject = urlResolveObject; +exports.format = urlFormat; -module.exports = Array.isArray || function (arr) { - return toString.call(arr) == '[object Array]'; -}; +exports.Url = Url; -},{}],63:[function(require,module,exports){ -'use strict'; +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; +} -// # All -// /^npm-debug\.log$/, // npm error log -// /^\..*\.swp$/, // Vim state +// Reference: RFC 3986, RFC 1808, RFC 2396 -// # macOS -// /^\.DS_Store$/, // Stores custom folder attributes -// /^\.AppleDouble$/, // Stores additional file resources -// /^\.LSOverride$/, // Contains the absolute path to the app to be used -// /^Icon\r$/, // Custom Finder icon: http://superuser.com/questions/298785/icon-file-on-os-x-desktop -// /^\._.*/, // Thumbnail -// /^\.Spotlight-V100(?:$|\/)/, // Directory that might appear on external disk -// /\.Trashes/, // File that might appear on external disk -// /^__MACOSX$/, // Resource fork +// define these here so at least they only have to be +// compiled once on the first module load. +var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, -// # Linux -// /~$/, // Backup file + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, -// # Windows -// /^Thumbs\.db$/, // Image file cache -// /^ehthumbs\.db$/, // Folder config file -// /^Desktop\.ini$/ // Stores custom folder attributes -// /^@eaDir$/ // Synology Diskstation "hidden" folder where the server stores thumbnails + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], -exports.regex = exports.re = /^npm-debug\.log$|^\..*\.swp$|^\.DS_Store$|^\.AppleDouble$|^\.LSOverride$|^Icon\r$|^\._.*|^\.Spotlight-V100(?:$|\/)|\.Trashes|^__MACOSX$|~$|^Thumbs\.db$|^ehthumbs\.db$|^Desktop\.ini$|^@eaDir$/; + // RFC 2396: characters not allowed for various reasons. + unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims), -exports.is = filename => exports.re.test(filename); + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = ['\''].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape), + hostEndingChars = ['/', '?', '#'], + hostnameMaxLen = 255, + hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + unsafeProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that never have a hostname. + hostlessProtocol = { + 'javascript': true, + 'javascript:': true + }, + // protocols that always contain a // bit. + slashedProtocol = { + 'http': true, + 'https': true, + 'ftp': true, + 'gopher': true, + 'file': true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true + }, + querystring = require('querystring'); -exports.not = filename => !exports.is(filename); +function urlParse(url, parseQueryString, slashesDenoteHost) { + if (url && util.isObject(url) && url instanceof Url) return url; -},{}],64:[function(require,module,exports){ -module.exports = magnetURIDecode -module.exports.decode = magnetURIDecode -module.exports.encode = magnetURIEncode + var u = new Url; + u.parse(url, parseQueryString, slashesDenoteHost); + return u; +} -var base32 = require('thirty-two') -var Buffer = require('safe-buffer').Buffer -var extend = require('xtend') -var uniq = require('uniq') +Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { + if (!util.isString(url)) { + throw new TypeError("Parameter 'url' must be a string, not " + typeof url); + } -/** - * Parse a magnet URI and return an object of keys/values - * - * @param {string} uri - * @return {Object} parsed uri - */ -function magnetURIDecode (uri) { - var result = {} + // Copy chrome, IE, opera backslash-handling behavior. + // Back slashes before the query string get converted to forward slashes + // See: https://code.google.com/p/chromium/issues/detail?id=25916 + var queryIndex = url.indexOf('?'), + splitter = + (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#', + uSplit = url.split(splitter), + slashRegex = /\\/g; + uSplit[0] = uSplit[0].replace(slashRegex, '/'); + url = uSplit.join(splitter); - // Support 'magnet:' and 'stream-magnet:' uris - var data = uri.split('magnet:?')[1] + var rest = url; - var params = (data && data.length >= 0) - ? data.split('&') - : [] + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); - params.forEach(function (param) { - var keyval = param.split('=') + if (!slashesDenoteHost && url.split('#').length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.path = rest; + this.href = rest; + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + if (parseQueryString) { + this.query = querystring.parse(this.search.substr(1)); + } else { + this.query = this.search.substr(1); + } + } else if (parseQueryString) { + this.search = ''; + this.query = {}; + } + return this; + } + } - // This keyval is invalid, skip it - if (keyval.length !== 2) return + var proto = protocolPattern.exec(rest); + if (proto) { + proto = proto[0]; + var lowerProto = proto.toLowerCase(); + this.protocol = lowerProto; + rest = rest.substr(proto.length); + } - var key = keyval[0] - var val = keyval[1] + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + var slashes = rest.substr(0, 2) === '//'; + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2); + this.slashes = true; + } + } - // Clean up torrent name - if (key === 'dn') val = decodeURIComponent(val).replace(/\+/g, ' ') + if (!hostlessProtocol[proto] && + (slashes || (proto && !slashedProtocol[proto]))) { - // Address tracker (tr), exact source (xs), and acceptable source (as) are encoded - // URIs, so decode them - if (key === 'tr' || key === 'xs' || key === 'as' || key === 'ws') { - val = decodeURIComponent(val) - } + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c - // Return keywords as an array - if (key === 'kt') val = decodeURIComponent(val).split('+') + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. - // Cast file index (ix) to a number - if (key === 'ix') val = Number(val) + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (var i = 0; i < hostEndingChars.length; i++) { + var hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } - // If there are repeated parameters, return an array of values - if (result[key]) { - if (Array.isArray(result[key])) { - result[key].push(val) - } else { - var old = result[key] - result[key] = [old, val] - } + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); } else { - result[key] = val + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); } - }) - // Convenience properties for parity with `parse-torrent-file` module - var m - if (result.xt) { - var xts = Array.isArray(result.xt) ? result.xt : [ result.xt ] - xts.forEach(function (xt) { - if ((m = xt.match(/^urn:btih:(.{40})/))) { - result.infoHash = m[1].toLowerCase() - } else if ((m = xt.match(/^urn:btih:(.{32})/))) { - var decodedStr = base32.decode(m[1]) - result.infoHash = Buffer.from(decodedStr, 'binary').toString('hex') - } - }) - } - if (result.infoHash) result.infoHashBuffer = Buffer.from(result.infoHash, 'hex') + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = decodeURIComponent(auth); + } - if (result.dn) result.name = result.dn - if (result.kt) result.keywords = result.kt - - if (typeof result.tr === 'string') result.announce = [ result.tr ] - else if (Array.isArray(result.tr)) result.announce = result.tr - else result.announce = [] - - result.urlList = [] - if (typeof result.as === 'string' || Array.isArray(result.as)) { - result.urlList = result.urlList.concat(result.as) - } - if (typeof result.ws === 'string' || Array.isArray(result.ws)) { - result.urlList = result.urlList.concat(result.ws) - } - - uniq(result.announce) - uniq(result.urlList) + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (var i = 0; i < nonHostChars.length; i++) { + var hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) + hostEnd = rest.length; - return result -} + this.host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); -function magnetURIEncode (obj) { - obj = extend(obj) // clone obj, so we can mutate it + // pull out port. + this.parseHost(); - // support using convenience names, in addition to spec names - // (example: `infoHash` for `xt`, `name` for `dn`) - if (obj.infoHashBuffer) obj.xt = 'urn:btih:' + obj.infoHashBuffer.toString('hex') - if (obj.infoHash) obj.xt = 'urn:btih:' + obj.infoHash - if (obj.name) obj.dn = obj.name - if (obj.keywords) obj.kt = obj.keywords - if (obj.announce) obj.tr = obj.announce - if (obj.urlList) { - obj.ws = obj.urlList - delete obj.as - } + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || ''; - var result = 'magnet:?' - Object.keys(obj) - .filter(function (key) { - return key.length === 2 - }) - .forEach(function (key, i) { - var values = Array.isArray(obj[key]) ? obj[key] : [ obj[key] ] - values.forEach(function (val, j) { - if ((i > 0 || j > 0) && (key !== 'kt' || j === 0)) result += '&' + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; - if (key === 'dn') val = encodeURIComponent(val).replace(/%20/g, '+') - if (key === 'tr' || key === 'xs' || key === 'as' || key === 'ws') { - val = encodeURIComponent(val) + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); + for (var i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i]; + if (!part) continue; + if (!part.match(hostnamePartPattern)) { + var newpart = ''; + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x'; + } else { + newpart += part[j]; + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i); + var notHost = hostparts.slice(i + 1); + var bit = part.match(hostnamePartStart); + if (bit) { + validParts.push(bit[1]); + notHost.unshift(bit[2]); + } + if (notHost.length) { + rest = '/' + notHost.join('.') + rest; + } + this.hostname = validParts.join('.'); + break; + } } - if (key === 'kt') val = encodeURIComponent(val) - - if (key === 'kt' && j > 0) result += '+' + val - else result += key + '=' + val - }) - }) - - return result -} + } + } -},{"safe-buffer":98,"thirty-two":109,"uniq":116,"xtend":131}],65:[function(require,module,exports){ -module.exports = MediaElementWrapper + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } -var inherits = require('inherits') -var stream = require('readable-stream') -var toArrayBuffer = require('to-arraybuffer') + if (!ipv6Hostname) { + // IDNA Support: Returns a punycoded representation of "domain". + // It only converts parts of the domain name that + // have non-ASCII characters, i.e. it doesn't matter if + // you call it with a domain that already is ASCII-only. + this.hostname = punycode.toASCII(this.hostname); + } -var MediaSource = typeof window !== 'undefined' && window.MediaSource + var p = this.port ? ':' + this.port : ''; + var h = this.hostname || ''; + this.host = h + p; + this.href += this.host; -var DEFAULT_BUFFER_DURATION = 60 // seconds + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + if (rest[0] !== '/') { + rest = '/' + rest; + } + } + } -function MediaElementWrapper (elem, opts) { - var self = this - if (!(self instanceof MediaElementWrapper)) return new MediaElementWrapper(elem, opts) + // now rest is set to the post-host stuff. + // chop off any delim chars. + if (!unsafeProtocol[lowerProto]) { - if (!MediaSource) throw new Error('web browser lacks MediaSource support') + // First, make 100% sure that any "autoEscape" chars get + // escaped, even if encodeURIComponent doesn't think they + // need to be. + for (var i = 0, l = autoEscape.length; i < l; i++) { + var ae = autoEscape[i]; + if (rest.indexOf(ae) === -1) + continue; + var esc = encodeURIComponent(ae); + if (esc === ae) { + esc = escape(ae); + } + rest = rest.split(ae).join(esc); + } + } - if (!opts) opts = {} - self._bufferDuration = opts.bufferDuration || DEFAULT_BUFFER_DURATION - self._elem = elem - self._mediaSource = new MediaSource() - self._streams = [] - self.detailedError = null - self._errorHandler = function () { - self._elem.removeEventListener('error', self._errorHandler) - var streams = self._streams.slice() - streams.forEach(function (stream) { - stream.destroy(self._elem.error) - }) + // chop off from the tail first. + var hash = rest.indexOf('#'); + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + var qm = rest.indexOf('?'); + if (qm !== -1) { + this.search = rest.substr(qm); + this.query = rest.substr(qm + 1); + if (parseQueryString) { + this.query = querystring.parse(this.query); + } + rest = rest.slice(0, qm); + } else if (parseQueryString) { + // no query string, but parseQueryString still requested + this.search = ''; + this.query = {}; + } + if (rest) this.pathname = rest; + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = '/'; } - self._elem.addEventListener('error', self._errorHandler) - self._elem.src = window.URL.createObjectURL(self._mediaSource) -} + //to support http.request + if (this.pathname || this.search) { + var p = this.pathname || ''; + var s = this.search || ''; + this.path = p + s; + } -/* - * `obj` can be a previous value returned by this function - * or a string - */ -MediaElementWrapper.prototype.createWriteStream = function (obj) { - var self = this + // finally, reconstruct the href based on what has been validated. + this.href = this.format(); + return this; +}; - return new MediaSourceStream(self, obj) +// format a parsed object into a url string +function urlFormat(obj) { + // ensure it's an object, and not a string url. + // If it's an obj, this is a no-op. + // this way, you can call url_format() on strings + // to clean up potentially wonky urls. + if (util.isString(obj)) obj = urlParse(obj); + if (!(obj instanceof Url)) return Url.prototype.format.call(obj); + return obj.format(); } -/* - * Use to trigger an error on the underlying media element - */ -MediaElementWrapper.prototype.error = function (err) { - var self = this - - // be careful not to overwrite any existing detailedError values - if (!self.detailedError) { - self.detailedError = err +Url.prototype.format = function() { + var auth = this.auth || ''; + if (auth) { + auth = encodeURIComponent(auth); + auth = auth.replace(/%3A/i, ':'); + auth += '@'; } - try { - self._mediaSource.endOfStream('decode') - } catch (err) {} -} - -inherits(MediaSourceStream, stream.Writable) -function MediaSourceStream (wrapper, obj) { - var self = this - stream.Writable.call(self) + var protocol = this.protocol || '', + pathname = this.pathname || '', + hash = this.hash || '', + host = false, + query = ''; - self._wrapper = wrapper - self._elem = wrapper._elem - self._mediaSource = wrapper._mediaSource - self._allStreams = wrapper._streams - self._allStreams.push(self) - self._bufferDuration = wrapper._bufferDuration - self._sourceBuffer = null - - self._openHandler = function () { - self._onSourceOpen() - } - self._flowHandler = function () { - self._flow() - } - - if (typeof obj === 'string') { - self._type = obj - // Need to create a new sourceBuffer - if (self._mediaSource.readyState === 'open') { - self._createSourceBuffer() - } else { - self._mediaSource.addEventListener('sourceopen', self._openHandler) + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(':') === -1 ? + this.hostname : + '[' + this.hostname + ']'); + if (this.port) { + host += ':' + this.port; } - } else if (obj._sourceBuffer === null) { - obj.destroy() - self._type = obj._type // The old stream was created but hasn't finished initializing - self._mediaSource.addEventListener('sourceopen', self._openHandler) - } else if (obj._sourceBuffer) { - obj.destroy() - self._type = obj._type - self._sourceBuffer = obj._sourceBuffer // Copy over the old sourceBuffer - self._sourceBuffer.addEventListener('updateend', self._flowHandler) - } else { - throw new Error('The argument to MediaElementWrapper.createWriteStream must be a string or a previous stream returned from that function') } - self._elem.addEventListener('timeupdate', self._flowHandler) - - self.on('error', function (err) { - self._wrapper.error(err) - }) + if (this.query && + util.isObject(this.query) && + Object.keys(this.query).length) { + query = querystring.stringify(this.query); + } - self.on('finish', function () { - if (self.destroyed) return - self._finished = true - if (self._allStreams.every(function (other) { return other._finished })) { - try { - self._mediaSource.endOfStream() - } catch (err) {} - } - }) -} + var search = this.search || (query && ('?' + query)) || ''; -MediaSourceStream.prototype._onSourceOpen = function () { - var self = this - if (self.destroyed) return + if (protocol && protocol.substr(-1) !== ':') protocol += ':'; - self._mediaSource.removeEventListener('sourceopen', self._openHandler) - self._createSourceBuffer() -} + // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. + // unless they had them to begin with. + if (this.slashes || + (!protocol || slashedProtocol[protocol]) && host !== false) { + host = '//' + (host || ''); + if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; + } else if (!host) { + host = ''; + } -MediaSourceStream.prototype.destroy = function (err) { - var self = this - if (self.destroyed) return - self.destroyed = true + if (hash && hash.charAt(0) !== '#') hash = '#' + hash; + if (search && search.charAt(0) !== '?') search = '?' + search; - // Remove from allStreams - self._allStreams.splice(self._allStreams.indexOf(self), 1) + pathname = pathname.replace(/[?#]/g, function(match) { + return encodeURIComponent(match); + }); + search = search.replace('#', '%23'); - self._mediaSource.removeEventListener('sourceopen', self._openHandler) - self._elem.removeEventListener('timeupdate', self._flowHandler) - if (self._sourceBuffer) { - self._sourceBuffer.removeEventListener('updateend', self._flowHandler) - if (self._mediaSource.readyState === 'open') { - self._sourceBuffer.abort() - } - } + return protocol + host + pathname + search + hash; +}; - if (err) self.emit('error', err) - self.emit('close') +function urlResolve(source, relative) { + return urlParse(source, false, true).resolve(relative); } -MediaSourceStream.prototype._createSourceBuffer = function () { - var self = this - if (self.destroyed) return +Url.prototype.resolve = function(relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); +}; - if (MediaSource.isTypeSupported(self._type)) { - self._sourceBuffer = self._mediaSource.addSourceBuffer(self._type) - self._sourceBuffer.addEventListener('updateend', self._flowHandler) - if (self._cb) { - var cb = self._cb - self._cb = null - cb() - } - } else { - self.destroy(new Error('The provided type is not supported')) - } +function urlResolveObject(source, relative) { + if (!source) return relative; + return urlParse(source, false, true).resolveObject(relative); } -MediaSourceStream.prototype._write = function (chunk, encoding, cb) { - var self = this - if (self.destroyed) return - if (!self._sourceBuffer) { - self._cb = function (err) { - if (err) return cb(err) - self._write(chunk, encoding, cb) - } - return - } - - if (self._sourceBuffer.updating) { - return cb(new Error('Cannot append buffer while source buffer updating')) +Url.prototype.resolveObject = function(relative) { + if (util.isString(relative)) { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; } - try { - self._sourceBuffer.appendBuffer(toArrayBuffer(chunk)) - } catch (err) { - // appendBuffer can throw for a number of reasons, most notably when the data - // being appended is invalid or if appendBuffer is called after another error - // already occurred on the media element. In Chrome, there may be useful debugging - // info in chrome://media-internals - self.destroy(err) - return + var result = new Url(); + var tkeys = Object.keys(this); + for (var tk = 0; tk < tkeys.length; tk++) { + var tkey = tkeys[tk]; + result[tkey] = this[tkey]; } - self._cb = cb -} -MediaSourceStream.prototype._flow = function () { - var self = this + // hash is always overridden, no matter what. + // even href="" will remove it. + result.hash = relative.hash; - if (self.destroyed || !self._sourceBuffer || self._sourceBuffer.updating) { - return + // if the relative url is empty, then there's nothing left to do here. + if (relative.href === '') { + result.href = result.format(); + return result; } - if (self._mediaSource.readyState === 'open') { - // check buffer size - if (self._getBufferDuration() > self._bufferDuration) { - return + // hrefs like //foo/bar always cut to the protocol. + if (relative.slashes && !relative.protocol) { + // take everything except the protocol from relative + var rkeys = Object.keys(relative); + for (var rk = 0; rk < rkeys.length; rk++) { + var rkey = rkeys[rk]; + if (rkey !== 'protocol') + result[rkey] = relative[rkey]; } - } - - if (self._cb) { - var cb = self._cb - self._cb = null - cb() - } -} - -// TODO: if zero actually works in all browsers, remove the logic associated with this below -var EPSILON = 0 - -MediaSourceStream.prototype._getBufferDuration = function () { - var self = this - - var buffered = self._sourceBuffer.buffered - var currentTime = self._elem.currentTime - var bufferEnd = -1 // end of the buffer - // This is a little over complex because some browsers seem to separate the - // buffered region into multiple sections with slight gaps. - for (var i = 0; i < buffered.length; i++) { - var start = buffered.start(i) - var end = buffered.end(i) + EPSILON - if (start > currentTime) { - // Reached past the joined buffer - break - } else if (bufferEnd >= 0 || currentTime <= end) { - // Found the start/continuation of the joined buffer - bufferEnd = end + //urlParse appends trailing / to urls like http://www.example.com + if (slashedProtocol[result.protocol] && + result.hostname && !result.pathname) { + result.path = result.pathname = '/'; } - } - var bufferedTime = bufferEnd - currentTime - if (bufferedTime < 0) { - bufferedTime = 0 + result.href = result.format(); + return result; } - return bufferedTime -} - -},{"inherits":58,"readable-stream":92,"to-arraybuffer":111}],66:[function(require,module,exports){ -(function (process){ -module.exports = Storage - -function Storage (chunkLength, opts) { - if (!(this instanceof Storage)) return new Storage(chunkLength, opts) - if (!opts) opts = {} - - this.chunkLength = Number(chunkLength) - if (!this.chunkLength) throw new Error('First argument must be a chunk length') - - this.chunks = [] - this.closed = false - this.length = Number(opts.length) || Infinity + if (relative.protocol && relative.protocol !== result.protocol) { + // if it's a known url protocol, then changing + // the protocol does weird things + // first, if it's not file:, then we MUST have a host, + // and if there was a path + // to begin with, then we MUST have a path. + // if it is file:, then the host is dropped, + // because that's known to be hostless. + // anything else is assumed to be absolute. + if (!slashedProtocol[relative.protocol]) { + var keys = Object.keys(relative); + for (var v = 0; v < keys.length; v++) { + var k = keys[v]; + result[k] = relative[k]; + } + result.href = result.format(); + return result; + } - if (this.length !== Infinity) { - this.lastChunkLength = (this.length % this.chunkLength) || this.chunkLength - this.lastChunkIndex = Math.ceil(this.length / this.chunkLength) - 1 + result.protocol = relative.protocol; + if (!relative.host && !hostlessProtocol[relative.protocol]) { + var relPath = (relative.pathname || '').split('/'); + while (relPath.length && !(relative.host = relPath.shift())); + if (!relative.host) relative.host = ''; + if (!relative.hostname) relative.hostname = ''; + if (relPath[0] !== '') relPath.unshift(''); + if (relPath.length < 2) relPath.unshift(''); + result.pathname = relPath.join('/'); + } else { + result.pathname = relative.pathname; + } + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ''; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ''; + var s = result.search || ''; + result.path = p + s; + } + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; } -} -Storage.prototype.put = function (index, buf, cb) { - if (this.closed) return nextTick(cb, new Error('Storage is closed')) + var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), + isRelAbs = ( + relative.host || + relative.pathname && relative.pathname.charAt(0) === '/' + ), + mustEndAbs = (isRelAbs || isSourceAbs || + (result.host && relative.pathname)), + removeAllDots = mustEndAbs, + srcPath = result.pathname && result.pathname.split('/') || [], + relPath = relative.pathname && relative.pathname.split('/') || [], + psychotic = result.protocol && !slashedProtocol[result.protocol]; - var isLastChunk = (index === this.lastChunkIndex) - if (isLastChunk && buf.length !== this.lastChunkLength) { - return nextTick(cb, new Error('Last chunk length must be ' + this.lastChunkLength)) - } - if (!isLastChunk && buf.length !== this.chunkLength) { - return nextTick(cb, new Error('Chunk length must be ' + this.chunkLength)) + // if the url is a non-slashed url, then relative + // links like ../.. should be able + // to crawl up to the hostname, as well. This is strange. + // result.protocol has already been set by now. + // Later on, put the first path part into the host field. + if (psychotic) { + result.hostname = ''; + result.port = null; + if (result.host) { + if (srcPath[0] === '') srcPath[0] = result.host; + else srcPath.unshift(result.host); + } + result.host = ''; + if (relative.protocol) { + relative.hostname = null; + relative.port = null; + if (relative.host) { + if (relPath[0] === '') relPath[0] = relative.host; + else relPath.unshift(relative.host); + } + relative.host = null; + } + mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); } - this.chunks[index] = buf - nextTick(cb, null) -} - -Storage.prototype.get = function (index, opts, cb) { - if (typeof opts === 'function') return this.get(index, null, opts) - if (this.closed) return nextTick(cb, new Error('Storage is closed')) - var buf = this.chunks[index] - if (!buf) return nextTick(cb, new Error('Chunk not found')) - if (!opts) return nextTick(cb, null, buf) - var offset = opts.offset || 0 - var len = opts.length || (buf.length - offset) - nextTick(cb, null, buf.slice(offset, len + offset)) -} -Storage.prototype.close = Storage.prototype.destroy = function (cb) { - if (this.closed) return nextTick(cb, new Error('Storage is closed')) - this.closed = true - this.chunks = null - nextTick(cb, null) -} + if (isRelAbs) { + // it's absolute. + result.host = (relative.host || relative.host === '') ? + relative.host : result.host; + result.hostname = (relative.hostname || relative.hostname === '') ? + relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; + srcPath = relPath; + // fall through to the dot-handling below. + } else if (relPath.length) { + // it's relative + // throw away the existing file, and take the new path instead. + if (!srcPath) srcPath = []; + srcPath.pop(); + srcPath = srcPath.concat(relPath); + result.search = relative.search; + result.query = relative.query; + } else if (!util.isNullOrUndefined(relative.search)) { + // just pull out the search. + // like href='?foo'. + // Put this after the other two cases because it simplifies the booleans + if (psychotic) { + result.hostname = result.host = srcPath.shift(); + //occationaly the auth can get stuck only in host + //this especially happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } + } + result.search = relative.search; + result.query = relative.query; + //to support http.request + if (!util.isNull(result.pathname) || !util.isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.href = result.format(); + return result; + } -function nextTick (cb, err, val) { - process.nextTick(function () { - if (cb) cb(err, val) - }) -} + if (!srcPath.length) { + // no path at all. easy. + // we've already handled the other stuff above. + result.pathname = null; + //to support http.request + if (result.search) { + result.path = '/' + result.search; + } else { + result.path = null; + } + result.href = result.format(); + return result; + } -}).call(this,require('_process')) -},{"_process":170}],67:[function(require,module,exports){ -(function (Buffer){ -// This is an intentionally recursive require. I don't like it either. -var Box = require('./index') -var Descriptor = require('./descriptor') + // if a url ENDs in . or .., then it must get a trailing slash. + // however, if it ends in anything else non-slashy, + // then it must NOT get a trailing slash. + var last = srcPath.slice(-1)[0]; + var hasTrailingSlash = ( + (result.host || relative.host || srcPath.length > 1) && + (last === '.' || last === '..') || last === ''); -var TIME_OFFSET = 2082844800000 + // strip single dots, resolve double dots to parent dir + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = srcPath.length; i >= 0; i--) { + last = srcPath[i]; + if (last === '.') { + srcPath.splice(i, 1); + } else if (last === '..') { + srcPath.splice(i, 1); + up++; + } else if (up) { + srcPath.splice(i, 1); + up--; + } + } -/* -TODO: -test these -add new box versions -*/ + // if the path is allowed to go above the root, restore leading ..s + if (!mustEndAbs && !removeAllDots) { + for (; up--; up) { + srcPath.unshift('..'); + } + } -// These have 'version' and 'flags' fields in the headers -exports.fullBoxes = {} -var fullBoxes = [ - 'mvhd', - 'tkhd', - 'mdhd', - 'vmhd', - 'smhd', - 'stsd', - 'esds', - 'stsz', - 'stco', - 'stss', - 'stts', - 'ctts', - 'stsc', - 'dref', - 'elst', - 'hdlr', - 'mehd', - 'trex', - 'mfhd', - 'tfhd', - 'tfdt', - 'trun' -] -fullBoxes.forEach(function (type) { - exports.fullBoxes[type] = true -}) + if (mustEndAbs && srcPath[0] !== '' && + (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { + srcPath.unshift(''); + } -exports.ftyp = {} -exports.ftyp.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(exports.ftyp.encodingLength(box)) - var brands = box.compatibleBrands || [] - buf.write(box.brand, 0, 4, 'ascii') - buf.writeUInt32BE(box.brandVersion, 4) - for (var i = 0; i < brands.length; i++) buf.write(brands[i], 8 + (i * 4), 4, 'ascii') - exports.ftyp.encode.bytes = 8 + brands.length * 4 - return buf -} -exports.ftyp.decode = function (buf, offset) { - buf = buf.slice(offset) - var brand = buf.toString('ascii', 0, 4) - var version = buf.readUInt32BE(4) - var compatibleBrands = [] - for (var i = 8; i < buf.length; i += 4) compatibleBrands.push(buf.toString('ascii', i, i + 4)) - return { - brand: brand, - brandVersion: version, - compatibleBrands: compatibleBrands + if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { + srcPath.push(''); } -} -exports.ftyp.encodingLength = function (box) { - return 8 + (box.compatibleBrands || []).length * 4 -} -exports.mvhd = {} -exports.mvhd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(96) - writeDate(box.ctime || new Date(), buf, 0) - writeDate(box.mtime || new Date(), buf, 4) - buf.writeUInt32BE(box.timeScale || 0, 8) - buf.writeUInt32BE(box.duration || 0, 12) - writeFixed32(box.preferredRate || 0, buf, 16) - writeFixed16(box.preferredVolume || 0, buf, 20) - writeReserved(buf, 22, 32) - writeMatrix(box.matrix, buf, 32) - buf.writeUInt32BE(box.previewTime || 0, 68) - buf.writeUInt32BE(box.previewDuration || 0, 72) - buf.writeUInt32BE(box.posterTime || 0, 76) - buf.writeUInt32BE(box.selectionTime || 0, 80) - buf.writeUInt32BE(box.selectionDuration || 0, 84) - buf.writeUInt32BE(box.currentTime || 0, 88) - buf.writeUInt32BE(box.nextTrackId || 0, 92) - exports.mvhd.encode.bytes = 96 - return buf -} -exports.mvhd.decode = function (buf, offset) { - buf = buf.slice(offset) - return { - ctime: readDate(buf, 0), - mtime: readDate(buf, 4), - timeScale: buf.readUInt32BE(8), - duration: buf.readUInt32BE(12), - preferredRate: readFixed32(buf, 16), - preferredVolume: readFixed16(buf, 20), - matrix: readMatrix(buf.slice(32, 68)), - previewTime: buf.readUInt32BE(68), - previewDuration: buf.readUInt32BE(72), - posterTime: buf.readUInt32BE(76), - selectionTime: buf.readUInt32BE(80), - selectionDuration: buf.readUInt32BE(84), - currentTime: buf.readUInt32BE(88), - nextTrackId: buf.readUInt32BE(92) - } -} -exports.mvhd.encodingLength = function (box) { - return 96 -} + var isAbsolute = srcPath[0] === '' || + (srcPath[0] && srcPath[0].charAt(0) === '/'); -exports.tkhd = {} -exports.tkhd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(80) - writeDate(box.ctime || new Date(), buf, 0) - writeDate(box.mtime || new Date(), buf, 4) - buf.writeUInt32BE(box.trackId || 0, 8) - writeReserved(buf, 12, 16) - buf.writeUInt32BE(box.duration || 0, 16) - writeReserved(buf, 20, 28) - buf.writeUInt16BE(box.layer || 0, 28) - buf.writeUInt16BE(box.alternateGroup || 0, 30) - buf.writeUInt16BE(box.volume || 0, 32) - writeMatrix(box.matrix, buf, 36) - buf.writeUInt32BE(box.trackWidth || 0, 72) - buf.writeUInt32BE(box.trackHeight || 0, 76) - exports.tkhd.encode.bytes = 80 - return buf -} -exports.tkhd.decode = function (buf, offset) { - buf = buf.slice(offset) - return { - ctime: readDate(buf, 0), - mtime: readDate(buf, 4), - trackId: buf.readUInt32BE(8), - duration: buf.readUInt32BE(16), - layer: buf.readUInt16BE(28), - alternateGroup: buf.readUInt16BE(30), - volume: buf.readUInt16BE(32), - matrix: readMatrix(buf.slice(36, 72)), - trackWidth: buf.readUInt32BE(72), - trackHeight: buf.readUInt32BE(76) + // put the host back + if (psychotic) { + result.hostname = result.host = isAbsolute ? '' : + srcPath.length ? srcPath.shift() : ''; + //occationaly the auth can get stuck only in host + //this especially happens in cases like + //url.resolveObject('mailto:local1@domain1', 'local2@domain2') + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); + } } -} -exports.tkhd.encodingLength = function (box) { - return 80 -} -exports.mdhd = {} -exports.mdhd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(20) - writeDate(box.ctime || new Date(), buf, 0) - writeDate(box.mtime || new Date(), buf, 4) - buf.writeUInt32BE(box.timeScale || 0, 8) - buf.writeUInt32BE(box.duration || 0, 12) - buf.writeUInt16BE(box.language || 0, 16) - buf.writeUInt16BE(box.quality || 0, 18) - exports.mdhd.encode.bytes = 20 - return buf -} -exports.mdhd.decode = function (buf, offset) { - buf = buf.slice(offset) - return { - ctime: readDate(buf, 0), - mtime: readDate(buf, 4), - timeScale: buf.readUInt32BE(8), - duration: buf.readUInt32BE(12), - language: buf.readUInt16BE(16), - quality: buf.readUInt16BE(18) - } -} -exports.mdhd.encodingLength = function (box) { - return 20 -} + mustEndAbs = mustEndAbs || (result.host && srcPath.length); -exports.vmhd = {} -exports.vmhd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(8) - buf.writeUInt16BE(box.graphicsMode || 0, 0) - var opcolor = box.opcolor || [0, 0, 0] - buf.writeUInt16BE(opcolor[0], 2) - buf.writeUInt16BE(opcolor[1], 4) - buf.writeUInt16BE(opcolor[2], 6) - exports.vmhd.encode.bytes = 8 - return buf -} -exports.vmhd.decode = function (buf, offset) { - buf = buf.slice(offset) - return { - graphicsMode: buf.readUInt16BE(0), - opcolor: [buf.readUInt16BE(2), buf.readUInt16BE(4), buf.readUInt16BE(6)] + if (mustEndAbs && !isAbsolute) { + srcPath.unshift(''); } -} -exports.vmhd.encodingLength = function (box) { - return 8 -} -exports.smhd = {} -exports.smhd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(4) - buf.writeUInt16BE(box.balance || 0, 0) - writeReserved(buf, 2, 4) - exports.smhd.encode.bytes = 4 - return buf -} -exports.smhd.decode = function (buf, offset) { - buf = buf.slice(offset) - return { - balance: buf.readUInt16BE(0) + if (!srcPath.length) { + result.pathname = null; + result.path = null; + } else { + result.pathname = srcPath.join('/'); } -} -exports.smhd.encodingLength = function (box) { - return 4 -} - -exports.stsd = {} -exports.stsd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(exports.stsd.encodingLength(box)) - var entries = box.entries || [] - buf.writeUInt32BE(entries.length, 0) + //to support request.http + if (!util.isNull(result.pathname) || !util.isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; +}; - var ptr = 4 - for (var i = 0; i < entries.length; i++) { - var entry = entries[i] - Box.encode(entry, buf, ptr) - ptr += Box.encode.bytes +Url.prototype.parseHost = function() { + var host = this.host; + var port = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ':') { + this.port = port.substr(1); + } + host = host.substr(0, host.length - port.length); } + if (host) this.hostname = host; +}; - exports.stsd.encode.bytes = ptr - return buf -} -exports.stsd.decode = function (buf, offset, end) { - buf = buf.slice(offset) - var num = buf.readUInt32BE(0) - var entries = new Array(num) - var ptr = 4 +},{"./util":38,"punycode":16,"querystring":19}],38:[function(require,module,exports){ +'use strict'; - for (var i = 0; i < num; i++) { - var entry = Box.decode(buf, ptr, end) - entries[i] = entry - ptr += entry.length +module.exports = { + isString: function(arg) { + return typeof(arg) === 'string'; + }, + isObject: function(arg) { + return typeof(arg) === 'object' && arg !== null; + }, + isNull: function(arg) { + return arg === null; + }, + isNullOrUndefined: function(arg) { + return arg == null; } +}; - return { - entries: entries - } -} -exports.stsd.encodingLength = function (box) { - var totalSize = 4 - if (!box.entries) return totalSize - for (var i = 0; i < box.entries.length; i++) { - totalSize += Box.encodingLength(box.entries[i]) - } - return totalSize -} +},{}],39:[function(require,module,exports){ +(function (global){ -exports.avc1 = exports.VisualSampleEntry = {} -exports.VisualSampleEntry.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(exports.VisualSampleEntry.encodingLength(box)) +/** + * Module exports. + */ - writeReserved(buf, 0, 6) - buf.writeUInt16BE(box.dataReferenceIndex || 0, 6) - writeReserved(buf, 8, 24) - buf.writeUInt16BE(box.width || 0, 24) - buf.writeUInt16BE(box.height || 0, 26) - buf.writeUInt32BE(box.hResolution || 0x480000, 28) - buf.writeUInt32BE(box.vResolution || 0x480000, 32) - writeReserved(buf, 36, 40) - buf.writeUInt16BE(box.frameCount || 1, 40) - var compressorName = box.compressorName || '' - var nameLen = Math.min(compressorName.length, 31) - buf.writeUInt8(nameLen, 42) - buf.write(compressorName, 43, nameLen, 'utf8') - buf.writeUInt16BE(box.depth || 0x18, 74) - buf.writeInt16BE(-1, 76) +module.exports = deprecate; - var ptr = 78 - var children = box.children || [] - children.forEach(function (child) { - Box.encode(child, buf, ptr) - ptr += Box.encode.bytes - }) - exports.VisualSampleEntry.encode.bytes = ptr -} -exports.VisualSampleEntry.decode = function (buf, offset, end) { - buf = buf.slice(offset) - var length = end - offset - var nameLen = Math.min(buf.readUInt8(42), 31) - var box = { - dataReferenceIndex: buf.readUInt16BE(6), - width: buf.readUInt16BE(24), - height: buf.readUInt16BE(26), - hResolution: buf.readUInt32BE(28), - vResolution: buf.readUInt32BE(32), - frameCount: buf.readUInt16BE(40), - compressorName: buf.toString('utf8', 43, 43 + nameLen), - depth: buf.readUInt16BE(74), - children: [] +/** + * Mark that a method should not be used. + * Returns a modified function which warns once by default. + * + * If `localStorage.noDeprecation = true` is set, then it is a no-op. + * + * If `localStorage.throwDeprecation = true` is set, then deprecated functions + * will throw an Error when invoked. + * + * If `localStorage.traceDeprecation = true` is set, then deprecated functions + * will invoke `console.trace()` instead of `console.error()`. + * + * @param {Function} fn - the function to deprecate + * @param {String} msg - the string to print to the console when `fn` is invoked + * @returns {Function} a new "deprecated" version of `fn` + * @api public + */ + +function deprecate (fn, msg) { + if (config('noDeprecation')) { + return fn; } - var ptr = 78 - while (length - ptr >= 8) { - var child = Box.decode(buf, ptr, length) - box.children.push(child) - box[child.type] = child - ptr += child.length + var warned = false; + function deprecated() { + if (!warned) { + if (config('throwDeprecation')) { + throw new Error(msg); + } else if (config('traceDeprecation')) { + console.trace(msg); + } else { + console.warn(msg); + } + warned = true; + } + return fn.apply(this, arguments); } - return box -} -exports.VisualSampleEntry.encodingLength = function (box) { - var len = 78 - var children = box.children || [] - children.forEach(function (child) { - len += Box.encodingLength(child) - }) - return len + return deprecated; } -exports.avcC = {} -exports.avcC.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : Buffer(box.buffer.length) - - box.buffer.copy(buf) - exports.avcC.encode.bytes = box.buffer.length -} -exports.avcC.decode = function (buf, offset, end) { - buf = buf.slice(offset, end) +/** + * Checks `localStorage` for boolean values for the given `name`. + * + * @param {String} name + * @returns {Boolean} + * @api private + */ - return { - mimeCodec: buf.toString('hex', 1, 4), - buffer: new Buffer(buf) +function config (name) { + // accessing global.localStorage can trigger a DOMException in sandboxed iframes + try { + if (!global.localStorage) return false; + } catch (_) { + return false; } -} -exports.avcC.encodingLength = function (box) { - return box.buffer.length + var val = global.localStorage[name]; + if (null == val) return false; + return String(val).toLowerCase() === 'true'; } -exports.mp4a = exports.AudioSampleEntry = {} -exports.AudioSampleEntry.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(exports.AudioSampleEntry.encodingLength(box)) +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],40:[function(require,module,exports){ +module.exports = extend - writeReserved(buf, 0, 6) - buf.writeUInt16BE(box.dataReferenceIndex || 0, 6) - writeReserved(buf, 8, 16) - buf.writeUInt16BE(box.channelCount || 2, 16) - buf.writeUInt16BE(box.sampleSize || 16, 18) - writeReserved(buf, 20, 24) - buf.writeUInt32BE(box.sampleRate || 0, 24) +var hasOwnProperty = Object.prototype.hasOwnProperty; - var ptr = 28 - var children = box.children || [] - children.forEach(function (child) { - Box.encode(child, buf, ptr) - ptr += Box.encode.bytes - }) - exports.AudioSampleEntry.encode.bytes = ptr -} -exports.AudioSampleEntry.decode = function (buf, offset, end) { - buf = buf.slice(offset, end) - var length = end - offset - var box = { - dataReferenceIndex: buf.readUInt16BE(6), - channelCount: buf.readUInt16BE(16), - sampleSize: buf.readUInt16BE(18), - sampleRate: buf.readUInt32BE(24), - children: [] - } +function extend() { + var target = {} - var ptr = 28 - while (length - ptr >= 8) { - var child = Box.decode(buf, ptr, length) - box.children.push(child) - box[child.type] = child - ptr += child.length - } + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i] - return box -} -exports.AudioSampleEntry.encodingLength = function (box) { - var len = 28 - var children = box.children || [] - children.forEach(function (child) { - len += Box.encodingLength(child) - }) - return len -} + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } -exports.esds = {} -exports.esds.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : Buffer(box.buffer.length) + return target +} - box.buffer.copy(buf, 0) - exports.esds.encode.bytes = box.buffer.length +},{}],41:[function(require,module,exports){ +/** + * Created by xieting on 2017/11/8. + */ + +module.exports = PearPlayer; + +var debug = require('debug')('pear:player'); +var inherits = require('inherits'); +var render = require('render-media'); +var PearDownloader = require('./src/index.downloader'); +var WebTorrent = require('webtorrent'); + +inherits(PearPlayer, PearDownloader); + +function PearPlayer(selector, token, opts) { + + var self = this; + if (!(self instanceof PearPlayer)) return new PearPlayer(selector, token, opts); + if (typeof token === 'object') return PearPlayer(selector, '', token); + if (!opts) opts = {}; + if (typeof selector === 'string') { + self.video = document.querySelector(selector); + } else if (Object.prototype.toString.call(selector) === '[object HTMLVideoElement]') { + self.video = selector; + } else { + throw new Error('illegal video selector'); + } + opts.selector = selector; + opts.render = render; + opts.sequencial = true; //player必须有序下载buffer + opts.interval = 3000; + if (!opts.algorithm) opts.algorithm = 'pull'; //algorithm默认‘pull’ + + //monitor + self.canPlayDelayStart = (new Date()).getTime(); + + if (opts.BTMode && opts.magnetURI) { + + var client = new WebTorrent(); + + return client.add(opts.magnetURI, function (torrent) { + // Got torrent metadata! + // debug('Client is downloading:', torrent.infoHash) + + torrent.files.forEach(function (file) { + + render.render(file, opts.selector, {autoplay: opts.autoplay}); + }) + }) + + } + + PearDownloader.call(self, opts.src || self.video.src, token, opts); + + self.setupListeners(); +} + +PearPlayer.prototype.setupListeners = function () { + var self = this; + + self.video.addEventListener('canplay', function () { + + self.canPlayDelayEnd = (new Date()).getTime(); + var canPlayDelay = (self.canPlayDelayEnd - self.canPlayDelayStart); + self.emit('canplay', canPlayDelay); + + + }); + + self.video.addEventListener('loadedmetadata', function () { + + var dispatcher = self.dispatcher; + + if (dispatcher) { + var bitrate = Math.ceil(dispatcher.fileSize/self.video.duration); + var windowLength = Math.ceil(bitrate * 15 / dispatcher.pieceLength); //根据码率和时间间隔来计算窗口长度 + // console.warn('dispatcher._windowLength:'+dispatcher._windowLength); + // self.normalWindowLength = self._windowLength; + if (windowLength < 3) { + windowLength = 3; + } else if (self._windowLength > 15) { + windowLength = 15; + } + dispatcher._windowLength = windowLength; + dispatcher.interval = 5000; + // console.warn('dispatcher._windowLength:'+dispatcher._windowLength); + // self._colddown = 5/self._slideInterval*self._interval2BufPos + 5; //窗口滑动的冷却时间 + // self._colddown = self._windowLength*2; + // self._colddown = 5; + self.emit('metadata', {'bitrate': bitrate, 'duration': self.video.duration}); + } + + + }); + +} + +PearPlayer.isWebRTCSupported = function () { + return PearDownloader.isWebRTCSupported(); +}; + +PearPlayer.isMSESupported = function () { + return isMSESupported(); +}; + +function isMSESupported() { + + return !!(window['MediaSource'] || window['WebKitMediaSource']); + } -exports.esds.decode = function (buf, offset, end) { - buf = buf.slice(offset, end) +},{"./src/index.downloader":179,"debug":87,"inherits":96,"render-media":133,"webtorrent":162}],42:[function(require,module,exports){ +var ADDR_RE = /^\[?([^\]]+)\]?:(\d+)$/ // ipv4/ipv6/hostname + port - var desc = Descriptor.Descriptor.decode(buf, 0, buf.length) - var esd = (desc.tagName === 'ESDescriptor') ? desc : {} - var dcd = esd.DecoderConfigDescriptor || {} - var oti = dcd.oti || 0 - var dsi = dcd.DecoderSpecificInfo - var audioConfig = dsi ? (dsi.buffer.readUInt8(0) & 0xf8) >> 3 : 0 +var cache = {} - var mimeCodec = null - if (oti) { - mimeCodec = oti.toString(16) - if (audioConfig) { - mimeCodec += '.' + audioConfig - } - } +// reset cache when it gets to 100,000 elements (~ 600KB of ipv4 addresses) +// so it will not grow to consume all memory in long-running processes +var size = 0 - return { - mimeCodec: mimeCodec, - buffer: new Buffer(buf.slice(0)) +module.exports = function addrToIPPort (addr) { + if (size === 100000) module.exports.reset() + if (!cache[addr]) { + var m = ADDR_RE.exec(addr) + if (!m) throw new Error('invalid addr: ' + addr) + cache[addr] = [ m[1], Number(m[2]) ] + size += 1 } -} -exports.esds.encodingLength = function (box) { - return box.buffer.length + return cache[addr] } -// TODO: integrate the two versions in a saner way -exports.stsz = {} -exports.stsz.encode = function (box, buf, offset) { - var entries = box.entries || [] - buf = buf ? buf.slice(offset) : Buffer(exports.stsz.encodingLength(box)) +module.exports.reset = function reset () { + cache = {} + size = 0 +} - buf.writeUInt32BE(0, 0) - buf.writeUInt32BE(entries.length, 4) +},{}],43:[function(require,module,exports){ +module.exports = require('./lib/axios'); +},{"./lib/axios":45}],44:[function(require,module,exports){ +(function (process){ +'use strict'; - for (var i = 0; i < entries.length; i++) { - buf.writeUInt32BE(entries[i], i * 4 + 8) - } +var utils = require('./../utils'); +var settle = require('./../core/settle'); +var buildURL = require('./../helpers/buildURL'); +var parseHeaders = require('./../helpers/parseHeaders'); +var isURLSameOrigin = require('./../helpers/isURLSameOrigin'); +var createError = require('../core/createError'); +var btoa = (typeof window !== 'undefined' && window.btoa && window.btoa.bind(window)) || require('./../helpers/btoa'); - exports.stsz.encode.bytes = 8 + entries.length * 4 - return buf -} -exports.stsz.decode = function (buf, offset) { - buf = buf.slice(offset) - var size = buf.readUInt32BE(0) - var num = buf.readUInt32BE(4) - var entries = new Array(num) +module.exports = function xhrAdapter(config) { + return new Promise(function dispatchXhrRequest(resolve, reject) { + var requestData = config.data; + var requestHeaders = config.headers; - for (var i = 0; i < num; i++) { - if (size === 0) { - entries[i] = buf.readUInt32BE(i * 4 + 8) - } else { - entries[i] = size + if (utils.isFormData(requestData)) { + delete requestHeaders['Content-Type']; // Let the browser set it } - } - - return { - entries: entries - } -} -exports.stsz.encodingLength = function (box) { - return 8 + box.entries.length * 4 -} -exports.stss = -exports.stco = {} -exports.stco.encode = function (box, buf, offset) { - var entries = box.entries || [] - buf = buf ? buf.slice(offset) : new Buffer(exports.stco.encodingLength(box)) + var request = new XMLHttpRequest(); + var loadEvent = 'onreadystatechange'; + var xDomain = false; - buf.writeUInt32BE(entries.length, 0) + // For IE 8/9 CORS support + // Only supports POST and GET calls and doesn't returns the response headers. + // DON'T do this for testing b/c XMLHttpRequest is mocked, not XDomainRequest. + if (process.env.NODE_ENV !== 'test' && + typeof window !== 'undefined' && + window.XDomainRequest && !('withCredentials' in request) && + !isURLSameOrigin(config.url)) { + request = new window.XDomainRequest(); + loadEvent = 'onload'; + xDomain = true; + request.onprogress = function handleProgress() {}; + request.ontimeout = function handleTimeout() {}; + } - for (var i = 0; i < entries.length; i++) { - buf.writeUInt32BE(entries[i], i * 4 + 4) - } + // HTTP basic authentication + if (config.auth) { + var username = config.auth.username || ''; + var password = config.auth.password || ''; + requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); + } - exports.stco.encode.bytes = 4 + entries.length * 4 - return buf -} -exports.stco.decode = function (buf, offset) { - buf = buf.slice(offset) - var num = buf.readUInt32BE(0) - var entries = new Array(num) + request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true); - for (var i = 0; i < num; i++) { - entries[i] = buf.readUInt32BE(i * 4 + 4) - } + // Set the request timeout in MS + request.timeout = config.timeout; - return { - entries: entries - } -} -exports.stco.encodingLength = function (box) { - return 4 + box.entries.length * 4 -} + // Listen for ready state + request[loadEvent] = function handleLoad() { + if (!request || (request.readyState !== 4 && !xDomain)) { + return; + } -exports.stts = {} -exports.stts.encode = function (box, buf, offset) { - var entries = box.entries || [] - buf = buf ? buf.slice(offset) : new Buffer(exports.stts.encodingLength(box)) + // The request errored out and we didn't get a response, this will be + // handled by onerror instead + // With one exception: request that using file: protocol, most browsers + // will return status as 0 even though it's a successful request + if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { + return; + } - buf.writeUInt32BE(entries.length, 0) + // Prepare the response + var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; + var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; + var response = { + data: responseData, + // IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201) + status: request.status === 1223 ? 204 : request.status, + statusText: request.status === 1223 ? 'No Content' : request.statusText, + headers: responseHeaders, + config: config, + request: request + }; - for (var i = 0; i < entries.length; i++) { - var ptr = i * 8 + 4 - buf.writeUInt32BE(entries[i].count || 0, ptr) - buf.writeUInt32BE(entries[i].duration || 0, ptr + 4) - } + settle(resolve, reject, response); - exports.stts.encode.bytes = 4 + box.entries.length * 8 - return buf -} -exports.stts.decode = function (buf, offset) { - buf = buf.slice(offset) - var num = buf.readUInt32BE(0) - var entries = new Array(num) + // Clean up request + request = null; + }; - for (var i = 0; i < num; i++) { - var ptr = i * 8 + 4 - entries[i] = { - count: buf.readUInt32BE(ptr), - duration: buf.readUInt32BE(ptr + 4) - } - } + // Handle low level network errors + request.onerror = function handleError() { + // Real errors are hidden from us by the browser + // onerror should only fire if it's a network error + reject(createError('Network Error', config, null, request)); - return { - entries: entries - } -} -exports.stts.encodingLength = function (box) { - return 4 + box.entries.length * 8 -} + // Clean up request + request = null; + }; -exports.ctts = {} -exports.ctts.encode = function (box, buf, offset) { - var entries = box.entries || [] - buf = buf ? buf.slice(offset) : new Buffer(exports.ctts.encodingLength(box)) + // Handle timeout + request.ontimeout = function handleTimeout() { + reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', + request)); - buf.writeUInt32BE(entries.length, 0) + // Clean up request + request = null; + }; - for (var i = 0; i < entries.length; i++) { - var ptr = i * 8 + 4 - buf.writeUInt32BE(entries[i].count || 0, ptr) - buf.writeUInt32BE(entries[i].compositionOffset || 0, ptr + 4) - } + // Add xsrf header + // This is only done if running in a standard browser environment. + // Specifically not if we're in a web worker, or react-native. + if (utils.isStandardBrowserEnv()) { + var cookies = require('./../helpers/cookies'); - exports.ctts.encode.bytes = 4 + entries.length * 8 - return buf -} -exports.ctts.decode = function (buf, offset) { - buf = buf.slice(offset) - var num = buf.readUInt32BE(0) - var entries = new Array(num) + // Add xsrf header + var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ? + cookies.read(config.xsrfCookieName) : + undefined; - for (var i = 0; i < num; i++) { - var ptr = i * 8 + 4 - entries[i] = { - count: buf.readUInt32BE(ptr), - compositionOffset: buf.readInt32BE(ptr + 4) + if (xsrfValue) { + requestHeaders[config.xsrfHeaderName] = xsrfValue; + } } - } - - return { - entries: entries - } -} -exports.ctts.encodingLength = function (box) { - return 4 + box.entries.length * 8 -} -exports.stsc = {} -exports.stsc.encode = function (box, buf, offset) { - var entries = box.entries || [] - buf = buf ? buf.slice(offset) : new Buffer(exports.stsc.encodingLength(box)) + // Add headers to the request + if ('setRequestHeader' in request) { + utils.forEach(requestHeaders, function setRequestHeader(val, key) { + if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { + // Remove Content-Type if data is undefined + delete requestHeaders[key]; + } else { + // Otherwise add header to the request + request.setRequestHeader(key, val); + } + }); + } - buf.writeUInt32BE(entries.length, 0) + // Add withCredentials to request if needed + if (config.withCredentials) { + request.withCredentials = true; + } - for (var i = 0; i < entries.length; i++) { - var ptr = i * 12 + 4 - buf.writeUInt32BE(entries[i].firstChunk || 0, ptr) - buf.writeUInt32BE(entries[i].samplesPerChunk || 0, ptr + 4) - buf.writeUInt32BE(entries[i].sampleDescriptionId || 0, ptr + 8) - } + // Add responseType to request if needed + if (config.responseType) { + try { + request.responseType = config.responseType; + } catch (e) { + // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2. + // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function. + if (config.responseType !== 'json') { + throw e; + } + } + } - exports.stsc.encode.bytes = 4 + entries.length * 12 - return buf -} -exports.stsc.decode = function (buf, offset) { - buf = buf.slice(offset) - var num = buf.readUInt32BE(0) - var entries = new Array(num) + // Handle progress if needed + if (typeof config.onDownloadProgress === 'function') { + request.addEventListener('progress', config.onDownloadProgress); + } - for (var i = 0; i < num; i++) { - var ptr = i * 12 + 4 - entries[i] = { - firstChunk: buf.readUInt32BE(ptr), - samplesPerChunk: buf.readUInt32BE(ptr + 4), - sampleDescriptionId: buf.readUInt32BE(ptr + 8) + // Not all browsers support upload events + if (typeof config.onUploadProgress === 'function' && request.upload) { + request.upload.addEventListener('progress', config.onUploadProgress); } - } - return { - entries: entries - } -} -exports.stsc.encodingLength = function (box) { - return 4 + box.entries.length * 12 -} + if (config.cancelToken) { + // Handle cancellation + config.cancelToken.promise.then(function onCanceled(cancel) { + if (!request) { + return; + } -exports.dref = {} -exports.dref.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(exports.dref.encodingLength(box)) - var entries = box.entries || [] + request.abort(); + reject(cancel); + // Clean up request + request = null; + }); + } - buf.writeUInt32BE(entries.length, 0) + if (requestData === undefined) { + requestData = null; + } - var ptr = 4 - for (var i = 0; i < entries.length; i++) { - var entry = entries[i] - var size = (entry.buf ? entry.buf.length : 0) + 4 + 4 + // Send the request + request.send(requestData); + }); +}; - buf.writeUInt32BE(size, ptr) - ptr += 4 +}).call(this,require('_process')) +},{"../core/createError":51,"./../core/settle":54,"./../helpers/btoa":58,"./../helpers/buildURL":59,"./../helpers/cookies":61,"./../helpers/isURLSameOrigin":63,"./../helpers/parseHeaders":65,"./../utils":67,"_process":15}],45:[function(require,module,exports){ +'use strict'; - buf.write(entry.type, ptr, 4, 'ascii') - ptr += 4 +var utils = require('./utils'); +var bind = require('./helpers/bind'); +var Axios = require('./core/Axios'); +var defaults = require('./defaults'); - if (entry.buf) { - entry.buf.copy(buf, ptr) - ptr += entry.buf.length - } - } +/** + * Create an instance of Axios + * + * @param {Object} defaultConfig The default config for the instance + * @return {Axios} A new instance of Axios + */ +function createInstance(defaultConfig) { + var context = new Axios(defaultConfig); + var instance = bind(Axios.prototype.request, context); - exports.dref.encode.bytes = ptr - return buf + // Copy axios.prototype to instance + utils.extend(instance, Axios.prototype, context); + + // Copy context to instance + utils.extend(instance, context); + + return instance; } -exports.dref.decode = function (buf, offset) { - buf = buf.slice(offset) - var num = buf.readUInt32BE(0) - var entries = new Array(num) - var ptr = 4 - for (var i = 0; i < num; i++) { - var size = buf.readUInt32BE(ptr) - var type = buf.toString('ascii', ptr + 4, ptr + 8) - var tmp = buf.slice(ptr + 8, ptr + size) - ptr += size +// Create the default instance to be exported +var axios = createInstance(defaults); - entries[i] = { - type: type, - buf: tmp - } - } +// Expose Axios class to allow class inheritance +axios.Axios = Axios; - return { - entries: entries - } -} -exports.dref.encodingLength = function (box) { - var totalSize = 4 - if (!box.entries) return totalSize - for (var i = 0; i < box.entries.length; i++) { - var buf = box.entries[i].buf - totalSize += (buf ? buf.length : 0) + 4 + 4 - } - return totalSize -} +// Factory for creating new instances +axios.create = function create(instanceConfig) { + return createInstance(utils.merge(defaults, instanceConfig)); +}; -exports.elst = {} -exports.elst.encode = function (box, buf, offset) { - var entries = box.entries || [] - buf = buf ? buf.slice(offset) : new Buffer(exports.elst.encodingLength(box)) +// Expose Cancel & CancelToken +axios.Cancel = require('./cancel/Cancel'); +axios.CancelToken = require('./cancel/CancelToken'); +axios.isCancel = require('./cancel/isCancel'); - buf.writeUInt32BE(entries.length, 0) +// Expose all/spread +axios.all = function all(promises) { + return Promise.all(promises); +}; +axios.spread = require('./helpers/spread'); - for (var i = 0; i < entries.length; i++) { - var ptr = i * 12 + 4 - buf.writeUInt32BE(entries[i].trackDuration || 0, ptr) - buf.writeUInt32BE(entries[i].mediaTime || 0, ptr + 4) - writeFixed32(entries[i].mediaRate || 0, buf, ptr + 8) - } +module.exports = axios; - exports.elst.encode.bytes = 4 + entries.length * 12 - return buf -} -exports.elst.decode = function (buf, offset) { - buf = buf.slice(offset) - var num = buf.readUInt32BE(0) - var entries = new Array(num) +// Allow use of default import syntax in TypeScript +module.exports.default = axios; - for (var i = 0; i < num; i++) { - var ptr = i * 12 + 4 - entries[i] = { - trackDuration: buf.readUInt32BE(ptr), - mediaTime: buf.readInt32BE(ptr + 4), - mediaRate: readFixed32(buf, ptr + 8) - } - } +},{"./cancel/Cancel":46,"./cancel/CancelToken":47,"./cancel/isCancel":48,"./core/Axios":49,"./defaults":56,"./helpers/bind":57,"./helpers/spread":66,"./utils":67}],46:[function(require,module,exports){ +'use strict'; - return { - entries: entries - } -} -exports.elst.encodingLength = function (box) { - return 4 + box.entries.length * 12 +/** + * A `Cancel` is an object that is thrown when an operation is canceled. + * + * @class + * @param {string=} message The message. + */ +function Cancel(message) { + this.message = message; } -exports.hdlr = {} -exports.hdlr.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(exports.hdlr.encodingLength(box)) +Cancel.prototype.toString = function toString() { + return 'Cancel' + (this.message ? ': ' + this.message : ''); +}; - var len = 21 + (box.name || '').length - buf.fill(0, 0, len) +Cancel.prototype.__CANCEL__ = true; - buf.write(box.handlerType || '', 4, 4, 'ascii') - writeString(box.name || '', buf, 20) +module.exports = Cancel; - exports.hdlr.encode.bytes = len - return buf -} -exports.hdlr.decode = function (buf, offset, end) { - buf = buf.slice(offset) - return { - handlerType: buf.toString('ascii', 4, 8), - name: readString(buf, 20, end) - } -} -exports.hdlr.encodingLength = function (box) { - return 21 + (box.name || '').length -} +},{}],47:[function(require,module,exports){ +'use strict'; -exports.mehd = {} -exports.mehd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(4) +var Cancel = require('./Cancel'); - buf.writeUInt32BE(box.fragmentDuration || 0, 0) - exports.mehd.encode.bytes = 4 - return buf -} -exports.mehd.decode = function (buf, offset) { - buf = buf.slice(offset) - return { - fragmentDuration: buf.readUInt32BE(0) +/** + * A `CancelToken` is an object that can be used to request cancellation of an operation. + * + * @class + * @param {Function} executor The executor function. + */ +function CancelToken(executor) { + if (typeof executor !== 'function') { + throw new TypeError('executor must be a function.'); } -} -exports.mehd.encodingLength = function (box) { - return 4 -} -exports.trex = {} -exports.trex.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(20) + var resolvePromise; + this.promise = new Promise(function promiseExecutor(resolve) { + resolvePromise = resolve; + }); - buf.writeUInt32BE(box.trackId || 0, 0) - buf.writeUInt32BE(box.defaultSampleDescriptionIndex || 0, 4) - buf.writeUInt32BE(box.defaultSampleDuration || 0, 8) - buf.writeUInt32BE(box.defaultSampleSize || 0, 12) - buf.writeUInt32BE(box.defaultSampleFlags || 0, 16) - exports.trex.encode.bytes = 20 - return buf -} -exports.trex.decode = function (buf, offset) { - buf = buf.slice(offset) - return { - trackId: buf.readUInt32BE(0), - defaultSampleDescriptionIndex: buf.readUInt32BE(4), - defaultSampleDuration: buf.readUInt32BE(8), - defaultSampleSize: buf.readUInt32BE(12), - defaultSampleFlags: buf.readUInt32BE(16) - } -} -exports.trex.encodingLength = function (box) { - return 20 + var token = this; + executor(function cancel(message) { + if (token.reason) { + // Cancellation has already been requested + return; + } + + token.reason = new Cancel(message); + resolvePromise(token.reason); + }); } -exports.mfhd = {} -exports.mfhd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(4) +/** + * Throws a `Cancel` if cancellation has been requested. + */ +CancelToken.prototype.throwIfRequested = function throwIfRequested() { + if (this.reason) { + throw this.reason; + } +}; - buf.writeUInt32BE(box.sequenceNumber || 0, 0) - exports.mfhd.encode.bytes = 4 - return buf -} -exports.mfhd.decode = function (buf, offset) { +/** + * Returns an object that contains a new `CancelToken` and a function that, when called, + * cancels the `CancelToken`. + */ +CancelToken.source = function source() { + var cancel; + var token = new CancelToken(function executor(c) { + cancel = c; + }); return { - sequenceNumber: buf.readUint32BE(0) - } -} -exports.mfhd.encodingLength = function (box) { - return 4 -} - -exports.tfhd = {} -exports.tfhd.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(4) - buf.writeUInt32BE(box.trackId, 0) - exports.tfhd.encode.bytes = 4 - return buf -} -exports.tfhd.decode = function (buf, offset) { - // TODO: this -} -exports.tfhd.encodingLength = function (box) { - // TODO: this is wrong! - return 4 -} - -exports.tfdt = {} -exports.tfdt.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(4) + token: token, + cancel: cancel + }; +}; - buf.writeUInt32BE(box.baseMediaDecodeTime || 0, 0) - exports.tfdt.encode.bytes = 4 - return buf -} -exports.tfdt.decode = function (buf, offset) { - // TODO: this -} -exports.tfdt.encodingLength = function (box) { - return 4 -} +module.exports = CancelToken; -exports.trun = {} -exports.trun.encode = function (box, buf, offset) { - buf = buf ? buf.slice(offset) : new Buffer(8 + box.entries.length * 16) +},{"./Cancel":46}],48:[function(require,module,exports){ +'use strict'; - // TODO: this is wrong - buf.writeUInt32BE(box.entries.length, 0) - buf.writeInt32BE(box.dataOffset, 4) - var ptr = 8 - for (var i = 0; i < box.entries.length; i++) { - var entry = box.entries[i] - buf.writeUInt32BE(entry.sampleDuration, ptr) - ptr += 4 +module.exports = function isCancel(value) { + return !!(value && value.__CANCEL__); +}; - buf.writeUInt32BE(entry.sampleSize, ptr) - ptr += 4 +},{}],49:[function(require,module,exports){ +'use strict'; - buf.writeUInt32BE(entry.sampleFlags, ptr) - ptr += 4 +var defaults = require('./../defaults'); +var utils = require('./../utils'); +var InterceptorManager = require('./InterceptorManager'); +var dispatchRequest = require('./dispatchRequest'); - buf.writeUInt32BE(entry.sampleCompositionTimeOffset, ptr) - ptr += 4 - } - exports.trun.encode.bytes = ptr -} -exports.trun.decode = function (buf, offset) { - // TODO: this -} -exports.trun.encodingLength = function (box) { - // TODO: this is wrong - return 8 + box.entries.length * 16 +/** + * Create a new instance of Axios + * + * @param {Object} instanceConfig The default config for the instance + */ +function Axios(instanceConfig) { + this.defaults = instanceConfig; + this.interceptors = { + request: new InterceptorManager(), + response: new InterceptorManager() + }; } -exports.mdat = {} -exports.mdat.encode = function (box, buf, offset) { - if (box.buffer) { - box.buffer.copy(buf, offset) - exports.mdat.encode.bytes = box.buffer.length - } else { - exports.mdat.encode.bytes = exports.mdat.encodingLength(box) - } -} -exports.mdat.decode = function (buf, start, end) { - return { - buffer: new Buffer(buf.slice(start, end)) +/** + * Dispatch a request + * + * @param {Object} config The config specific for this request (merged with this.defaults) + */ +Axios.prototype.request = function request(config) { + /*eslint no-param-reassign:0*/ + // Allow for axios('example/url'[, config]) a la fetch API + if (typeof config === 'string') { + config = utils.merge({ + url: arguments[0] + }, arguments[1]); } -} -exports.mdat.encodingLength = function (box) { - return box.buffer ? box.buffer.length : box.contentLength -} -function writeReserved (buf, offset, end) { - for (var i = offset; i < end; i++) buf[i] = 0 -} + config = utils.merge(defaults, this.defaults, { method: 'get' }, config); + config.method = config.method.toLowerCase(); -function writeDate (date, buf, offset) { - buf.writeUInt32BE(Math.floor((date.getTime() + TIME_OFFSET) / 1000), offset) -} + // Hook up interceptors middleware + var chain = [dispatchRequest, undefined]; + var promise = Promise.resolve(config); -// TODO: think something is wrong here -function writeFixed32 (num, buf, offset) { - buf.writeUInt16BE(Math.floor(num) % (256 * 256), offset) - buf.writeUInt16BE(Math.floor(num * 256 * 256) % (256 * 256), offset + 2) -} + this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { + chain.unshift(interceptor.fulfilled, interceptor.rejected); + }); -function writeFixed16 (num, buf, offset) { - buf[offset] = Math.floor(num) % 256 - buf[offset + 1] = Math.floor(num * 256) % 256 -} + this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { + chain.push(interceptor.fulfilled, interceptor.rejected); + }); -function writeMatrix (list, buf, offset) { - if (!list) list = [0, 0, 0, 0, 0, 0, 0, 0, 0] - for (var i = 0; i < list.length; i++) { - writeFixed32(list[i], buf, offset + i * 4) + while (chain.length) { + promise = promise.then(chain.shift(), chain.shift()); } -} -function writeString (str, buf, offset) { - var strBuffer = new Buffer(str, 'utf8') - strBuffer.copy(buf, offset) - buf[offset + strBuffer.length] = 0 -} + return promise; +}; -function readMatrix (buf) { - var list = new Array(buf.length / 4) - for (var i = 0; i < list.length; i++) list[i] = readFixed32(buf, i * 4) - return list -} +// Provide aliases for supported request methods +utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function(url, config) { + return this.request(utils.merge(config || {}, { + method: method, + url: url + })); + }; +}); -function readDate (buf, offset) { - return new Date(buf.readUInt32BE(offset) * 1000 - TIME_OFFSET) -} +utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function(url, data, config) { + return this.request(utils.merge(config || {}, { + method: method, + url: url, + data: data + })); + }; +}); -function readFixed32 (buf, offset) { - return buf.readUInt16BE(offset) + buf.readUInt16BE(offset + 2) / (256 * 256) -} +module.exports = Axios; -function readFixed16 (buf, offset) { - return buf[offset] + buf[offset + 1] / 256 -} +},{"./../defaults":56,"./../utils":67,"./InterceptorManager":50,"./dispatchRequest":52}],50:[function(require,module,exports){ +'use strict'; -function readString (buf, offset, length) { - var i - for (i = 0; i < length; i++) { - if (buf[offset + i] === 0) { - break - } - } - return buf.toString('utf8', offset, offset + i) -} +var utils = require('./../utils'); -}).call(this,require("buffer").Buffer) -},{"./descriptor":68,"./index":69,"buffer":159}],68:[function(require,module,exports){ -(function (Buffer){ -var tagToName = { - 0x03: 'ESDescriptor', - 0x04: 'DecoderConfigDescriptor', - 0x05: 'DecoderSpecificInfo', - 0x06: 'SLConfigDescriptor' +function InterceptorManager() { + this.handlers = []; } -exports.Descriptor = {} -exports.Descriptor.decode = function (buf, start, end) { - var tag = buf.readUInt8(start) - var ptr = start + 1 - var lenByte - var len = 0 - do { - lenByte = buf.readUInt8(ptr++) - len = (len << 7) | (lenByte & 0x7f) - } while (lenByte & 0x80) +/** + * Add a new interceptor to the stack + * + * @param {Function} fulfilled The function to handle `then` for a `Promise` + * @param {Function} rejected The function to handle `reject` for a `Promise` + * + * @return {Number} An ID used to remove interceptor later + */ +InterceptorManager.prototype.use = function use(fulfilled, rejected) { + this.handlers.push({ + fulfilled: fulfilled, + rejected: rejected + }); + return this.handlers.length - 1; +}; - var obj - var tagName = tagToName[tag] // May be undefined; that's ok - if (exports[tagName]) { - obj = exports[tagName].decode(buf, ptr, end) - } else { - obj = { - buffer: new Buffer(buf.slice(ptr, ptr + len)) - } - } - - obj.tag = tag - obj.tagName = tagName - obj.length = (ptr - start) + len - obj.contentsLen = len - return obj -} - -exports.DescriptorArray = {} -exports.DescriptorArray.decode = function (buf, start, end) { - var ptr = start - var obj = {} - while (ptr + 2 <= end) { - var descriptor = exports.Descriptor.decode(buf, ptr, end) - ptr += descriptor.length - var tagName = tagToName[descriptor.tag] || ('Descriptor' + descriptor.tag) - obj[tagName] = descriptor - } - return obj -} - -exports.ESDescriptor = {} -exports.ESDescriptor.decode = function (buf, start, end) { - var flags = buf.readUInt8(start + 2) - var ptr = start + 3 - if (flags & 0x80) { - ptr += 2 - } - if (flags & 0x40) { - var len = buf.readUInt8(ptr) - ptr += len + 1 - } - if (flags & 0x20) { - ptr += 2 +/** + * Remove an interceptor from the stack + * + * @param {Number} id The ID that was returned by `use` + */ +InterceptorManager.prototype.eject = function eject(id) { + if (this.handlers[id]) { + this.handlers[id] = null; } - return exports.DescriptorArray.decode(buf, ptr, end) -} - -exports.DecoderConfigDescriptor = {} -exports.DecoderConfigDescriptor.decode = function (buf, start, end) { - var oti = buf.readUInt8(start) - var obj = exports.DescriptorArray.decode(buf, start + 13, end) - obj.oti = oti - return obj -} +}; -}).call(this,require("buffer").Buffer) -},{"buffer":159}],69:[function(require,module,exports){ -(function (Buffer){ -// var assert = require('assert') -var uint64be = require('uint64be') +/** + * Iterate over all the registered interceptors + * + * This method is particularly useful for skipping over any + * interceptors that may have become `null` calling `eject`. + * + * @param {Function} fn The function to call for each interceptor + */ +InterceptorManager.prototype.forEach = function forEach(fn) { + utils.forEach(this.handlers, function forEachHandler(h) { + if (h !== null) { + fn(h); + } + }); +}; -var boxes = require('./boxes') +module.exports = InterceptorManager; -var UINT32_MAX = 4294967295 +},{"./../utils":67}],51:[function(require,module,exports){ +'use strict'; -var Box = exports +var enhanceError = require('./enhanceError'); -/* - * Lists the proper order for boxes inside containers. - * Five-character names ending in 's' indicate arrays instead of single elements. +/** + * Create an Error with the specified message, config, error code, request and response. + * + * @param {string} message The error message. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The created error. */ -var containers = exports.containers = { - 'moov': ['mvhd', 'meta', 'traks', 'mvex'], - 'trak': ['tkhd', 'tref', 'trgr', 'edts', 'meta', 'mdia', 'udta'], - 'edts': ['elst'], - 'mdia': ['mdhd', 'hdlr', 'elng', 'minf'], - 'minf': ['vmhd', 'smhd', 'hmhd', 'sthd', 'nmhd', 'dinf', 'stbl'], - 'dinf': ['dref'], - 'stbl': ['stsd', 'stts', 'ctts', 'cslg', 'stsc', 'stsz', 'stz2', 'stco', 'co64', 'stss', 'stsh', 'padb', 'stdp', 'sdtp', 'sbgps', 'sgpds', 'subss', 'saizs', 'saios'], - 'mvex': ['mehd', 'trexs', 'leva'], - 'moof': ['mfhd', 'meta', 'trafs'], - 'traf': ['tfhd', 'trun', 'sbgps', 'sgpds', 'subss', 'saizs', 'saios', 'tfdt', 'meta'] -} +module.exports = function createError(message, config, code, request, response) { + var error = new Error(message); + return enhanceError(error, config, code, request, response); +}; -Box.encode = function (obj, buffer, offset) { - Box.encodingLength(obj) // sets every level appropriately - offset = offset || 0 - buffer = buffer || new Buffer(obj.length) - return Box._encode(obj, buffer, offset) -} +},{"./enhanceError":53}],52:[function(require,module,exports){ +'use strict'; -Box._encode = function (obj, buffer, offset) { - var type = obj.type - var len = obj.length - if (len > UINT32_MAX) { - len = 1 - } - buffer.writeUInt32BE(len, offset) - buffer.write(obj.type, offset + 4, 4, 'ascii') - var ptr = offset + 8 - if (len === 1) { - uint64be.encode(obj.length, buffer, ptr) - ptr += 8 - } - if (boxes.fullBoxes[type]) { - buffer.writeUInt32BE(obj.flags || 0, ptr) - buffer.writeUInt8(obj.version || 0, ptr) - ptr += 4 - } +var utils = require('./../utils'); +var transformData = require('./transformData'); +var isCancel = require('../cancel/isCancel'); +var defaults = require('../defaults'); +var isAbsoluteURL = require('./../helpers/isAbsoluteURL'); +var combineURLs = require('./../helpers/combineURLs'); - if (containers[type]) { - var contents = containers[type] - contents.forEach(function (childType) { - if (childType.length === 5) { - var entry = obj[childType] || [] - childType = childType.substr(0, 4) - entry.forEach(function (child) { - Box._encode(child, buffer, ptr) - ptr += Box.encode.bytes - }) - } else if (obj[childType]) { - Box._encode(obj[childType], buffer, ptr) - ptr += Box.encode.bytes - } - }) - if (obj.otherBoxes) { - obj.otherBoxes.forEach(function (child) { - Box._encode(child, buffer, ptr) - ptr += Box.encode.bytes - }) - } - } else if (boxes[type]) { - var encode = boxes[type].encode - encode(obj, buffer, ptr) - ptr += encode.bytes - } else if (obj.buffer) { - var buf = obj.buffer - buf.copy(buffer, ptr) - ptr += obj.buffer.length - } else { - throw new Error('Either `type` must be set to a known type (not\'' + type + '\') or `buffer` must be set') +/** + * Throws a `Cancel` if cancellation has been requested. + */ +function throwIfCancellationRequested(config) { + if (config.cancelToken) { + config.cancelToken.throwIfRequested(); } - - Box.encode.bytes = ptr - offset - // assert.equal(ptr - offset, obj.length, 'Error encoding \'' + type + '\': wrote ' + ptr - offset + ' bytes, expecting ' + obj.length) - return buffer } -/* - * Returns an object with `type` and `size` fields, - * or if there isn't enough data, returns the total - * number of bytes needed to read the headers +/** + * Dispatch a request to the server using the configured adapter. + * + * @param {object} config The config that is to be used for the request + * @returns {Promise} The Promise to be fulfilled */ -Box.readHeaders = function (buffer, start, end) { - start = start || 0 - end = end || buffer.length - if (end - start < 8) { - return 8 +module.exports = function dispatchRequest(config) { + throwIfCancellationRequested(config); + + // Support baseURL config + if (config.baseURL && !isAbsoluteURL(config.url)) { + config.url = combineURLs(config.baseURL, config.url); } - var len = buffer.readUInt32BE(start) - var type = buffer.toString('ascii', start + 4, start + 8) - var ptr = start + 8 + // Ensure headers exist + config.headers = config.headers || {}; - if (len === 1) { - if (end - start < 16) { - return 16 - } + // Transform request data + config.data = transformData( + config.data, + config.headers, + config.transformRequest + ); - len = uint64be.decode(buffer, ptr) - ptr += 8 - } + // Flatten headers + config.headers = utils.merge( + config.headers.common || {}, + config.headers[config.method] || {}, + config.headers || {} + ); - var version - var flags - if (boxes.fullBoxes[type]) { - version = buffer.readUInt8(ptr) - flags = buffer.readUInt32BE(ptr) & 0xffffff - ptr += 4 - } + utils.forEach( + ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], + function cleanHeaderConfig(method) { + delete config.headers[method]; + } + ); - return { - length: len, - headersLen: ptr - start, - contentLen: len - (ptr - start), - type: type, - version: version, - flags: flags - } -} + var adapter = config.adapter || defaults.adapter; -Box.decode = function (buffer, start, end) { - start = start || 0 - end = end || buffer.length - var headers = Box.readHeaders(buffer, start, end) - if (!headers || headers.length > end - start) { - throw new Error('Data too short') - } + return adapter(config).then(function onAdapterResolution(response) { + throwIfCancellationRequested(config); - return Box.decodeWithoutHeaders(headers, buffer, start + headers.headersLen, start + headers.length) -} + // Transform response data + response.data = transformData( + response.data, + response.headers, + config.transformResponse + ); -Box.decodeWithoutHeaders = function (headers, buffer, start, end) { - start = start || 0 - end = end || buffer.length - var type = headers.type - var obj = {} - if (containers[type]) { - obj.otherBoxes = [] - var contents = containers[type] - var ptr = start - while (end - ptr >= 8) { - var child = Box.decode(buffer, ptr, end) - ptr += child.length - if (contents.indexOf(child.type) >= 0) { - obj[child.type] = child - } else if (contents.indexOf(child.type + 's') >= 0) { - var childType = child.type + 's' - var entry = obj[childType] = obj[childType] || [] - entry.push(child) - } else { - obj.otherBoxes.push(child) + return response; + }, function onAdapterRejection(reason) { + if (!isCancel(reason)) { + throwIfCancellationRequested(config); + + // Transform response data + if (reason && reason.response) { + reason.response.data = transformData( + reason.response.data, + reason.response.headers, + config.transformResponse + ); } } - } else if (boxes[type]) { - var decode = boxes[type].decode - obj = decode(buffer, start, end) - } else { - obj.buffer = new Buffer(buffer.slice(start, end)) - } - obj.length = headers.length - obj.contentLen = headers.contentLen - obj.type = headers.type - obj.version = headers.version - obj.flags = headers.flags - return obj -} + return Promise.reject(reason); + }); +}; -Box.encodingLength = function (obj) { - var type = obj.type +},{"../cancel/isCancel":48,"../defaults":56,"./../helpers/combineURLs":60,"./../helpers/isAbsoluteURL":62,"./../utils":67,"./transformData":55}],53:[function(require,module,exports){ +'use strict'; - var len = 8 - if (boxes.fullBoxes[type]) { - len += 4 +/** + * Update an Error with the specified config, error code, and response. + * + * @param {Error} error The error to update. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The error. + */ +module.exports = function enhanceError(error, config, code, request, response) { + error.config = config; + if (code) { + error.code = code; } + error.request = request; + error.response = response; + return error; +}; - if (containers[type]) { - var contents = containers[type] - contents.forEach(function (childType) { - if (childType.length === 5) { - var entry = obj[childType] || [] - childType = childType.substr(0, 4) - entry.forEach(function (child) { - child.type = childType - len += Box.encodingLength(child) - }) - } else if (obj[childType]) { - var child = obj[childType] - child.type = childType - len += Box.encodingLength(child) - } - }) - if (obj.otherBoxes) { - obj.otherBoxes.forEach(function (child) { - len += Box.encodingLength(child) - }) - } - } else if (boxes[type]) { - len += boxes[type].encodingLength(obj) - } else if (obj.buffer) { - len += obj.buffer.length - } else { - throw new Error('Either `type` must be set to a known type (not\'' + type + '\') or `buffer` must be set') - } +},{}],54:[function(require,module,exports){ +'use strict'; - if (len > UINT32_MAX) { - len += 8 +var createError = require('./createError'); + +/** + * Resolve or reject a Promise based on response status. + * + * @param {Function} resolve A function that resolves the promise. + * @param {Function} reject A function that rejects the promise. + * @param {object} response The response. + */ +module.exports = function settle(resolve, reject, response) { + var validateStatus = response.config.validateStatus; + // Note: status is not exposed by XDomainRequest + if (!response.status || !validateStatus || validateStatus(response.status)) { + resolve(response); + } else { + reject(createError( + 'Request failed with status code ' + response.status, + response.config, + null, + response.request, + response + )); } +}; - obj.length = len - return len -} +},{"./createError":51}],55:[function(require,module,exports){ +'use strict'; -}).call(this,require("buffer").Buffer) -},{"./boxes":67,"buffer":159,"uint64be":115}],70:[function(require,module,exports){ -(function (Buffer){ -var stream = require('readable-stream') -var inherits = require('inherits') -var nextEvent = require('next-event') -var Box = require('mp4-box-encoding') +var utils = require('./../utils'); -var EMPTY = new Buffer(0) +/** + * Transform the data for a request or a response + * + * @param {Object|String} data The data to be transformed + * @param {Array} headers The headers for the request or response + * @param {Array|Function} fns A single function or Array of functions + * @returns {*} The resulting transformed data + */ +module.exports = function transformData(data, headers, fns) { + /*eslint no-param-reassign:0*/ + utils.forEach(fns, function transform(fn) { + data = fn(data, headers); + }); -module.exports = Decoder + return data; +}; -function Decoder () { - if (!(this instanceof Decoder)) return new Decoder() - stream.Writable.call(this) +},{"./../utils":67}],56:[function(require,module,exports){ +(function (process){ +'use strict'; - this.destroyed = false +var utils = require('./utils'); +var normalizeHeaderName = require('./helpers/normalizeHeaderName'); - this._pending = 0 - this._missing = 0 - this._buf = null - this._str = null - this._cb = null - this._ondrain = null - this._writeBuffer = null - this._writeCb = null +var DEFAULT_CONTENT_TYPE = { + 'Content-Type': 'application/x-www-form-urlencoded' +}; - this._ondrain = null - this._kick() +function setContentTypeIfUnset(headers, value) { + if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { + headers['Content-Type'] = value; + } } -inherits(Decoder, stream.Writable) - -Decoder.prototype.destroy = function (err) { - if (this.destroyed) return - this.destroyed = true - if (err) this.emit('error', err) - this.emit('close') +function getDefaultAdapter() { + var adapter; + if (typeof XMLHttpRequest !== 'undefined') { + // For browsers use XHR adapter + adapter = require('./adapters/xhr'); + } else if (typeof process !== 'undefined') { + // For node use HTTP adapter + adapter = require('./adapters/http'); + } + return adapter; } -Decoder.prototype._write = function (data, enc, next) { - if (this.destroyed) return - var drained = !this._str || !this._str._writableState.needDrain +var defaults = { + adapter: getDefaultAdapter(), - while (data.length && !this.destroyed) { - if (!this._missing) { - this._writeBuffer = data - this._writeCb = next - return + transformRequest: [function transformRequest(data, headers) { + normalizeHeaderName(headers, 'Content-Type'); + if (utils.isFormData(data) || + utils.isArrayBuffer(data) || + utils.isBuffer(data) || + utils.isStream(data) || + utils.isFile(data) || + utils.isBlob(data) + ) { + return data; + } + if (utils.isArrayBufferView(data)) { + return data.buffer; + } + if (utils.isURLSearchParams(data)) { + setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); + return data.toString(); + } + if (utils.isObject(data)) { + setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); + return JSON.stringify(data); } + return data; + }], - var consumed = data.length < this._missing ? data.length : this._missing - if (this._buf) data.copy(this._buf, this._buf.length - this._missing) - else if (this._str) drained = this._str.write(consumed === data.length ? data : data.slice(0, consumed)) + transformResponse: [function transformResponse(data) { + /*eslint no-param-reassign:0*/ + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { /* Ignore */ } + } + return data; + }], - this._missing -= consumed + timeout: 0, - if (!this._missing) { - var buf = this._buf - var cb = this._cb - var stream = this._str + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN', - this._buf = this._cb = this._str = this._ondrain = null - drained = true - - if (stream) stream.end() - if (cb) cb(buf) - } + maxContentLength: -1, - data = consumed === data.length ? EMPTY : data.slice(consumed) + validateStatus: function validateStatus(status) { + return status >= 200 && status < 300; } +}; - if (this._pending && !this._missing) { - this._writeBuffer = data - this._writeCb = next - return +defaults.headers = { + common: { + 'Accept': 'application/json, text/plain, */*' } +}; - if (drained) next() - else this._ondrain(next) -} +utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { + defaults.headers[method] = {}; +}); -Decoder.prototype._buffer = function (size, cb) { - this._missing = size - this._buf = new Buffer(size) - this._cb = cb -} +utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); +}); -Decoder.prototype._stream = function (size, cb) { - var self = this - this._missing = size - this._str = new MediaData(this) - this._ondrain = nextEvent(this._str, 'drain') - this._pending++ - this._str.on('end', function () { - self._pending-- - self._kick() - }) - this._cb = cb - return this._str -} +module.exports = defaults; -Decoder.prototype._readBox = function () { - var self = this - bufferHeaders(8) +}).call(this,require('_process')) +},{"./adapters/http":44,"./adapters/xhr":44,"./helpers/normalizeHeaderName":64,"./utils":67,"_process":15}],57:[function(require,module,exports){ +'use strict'; - function bufferHeaders (len, buf) { - self._buffer(len, function (additionalBuf) { - if (buf) { - buf = Buffer.concat([buf, additionalBuf]) - } else { - buf = additionalBuf - } - var headers = Box.readHeaders(buf) - if (typeof headers === 'number') { - bufferHeaders(headers - buf.length, buf) - } else { - self._pending++ - self._headers = headers - self.emit('box', headers) - } - }) - } -} +module.exports = function bind(fn, thisArg) { + return function wrap() { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + return fn.apply(thisArg, args); + }; +}; -Decoder.prototype.stream = function () { - var self = this - if (!self._headers) throw new Error('this function can only be called once after \'box\' is emitted') - var headers = self._headers - self._headers = null +},{}],58:[function(require,module,exports){ +'use strict'; - return self._stream(headers.contentLen, null) -} +// btoa polyfill for IE<10 courtesy https://github.com/davidchambers/Base64.js -Decoder.prototype.decode = function (cb) { - var self = this - if (!self._headers) throw new Error('this function can only be called once after \'box\' is emitted') - var headers = self._headers - self._headers = null +var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; - self._buffer(headers.contentLen, function (buf) { - var box = Box.decodeWithoutHeaders(headers, buf) - cb(box) - self._pending-- - self._kick() - }) +function E() { + this.message = 'String contains an invalid character'; } +E.prototype = new Error; +E.prototype.code = 5; +E.prototype.name = 'InvalidCharacterError'; -Decoder.prototype.ignore = function () { - var self = this - if (!self._headers) throw new Error('this function can only be called once after \'box\' is emitted') - var headers = self._headers - self._headers = null - - this._missing = headers.contentLen - this._cb = function () { - self._pending-- - self._kick() +function btoa(input) { + var str = String(input); + var output = ''; + for ( + // initialize result and counter + var block, charCode, idx = 0, map = chars; + // if the next str index does not exist: + // change the mapping table to "=" + // check if d has no fractional digits + str.charAt(idx | 0) || (map = '=', idx % 1); + // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = str.charCodeAt(idx += 3 / 4); + if (charCode > 0xFF) { + throw new E(); + } + block = block << 8 | charCode; } + return output; } -Decoder.prototype._kick = function () { - if (this._pending) return - if (!this._buf && !this._str) this._readBox() - if (this._writeBuffer) { - var next = this._writeCb - var buffer = this._writeBuffer - this._writeBuffer = null - this._writeCb = null - this._write(buffer, null, next) - } -} +module.exports = btoa; -function MediaData (parent) { - this._parent = parent - this.destroyed = false - stream.PassThrough.call(this) -} +},{}],59:[function(require,module,exports){ +'use strict'; -inherits(MediaData, stream.PassThrough) +var utils = require('./../utils'); -MediaData.prototype.destroy = function (err) { - if (this.destroyed) return - this.destroyed = true - this._parent.destroy(err) - if (err) this.emit('error', err) - this.emit('close') +function encode(val) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, '+'). + replace(/%5B/gi, '['). + replace(/%5D/gi, ']'); } -}).call(this,require("buffer").Buffer) -},{"buffer":159,"inherits":58,"mp4-box-encoding":69,"next-event":74,"readable-stream":92}],71:[function(require,module,exports){ -(function (process,Buffer){ -var stream = require('readable-stream') -var inherits = require('inherits') -var Box = require('mp4-box-encoding') - -module.exports = Encoder +/** + * Build a URL by appending params to the end + * + * @param {string} url The base of the url (e.g., http://www.google.com) + * @param {object} [params] The params to be appended + * @returns {string} The formatted url + */ +module.exports = function buildURL(url, params, paramsSerializer) { + /*eslint no-param-reassign:0*/ + if (!params) { + return url; + } -function noop () {} + var serializedParams; + if (paramsSerializer) { + serializedParams = paramsSerializer(params); + } else if (utils.isURLSearchParams(params)) { + serializedParams = params.toString(); + } else { + var parts = []; -function Encoder () { - if (!(this instanceof Encoder)) return new Encoder() - stream.Readable.call(this) + utils.forEach(params, function serialize(val, key) { + if (val === null || typeof val === 'undefined') { + return; + } - this.destroyed = false + if (utils.isArray(val)) { + key = key + '[]'; + } - this._reading = false - this._stream = null - this._drain = null - this._want = false - this._onreadable = onreadable - this._onend = onend + if (!utils.isArray(val)) { + val = [val]; + } - var self = this + utils.forEach(val, function parseValue(v) { + if (utils.isDate(v)) { + v = v.toISOString(); + } else if (utils.isObject(v)) { + v = JSON.stringify(v); + } + parts.push(encode(key) + '=' + encode(v)); + }); + }); - function onreadable () { - if (!self._want) return - self._want = false - self._read() + serializedParams = parts.join('&'); } - function onend () { - self._stream = null + if (serializedParams) { + url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; } -} -inherits(Encoder, stream.Readable) - -Encoder.prototype.mediaData = -Encoder.prototype.mdat = function (size, cb) { - var stream = new MediaData(this) - this.box({type: 'mdat', contentLength: size, encodeBufferLen: 8, stream: stream}, cb) - return stream -} + return url; +}; -Encoder.prototype.box = function (box, cb) { - if (!cb) cb = noop - if (this.destroyed) return cb(new Error('Encoder is destroyed')) +},{"./../utils":67}],60:[function(require,module,exports){ +'use strict'; - var buf - if (box.encodeBufferLen) { - buf = new Buffer(box.encodeBufferLen) - } - if (box.stream) { - box.buffer = null - buf = Box.encode(box, buf) - this.push(buf) - this._stream = box.stream - this._stream.on('readable', this._onreadable) - this._stream.on('end', this._onend) - this._stream.on('end', cb) - this._forward() - } else { - buf = Box.encode(box, buf) - var drained = this.push(buf) - if (drained) return process.nextTick(cb) - this._drain = cb - } -} +/** + * Creates a new URL by combining the specified URLs + * + * @param {string} baseURL The base URL + * @param {string} relativeURL The relative URL + * @returns {string} The combined URL + */ +module.exports = function combineURLs(baseURL, relativeURL) { + return relativeURL + ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') + : baseURL; +}; -Encoder.prototype.destroy = function (err) { - if (this.destroyed) return - this.destroyed = true - if (this._stream && this._stream.destroy) this._stream.destroy() - this._stream = null - if (this._drain) { - var cb = this._drain - this._drain = null - cb(err) - } - if (err) this.emit('error', err) - this.emit('close') -} +},{}],61:[function(require,module,exports){ +'use strict'; -Encoder.prototype.finalize = function () { - this.push(null) -} +var utils = require('./../utils'); -Encoder.prototype._forward = function () { - if (!this._stream) return +module.exports = ( + utils.isStandardBrowserEnv() ? - while (!this.destroyed) { - var buf = this._stream.read() + // Standard browser envs support document.cookie + (function standardBrowserEnv() { + return { + write: function write(name, value, expires, path, domain, secure) { + var cookie = []; + cookie.push(name + '=' + encodeURIComponent(value)); - if (!buf) { - this._want = !!this._stream - return - } + if (utils.isNumber(expires)) { + cookie.push('expires=' + new Date(expires).toGMTString()); + } - if (!this.push(buf)) return - } -} + if (utils.isString(path)) { + cookie.push('path=' + path); + } -Encoder.prototype._read = function () { - if (this._reading || this.destroyed) return - this._reading = true + if (utils.isString(domain)) { + cookie.push('domain=' + domain); + } - if (this._stream) this._forward() - if (this._drain) { - var drain = this._drain - this._drain = null - drain() - } + if (secure === true) { + cookie.push('secure'); + } - this._reading = false -} + document.cookie = cookie.join('; '); + }, -function MediaData (parent) { - this._parent = parent - this.destroyed = false - stream.PassThrough.call(this) -} + read: function read(name) { + var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); + return (match ? decodeURIComponent(match[3]) : null); + }, -inherits(MediaData, stream.PassThrough) + remove: function remove(name) { + this.write(name, '', Date.now() - 86400000); + } + }; + })() : -MediaData.prototype.destroy = function (err) { - if (this.destroyed) return - this.destroyed = true - this._parent.destroy(err) - if (err) this.emit('error', err) - this.emit('close') -} + // Non standard browser env (web workers, react-native) lack needed support. + (function nonStandardBrowserEnv() { + return { + write: function write() {}, + read: function read() { return null; }, + remove: function remove() {} + }; + })() +); -}).call(this,require('_process'),require("buffer").Buffer) -},{"_process":170,"buffer":159,"inherits":58,"mp4-box-encoding":69,"readable-stream":92}],72:[function(require,module,exports){ -exports.decode = require('./decode') -exports.encode = require('./encode') +},{"./../utils":67}],62:[function(require,module,exports){ +'use strict'; -},{"./decode":70,"./encode":71}],73:[function(require,module,exports){ -module.exports = MultiStream +/** + * Determines whether the specified URL is absolute + * + * @param {string} url The URL to test + * @returns {boolean} True if the specified URL is absolute, otherwise false + */ +module.exports = function isAbsoluteURL(url) { + // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). + // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed + // by any combination of letters, digits, plus, period, or hyphen. + return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); +}; -var inherits = require('inherits') -var stream = require('readable-stream') +},{}],63:[function(require,module,exports){ +'use strict'; -inherits(MultiStream, stream.Readable) +var utils = require('./../utils'); -function MultiStream (streams, opts) { - var self = this - if (!(self instanceof MultiStream)) return new MultiStream(streams, opts) - stream.Readable.call(self, opts) +module.exports = ( + utils.isStandardBrowserEnv() ? - self.destroyed = false + // Standard browser envs have full support of the APIs needed to test + // whether the request URL is of the same origin as current location. + (function standardBrowserEnv() { + var msie = /(msie|trident)/i.test(navigator.userAgent); + var urlParsingNode = document.createElement('a'); + var originURL; - self._drained = false - self._forwarding = false - self._current = null + /** + * Parse a URL to discover it's components + * + * @param {String} url The URL to be parsed + * @returns {Object} + */ + function resolveURL(url) { + var href = url; - if (typeof streams === 'function') { - self._queue = streams - } else { - self._queue = streams.map(toStreams2) - self._queue.forEach(function (stream) { - if (typeof stream !== 'function') self._attachErrorListener(stream) - }) - } + if (msie) { + // IE needs attribute set twice to normalize properties + urlParsingNode.setAttribute('href', href); + href = urlParsingNode.href; + } - self._next() -} + urlParsingNode.setAttribute('href', href); -MultiStream.obj = function (streams) { - return new MultiStream(streams, { objectMode: true, highWaterMark: 16 }) -} + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') ? + urlParsingNode.pathname : + '/' + urlParsingNode.pathname + }; + } -MultiStream.prototype._read = function () { - this._drained = true - this._forward() -} + originURL = resolveURL(window.location.href); -MultiStream.prototype._forward = function () { - if (this._forwarding || !this._drained || !this._current) return - this._forwarding = true + /** + * Determine if a URL shares the same origin as the current location + * + * @param {String} requestURL The URL to test + * @returns {boolean} True if URL shares the same origin, otherwise false + */ + return function isURLSameOrigin(requestURL) { + var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; + return (parsed.protocol === originURL.protocol && + parsed.host === originURL.host); + }; + })() : - var chunk - while ((chunk = this._current.read()) !== null) { - this._drained = this.push(chunk) - } + // Non standard browser envs (web workers, react-native) lack needed support. + (function nonStandardBrowserEnv() { + return function isURLSameOrigin() { + return true; + }; + })() +); - this._forwarding = false -} +},{"./../utils":67}],64:[function(require,module,exports){ +'use strict'; -MultiStream.prototype.destroy = function (err) { - if (this.destroyed) return - this.destroyed = true +var utils = require('../utils'); - if (this._current && this._current.destroy) this._current.destroy() - if (typeof this._queue !== 'function') { - this._queue.forEach(function (stream) { - if (stream.destroy) stream.destroy() - }) - } +module.exports = function normalizeHeaderName(headers, normalizedName) { + utils.forEach(headers, function processHeader(value, name) { + if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { + headers[normalizedName] = value; + delete headers[name]; + } + }); +}; - if (err) this.emit('error', err) - this.emit('close') -} +},{"../utils":67}],65:[function(require,module,exports){ +'use strict'; -MultiStream.prototype._next = function () { - var self = this - self._current = null - - if (typeof self._queue === 'function') { - self._queue(function (err, stream) { - if (err) return self.destroy(err) - stream = toStreams2(stream) - self._attachErrorListener(stream) - self._gotNextStream(stream) - }) - } else { - var stream = self._queue.shift() - if (typeof stream === 'function') { - stream = toStreams2(stream()) - self._attachErrorListener(stream) - } - self._gotNextStream(stream) - } -} - -MultiStream.prototype._gotNextStream = function (stream) { - var self = this +var utils = require('./../utils'); - if (!stream) { - self.push(null) - self.destroy() - return - } +// Headers whose duplicates are ignored by node +// c.f. https://nodejs.org/api/http.html#http_message_headers +var ignoreDuplicateOf = [ + 'age', 'authorization', 'content-length', 'content-type', 'etag', + 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', + 'last-modified', 'location', 'max-forwards', 'proxy-authorization', + 'referer', 'retry-after', 'user-agent' +]; - self._current = stream - self._forward() +/** + * Parse headers into an object + * + * ``` + * Date: Wed, 27 Aug 2014 08:58:49 GMT + * Content-Type: application/json + * Connection: keep-alive + * Transfer-Encoding: chunked + * ``` + * + * @param {String} headers Headers needing to be parsed + * @returns {Object} Headers parsed into an object + */ +module.exports = function parseHeaders(headers) { + var parsed = {}; + var key; + var val; + var i; - stream.on('readable', onReadable) - stream.once('end', onEnd) - stream.once('close', onClose) + if (!headers) { return parsed; } - function onReadable () { - self._forward() - } + utils.forEach(headers.split('\n'), function parser(line) { + i = line.indexOf(':'); + key = utils.trim(line.substr(0, i)).toLowerCase(); + val = utils.trim(line.substr(i + 1)); - function onClose () { - if (!stream._readableState.ended) { - self.destroy() + if (key) { + if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { + return; + } + if (key === 'set-cookie') { + parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); + } else { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } } - } + }); - function onEnd () { - self._current = null - stream.removeListener('readable', onReadable) - stream.removeListener('end', onEnd) - stream.removeListener('close', onClose) - self._next() - } -} + return parsed; +}; -MultiStream.prototype._attachErrorListener = function (stream) { - var self = this - if (!stream) return +},{"./../utils":67}],66:[function(require,module,exports){ +'use strict'; - stream.once('error', onError) +/** + * Syntactic sugar for invoking a function and expanding an array for arguments. + * + * Common use case would be to use `Function.prototype.apply`. + * + * ```js + * function f(x, y, z) {} + * var args = [1, 2, 3]; + * f.apply(null, args); + * ``` + * + * With `spread` this example can be re-written. + * + * ```js + * spread(function(x, y, z) {})([1, 2, 3]); + * ``` + * + * @param {Function} callback + * @returns {Function} + */ +module.exports = function spread(callback) { + return function wrap(arr) { + return callback.apply(null, arr); + }; +}; - function onError (err) { - stream.removeListener('error', onError) - self.destroy(err) - } -} +},{}],67:[function(require,module,exports){ +'use strict'; -function toStreams2 (s) { - if (!s || typeof s === 'function' || s._readableState) return s +var bind = require('./helpers/bind'); +var isBuffer = require('is-buffer'); - var wrap = new stream.Readable().wrap(s) - if (s.destroy) { - wrap.destroy = s.destroy.bind(s) - } - return wrap -} +/*global toString:true*/ -},{"inherits":58,"readable-stream":92}],74:[function(require,module,exports){ -module.exports = nextEvent +// utils is a library of generic helper functions non-specific to axios -function nextEvent (emitter, name) { - var next = null - emitter.on(name, function (data) { - if (!next) return - var fn = next - next = null - fn(data) - }) +var toString = Object.prototype.toString; - return function (once) { - next = once - } +/** + * Determine if a value is an Array + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Array, otherwise false + */ +function isArray(val) { + return toString.call(val) === '[object Array]'; } -},{}],75:[function(require,module,exports){ -var wrappy = require('wrappy') -module.exports = wrappy(once) -module.exports.strict = wrappy(onceStrict) - -once.proto = once(function () { - Object.defineProperty(Function.prototype, 'once', { - value: function () { - return once(this) - }, - configurable: true - }) - - Object.defineProperty(Function.prototype, 'onceStrict', { - value: function () { - return onceStrict(this) - }, - configurable: true - }) -}) - -function once (fn) { - var f = function () { - if (f.called) return f.value - f.called = true - return f.value = fn.apply(this, arguments) - } - f.called = false - return f +/** + * Determine if a value is an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an ArrayBuffer, otherwise false + */ +function isArrayBuffer(val) { + return toString.call(val) === '[object ArrayBuffer]'; } -function onceStrict (fn) { - var f = function () { - if (f.called) - throw new Error(f.onceError) - f.called = true - return f.value = fn.apply(this, arguments) - } - var name = fn.name || 'Function wrapped with `once`' - f.onceError = name + " shouldn't be called more than once" - f.called = false - return f +/** + * Determine if a value is a FormData + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an FormData, otherwise false + */ +function isFormData(val) { + return (typeof FormData !== 'undefined') && (val instanceof FormData); } -},{"wrappy":130}],76:[function(require,module,exports){ -(function (Buffer){ -module.exports = decodeTorrentFile -module.exports.decode = decodeTorrentFile -module.exports.encode = encodeTorrentFile - -var bencode = require('bencode') -var path = require('path') -var sha1 = require('simple-sha1') -var uniq = require('uniq') - /** - * Parse a torrent. Throws an exception if the torrent is missing required fields. - * @param {Buffer|Object} torrent - * @return {Object} parsed torrent + * Determine if a value is a view on an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false */ -function decodeTorrentFile (torrent) { - if (Buffer.isBuffer(torrent)) { - torrent = bencode.decode(torrent) - } - - // sanity check - ensure(torrent.info, 'info') - ensure(torrent.info['name.utf-8'] || torrent.info.name, 'info.name') - ensure(torrent.info['piece length'], 'info[\'piece length\']') - ensure(torrent.info.pieces, 'info.pieces') - - if (torrent.info.files) { - torrent.info.files.forEach(function (file) { - ensure(typeof file.length === 'number', 'info.files[0].length') - ensure(file['path.utf-8'] || file.path, 'info.files[0].path') - }) +function isArrayBufferView(val) { + var result; + if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { + result = ArrayBuffer.isView(val); } else { - ensure(typeof torrent.info.length === 'number', 'info.length') + result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); } + return result; +} - var result = {} - result.info = torrent.info - result.infoBuffer = bencode.encode(torrent.info) - result.infoHash = sha1.sync(result.infoBuffer) - result.infoHashBuffer = Buffer.from(result.infoHash, 'hex') - - result.name = (torrent.info['name.utf-8'] || torrent.info.name).toString() - - if (torrent.info.private !== undefined) result.private = !!torrent.info.private +/** + * Determine if a value is a String + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a String, otherwise false + */ +function isString(val) { + return typeof val === 'string'; +} - if (torrent['creation date']) result.created = new Date(torrent['creation date'] * 1000) - if (torrent['created by']) result.createdBy = torrent['created by'].toString() +/** + * Determine if a value is a Number + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Number, otherwise false + */ +function isNumber(val) { + return typeof val === 'number'; +} - if (Buffer.isBuffer(torrent.comment)) result.comment = torrent.comment.toString() +/** + * Determine if a value is undefined + * + * @param {Object} val The value to test + * @returns {boolean} True if the value is undefined, otherwise false + */ +function isUndefined(val) { + return typeof val === 'undefined'; +} - // announce and announce-list will be missing if metadata fetched via ut_metadata - result.announce = [] - if (torrent['announce-list'] && torrent['announce-list'].length) { - torrent['announce-list'].forEach(function (urls) { - urls.forEach(function (url) { - result.announce.push(url.toString()) - }) - }) - } else if (torrent.announce) { - result.announce.push(torrent.announce.toString()) - } +/** + * Determine if a value is an Object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Object, otherwise false + */ +function isObject(val) { + return val !== null && typeof val === 'object'; +} - // handle url-list (BEP19 / web seeding) - if (Buffer.isBuffer(torrent['url-list'])) { - // some clients set url-list to empty string - torrent['url-list'] = torrent['url-list'].length > 0 - ? [ torrent['url-list'] ] - : [] - } - result.urlList = (torrent['url-list'] || []).map(function (url) { - return url.toString() - }) +/** + * Determine if a value is a Date + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Date, otherwise false + */ +function isDate(val) { + return toString.call(val) === '[object Date]'; +} - uniq(result.announce) - uniq(result.urlList) +/** + * Determine if a value is a File + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a File, otherwise false + */ +function isFile(val) { + return toString.call(val) === '[object File]'; +} - var files = torrent.info.files || [ torrent.info ] - result.files = files.map(function (file, i) { - var parts = [].concat(result.name, file['path.utf-8'] || file.path || []).map(function (p) { - return p.toString() - }) - return { - path: path.join.apply(null, [path.sep].concat(parts)).slice(1), - name: parts[parts.length - 1], - length: file.length, - offset: files.slice(0, i).reduce(sumLength, 0) - } - }) +/** + * Determine if a value is a Blob + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Blob, otherwise false + */ +function isBlob(val) { + return toString.call(val) === '[object Blob]'; +} - result.length = files.reduce(sumLength, 0) +/** + * Determine if a value is a Function + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Function, otherwise false + */ +function isFunction(val) { + return toString.call(val) === '[object Function]'; +} - var lastFile = result.files[result.files.length - 1] +/** + * Determine if a value is a Stream + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Stream, otherwise false + */ +function isStream(val) { + return isObject(val) && isFunction(val.pipe); +} - result.pieceLength = torrent.info['piece length'] - result.lastPieceLength = ((lastFile.offset + lastFile.length) % result.pieceLength) || result.pieceLength - result.pieces = splitPieces(torrent.info.pieces) +/** + * Determine if a value is a URLSearchParams object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a URLSearchParams object, otherwise false + */ +function isURLSearchParams(val) { + return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; +} - return result +/** + * Trim excess whitespace off the beginning and end of a string + * + * @param {String} str The String to trim + * @returns {String} The String freed of excess whitespace + */ +function trim(str) { + return str.replace(/^\s*/, '').replace(/\s*$/, ''); } /** - * Convert a parsed torrent object back into a .torrent file buffer. - * @param {Object} parsed parsed torrent - * @return {Buffer} + * Determine if we're running in a standard browser environment + * + * This allows axios to run in a web worker, and react-native. + * Both environments support XMLHttpRequest, but not fully standard globals. + * + * web workers: + * typeof window -> undefined + * typeof document -> undefined + * + * react-native: + * navigator.product -> 'ReactNative' */ -function encodeTorrentFile (parsed) { - var torrent = { - info: parsed.info +function isStandardBrowserEnv() { + if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { + return false; } + return ( + typeof window !== 'undefined' && + typeof document !== 'undefined' + ); +} - torrent['announce-list'] = (parsed.announce || []).map(function (url) { - if (!torrent.announce) torrent.announce = url - url = Buffer.from(url, 'utf8') - return [ url ] - }) - - torrent['url-list'] = parsed.urlList || [] +/** + * Iterate over an Array or an Object invoking a function for each item. + * + * If `obj` is an Array callback will be called passing + * the value, index, and complete array for each item. + * + * If 'obj' is an Object callback will be called passing + * the value, key, and complete object for each property. + * + * @param {Object|Array} obj The object to iterate + * @param {Function} fn The callback to invoke for each item + */ +function forEach(obj, fn) { + // Don't bother if no value provided + if (obj === null || typeof obj === 'undefined') { + return; + } - if (parsed.created) { - torrent['creation date'] = (parsed.created.getTime() / 1000) | 0 + // Force an array if not already something iterable + if (typeof obj !== 'object') { + /*eslint no-param-reassign:0*/ + obj = [obj]; } - if (parsed.createdBy) { - torrent['created by'] = parsed.createdBy + if (isArray(obj)) { + // Iterate over array values + for (var i = 0, l = obj.length; i < l; i++) { + fn.call(null, obj[i], i, obj); + } + } else { + // Iterate over object keys + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + fn.call(null, obj[key], key, obj); + } + } } +} - if (parsed.comment) { - torrent.comment = parsed.comment +/** + * Accepts varargs expecting each argument to be an object, then + * immutably merges the properties of each object and returns result. + * + * When multiple objects contain the same key the later object in + * the arguments list will take precedence. + * + * Example: + * + * ```js + * var result = merge({foo: 123}, {foo: 456}); + * console.log(result.foo); // outputs 456 + * ``` + * + * @param {Object} obj1 Object to merge + * @returns {Object} Result of all merge properties + */ +function merge(/* obj1, obj2, obj3, ... */) { + var result = {}; + function assignValue(val, key) { + if (typeof result[key] === 'object' && typeof val === 'object') { + result[key] = merge(result[key], val); + } else { + result[key] = val; + } } - return bencode.encode(torrent) + for (var i = 0, l = arguments.length; i < l; i++) { + forEach(arguments[i], assignValue); + } + return result; } -function sumLength (sum, file) { - return sum + file.length -} - -function splitPieces (buf) { - var pieces = [] - for (var i = 0; i < buf.length; i += 20) { - pieces.push(buf.slice(i, i + 20).toString('hex')) - } - return pieces +/** + * Extends object a by mutably adding to it the properties of object b. + * + * @param {Object} a The object to be extended + * @param {Object} b The object to copy properties from + * @param {Object} thisArg The object to bind function to + * @return {Object} The resulting value of object a + */ +function extend(a, b, thisArg) { + forEach(b, function assignValue(val, key) { + if (thisArg && typeof val === 'function') { + a[key] = bind(val, thisArg); + } else { + a[key] = val; + } + }); + return a; } -function ensure (bool, fieldName) { - if (!bool) throw new Error('Torrent is missing required field: ' + fieldName) -} +module.exports = { + isArray: isArray, + isArrayBuffer: isArrayBuffer, + isBuffer: isBuffer, + isFormData: isFormData, + isArrayBufferView: isArrayBufferView, + isString: isString, + isNumber: isNumber, + isObject: isObject, + isUndefined: isUndefined, + isDate: isDate, + isFile: isFile, + isBlob: isBlob, + isFunction: isFunction, + isStream: isStream, + isURLSearchParams: isURLSearchParams, + isStandardBrowserEnv: isStandardBrowserEnv, + forEach: forEach, + merge: merge, + extend: extend, + trim: trim +}; -}).call(this,require("buffer").Buffer) -},{"bencode":35,"buffer":159,"path":168,"simple-sha1":102,"uniq":116}],77:[function(require,module,exports){ -(function (process,Buffer){ -/* global Blob */ +},{"./helpers/bind":57,"is-buffer":98}],68:[function(require,module,exports){ +'use strict' -module.exports = parseTorrent -module.exports.remote = parseTorrentRemote +exports.byteLength = byteLength +exports.toByteArray = toByteArray +exports.fromByteArray = fromByteArray -var blobToBuffer = require('blob-to-buffer') -var fs = require('fs') // browser exclude -var get = require('simple-get') -var magnet = require('magnet-uri') -var parseTorrentFile = require('parse-torrent-file') +var lookup = [] +var revLookup = [] +var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array -module.exports.toMagnetURI = magnet.encode -module.exports.toTorrentFile = parseTorrentFile.encode +var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +for (var i = 0, len = code.length; i < len; ++i) { + lookup[i] = code[i] + revLookup[code.charCodeAt(i)] = i +} -/** - * Parse a torrent identifier (magnet uri, .torrent file, info hash) - * @param {string|Buffer|Object} torrentId - * @return {Object} - */ -function parseTorrent (torrentId) { - if (typeof torrentId === 'string' && /^(stream-)?magnet:/.test(torrentId)) { - // magnet uri (string) - return magnet(torrentId) - } else if (typeof torrentId === 'string' && (/^[a-f0-9]{40}$/i.test(torrentId) || /^[a-z2-7]{32}$/i.test(torrentId))) { - // info hash (hex/base-32 string) - return magnet('magnet:?xt=urn:btih:' + torrentId) - } else if (Buffer.isBuffer(torrentId) && torrentId.length === 20) { - // info hash (buffer) - return magnet('magnet:?xt=urn:btih:' + torrentId.toString('hex')) - } else if (Buffer.isBuffer(torrentId)) { - // .torrent file (buffer) - return parseTorrentFile(torrentId) // might throw - } else if (torrentId && torrentId.infoHash) { - // parsed torrent (from `parse-torrent`, `parse-torrent-file`, or `magnet-uri`) - if (!torrentId.announce) torrentId.announce = [] - if (typeof torrentId.announce === 'string') { - torrentId.announce = [ torrentId.announce ] - } - if (!torrentId.urlList) torrentId.urlList = [] - return torrentId - } else { - throw new Error('Invalid torrent identifier') +revLookup['-'.charCodeAt(0)] = 62 +revLookup['_'.charCodeAt(0)] = 63 + +function placeHoldersCount (b64) { + var len = b64.length + if (len % 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 + return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0 } -function parseTorrentRemote (torrentId, cb) { - var parsedTorrent - if (typeof cb !== 'function') throw new Error('second argument must be a Function') +function byteLength (b64) { + // base64 is 4/3 + up to two characters of the original data + return (b64.length * 3 / 4) - placeHoldersCount(b64) +} - try { - parsedTorrent = parseTorrent(torrentId) - } catch (err) { - // If torrent fails to parse, it could be a Blob, http/https URL or - // filesystem path, so don't consider it an error yet. - } +function toByteArray (b64) { + var i, l, tmp, placeHolders, arr + var len = b64.length + placeHolders = placeHoldersCount(b64) - if (parsedTorrent && parsedTorrent.infoHash) { - process.nextTick(function () { - cb(null, parsedTorrent) - }) - } else if (isBlob(torrentId)) { - blobToBuffer(torrentId, function (err, torrentBuf) { - if (err) return cb(new Error('Error converting Blob: ' + err.message)) - parseOrThrow(torrentBuf) - }) - } else if (typeof get === 'function' && /^https?:/.test(torrentId)) { - // http, or https url to torrent file - get.concat({ - url: torrentId, - timeout: 30 * 1000, - headers: { 'user-agent': 'WebTorrent (http://webtorrent.io)' } - }, function (err, res, torrentBuf) { - if (err) return cb(new Error('Error downloading torrent: ' + err.message)) - parseOrThrow(torrentBuf) - }) - } else if (typeof fs.readFile === 'function' && typeof torrentId === 'string') { - // assume it's a filesystem path - fs.readFile(torrentId, function (err, torrentBuf) { - if (err) return cb(new Error('Invalid torrent identifier')) - parseOrThrow(torrentBuf) - }) - } else { - process.nextTick(function () { - cb(new Error('Invalid torrent identifier')) - }) + arr = new Arr((len * 3 / 4) - placeHolders) + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? len - 4 : len + + var L = 0 + + for (i = 0; i < l; i += 4) { + tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)] + arr[L++] = (tmp >> 16) & 0xFF + arr[L++] = (tmp >> 8) & 0xFF + arr[L++] = tmp & 0xFF } - function parseOrThrow (torrentBuf) { - try { - parsedTorrent = parseTorrent(torrentBuf) - } catch (err) { - return cb(err) - } - if (parsedTorrent && parsedTorrent.infoHash) cb(null, parsedTorrent) - else cb(new Error('Invalid torrent identifier')) + if (placeHolders === 2) { + tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[L++] = tmp & 0xFF + } else if (placeHolders === 1) { + tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[L++] = (tmp >> 8) & 0xFF + arr[L++] = tmp & 0xFF } + + return arr } -/** - * Check if `obj` is a W3C `Blob` or `File` object - * @param {*} obj - * @return {boolean} - */ -function isBlob (obj) { - return typeof Blob !== 'undefined' && obj instanceof Blob +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F] } -// Workaround Browserify v13 bug -// https://github.com/substack/node-browserify/issues/1483 -;(function () { Buffer.alloc(0) })() +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} -}).call(this,require('_process'),require("buffer").Buffer) -},{"_process":170,"blob-to-buffer":43,"buffer":159,"fs":156,"magnet-uri":64,"parse-torrent-file":76,"simple-get":100}],78:[function(require,module,exports){ -var closest = require('closest-to') -var kB = Math.pow(2, 10) +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var output = '' + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 -// Create a range from 16kb–4mb -var p = 13, range = [] -while (p++ < 22) range.push(Math.pow(2, p)) + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) + } -module.exports = function (bytes) { - return closest(bytes / kB, range) -} + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + output += lookup[tmp >> 2] + output += lookup[(tmp << 4) & 0x3F] + output += '==' + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + (uint8[len - 1]) + output += lookup[tmp >> 10] + output += lookup[(tmp >> 4) & 0x3F] + output += lookup[(tmp << 2) & 0x3F] + output += '=' + } -},{"closest-to":48}],79:[function(require,module,exports){ -(function (process){ -'use strict'; + parts.push(output) -if (!process.version || - process.version.indexOf('v0.') === 0 || - process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { - module.exports = nextTick; -} else { - module.exports = process.nextTick; + return parts.join('') } -function nextTick(fn, arg1, arg2, arg3) { - if (typeof fn !== 'function') { - throw new TypeError('"callback" argument must be a function'); - } - var len = arguments.length; - var args, i; - switch (len) { - case 0: - case 1: - return process.nextTick(fn); - case 2: - return process.nextTick(function afterTickOne() { - fn.call(null, arg1); - }); - case 3: - return process.nextTick(function afterTickTwo() { - fn.call(null, arg1, arg2); - }); - case 4: - return process.nextTick(function afterTickThree() { - fn.call(null, arg1, arg2, arg3); - }); - default: - args = new Array(len - 1); - i = 0; - while (i < args.length) { - args[i++] = arguments[i]; +},{}],69:[function(require,module,exports){ +(function (Buffer){ +const INTEGER_START = 0x69 // 'i' +const STRING_DELIM = 0x3A // ':' +const DICTIONARY_START = 0x64 // 'd' +const LIST_START = 0x6C // 'l' +const END_OF_TYPE = 0x65 // 'e' + +/** + * replaces parseInt(buffer.toString('ascii', start, end)). + * For strings with less then ~30 charachters, this is actually a lot faster. + * + * @param {Buffer} data + * @param {Number} start + * @param {Number} end + * @return {Number} calculated number + */ +function getIntFromBuffer (buffer, start, end) { + var sum = 0 + var sign = 1 + + for (var i = start; i < end; i++) { + var num = buffer[i] + + if (num < 58 && num >= 48) { + sum = sum * 10 + (num - 48) + continue } - return process.nextTick(function afterTick() { - fn.apply(null, args); - }); - } -} -}).call(this,require('_process')) -},{"_process":170}],80:[function(require,module,exports){ -var once = require('once') -var eos = require('end-of-stream') -var fs = require('fs') // we only need fs to get the ReadStream and WriteStream prototypes + if (i === start && num === 43) { // + + continue + } -var noop = function () {} + if (i === start && num === 45) { // - + sign = -1 + continue + } -var isFn = function (fn) { - return typeof fn === 'function' -} + if (num === 46) { // . + // its a float. break here. + break + } -var isFS = function (stream) { - if (!fs) return false // browser - return (stream instanceof (fs.ReadStream || noop) || stream instanceof (fs.WriteStream || noop)) && isFn(stream.close) -} + throw new Error('not a number: buffer[' + i + '] = ' + num) + } -var isRequest = function (stream) { - return stream.setHeader && isFn(stream.abort) + return sum * sign } -var destroyer = function (stream, reading, writing, callback) { - callback = once(callback) +/** + * Decodes bencoded data. + * + * @param {Buffer} data + * @param {Number} start (optional) + * @param {Number} end (optional) + * @param {String} encoding (optional) + * @return {Object|Array|Buffer|String|Number} + */ +function decode (data, start, end, encoding) { + if (data == null || data.length === 0) { + return null + } - var closed = false - stream.on('close', function () { - closed = true - }) + if (typeof start !== 'number' && encoding == null) { + encoding = start + start = undefined + } - eos(stream, {readable: reading, writable: writing}, function (err) { - if (err) return callback(err) - closed = true - callback() - }) + if (typeof end !== 'number' && encoding == null) { + encoding = end + end = undefined + } - var destroyed = false - return function (err) { - if (closed) return - if (destroyed) return - destroyed = true + decode.position = 0 + decode.encoding = encoding || null - if (isFS(stream)) return stream.close() // use close for fs streams to avoid fd leaks - if (isRequest(stream)) return stream.abort() // request.destroy just do .end - .abort is what we want + decode.data = !(Buffer.isBuffer(data)) + ? new Buffer(data) + : data.slice(start, end) - if (isFn(stream.destroy)) return stream.destroy() + decode.bytes = decode.data.length - callback(err || new Error('stream was destroyed')) - } + return decode.next() } -var call = function (fn) { - fn() +decode.bytes = 0 +decode.position = 0 +decode.data = null +decode.encoding = null + +decode.next = function () { + switch (decode.data[decode.position]) { + case DICTIONARY_START: + return decode.dictionary() + case LIST_START: + return decode.list() + case INTEGER_START: + return decode.integer() + default: + return decode.buffer() + } } -var pipe = function (from, to) { - return from.pipe(to) +decode.find = function (chr) { + var i = decode.position + var c = decode.data.length + var d = decode.data + + while (i < c) { + if (d[i] === chr) return i + i++ + } + + throw new Error( + 'Invalid data: Missing delimiter "' + + String.fromCharCode(chr) + '" [0x' + + chr.toString(16) + ']' + ) } -var pump = function () { - var streams = Array.prototype.slice.call(arguments) - var callback = isFn(streams[streams.length - 1] || noop) && streams.pop() || noop +decode.dictionary = function () { + decode.position++ - if (Array.isArray(streams[0])) streams = streams[0] - if (streams.length < 2) throw new Error('pump requires two streams per minimum') + var dict = {} - var error - var destroys = streams.map(function (stream, i) { - var reading = i < streams.length - 1 - var writing = i > 0 - return destroyer(stream, reading, writing, function (err) { - if (!error) error = err - if (err) destroys.forEach(call) - if (reading) return - destroys.forEach(call) - callback(error) - }) - }) + while (decode.data[decode.position] !== END_OF_TYPE) { + dict[decode.buffer()] = decode.next() + } - return streams.reduce(pipe) + decode.position++ + + return dict } -module.exports = pump +decode.list = function () { + decode.position++ -},{"end-of-stream":52,"fs":158,"once":75}],81:[function(require,module,exports){ -var iterate = function (list) { - var offset = 0 - return function () { - if (offset === list.length) return null + var lst = [] - var len = list.length - offset - var i = (Math.random() * len) | 0 - var el = list[offset + i] + while (decode.data[decode.position] !== END_OF_TYPE) { + lst.push(decode.next()) + } - var tmp = list[offset] - list[offset] = el - list[offset + i] = tmp - offset++ + decode.position++ - return el - } + return lst } -module.exports = iterate +decode.integer = function () { + var end = decode.find(END_OF_TYPE) + var number = getIntFromBuffer(decode.data, decode.position + 1, end) -},{}],82:[function(require,module,exports){ -(function (process,global){ -'use strict' + decode.position += end + 1 - decode.position -function oldBrowser () { - throw new Error('secure random number generation not supported by this browser\nuse chrome, FireFox or Internet Explorer 11') + return number } -var Buffer = require('safe-buffer').Buffer -var crypto = global.crypto || global.msCrypto +decode.buffer = function () { + var sep = decode.find(STRING_DELIM) + var length = getIntFromBuffer(decode.data, decode.position, sep) + var end = ++sep + length -if (crypto && crypto.getRandomValues) { - module.exports = randomBytes -} else { - module.exports = oldBrowser + decode.position = end + + return decode.encoding + ? decode.data.toString(decode.encoding, sep, end) + : decode.data.slice(sep, end) } -function randomBytes (size, cb) { - // phantomjs needs to throw - if (size > 65536) throw new Error('requested too many random bytes') - // in case browserify isn't using the Uint8Array version - var rawBytes = new global.Uint8Array(size) +module.exports = decode - // This will not work in older browsers. - // See https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues - if (size > 0) { // getRandomValues fails on IE if size == 0 - crypto.getRandomValues(rawBytes) - } +}).call(this,require("buffer").Buffer) +},{"buffer":4}],70:[function(require,module,exports){ +var Buffer = require('safe-buffer').Buffer - // XXX: phantomjs doesn't like a buffer being passed here - var bytes = Buffer.from(rawBytes.buffer) +/** + * Encodes data in bencode. + * + * @param {Buffer|Array|String|Object|Number|Boolean} data + * @return {Buffer} + */ +function encode (data, buffer, offset) { + var buffers = [] + var result = null - if (typeof cb === 'function') { - return process.nextTick(function () { - cb(null, bytes) - }) + encode._encode(buffers, data) + result = Buffer.concat(buffers) + encode.bytes = result.length + + if (Buffer.isBuffer(buffer)) { + result.copy(buffer, offset) + return buffer } - return bytes + return result } -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"_process":170,"safe-buffer":98}],83:[function(require,module,exports){ -/* -Instance of writable stream. +encode.bytes = -1 +encode._floatConversionDetected = false -call .get(length) or .discard(length) to get a stream (relative to the last end) +encode._encode = function (buffers, data) { + if (Buffer.isBuffer(data)) { + buffers.push(Buffer.from(data.length + ':')) + buffers.push(data) + return + } -emits 'stalled' once everything is written + if (data == null) { return } + switch (typeof data) { + case 'string': + encode.buffer(buffers, data) + break + case 'number': + encode.number(buffers, data) + break + case 'object': + data.constructor === Array + ? encode.list(buffers, data) + : encode.dict(buffers, data) + break + case 'boolean': + encode.number(buffers, data ? 1 : 0) + break + } +} -*/ -var inherits = require('inherits') -var stream = require('readable-stream') +var buffE = Buffer.from('e') +var buffD = Buffer.from('d') +var buffL = Buffer.from('l') -module.exports = RangeSliceStream +encode.buffer = function (buffers, data) { + buffers.push(Buffer.from(Buffer.byteLength(data) + ':' + data)) +} -inherits(RangeSliceStream, stream.Writable) +encode.number = function (buffers, data) { + var maxLo = 0x80000000 + var hi = (data / maxLo) << 0 + var lo = (data % maxLo) << 0 + var val = hi * maxLo + lo -function RangeSliceStream (offset, opts) { - var self = this - if (!(self instanceof RangeSliceStream)) return new RangeSliceStream(offset) - stream.Writable.call(self, opts) + buffers.push(Buffer.from('i' + val + 'e')) - self.destroyed = false - self._queue = [] - self._position = offset || 0 - self._cb = null - self._buffer = null - self._out = null + if (val !== data && !encode._floatConversionDetected) { + encode._floatConversionDetected = true + console.warn( + 'WARNING: Possible data corruption detected with value "' + data + '":', + 'Bencoding only defines support for integers, value was converted to "' + val + '"' + ) + console.trace() + } } -RangeSliceStream.prototype._write = function (chunk, encoding, cb) { - var self = this - - var drained = true - - while (true) { - if (self.destroyed) { - return - } +encode.dict = function (buffers, data) { + buffers.push(buffD) - // Wait for more queue entries - if (self._queue.length === 0) { - self._buffer = chunk - self._cb = cb - return - } + var j = 0 + var k + // fix for issue #13 - sorted dicts + var keys = Object.keys(data).sort() + var kl = keys.length - self._buffer = null - var currRange = self._queue[0] - // Relative to the start of chunk, what data do we need? - var writeStart = Math.max(currRange.start - self._position, 0) - var writeEnd = currRange.end - self._position + for (; j < kl; j++) { + k = keys[j] + if (data[k] == null) continue + encode.buffer(buffers, k) + encode._encode(buffers, data[k]) + } - // Check if we need to throw it all away - if (writeStart >= chunk.length) { - self._position += chunk.length - return cb(null) - } + buffers.push(buffE) +} - // Check if we need to use it all - var toWrite - if (writeEnd > chunk.length) { - self._position += chunk.length - if (writeStart === 0) { - toWrite = chunk - } else { - toWrite = chunk.slice(writeStart) - } - drained = currRange.stream.write(toWrite) && drained - break - } +encode.list = function (buffers, data) { + var i = 0 + var c = data.length + buffers.push(buffL) - self._position += writeEnd - if (writeStart === 0 && writeEnd === chunk.length) { - toWrite = chunk - } else { - toWrite = chunk.slice(writeStart, writeEnd) - } - drained = currRange.stream.write(toWrite) && drained - if (currRange.last) { - currRange.stream.end() - } - chunk = chunk.slice(writeEnd) - self._queue.shift() - } + for (; i < c; i++) { + if (data[i] == null) continue + encode._encode(buffers, data[i]) + } - if (drained) { - cb(null) - } else { - currRange.stream.once('drain', cb.bind(null, null)) - } + buffers.push(buffE) } -RangeSliceStream.prototype.slice = function (ranges) { - var self = this - - if (self.destroyed) return null - - if (!(ranges instanceof Array)) { - ranges = [ranges] - } +module.exports = encode - var str = new stream.PassThrough() +},{"safe-buffer":138}],71:[function(require,module,exports){ +var bencode = module.exports - ranges.forEach(function (range, i) { - self._queue.push({ - start: range.start, - end: range.end, - stream: str, - last: i === (ranges.length - 1) - }) - }) - if (self._buffer) { - self._write(self._buffer, null, self._cb) - } +bencode.encode = require('./encode') +bencode.decode = require('./decode') - return str +/** + * Determines the amount of bytes + * needed to encode the given value + * @param {Object|Array|Buffer|String|Number|Boolean} value + * @return {Number} byteCount + */ +bencode.byteLength = bencode.encodingLength = function (value) { + return bencode.encode(value).length } -RangeSliceStream.prototype.destroy = function (err) { - var self = this - if (self.destroyed) return - self.destroyed = true +},{"./decode":69,"./encode":70}],72:[function(require,module,exports){ +module.exports = function(haystack, needle, comparator, low, high) { + var mid, cmp; - if (err) self.emit('error', err) -} + if(low === undefined) + low = 0; -},{"inherits":58,"readable-stream":92}],84:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. + else { + low = low|0; + if(low < 0 || low >= haystack.length) + throw new RangeError("invalid lower bound"); + } -// a duplex stream is just a stream that is both readable and writable. -// Since JS doesn't have multiple prototypal inheritance, this class -// prototypally inherits from Readable, and then parasitically from -// Writable. + if(high === undefined) + high = haystack.length - 1; -'use strict'; + else { + high = high|0; + if(high < low || high >= haystack.length) + throw new RangeError("invalid upper bound"); + } -/**/ + while(low <= high) { + /* Note that "(low + high) >>> 1" may overflow, and results in a typecast + * to double (which gives the wrong results). */ + mid = low + (high - low >> 1); + cmp = +comparator(haystack[mid], needle, mid, haystack); -var processNextTick = require('process-nextick-args'); -/**/ + /* Too low. */ + if(cmp < 0.0) + low = mid + 1; -/**/ -var objectKeys = Object.keys || function (obj) { - var keys = []; - for (var key in obj) { - keys.push(key); - }return keys; -}; -/**/ + /* Too high. */ + else if(cmp > 0.0) + high = mid - 1; -module.exports = Duplex; + /* Key found. */ + else + return mid; + } -/**/ -var util = require('core-util-is'); -util.inherits = require('inherits'); -/**/ + /* Key not found. */ + return ~low; +} -var Readable = require('./_stream_readable'); -var Writable = require('./_stream_writable'); +},{}],73:[function(require,module,exports){ +(function (Buffer){ +var Container = typeof Buffer !== "undefined" ? Buffer //in node, use buffers + : typeof Int8Array !== "undefined" ? Int8Array //in newer browsers, use webgl int8arrays + : function(l){ var a = new Array(l); for(var i = 0; i < l; i++) a[i]=0; }; //else, do something similar + +function BitField(data, opts){ + if(!(this instanceof BitField)) { + return new BitField(data, opts); + } + + if(arguments.length === 0){ + data = 0; + } + + this.grow = opts && (isFinite(opts.grow) && getByteSize(opts.grow) || opts.grow) || 0; + + if(typeof data === "number" || data === undefined){ + data = new Container(getByteSize(data)); + if(data.fill && !data._isBuffer) data.fill(0); // clear node buffers of garbage + } + this.buffer = data; +} + +function getByteSize(num){ + var out = num >> 3; + if(num % 8 !== 0) out++; + return out; +} + +BitField.prototype.get = function(i){ + var j = i >> 3; + return (j < this.buffer.length) && + !!(this.buffer[j] & (128 >> (i % 8))); +}; + +BitField.prototype.set = function(i, b){ + var j = i >> 3; + if (b || arguments.length === 1){ + if (this.buffer.length < j + 1) this._grow(Math.max(j + 1, Math.min(2 * this.buffer.length, this.grow))); + // Set + this.buffer[j] |= 128 >> (i % 8); + } else if (j < this.buffer.length) { + /// Clear + this.buffer[j] &= ~(128 >> (i % 8)); + } +}; + +BitField.prototype._grow = function(length) { + if (this.buffer.length < length && length <= this.grow) { + var newBuffer = new Container(length); + if (newBuffer.fill) newBuffer.fill(0); + if (this.buffer.copy) this.buffer.copy(newBuffer, 0); + else { + for(var i = 0; i < this.buffer.length; i++) { + newBuffer[i] = this.buffer[i]; + } + } + this.buffer = newBuffer; + } +}; + +if(typeof module !== "undefined") module.exports = BitField; -util.inherits(Duplex, Readable); +}).call(this,require("buffer").Buffer) +},{"buffer":4}],74:[function(require,module,exports){ +module.exports = Wire -var keys = objectKeys(Writable.prototype); -for (var v = 0; v < keys.length; v++) { - var method = keys[v]; - if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; -} +var arrayRemove = require('unordered-array-remove') +var bencode = require('bencode') +var BitField = require('bitfield') +var Buffer = require('safe-buffer').Buffer +var debug = require('debug')('bittorrent-protocol') +var extend = require('xtend') +var inherits = require('inherits') +var randombytes = require('randombytes') +var speedometer = require('speedometer') +var stream = require('readable-stream') -function Duplex(options) { - if (!(this instanceof Duplex)) return new Duplex(options); +var BITFIELD_GROW = 400000 +var KEEP_ALIVE_TIMEOUT = 55000 - Readable.call(this, options); - Writable.call(this, options); +var MESSAGE_PROTOCOL = Buffer.from('\u0013BitTorrent protocol') +var MESSAGE_KEEP_ALIVE = Buffer.from([0x00, 0x00, 0x00, 0x00]) +var MESSAGE_CHOKE = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x00]) +var MESSAGE_UNCHOKE = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x01]) +var MESSAGE_INTERESTED = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x02]) +var MESSAGE_UNINTERESTED = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x03]) - if (options && options.readable === false) this.readable = false; +var MESSAGE_RESERVED = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +var MESSAGE_PORT = [0x00, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00] - if (options && options.writable === false) this.writable = false; +function Request (piece, offset, length, callback) { + this.piece = piece + this.offset = offset + this.length = length + this.callback = callback +} - this.allowHalfOpen = true; - if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; +inherits(Wire, stream.Duplex) - this.once('end', onend); -} +function Wire () { + if (!(this instanceof Wire)) return new Wire() + stream.Duplex.call(this) -// the no-half-open enforcer -function onend() { - // if we allow half-open state, or if the writable side ended, - // then we're ok. - if (this.allowHalfOpen || this._writableState.ended) return; + this._debugId = randombytes(4).toString('hex') + this._debug('new wire') - // no more data can be written. - // But allow more writes to happen in this tick. - processNextTick(onEndNT, this); -} + this.peerId = null // remote peer id (hex string) + this.peerIdBuffer = null // remote peer id (buffer) + this.type = null // connection type ('webrtc', 'tcpIncoming', 'tcpOutgoing', 'webSeed') -function onEndNT(self) { - self.end(); -} + this.amChoking = true // are we choking the peer? + this.amInterested = false // are we interested in the peer? -Object.defineProperty(Duplex.prototype, 'destroyed', { - get: function () { - if (this._readableState === undefined || this._writableState === undefined) { - return false; - } - return this._readableState.destroyed && this._writableState.destroyed; - }, - set: function (value) { - // we ignore the value if the stream - // has not been initialized yet - if (this._readableState === undefined || this._writableState === undefined) { - return; - } + this.peerChoking = true // is the peer choking us? + this.peerInterested = false // is the peer interested in us? - // backward compatibility, the user is explicitly - // managing destroyed - this._readableState.destroyed = value; - this._writableState.destroyed = value; - } -}); + // The largest torrent that I know of (the Geocities archive) is ~641 GB and has + // ~41,000 pieces. Therefore, cap bitfield to 10x larger (400,000 bits) to support all + // possible torrents but prevent malicious peers from growing bitfield to fill memory. + this.peerPieces = new BitField(0, { grow: BITFIELD_GROW }) -Duplex.prototype._destroy = function (err, cb) { - this.push(null); - this.end(); + this.peerExtensions = {} - processNextTick(cb, err); -}; + this.requests = [] // outgoing + this.peerRequests = [] // incoming -function forEach(xs, f) { - for (var i = 0, l = xs.length; i < l; i++) { - f(xs[i], i); - } -} -},{"./_stream_readable":86,"./_stream_writable":88,"core-util-is":49,"inherits":58,"process-nextick-args":79}],85:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. + this.extendedMapping = {} // number -> string, ex: 1 -> 'ut_metadata' + this.peerExtendedMapping = {} // string -> number, ex: 9 -> 'ut_metadata' -// a passthrough stream. -// basically just the most minimal sort of Transform stream. -// Every written chunk gets output as-is. + // The extended handshake to send, minus the "m" field, which gets automatically + // filled from `this.extendedMapping` + this.extendedHandshake = {} -'use strict'; + this.peerExtendedHandshake = {} // remote peer's extended handshake -module.exports = PassThrough; + this._ext = {} // string -> function, ex 'ut_metadata' -> ut_metadata() + this._nextExt = 1 -var Transform = require('./_stream_transform'); + this.uploaded = 0 + this.downloaded = 0 + this.uploadSpeed = speedometer() + this.downloadSpeed = speedometer() -/**/ -var util = require('core-util-is'); -util.inherits = require('inherits'); -/**/ + this._keepAliveInterval = null + this._timeout = null + this._timeoutMs = 0 -util.inherits(PassThrough, Transform); + this.destroyed = false // was the wire ended by calling `destroy`? + this._finished = false -function PassThrough(options) { - if (!(this instanceof PassThrough)) return new PassThrough(options); + this._parserSize = 0 // number of needed bytes to parse next message from remote peer + this._parser = null // function to call once `this._parserSize` bytes are available - Transform.call(this, options); + this._buffer = [] // incomplete message data + this._bufferSize = 0 // cached total length of buffers in `this._buffer` + + this.on('finish', this._onFinish) + + this._parseHandshake() } -PassThrough.prototype._transform = function (chunk, encoding, cb) { - cb(null, chunk); -}; -},{"./_stream_transform":87,"core-util-is":49,"inherits":58}],86:[function(require,module,exports){ -(function (process,global){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -'use strict'; - -/**/ +/** + * Set whether to send a "keep-alive" ping (sent every 55s) + * @param {boolean} enable + */ +Wire.prototype.setKeepAlive = function (enable) { + var self = this + self._debug('setKeepAlive %s', enable) + clearInterval(self._keepAliveInterval) + if (enable === false) return + self._keepAliveInterval = setInterval(function () { + self.keepAlive() + }, KEEP_ALIVE_TIMEOUT) +} -var processNextTick = require('process-nextick-args'); -/**/ +/** + * Set the amount of time to wait before considering a request to be "timed out" + * @param {number} ms + * @param {boolean=} unref (should the timer be unref'd? default: false) + */ +Wire.prototype.setTimeout = function (ms, unref) { + this._debug('setTimeout ms=%d unref=%s', ms, unref) + this._clearTimeout() + this._timeoutMs = ms + this._timeoutUnref = !!unref + this._updateTimeout() +} -module.exports = Readable; +Wire.prototype.destroy = function () { + if (this.destroyed) return + this.destroyed = true + this._debug('destroy') + this.emit('close') + this.end() +} -/**/ -var isArray = require('isarray'); -/**/ +Wire.prototype.end = function () { + this._debug('end') + this._onUninterested() + this._onChoke() + stream.Duplex.prototype.end.apply(this, arguments) +} -/**/ -var Duplex; -/**/ +/** + * Use the specified protocol extension. + * @param {function} Extension + */ +Wire.prototype.use = function (Extension) { + var name = Extension.prototype.name + if (!name) { + throw new Error('Extension class requires a "name" property on the prototype') + } + this._debug('use extension.name=%s', name) -Readable.ReadableState = ReadableState; + var ext = this._nextExt + var handler = new Extension(this) -/**/ -var EE = require('events').EventEmitter; + function noop () {} -var EElistenerCount = function (emitter, type) { - return emitter.listeners(type).length; -}; -/**/ + if (typeof handler.onHandshake !== 'function') { + handler.onHandshake = noop + } + if (typeof handler.onExtendedHandshake !== 'function') { + handler.onExtendedHandshake = noop + } + if (typeof handler.onMessage !== 'function') { + handler.onMessage = noop + } -/**/ -var Stream = require('./internal/streams/stream'); -/**/ + this.extendedMapping[ext] = name + this._ext[name] = handler + this[name] = handler -// TODO(bmeurer): Change this back to const once hole checks are -// properly optimized away early in Ignition+TurboFan. -/**/ -var Buffer = require('safe-buffer').Buffer; -var OurUint8Array = global.Uint8Array || function () {}; -function _uint8ArrayToBuffer(chunk) { - return Buffer.from(chunk); -} -function _isUint8Array(obj) { - return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; + this._nextExt += 1 } -/**/ -/**/ -var util = require('core-util-is'); -util.inherits = require('inherits'); -/**/ +// +// OUTGOING MESSAGES +// -/**/ -var debugUtil = require('util'); -var debug = void 0; -if (debugUtil && debugUtil.debuglog) { - debug = debugUtil.debuglog('stream'); -} else { - debug = function () {}; +/** + * Message "keep-alive": + */ +Wire.prototype.keepAlive = function () { + this._debug('keep-alive') + this._push(MESSAGE_KEEP_ALIVE) } -/**/ - -var BufferList = require('./internal/streams/BufferList'); -var destroyImpl = require('./internal/streams/destroy'); -var StringDecoder; - -util.inherits(Readable, Stream); -var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; - -function prependListener(emitter, event, fn) { - // Sadly this is not cacheable as some libraries bundle their own - // event emitter implementation with them. - if (typeof emitter.prependListener === 'function') { - return emitter.prependListener(event, fn); +/** + * Message: "handshake" + * @param {Buffer|string} infoHash (as Buffer or *hex* string) + * @param {Buffer|string} peerId + * @param {Object} extensions + */ +Wire.prototype.handshake = function (infoHash, peerId, extensions) { + var infoHashBuffer, peerIdBuffer + if (typeof infoHash === 'string') { + infoHashBuffer = Buffer.from(infoHash, 'hex') } else { - // This is a hack to make sure that our error handler is attached before any - // userland ones. NEVER DO THIS. This is here only because this code needs - // to continue to work with older versions of Node.js that do not include - // the prependListener() method. The goal is to eventually remove this hack. - if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; + infoHashBuffer = infoHash + infoHash = infoHashBuffer.toString('hex') + } + if (typeof peerId === 'string') { + peerIdBuffer = Buffer.from(peerId, 'hex') + } else { + peerIdBuffer = peerId + peerId = peerIdBuffer.toString('hex') } -} - -function ReadableState(options, stream) { - Duplex = Duplex || require('./_stream_duplex'); - - options = options || {}; - - // object stream flag. Used to make read(n) ignore n and to - // make all the buffer merging and length checks go away - this.objectMode = !!options.objectMode; - - if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.readableObjectMode; - - // the point at which it stops calling _read() to fill the buffer - // Note: 0 is a valid value, means "don't call _read preemptively ever" - var hwm = options.highWaterMark; - var defaultHwm = this.objectMode ? 16 : 16 * 1024; - this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; - - // cast to ints. - this.highWaterMark = Math.floor(this.highWaterMark); - - // A linked list is used to store data chunks instead of an array because the - // linked list can remove elements from the beginning faster than - // array.shift() - this.buffer = new BufferList(); - this.length = 0; - this.pipes = null; - this.pipesCount = 0; - this.flowing = null; - this.ended = false; - this.endEmitted = false; - this.reading = false; - // a flag to be able to tell if the event 'readable'/'data' is emitted - // immediately, or on a later tick. We set this to true at first, because - // any actions that shouldn't happen until "later" should generally also - // not happen before the first read call. - this.sync = true; + if (infoHashBuffer.length !== 20 || peerIdBuffer.length !== 20) { + throw new Error('infoHash and peerId MUST have length 20') + } - // whenever we return null, then we set a flag to say - // that we're awaiting a 'readable' event emission. - this.needReadable = false; - this.emittedReadable = false; - this.readableListening = false; - this.resumeScheduled = false; + this._debug('handshake i=%s p=%s exts=%o', infoHash, peerId, extensions) - // has it been destroyed - this.destroyed = false; + var reserved = Buffer.from(MESSAGE_RESERVED) - // Crypto is kind of old and crusty. Historically, its default string - // encoding is 'binary' so we have to make this configurable. - // Everything else in the universe uses 'utf8', though. - this.defaultEncoding = options.defaultEncoding || 'utf8'; + // enable extended message + reserved[5] |= 0x10 - // the number of writers that are awaiting a drain event in .pipe()s - this.awaitDrain = 0; + if (extensions && extensions.dht) reserved[7] |= 1 - // if true, a maybeReadMore has been scheduled - this.readingMore = false; + this._push(Buffer.concat([MESSAGE_PROTOCOL, reserved, infoHashBuffer, peerIdBuffer])) + this._handshakeSent = true - this.decoder = null; - this.encoding = null; - if (options.encoding) { - if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; - this.decoder = new StringDecoder(options.encoding); - this.encoding = options.encoding; + if (this.peerExtensions.extended && !this._extendedHandshakeSent) { + // Peer's handshake indicated support already + // (incoming connection) + this._sendExtendedHandshake() } } -function Readable(options) { - Duplex = Duplex || require('./_stream_duplex'); - - if (!(this instanceof Readable)) return new Readable(options); - - this._readableState = new ReadableState(options, this); - - // legacy - this.readable = true; - - if (options) { - if (typeof options.read === 'function') this._read = options.read; - - if (typeof options.destroy === 'function') this._destroy = options.destroy; +/* Peer supports BEP-0010, send extended handshake. + * + * This comes after the 'handshake' event to give the user a chance to populate + * `this.extendedHandshake` and `this.extendedMapping` before the extended handshake + * is sent to the remote peer. + */ +Wire.prototype._sendExtendedHandshake = function () { + // Create extended message object from registered extensions + var msg = extend(this.extendedHandshake) + msg.m = {} + for (var ext in this.extendedMapping) { + var name = this.extendedMapping[ext] + msg.m[name] = Number(ext) } - Stream.call(this); + // Send extended handshake + this.extended(0, bencode.encode(msg)) + this._extendedHandshakeSent = true } -Object.defineProperty(Readable.prototype, 'destroyed', { - get: function () { - if (this._readableState === undefined) { - return false; - } - return this._readableState.destroyed; - }, - set: function (value) { - // we ignore the value if the stream - // has not been initialized yet - if (!this._readableState) { - return; - } - - // backward compatibility, the user is explicitly - // managing destroyed - this._readableState.destroyed = value; +/** + * Message "choke": + */ +Wire.prototype.choke = function () { + if (this.amChoking) return + this.amChoking = true + this._debug('choke') + while (this.peerRequests.length) { + this.peerRequests.pop() } -}); + this._push(MESSAGE_CHOKE) +} -Readable.prototype.destroy = destroyImpl.destroy; -Readable.prototype._undestroy = destroyImpl.undestroy; -Readable.prototype._destroy = function (err, cb) { - this.push(null); - cb(err); -}; +/** + * Message "unchoke": + */ +Wire.prototype.unchoke = function () { + if (!this.amChoking) return + this.amChoking = false + this._debug('unchoke') + this._push(MESSAGE_UNCHOKE) +} -// Manually shove something into the read() buffer. -// This returns true if the highWaterMark has not been hit yet, -// similar to how Writable.write() returns true if you should -// write() some more. -Readable.prototype.push = function (chunk, encoding) { - var state = this._readableState; - var skipChunkCheck; +/** + * Message "interested": + */ +Wire.prototype.interested = function () { + if (this.amInterested) return + this.amInterested = true + this._debug('interested') + this._push(MESSAGE_INTERESTED) +} - if (!state.objectMode) { - if (typeof chunk === 'string') { - encoding = encoding || state.defaultEncoding; - if (encoding !== state.encoding) { - chunk = Buffer.from(chunk, encoding); - encoding = ''; - } - skipChunkCheck = true; - } - } else { - skipChunkCheck = true; - } +/** + * Message "uninterested": + */ +Wire.prototype.uninterested = function () { + if (!this.amInterested) return + this.amInterested = false + this._debug('uninterested') + this._push(MESSAGE_UNINTERESTED) +} - return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); -}; +/** + * Message "have": + * @param {number} index + */ +Wire.prototype.have = function (index) { + this._debug('have %d', index) + this._message(4, [index], null) +} -// Unshift should *always* be something directly out of read() -Readable.prototype.unshift = function (chunk) { - return readableAddChunk(this, chunk, null, true, false); -}; +/** + * Message "bitfield": + * @param {BitField|Buffer} bitfield + */ +Wire.prototype.bitfield = function (bitfield) { + this._debug('bitfield') + if (!Buffer.isBuffer(bitfield)) bitfield = bitfield.buffer + this._message(5, [], bitfield) +} -function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { - var state = stream._readableState; - if (chunk === null) { - state.reading = false; - onEofChunk(stream, state); - } else { - var er; - if (!skipChunkCheck) er = chunkInvalid(state, chunk); - if (er) { - stream.emit('error', er); - } else if (state.objectMode || chunk && chunk.length > 0) { - if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { - chunk = _uint8ArrayToBuffer(chunk); - } +/** + * Message "request": + * @param {number} index + * @param {number} offset + * @param {number} length + * @param {function} cb + */ +Wire.prototype.request = function (index, offset, length, cb) { + if (!cb) cb = function () {} + if (this._finished) return cb(new Error('wire is closed')) + if (this.peerChoking) return cb(new Error('peer is choking')) - if (addToFront) { - if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); - } else if (state.ended) { - stream.emit('error', new Error('stream.push() after EOF')); - } else { - state.reading = false; - if (state.decoder && !encoding) { - chunk = state.decoder.write(chunk); - if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); - } else { - addChunk(stream, state, chunk, false); - } - } - } else if (!addToFront) { - state.reading = false; - } - } + this._debug('request index=%d offset=%d length=%d', index, offset, length) - return needMoreData(state); + this.requests.push(new Request(index, offset, length, cb)) + this._updateTimeout() + this._message(6, [index, offset, length], null) } -function addChunk(stream, state, chunk, addToFront) { - if (state.flowing && state.length === 0 && !state.sync) { - stream.emit('data', chunk); - stream.read(0); - } else { - // update the buffer info. - state.length += state.objectMode ? 1 : chunk.length; - if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); - - if (state.needReadable) emitReadable(stream); - } - maybeReadMore(stream, state); +/** + * Message "piece": + * @param {number} index + * @param {number} offset + * @param {Buffer} buffer + */ +Wire.prototype.piece = function (index, offset, buffer) { + this._debug('piece index=%d offset=%d', index, offset) + this.uploaded += buffer.length + this.uploadSpeed(buffer.length) + this.emit('upload', buffer.length) + this._message(7, [index, offset], buffer) } -function chunkInvalid(state, chunk) { - var er; - if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { - er = new TypeError('Invalid non-string/buffer chunk'); - } - return er; +/** + * Message "cancel": + * @param {number} index + * @param {number} offset + * @param {number} length + */ +Wire.prototype.cancel = function (index, offset, length) { + this._debug('cancel index=%d offset=%d length=%d', index, offset, length) + this._callback( + pull(this.requests, index, offset, length), + new Error('request was cancelled'), + null + ) + this._message(8, [index, offset, length], null) } -// if it's past the high water mark, we can push in some more. -// Also, if we have no data yet, we can stand some -// more bytes. This is to work around cases where hwm=0, -// such as the repl. Also, if the push() triggered a -// readable event, and the user called read(largeNumber) such that -// needReadable was set, then we ought to push more, so that another -// 'readable' event will be triggered. -function needMoreData(state) { - return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); +/** + * Message: "port" + * @param {Number} port + */ +Wire.prototype.port = function (port) { + this._debug('port %d', port) + var message = Buffer.from(MESSAGE_PORT) + message.writeUInt16BE(port, 5) + this._push(message) } -Readable.prototype.isPaused = function () { - return this._readableState.flowing === false; -}; +/** + * Message: "extended" + * @param {number|string} ext + * @param {Object} obj + */ +Wire.prototype.extended = function (ext, obj) { + this._debug('extended ext=%s', ext) + if (typeof ext === 'string' && this.peerExtendedMapping[ext]) { + ext = this.peerExtendedMapping[ext] + } + if (typeof ext === 'number') { + var extId = Buffer.from([ext]) + var buf = Buffer.isBuffer(obj) ? obj : bencode.encode(obj) -// backwards compatibility. -Readable.prototype.setEncoding = function (enc) { - if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; - this._readableState.decoder = new StringDecoder(enc); - this._readableState.encoding = enc; - return this; -}; - -// Don't raise the hwm > 8MB -var MAX_HWM = 0x800000; -function computeNewHighWaterMark(n) { - if (n >= MAX_HWM) { - n = MAX_HWM; + this._message(20, [], Buffer.concat([extId, buf])) } else { - // Get the next highest power of 2 to prevent increasing hwm excessively in - // tiny amounts - n--; - n |= n >>> 1; - n |= n >>> 2; - n |= n >>> 4; - n |= n >>> 8; - n |= n >>> 16; - n++; + throw new Error('Unrecognized extension: ' + ext) } - return n; } -// This function is designed to be inlinable, so please take care when making -// changes to the function body. -function howMuchToRead(n, state) { - if (n <= 0 || state.length === 0 && state.ended) return 0; - if (state.objectMode) return 1; - if (n !== n) { - // Only flow one buffer at a time - if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; - } - // If we're asking for more than the current hwm, then raise the hwm. - if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); - if (n <= state.length) return n; - // Don't have enough - if (!state.ended) { - state.needReadable = true; - return 0; +/** + * Duplex stream method. Called whenever the remote peer stream wants data. No-op + * since we'll just push data whenever we get it. + */ +Wire.prototype._read = function () {} + +/** + * Send a message to the remote peer. + */ +Wire.prototype._message = function (id, numbers, data) { + var dataLength = data ? data.length : 0 + var buffer = Buffer.allocUnsafe(5 + (4 * numbers.length)) + + buffer.writeUInt32BE(buffer.length + dataLength - 4, 0) + buffer[4] = id + for (var i = 0; i < numbers.length; i++) { + buffer.writeUInt32BE(numbers[i], 5 + (4 * i)) } - return state.length; + + this._push(buffer) + if (data) this._push(data) } -// you can override either this method, or the async _read(n) below. -Readable.prototype.read = function (n) { - debug('read', n); - n = parseInt(n, 10); - var state = this._readableState; - var nOrig = n; +Wire.prototype._push = function (data) { + if (this._finished) return + return this.push(data) +} - if (n !== 0) state.emittedReadable = false; +// +// INCOMING MESSAGES +// - // if we're doing read(0) to trigger a readable event, but we - // already have a bunch of data in the buffer, then just trigger - // the 'readable' event and move on. - if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { - debug('read: emitReadable', state.length, state.ended); - if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); - return null; - } +Wire.prototype._onKeepAlive = function () { + this._debug('got keep-alive') + this.emit('keep-alive') +} - n = howMuchToRead(n, state); +Wire.prototype._onHandshake = function (infoHashBuffer, peerIdBuffer, extensions) { + var infoHash = infoHashBuffer.toString('hex') + var peerId = peerIdBuffer.toString('hex') - // if we've ended, and we're now clear, then finish it up. - if (n === 0 && state.ended) { - if (state.length === 0) endReadable(this); - return null; - } + this._debug('got handshake i=%s p=%s exts=%o', infoHash, peerId, extensions) - // All the actual chunk generation logic needs to be - // *below* the call to _read. The reason is that in certain - // synthetic stream cases, such as passthrough streams, _read - // may be a completely synchronous operation which may change - // the state of the read buffer, providing enough data when - // before there was *not* enough. - // - // So, the steps are: - // 1. Figure out what the state of things will be after we do - // a read from the buffer. - // - // 2. If that resulting state will trigger a _read, then call _read. - // Note that this may be asynchronous, or synchronous. Yes, it is - // deeply ugly to write APIs this way, but that still doesn't mean - // that the Readable class should behave improperly, as streams are - // designed to be sync/async agnostic. - // Take note if the _read call is sync or async (ie, if the read call - // has returned yet), so that we know whether or not it's safe to emit - // 'readable' etc. - // - // 3. Actually pull the requested chunks out of the buffer and return. + this.peerId = peerId + this.peerIdBuffer = peerIdBuffer + this.peerExtensions = extensions - // if we need a readable event, then we need to do some reading. - var doRead = state.needReadable; - debug('need readable', doRead); + this.emit('handshake', infoHash, peerId, extensions) - // if we currently have less than the highWaterMark, then also read some - if (state.length === 0 || state.length - n < state.highWaterMark) { - doRead = true; - debug('length less than watermark', doRead); + var name + for (name in this._ext) { + this._ext[name].onHandshake(infoHash, peerId, extensions) } - // however, if we've ended, then there's no point, and if we're already - // reading, then it's unnecessary. - if (state.ended || state.reading) { - doRead = false; - debug('reading or ended', doRead); - } else if (doRead) { - debug('do read'); - state.reading = true; - state.sync = true; - // if the length is currently zero, then we *need* a readable event. - if (state.length === 0) state.needReadable = true; - // call internal read method - this._read(state.highWaterMark); - state.sync = false; - // If _read pushed data synchronously, then `reading` will be false, - // and we need to re-evaluate how much data we can return to the user. - if (!state.reading) n = howMuchToRead(nOrig, state); + if (extensions.extended && this._handshakeSent && + !this._extendedHandshakeSent) { + // outgoing connection + this._sendExtendedHandshake() } +} - var ret; - if (n > 0) ret = fromList(n, state);else ret = null; - - if (ret === null) { - state.needReadable = true; - n = 0; - } else { - state.length -= n; +Wire.prototype._onChoke = function () { + this.peerChoking = true + this._debug('got choke') + this.emit('choke') + while (this.requests.length) { + this._callback(this.requests.pop(), new Error('peer is choking'), null) } +} - if (state.length === 0) { - // If we have nothing in the buffer, then we want to know - // as soon as we *do* get something into the buffer. - if (!state.ended) state.needReadable = true; - - // If we tried to read() past the EOF, then emit end on the next tick. - if (nOrig !== n && state.ended) endReadable(this); - } +Wire.prototype._onUnchoke = function () { + this.peerChoking = false + this._debug('got unchoke') + this.emit('unchoke') +} - if (ret !== null) this.emit('data', ret); +Wire.prototype._onInterested = function () { + this.peerInterested = true + this._debug('got interested') + this.emit('interested') +} - return ret; -}; +Wire.prototype._onUninterested = function () { + this.peerInterested = false + this._debug('got uninterested') + this.emit('uninterested') +} -function onEofChunk(stream, state) { - if (state.ended) return; - if (state.decoder) { - var chunk = state.decoder.end(); - if (chunk && chunk.length) { - state.buffer.push(chunk); - state.length += state.objectMode ? 1 : chunk.length; - } - } - state.ended = true; +Wire.prototype._onHave = function (index) { + if (this.peerPieces.get(index)) return + this._debug('got have %d', index) - // emit 'readable' now to make sure it gets picked up. - emitReadable(stream); + this.peerPieces.set(index, true) + this.emit('have', index) } -// Don't emit readable right away in sync mode, because this can trigger -// another read() call => stack overflow. This way, it might trigger -// a nextTick recursion warning, but that's not so bad. -function emitReadable(stream) { - var state = stream._readableState; - state.needReadable = false; - if (!state.emittedReadable) { - debug('emitReadable', state.flowing); - state.emittedReadable = true; - if (state.sync) processNextTick(emitReadable_, stream);else emitReadable_(stream); - } +Wire.prototype._onBitField = function (buffer) { + this.peerPieces = new BitField(buffer) + this._debug('got bitfield') + this.emit('bitfield', this.peerPieces) } -function emitReadable_(stream) { - debug('emit readable'); - stream.emit('readable'); - flow(stream); -} +Wire.prototype._onRequest = function (index, offset, length) { + var self = this + if (self.amChoking) return + self._debug('got request index=%d offset=%d length=%d', index, offset, length) -// at this point, the user has presumably seen the 'readable' event, -// and called read() to consume some data. that may have triggered -// in turn another _read(n) call, in which case reading = true if -// it's in progress. -// However, if we're not ended, or reading, and the length < hwm, -// then go ahead and try to read some more preemptively. -function maybeReadMore(stream, state) { - if (!state.readingMore) { - state.readingMore = true; - processNextTick(maybeReadMore_, stream, state); + var respond = function (err, buffer) { + if (request !== pull(self.peerRequests, index, offset, length)) return + if (err) return self._debug('error satisfying request index=%d offset=%d length=%d (%s)', index, offset, length, err.message) + self.piece(index, offset, buffer) } + + var request = new Request(index, offset, length, respond) + self.peerRequests.push(request) + self.emit('request', index, offset, length, respond) } -function maybeReadMore_(stream, state) { - var len = state.length; - while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { - debug('maybeReadMore read 0'); - stream.read(0); - if (len === state.length) - // didn't get any data, stop spinning. - break;else len = state.length; - } - state.readingMore = false; +Wire.prototype._onPiece = function (index, offset, buffer) { + this._debug('got piece index=%d offset=%d', index, offset) + this._callback(pull(this.requests, index, offset, buffer.length), null, buffer) + this.downloaded += buffer.length + this.downloadSpeed(buffer.length) + this.emit('download', buffer.length) + this.emit('piece', index, offset, buffer) } -// abstract method. to be overridden in specific implementation classes. -// call cb(er, data) where data is <= n in length. -// for virtual (non-string, non-buffer) streams, "length" is somewhat -// arbitrary, and perhaps not very meaningful. -Readable.prototype._read = function (n) { - this.emit('error', new Error('_read() is not implemented')); -}; - -Readable.prototype.pipe = function (dest, pipeOpts) { - var src = this; - var state = this._readableState; +Wire.prototype._onCancel = function (index, offset, length) { + this._debug('got cancel index=%d offset=%d length=%d', index, offset, length) + pull(this.peerRequests, index, offset, length) + this.emit('cancel', index, offset, length) +} - switch (state.pipesCount) { - case 0: - state.pipes = dest; - break; - case 1: - state.pipes = [state.pipes, dest]; - break; - default: - state.pipes.push(dest); - break; - } - state.pipesCount += 1; - debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); +Wire.prototype._onPort = function (port) { + this._debug('got port %d', port) + this.emit('port', port) +} - var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; +Wire.prototype._onExtended = function (ext, buf) { + if (ext === 0) { + var info + try { + info = bencode.decode(buf) + } catch (err) { + this._debug('ignoring invalid extended handshake: %s', err.message || err) + } - var endFn = doEnd ? onend : unpipe; - if (state.endEmitted) processNextTick(endFn);else src.once('end', endFn); + if (!info) return + this.peerExtendedHandshake = info - dest.on('unpipe', onunpipe); - function onunpipe(readable, unpipeInfo) { - debug('onunpipe'); - if (readable === src) { - if (unpipeInfo && unpipeInfo.hasUnpiped === false) { - unpipeInfo.hasUnpiped = true; - cleanup(); + var name + if (typeof info.m === 'object') { + for (name in info.m) { + this.peerExtendedMapping[name] = Number(info.m[name].toString()) } } - } - - function onend() { - debug('onend'); - dest.end(); - } - - // when the dest drains, it reduces the awaitDrain counter - // on the source. This would be more elegant with a .once() - // handler in flow(), but adding and removing repeatedly is - // too slow. - var ondrain = pipeOnDrain(src); - dest.on('drain', ondrain); - - var cleanedUp = false; - function cleanup() { - debug('cleanup'); - // cleanup event handlers once the pipe is broken - dest.removeListener('close', onclose); - dest.removeListener('finish', onfinish); - dest.removeListener('drain', ondrain); - dest.removeListener('error', onerror); - dest.removeListener('unpipe', onunpipe); - src.removeListener('end', onend); - src.removeListener('end', unpipe); - src.removeListener('data', ondata); - - cleanedUp = true; - - // if the reader is waiting for a drain event from this - // specific writer, then it would cause it to never start - // flowing again. - // So, if this is awaiting a drain, then we just call it now. - // If we don't know, then assume that we are waiting for one. - if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); - } - - // If the user pushes more data while we're writing to dest then we'll end up - // in ondata again. However, we only want to increase awaitDrain once because - // dest will only emit one 'drain' event for the multiple writes. - // => Introduce a guard on increasing awaitDrain. - var increasedAwaitDrain = false; - src.on('data', ondata); - function ondata(chunk) { - debug('ondata'); - increasedAwaitDrain = false; - var ret = dest.write(chunk); - if (false === ret && !increasedAwaitDrain) { - // If the user unpiped during `dest.write()`, it is possible - // to get stuck in a permanently paused state if that write - // also returned false. - // => Check whether `dest` is still a piping destination. - if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { - debug('false write response, pause', src._readableState.awaitDrain); - src._readableState.awaitDrain++; - increasedAwaitDrain = true; + for (name in this._ext) { + if (this.peerExtendedMapping[name]) { + this._ext[name].onExtendedHandshake(this.peerExtendedHandshake) } - src.pause(); } + this._debug('got extended handshake') + this.emit('extended', 'handshake', this.peerExtendedHandshake) + } else { + if (this.extendedMapping[ext]) { + ext = this.extendedMapping[ext] // friendly name for extension + if (this._ext[ext]) { + // there is an registered extension handler, so call it + this._ext[ext].onMessage(buf) + } + } + this._debug('got extended message ext=%s', ext) + this.emit('extended', ext, buf) } +} - // if the dest has an error, then stop piping into it. - // however, don't suppress the throwing behavior for this. - function onerror(er) { - debug('onerror', er); - unpipe(); - dest.removeListener('error', onerror); - if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); - } - - // Make sure our error handler is attached before userland ones. - prependListener(dest, 'error', onerror); +Wire.prototype._onTimeout = function () { + this._debug('request timed out') + this._callback(this.requests.shift(), new Error('request has timed out'), null) + this.emit('timeout') +} - // Both close and finish should trigger unpipe, but only once. - function onclose() { - dest.removeListener('finish', onfinish); - unpipe(); - } - dest.once('close', onclose); - function onfinish() { - debug('onfinish'); - dest.removeListener('close', onclose); - unpipe(); - } - dest.once('finish', onfinish); +/** + * Duplex stream method. Called whenever the remote peer has data for us. Data that the + * remote peer sends gets buffered (i.e. not actually processed) until the right number + * of bytes have arrived, determined by the last call to `this._parse(number, callback)`. + * Once enough bytes have arrived to process the message, the callback function + * (i.e. `this._parser`) gets called with the full buffer of data. + * @param {Buffer} data + * @param {string} encoding + * @param {function} cb + */ +Wire.prototype._write = function (data, encoding, cb) { + this._bufferSize += data.length + this._buffer.push(data) - function unpipe() { - debug('unpipe'); - src.unpipe(dest); + while (this._bufferSize >= this._parserSize) { + var buffer = (this._buffer.length === 1) + ? this._buffer[0] + : Buffer.concat(this._buffer) + this._bufferSize -= this._parserSize + this._buffer = this._bufferSize + ? [buffer.slice(this._parserSize)] + : [] + this._parser(buffer.slice(0, this._parserSize)) } - // tell the dest that it's being piped to - dest.emit('pipe', src); + cb(null) // Signal that we're ready for more data +} - // start the flow if it hasn't been started already. - if (!state.flowing) { - debug('pipe resume'); - src.resume(); - } +Wire.prototype._callback = function (request, err, buffer) { + if (!request) return - return dest; -}; + this._clearTimeout() -function pipeOnDrain(src) { - return function () { - var state = src._readableState; - debug('pipeOnDrain', state.awaitDrain); - if (state.awaitDrain) state.awaitDrain--; - if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { - state.flowing = true; - flow(src); - } - }; + if (!this.peerChoking && !this._finished) this._updateTimeout() + request.callback(err, buffer) } -Readable.prototype.unpipe = function (dest) { - var state = this._readableState; - var unpipeInfo = { hasUnpiped: false }; - - // if we're not piping anywhere, then do nothing. - if (state.pipesCount === 0) return this; +Wire.prototype._clearTimeout = function () { + if (!this._timeout) return - // just one destination. most common case. - if (state.pipesCount === 1) { - // passed in one, but it's not the right one. - if (dest && dest !== state.pipes) return this; + clearTimeout(this._timeout) + this._timeout = null +} - if (!dest) dest = state.pipes; +Wire.prototype._updateTimeout = function () { + var self = this + if (!self._timeoutMs || !self.requests.length || self._timeout) return - // got a match. - state.pipes = null; - state.pipesCount = 0; - state.flowing = false; - if (dest) dest.emit('unpipe', this, unpipeInfo); - return this; - } + self._timeout = setTimeout(function () { + self._onTimeout() + }, self._timeoutMs) + if (self._timeoutUnref && self._timeout.unref) self._timeout.unref() +} - // slow case. multiple pipe destinations. - - if (!dest) { - // remove all. - var dests = state.pipes; - var len = state.pipesCount; - state.pipes = null; - state.pipesCount = 0; - state.flowing = false; +/** + * Takes a number of bytes that the local peer is waiting to receive from the remote peer + * in order to parse a complete message, and a callback function to be called once enough + * bytes have arrived. + * @param {number} size + * @param {function} parser + */ +Wire.prototype._parse = function (size, parser) { + this._parserSize = size + this._parser = parser +} - for (var i = 0; i < len; i++) { - dests[i].emit('unpipe', this, unpipeInfo); - }return this; +/** + * Handle the first 4 bytes of a message, to determine the length of bytes that must be + * waited for in order to have the whole message. + * @param {Buffer} buffer + */ +Wire.prototype._onMessageLength = function (buffer) { + var length = buffer.readUInt32BE(0) + if (length > 0) { + this._parse(length, this._onMessage) + } else { + this._onKeepAlive() + this._parse(4, this._onMessageLength) } +} - // try to find the right one. - var index = indexOf(state.pipes, dest); - if (index === -1) return this; - - state.pipes.splice(index, 1); - state.pipesCount -= 1; - if (state.pipesCount === 1) state.pipes = state.pipes[0]; - - dest.emit('unpipe', this, unpipeInfo); - - return this; -}; - -// set up data events if they are asked for -// Ensure readable listeners eventually get something -Readable.prototype.on = function (ev, fn) { - var res = Stream.prototype.on.call(this, ev, fn); +/** + * Handle a message from the remote peer. + * @param {Buffer} buffer + */ +Wire.prototype._onMessage = function (buffer) { + this._parse(4, this._onMessageLength) + switch (buffer[0]) { + case 0: + return this._onChoke() + case 1: + return this._onUnchoke() + case 2: + return this._onInterested() + case 3: + return this._onUninterested() + case 4: + return this._onHave(buffer.readUInt32BE(1)) + case 5: + return this._onBitField(buffer.slice(1)) + case 6: + return this._onRequest(buffer.readUInt32BE(1), + buffer.readUInt32BE(5), buffer.readUInt32BE(9)) + case 7: + return this._onPiece(buffer.readUInt32BE(1), + buffer.readUInt32BE(5), buffer.slice(9)) + case 8: + return this._onCancel(buffer.readUInt32BE(1), + buffer.readUInt32BE(5), buffer.readUInt32BE(9)) + case 9: + return this._onPort(buffer.readUInt16BE(1)) + case 20: + return this._onExtended(buffer.readUInt8(1), buffer.slice(2)) + default: + this._debug('got unknown message') + return this.emit('unknownmessage', buffer) + } +} - if (ev === 'data') { - // Start flowing on next tick if stream isn't explicitly paused - if (this._readableState.flowing !== false) this.resume(); - } else if (ev === 'readable') { - var state = this._readableState; - if (!state.endEmitted && !state.readableListening) { - state.readableListening = state.needReadable = true; - state.emittedReadable = false; - if (!state.reading) { - processNextTick(nReadingNextTick, this); - } else if (state.length) { - emitReadable(this); +Wire.prototype._parseHandshake = function () { + var self = this + self._parse(1, function (buffer) { + var pstrlen = buffer.readUInt8(0) + self._parse(pstrlen + 48, function (handshake) { + var protocol = handshake.slice(0, pstrlen) + if (protocol.toString() !== 'BitTorrent protocol') { + self._debug('Error: wire not speaking BitTorrent protocol (%s)', protocol.toString()) + self.end() + return } - } - } + handshake = handshake.slice(pstrlen) + self._onHandshake(handshake.slice(8, 28), handshake.slice(28, 48), { + dht: !!(handshake[7] & 0x01), // see bep_0005 + extended: !!(handshake[5] & 0x10) // see bep_0010 + }) + self._parse(4, self._onMessageLength) + }) + }) +} - return res; -}; -Readable.prototype.addListener = Readable.prototype.on; +Wire.prototype._onFinish = function () { + this._finished = true -function nReadingNextTick(self) { - debug('readable nexttick read 0'); - self.read(0); -} + this.push(null) // stream cannot be half open, so signal the end of it + while (this.read()) {} // consume and discard the rest of the stream data -// pause() and resume() are remnants of the legacy readable stream API -// If the user uses them, then switch into old mode. -Readable.prototype.resume = function () { - var state = this._readableState; - if (!state.flowing) { - debug('resume'); - state.flowing = true; - resume(this, state); + clearInterval(this._keepAliveInterval) + this._parse(Number.MAX_VALUE, function () {}) + while (this.peerRequests.length) { + this.peerRequests.pop() } - return this; -}; - -function resume(stream, state) { - if (!state.resumeScheduled) { - state.resumeScheduled = true; - processNextTick(resume_, stream, state); + while (this.requests.length) { + this._callback(this.requests.pop(), new Error('wire was closed'), null) } } -function resume_(stream, state) { - if (!state.reading) { - debug('resume read 0'); - stream.read(0); - } - - state.resumeScheduled = false; - state.awaitDrain = 0; - stream.emit('resume'); - flow(stream); - if (state.flowing && !state.reading) stream.read(0); +Wire.prototype._debug = function () { + var args = [].slice.call(arguments) + args[0] = '[' + this._debugId + '] ' + args[0] + debug.apply(null, args) } -Readable.prototype.pause = function () { - debug('call pause flowing=%j', this._readableState.flowing); - if (false !== this._readableState.flowing) { - debug('pause'); - this._readableState.flowing = false; - this.emit('pause'); +function pull (requests, piece, offset, length) { + for (var i = 0; i < requests.length; i++) { + var req = requests[i] + if (req.piece === piece && req.offset === offset && req.length === length) { + arrayRemove(requests, i) + return req + } } - return this; -}; - -function flow(stream) { - var state = stream._readableState; - debug('flow', state.flowing); - while (state.flowing && stream.read() !== null) {} + return null } -// wrap an old-style stream as the async data source. -// This is *not* part of the readable stream interface. -// It is an ugly unfortunate mess of history. -Readable.prototype.wrap = function (stream) { - var state = this._readableState; - var paused = false; - - var self = this; - stream.on('end', function () { - debug('wrapped end'); - if (state.decoder && !state.ended) { - var chunk = state.decoder.end(); - if (chunk && chunk.length) self.push(chunk); - } +},{"bencode":71,"bitfield":73,"debug":87,"inherits":96,"randombytes":122,"readable-stream":132,"safe-buffer":138,"speedometer":144,"unordered-array-remove":157,"xtend":171}],75:[function(require,module,exports){ +(function (process){ +module.exports = Client - self.push(null); - }); +var Buffer = require('safe-buffer').Buffer +var debug = require('debug')('bittorrent-tracker:client') +var EventEmitter = require('events').EventEmitter +var extend = require('xtend') +var inherits = require('inherits') +var once = require('once') +var parallel = require('run-parallel') +var Peer = require('simple-peer') +var uniq = require('uniq') +var url = require('url') - stream.on('data', function (chunk) { - debug('wrapped data'); - if (state.decoder) chunk = state.decoder.write(chunk); +var common = require('./lib/common') +var HTTPTracker = require('./lib/client/http-tracker') // empty object in browser +var UDPTracker = require('./lib/client/udp-tracker') // empty object in browser +var WebSocketTracker = require('./lib/client/websocket-tracker') - // don't skip over falsy values in objectMode - if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; +inherits(Client, EventEmitter) - var ret = self.push(chunk); - if (!ret) { - paused = true; - stream.pause(); - } - }); +/** + * BitTorrent tracker client. + * + * Find torrent peers, to help a torrent client participate in a torrent swarm. + * + * @param {Object} opts options object + * @param {string|Buffer} opts.infoHash torrent info hash + * @param {string|Buffer} opts.peerId peer id + * @param {string|Array.} opts.announce announce + * @param {number} opts.port torrent client listening port + * @param {function} opts.getAnnounceOpts callback to provide data to tracker + * @param {number} opts.rtcConfig RTCPeerConnection configuration object + * @param {number} opts.userAgent User-Agent header for http requests + * @param {number} opts.wrtc custom webrtc impl (useful in node.js) + */ +function Client (opts) { + var self = this + if (!(self instanceof Client)) return new Client(opts) + EventEmitter.call(self) + if (!opts) opts = {} - // proxy all the other methods. - // important when wrapping filters and duplexes. - for (var i in stream) { - if (this[i] === undefined && typeof stream[i] === 'function') { - this[i] = function (method) { - return function () { - return stream[method].apply(stream, arguments); - }; - }(i); - } - } + if (!opts.peerId) throw new Error('Option `peerId` is required') + if (!opts.infoHash) throw new Error('Option `infoHash` is required') + if (!opts.announce) throw new Error('Option `announce` is required') + if (!process.browser && !opts.port) throw new Error('Option `port` is required') - // proxy certain important events. - for (var n = 0; n < kProxyEvents.length; n++) { - stream.on(kProxyEvents[n], self.emit.bind(self, kProxyEvents[n])); - } + self.peerId = typeof opts.peerId === 'string' + ? opts.peerId + : opts.peerId.toString('hex') + self._peerIdBuffer = Buffer.from(self.peerId, 'hex') + self._peerIdBinary = self._peerIdBuffer.toString('binary') - // when we try to consume some more bytes, simply unpause the - // underlying stream. - self._read = function (n) { - debug('wrapped _read', n); - if (paused) { - paused = false; - stream.resume(); - } - }; + self.infoHash = typeof opts.infoHash === 'string' + ? opts.infoHash + : opts.infoHash.toString('hex') + self._infoHashBuffer = Buffer.from(self.infoHash, 'hex') + self._infoHashBinary = self._infoHashBuffer.toString('binary') - return self; -}; + debug('new client %s', self.infoHash) -// exposed for testing purposes only. -Readable._fromList = fromList; + self.destroyed = false -// Pluck off n bytes from an array of buffers. -// Length is the combined lengths of all the buffers in the list. -// This function is designed to be inlinable, so please take care when making -// changes to the function body. -function fromList(n, state) { - // nothing buffered - if (state.length === 0) return null; + self._port = opts.port + self._getAnnounceOpts = opts.getAnnounceOpts + self._rtcConfig = opts.rtcConfig + self._userAgent = opts.userAgent - var ret; - if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { - // read it all, truncate the list - if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); - state.buffer.clear(); - } else { - // read part of list - ret = fromListPartial(n, state.buffer, state.decoder); - } + // Support lazy 'wrtc' module initialization + // See: https://github.com/webtorrent/webtorrent-hybrid/issues/46 + self._wrtc = typeof opts.wrtc === 'function' ? opts.wrtc() : opts.wrtc - return ret; -} + var announce = typeof opts.announce === 'string' + ? [ opts.announce ] + : opts.announce == null ? [] : opts.announce -// Extracts only enough buffered data to satisfy the amount requested. -// This function is designed to be inlinable, so please take care when making -// changes to the function body. -function fromListPartial(n, list, hasStrings) { - var ret; - if (n < list.head.data.length) { - // slice is the same for buffers and strings - ret = list.head.data.slice(0, n); - list.head.data = list.head.data.slice(n); - } else if (n === list.head.data.length) { - // first chunk is a perfect match - ret = list.shift(); - } else { - // result spans more than one buffer - ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); - } - return ret; -} + // Remove trailing slash from trackers to catch duplicates + announce = announce.map(function (announceUrl) { + announceUrl = announceUrl.toString() + if (announceUrl[announceUrl.length - 1] === '/') { + announceUrl = announceUrl.substring(0, announceUrl.length - 1) + } + return announceUrl + }) + announce = uniq(announce) -// Copies a specified amount of characters from the list of buffered data -// chunks. -// This function is designed to be inlinable, so please take care when making -// changes to the function body. -function copyFromBufferString(n, list) { - var p = list.head; - var c = 1; - var ret = p.data; - n -= ret.length; - while (p = p.next) { - var str = p.data; - var nb = n > str.length ? str.length : n; - if (nb === str.length) ret += str;else ret += str.slice(0, n); - n -= nb; - if (n === 0) { - if (nb === str.length) { - ++c; - if (p.next) list.head = p.next;else list.head = list.tail = null; + var webrtcSupport = self._wrtc !== false && (!!self._wrtc || Peer.WEBRTC_SUPPORT) + + self._trackers = announce + .map(function (announceUrl) { + var protocol = url.parse(announceUrl).protocol + if ((protocol === 'http:' || protocol === 'https:') && + typeof HTTPTracker === 'function') { + return new HTTPTracker(self, announceUrl) + } else if (protocol === 'udp:' && typeof UDPTracker === 'function') { + return new UDPTracker(self, announceUrl) + } else if ((protocol === 'ws:' || protocol === 'wss:') && webrtcSupport) { + // Skip ws:// trackers on https:// sites because they throw SecurityError + if (protocol === 'ws:' && typeof window !== 'undefined' && + window.location.protocol === 'https:') { + nextTickWarn(new Error('Unsupported tracker protocol: ' + announceUrl)) + return null + } + return new WebSocketTracker(self, announceUrl) } else { - list.head = p; - p.data = str.slice(nb); + nextTickWarn(new Error('Unsupported tracker protocol: ' + announceUrl)) + return null } - break; - } - ++c; + }) + .filter(Boolean) + + function nextTickWarn (err) { + process.nextTick(function () { + self.emit('warning', err) + }) } - list.length -= c; - return ret; } -// Copies a specified amount of bytes from the list of buffered data chunks. -// This function is designed to be inlinable, so please take care when making -// changes to the function body. -function copyFromBuffer(n, list) { - var ret = Buffer.allocUnsafe(n); - var p = list.head; - var c = 1; - p.data.copy(ret); - n -= p.data.length; - while (p = p.next) { - var buf = p.data; - var nb = n > buf.length ? buf.length : n; - buf.copy(ret, ret.length - n, 0, nb); - n -= nb; - if (n === 0) { - if (nb === buf.length) { - ++c; - if (p.next) list.head = p.next;else list.head = list.tail = null; +/** + * Simple convenience function to scrape a tracker for an info hash without needing to + * create a Client, pass it a parsed torrent, etc. Support scraping a tracker for multiple + * torrents at the same time. + * @params {Object} opts + * @param {string|Array.} opts.infoHash + * @param {string} opts.announce + * @param {function} cb + */ +Client.scrape = function (opts, cb) { + cb = once(cb) + + if (!opts.infoHash) throw new Error('Option `infoHash` is required') + if (!opts.announce) throw new Error('Option `announce` is required') + + var clientOpts = extend(opts, { + infoHash: Array.isArray(opts.infoHash) ? opts.infoHash[0] : opts.infoHash, + peerId: Buffer.from('01234567890123456789'), // dummy value + port: 6881 // dummy value + }) + + var client = new Client(clientOpts) + client.once('error', cb) + client.once('warning', cb) + + var len = Array.isArray(opts.infoHash) ? opts.infoHash.length : 1 + var results = {} + client.on('scrape', function (data) { + len -= 1 + results[data.infoHash] = data + if (len === 0) { + client.destroy() + var keys = Object.keys(results) + if (keys.length === 1) { + cb(null, results[keys[0]]) } else { - list.head = p; - p.data = buf.slice(nb); + cb(null, results) } - break; } - ++c; - } - list.length -= c; - return ret; -} - -function endReadable(stream) { - var state = stream._readableState; - - // If we get here before consuming all the bytes, then that is a - // bug in node. Should never happen. - if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); + }) - if (!state.endEmitted) { - state.ended = true; - processNextTick(endReadableNT, state, stream); - } + opts.infoHash = Array.isArray(opts.infoHash) + ? opts.infoHash.map(function (infoHash) { + return Buffer.from(infoHash, 'hex') + }) + : Buffer.from(opts.infoHash, 'hex') + client.scrape({ infoHash: opts.infoHash }) + return client } -function endReadableNT(state, stream) { - // Check that we didn't get one last unshift. - if (!state.endEmitted && state.length === 0) { - state.endEmitted = true; - stream.readable = false; - stream.emit('end'); - } +/** + * Send a `start` announce to the trackers. + * @param {Object} opts + * @param {number=} opts.uploaded + * @param {number=} opts.downloaded + * @param {number=} opts.left (if not set, calculated automatically) + */ +Client.prototype.start = function (opts) { + var self = this + debug('send `start`') + opts = self._defaultAnnounceOpts(opts) + opts.event = 'started' + self._announce(opts) + + // start announcing on intervals + self._trackers.forEach(function (tracker) { + tracker.setInterval() + }) } -function forEach(xs, f) { - for (var i = 0, l = xs.length; i < l; i++) { - f(xs[i], i); - } +/** + * Send a `stop` announce to the trackers. + * @param {Object} opts + * @param {number=} opts.uploaded + * @param {number=} opts.downloaded + * @param {number=} opts.numwant + * @param {number=} opts.left (if not set, calculated automatically) + */ +Client.prototype.stop = function (opts) { + var self = this + debug('send `stop`') + opts = self._defaultAnnounceOpts(opts) + opts.event = 'stopped' + self._announce(opts) } -function indexOf(xs, x) { - for (var i = 0, l = xs.length; i < l; i++) { - if (xs[i] === x) return i; - } - return -1; +/** + * Send a `complete` announce to the trackers. + * @param {Object} opts + * @param {number=} opts.uploaded + * @param {number=} opts.downloaded + * @param {number=} opts.numwant + * @param {number=} opts.left (if not set, calculated automatically) + */ +Client.prototype.complete = function (opts) { + var self = this + debug('send `complete`') + if (!opts) opts = {} + opts = self._defaultAnnounceOpts(opts) + opts.event = 'completed' + self._announce(opts) } -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./_stream_duplex":84,"./internal/streams/BufferList":89,"./internal/streams/destroy":90,"./internal/streams/stream":91,"_process":170,"core-util-is":49,"events":162,"inherits":58,"isarray":62,"process-nextick-args":79,"safe-buffer":98,"string_decoder/":108,"util":158}],87:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. -// a transform stream is a readable/writable stream where you do -// something with the data. Sometimes it's called a "filter", -// but that's not a great name for it, since that implies a thing where -// some bits pass through, and others are simply ignored. (That would -// be a valid example of a transform, of course.) -// -// While the output is causally related to the input, it's not a -// necessarily symmetric or synchronous transformation. For example, -// a zlib stream might take multiple plain-text writes(), and then -// emit a single compressed chunk some time in the future. -// -// Here's how this works: -// -// The Transform stream has all the aspects of the readable and writable -// stream classes. When you write(chunk), that calls _write(chunk,cb) -// internally, and returns false if there's a lot of pending writes -// buffered up. When you call read(), that calls _read(n) until -// there's enough pending readable data buffered up. -// -// In a transform stream, the written data is placed in a buffer. When -// _read(n) is called, it transforms the queued up data, calling the -// buffered _write cb's as it consumes chunks. If consuming a single -// written chunk would result in multiple output chunks, then the first -// outputted bit calls the readcb, and subsequent chunks just go into -// the read buffer, and will cause it to emit 'readable' if necessary. -// -// This way, back-pressure is actually determined by the reading side, -// since _read has to be called to start processing a new chunk. However, -// a pathological inflate type of transform can cause excessive buffering -// here. For example, imagine a stream where every byte of input is -// interpreted as an integer from 0-255, and then results in that many -// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in -// 1kb of data being output. In this case, you could write a very small -// amount of input, and end up with a very large amount of output. In -// such a pathological inflating mechanism, there'd be no way to tell -// the system to stop doing the transform. A single 4MB write could -// cause the system to run out of memory. -// -// However, even in such a pathological case, only a single written chunk -// would be consumed, and then the rest would wait (un-transformed) until -// the results of the previous transformed chunk were consumed. +/** + * Send a `update` announce to the trackers. + * @param {Object} opts + * @param {number=} opts.uploaded + * @param {number=} opts.downloaded + * @param {number=} opts.numwant + * @param {number=} opts.left (if not set, calculated automatically) + */ +Client.prototype.update = function (opts) { + var self = this + debug('send `update`') + opts = self._defaultAnnounceOpts(opts) + if (opts.event) delete opts.event + self._announce(opts) +} -'use strict'; +Client.prototype._announce = function (opts) { + var self = this + self._trackers.forEach(function (tracker) { + // tracker should not modify `opts` object, it's passed to all trackers + tracker.announce(opts) + }) +} -module.exports = Transform; +/** + * Send a scrape request to the trackers. + * @param {Object} opts + */ +Client.prototype.scrape = function (opts) { + var self = this + debug('send `scrape`') + if (!opts) opts = {} + self._trackers.forEach(function (tracker) { + // tracker should not modify `opts` object, it's passed to all trackers + tracker.scrape(opts) + }) +} -var Duplex = require('./_stream_duplex'); +Client.prototype.setInterval = function (intervalMs) { + var self = this + debug('setInterval %d', intervalMs) + self._trackers.forEach(function (tracker) { + tracker.setInterval(intervalMs) + }) +} -/**/ -var util = require('core-util-is'); -util.inherits = require('inherits'); -/**/ +Client.prototype.destroy = function (cb) { + var self = this + if (self.destroyed) return + self.destroyed = true + debug('destroy') -util.inherits(Transform, Duplex); + var tasks = self._trackers.map(function (tracker) { + return function (cb) { + tracker.destroy(cb) + } + }) -function TransformState(stream) { - this.afterTransform = function (er, data) { - return afterTransform(stream, er, data); - }; + parallel(tasks, cb) - this.needTransform = false; - this.transforming = false; - this.writecb = null; - this.writechunk = null; - this.writeencoding = null; + self._trackers = [] + self._getAnnounceOpts = null } -function afterTransform(stream, er, data) { - var ts = stream._transformState; - ts.transforming = false; - - var cb = ts.writecb; - - if (!cb) { - return stream.emit('error', new Error('write callback called multiple times')); - } - - ts.writechunk = null; - ts.writecb = null; +Client.prototype._defaultAnnounceOpts = function (opts) { + var self = this + if (!opts) opts = {} - if (data !== null && data !== undefined) stream.push(data); + if (opts.numwant == null) opts.numwant = common.DEFAULT_ANNOUNCE_PEERS - cb(er); + if (opts.uploaded == null) opts.uploaded = 0 + if (opts.downloaded == null) opts.downloaded = 0 - var rs = stream._readableState; - rs.reading = false; - if (rs.needReadable || rs.length < rs.highWaterMark) { - stream._read(rs.highWaterMark); - } + if (self._getAnnounceOpts) opts = extend(opts, self._getAnnounceOpts()) + return opts } -function Transform(options) { - if (!(this instanceof Transform)) return new Transform(options); +}).call(this,require('_process')) +},{"./lib/client/http-tracker":3,"./lib/client/udp-tracker":3,"./lib/client/websocket-tracker":77,"./lib/common":78,"_process":15,"debug":87,"events":7,"inherits":96,"once":115,"run-parallel":136,"safe-buffer":138,"simple-peer":141,"uniq":156,"url":37,"xtend":171}],76:[function(require,module,exports){ +module.exports = Tracker - Duplex.call(this, options); +var EventEmitter = require('events').EventEmitter +var inherits = require('inherits') - this._transformState = new TransformState(this); +inherits(Tracker, EventEmitter) - var stream = this; +function Tracker (client, announceUrl) { + var self = this + EventEmitter.call(self) + self.client = client + self.announceUrl = announceUrl - // start out asking for a readable event once data is transformed. - this._readableState.needReadable = true; + self.interval = null + self.destroyed = false +} - // we have implemented the _read method, and done the other things - // that Readable wants before the first _read call, so unset the - // sync guard flag. - this._readableState.sync = false; +Tracker.prototype.setInterval = function (intervalMs) { + var self = this + if (intervalMs == null) intervalMs = self.DEFAULT_ANNOUNCE_INTERVAL - if (options) { - if (typeof options.transform === 'function') this._transform = options.transform; + clearInterval(self.interval) - if (typeof options.flush === 'function') this._flush = options.flush; + if (intervalMs) { + self.interval = setInterval(function () { + self.announce(self.client._defaultAnnounceOpts()) + }, intervalMs) + if (self.interval.unref) self.interval.unref() } - - // When the writable side finishes, then flush out anything remaining. - this.once('prefinish', function () { - if (typeof this._flush === 'function') this._flush(function (er, data) { - done(stream, er, data); - });else done(stream); - }); } -Transform.prototype.push = function (chunk, encoding) { - this._transformState.needTransform = false; - return Duplex.prototype.push.call(this, chunk, encoding); -}; +},{"events":7,"inherits":96}],77:[function(require,module,exports){ +module.exports = WebSocketTracker -// This is the part where you do stuff! -// override this function in implementation classes. -// 'chunk' is an input chunk. -// -// Call `push(newChunk)` to pass along transformed output -// to the readable side. You may call 'push' zero or more times. -// -// Call `cb(err)` when you are done with this chunk. If you pass -// an error, then that'll put the hurt on the whole operation. If you -// never call cb(), then you'll never get another chunk. -Transform.prototype._transform = function (chunk, encoding, cb) { - throw new Error('_transform() is not implemented'); -}; - -Transform.prototype._write = function (chunk, encoding, cb) { - var ts = this._transformState; - ts.writecb = cb; - ts.writechunk = chunk; - ts.writeencoding = encoding; - if (!ts.transforming) { - var rs = this._readableState; - if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); - } -}; - -// Doesn't matter what the args are here. -// _transform does all the work. -// That we got here means that the readable side wants more data. -Transform.prototype._read = function (n) { - var ts = this._transformState; +var debug = require('debug')('bittorrent-tracker:websocket-tracker') +var extend = require('xtend') +var inherits = require('inherits') +var Peer = require('simple-peer') +var randombytes = require('randombytes') +var Socket = require('simple-websocket') - if (ts.writechunk !== null && ts.writecb && !ts.transforming) { - ts.transforming = true; - this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); - } else { - // mark that we need a transform, so that any data that comes in - // will get processed, now that we've asked for it. - ts.needTransform = true; - } -}; +var common = require('../common') +var Tracker = require('./tracker') -Transform.prototype._destroy = function (err, cb) { - var _this = this; +// Use a socket pool, so tracker clients share WebSocket objects for the same server. +// In practice, WebSockets are pretty slow to establish, so this gives a nice performance +// boost, and saves browser resources. +var socketPool = {} - Duplex.prototype._destroy.call(this, err, function (err2) { - cb(err2); - _this.emit('close'); - }); -}; +var RECONNECT_MINIMUM = 15 * 1000 +var RECONNECT_MAXIMUM = 30 * 60 * 1000 +var RECONNECT_VARIANCE = 30 * 1000 +var OFFER_TIMEOUT = 50 * 1000 -function done(stream, er, data) { - if (er) return stream.emit('error', er); +inherits(WebSocketTracker, Tracker) - if (data !== null && data !== undefined) stream.push(data); +function WebSocketTracker (client, announceUrl, opts) { + var self = this + Tracker.call(self, client, announceUrl) + debug('new websocket tracker %s', announceUrl) - // if there's nothing in the write buffer, then that means - // that nothing more will ever be provided - var ws = stream._writableState; - var ts = stream._transformState; + self.peers = {} // peers (offer id -> peer) + self.socket = null - if (ws.length) throw new Error('Calling transform done when ws.length != 0'); + self.reconnecting = false + self.retries = 0 + self.reconnectTimer = null - if (ts.transforming) throw new Error('Calling transform done when still transforming'); + // Simple boolean flag to track whether the socket has received data from + // the websocket server since the last time socket.send() was called. + self.expectingResponse = false - return stream.push(null); + self._openSocket() } -},{"./_stream_duplex":84,"core-util-is":49,"inherits":58}],88:[function(require,module,exports){ -(function (process,global){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// A bit simpler than readable streams. -// Implement an async ._write(chunk, encoding, cb), and it'll handle all -// the drain event emission and buffering. -'use strict'; +WebSocketTracker.prototype.DEFAULT_ANNOUNCE_INTERVAL = 30 * 1000 // 30 seconds -/**/ +WebSocketTracker.prototype.announce = function (opts) { + var self = this + if (self.destroyed || self.reconnecting) return + if (!self.socket.connected) { + self.socket.once('connect', function () { + self.announce(opts) + }) + return + } -var processNextTick = require('process-nextick-args'); -/**/ + var params = extend(opts, { + action: 'announce', + info_hash: self.client._infoHashBinary, + peer_id: self.client._peerIdBinary + }) + if (self._trackerId) params.trackerid = self._trackerId -module.exports = Writable; + if (opts.event === 'stopped' || opts.event === 'completed') { + // Don't include offers with 'stopped' or 'completed' event + self._send(params) + } else { + // Limit the number of offers that are generated, since it can be slow + var numwant = Math.min(opts.numwant, 10) -/* */ -function WriteReq(chunk, encoding, cb) { - this.chunk = chunk; - this.encoding = encoding; - this.callback = cb; - this.next = null; + self._generateOffers(numwant, function (offers) { + params.numwant = numwant + params.offers = offers + self._send(params) + }) + } } -// It seems a linked list but it is not -// there will be only 2 of these for each stream -function CorkedRequest(state) { - var _this = this; - - this.next = null; - this.entry = null; - this.finish = function () { - onCorkedFinish(_this, state); - }; -} -/* */ +WebSocketTracker.prototype.scrape = function (opts) { + var self = this + if (self.destroyed || self.reconnecting) return + if (!self.socket.connected) { + self.socket.once('connect', function () { + self.scrape(opts) + }) + return + } -/**/ -var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : processNextTick; -/**/ + var infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0) + ? opts.infoHash.map(function (infoHash) { + return infoHash.toString('binary') + }) + : (opts.infoHash && opts.infoHash.toString('binary')) || self.client._infoHashBinary + var params = { + action: 'scrape', + info_hash: infoHashes + } -/**/ -var Duplex; -/**/ + self._send(params) +} -Writable.WritableState = WritableState; +WebSocketTracker.prototype.destroy = function (cb) { + var self = this + if (!cb) cb = noop + if (self.destroyed) return cb(null) -/**/ -var util = require('core-util-is'); -util.inherits = require('inherits'); -/**/ + self.destroyed = true -/**/ -var internalUtil = { - deprecate: require('util-deprecate') -}; -/**/ + clearInterval(self.interval) + clearTimeout(self.reconnectTimer) -/**/ -var Stream = require('./internal/streams/stream'); -/**/ + // Destroy peers + for (var peerId in self.peers) { + var peer = self.peers[peerId] + clearTimeout(peer.trackerTimeout) + peer.destroy() + } + self.peers = null -/**/ -var Buffer = require('safe-buffer').Buffer; -var OurUint8Array = global.Uint8Array || function () {}; -function _uint8ArrayToBuffer(chunk) { - return Buffer.from(chunk); -} -function _isUint8Array(obj) { - return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; -} -/**/ + if (self.socket) { + self.socket.removeListener('connect', self._onSocketConnectBound) + self.socket.removeListener('data', self._onSocketDataBound) + self.socket.removeListener('close', self._onSocketCloseBound) + self.socket.removeListener('error', self._onSocketErrorBound) + self.socket = null + } -var destroyImpl = require('./internal/streams/destroy'); + self._onSocketConnectBound = null + self._onSocketErrorBound = null + self._onSocketDataBound = null + self._onSocketCloseBound = null -util.inherits(Writable, Stream); + if (socketPool[self.announceUrl]) { + socketPool[self.announceUrl].consumers -= 1 + } -function nop() {} + // Other instances are using the socket, so there's nothing left to do here + if (socketPool[self.announceUrl].consumers > 0) return cb() -function WritableState(options, stream) { - Duplex = Duplex || require('./_stream_duplex'); + var socket = socketPool[self.announceUrl] + delete socketPool[self.announceUrl] + socket.on('error', noop) // ignore all future errors + socket.once('close', cb) - options = options || {}; + // If there is no data response expected, destroy immediately. + if (!self.expectingResponse) return destroyCleanup() - // object stream flag to indicate whether or not this stream - // contains buffers or objects. - this.objectMode = !!options.objectMode; + // Otherwise, wait a short time for potential responses to come in from the + // server, then force close the socket. + var timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT) - if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.writableObjectMode; + // But, if a response comes from the server before the timeout fires, do cleanup + // right away. + socket.once('data', destroyCleanup) - // the point at which write() starts returning false - // Note: 0 is a valid value, means that we always return false if - // the entire buffer is not flushed immediately on write() - var hwm = options.highWaterMark; - var defaultHwm = this.objectMode ? 16 : 16 * 1024; - this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; - - // cast to ints. - this.highWaterMark = Math.floor(this.highWaterMark); + function destroyCleanup () { + if (timeout) { + clearTimeout(timeout) + timeout = null + } + socket.removeListener('data', destroyCleanup) + socket.destroy() + socket = null + } +} - // if _final has been called - this.finalCalled = false; +WebSocketTracker.prototype._openSocket = function () { + var self = this + self.destroyed = false - // drain event flag. - this.needDrain = false; - // at the start of calling end() - this.ending = false; - // when end() has been called, and returned - this.ended = false; - // when 'finish' is emitted - this.finished = false; + if (!self.peers) self.peers = {} - // has it been destroyed - this.destroyed = false; + self._onSocketConnectBound = function () { + self._onSocketConnect() + } + self._onSocketErrorBound = function (err) { + self._onSocketError(err) + } + self._onSocketDataBound = function (data) { + self._onSocketData(data) + } + self._onSocketCloseBound = function () { + self._onSocketClose() + } - // should we decode strings into buffers before passing to _write? - // this is here so that some node-core streams can optimize string - // handling at a lower level. - var noDecode = options.decodeStrings === false; - this.decodeStrings = !noDecode; + self.socket = socketPool[self.announceUrl] + if (self.socket) { + socketPool[self.announceUrl].consumers += 1 + } else { + self.socket = socketPool[self.announceUrl] = new Socket(self.announceUrl) + self.socket.consumers = 1 + self.socket.once('connect', self._onSocketConnectBound) + } - // Crypto is kind of old and crusty. Historically, its default string - // encoding is 'binary' so we have to make this configurable. - // Everything else in the universe uses 'utf8', though. - this.defaultEncoding = options.defaultEncoding || 'utf8'; + self.socket.on('data', self._onSocketDataBound) + self.socket.once('close', self._onSocketCloseBound) + self.socket.once('error', self._onSocketErrorBound) +} - // not an actual buffer we keep track of, but a measurement - // of how much we're waiting to get pushed to some underlying - // socket or file. - this.length = 0; +WebSocketTracker.prototype._onSocketConnect = function () { + var self = this + if (self.destroyed) return - // a flag to see when we're in the middle of a write. - this.writing = false; + if (self.reconnecting) { + self.reconnecting = false + self.retries = 0 + self.announce(self.client._defaultAnnounceOpts()) + } +} - // when true all writes will be buffered until .uncork() call - this.corked = 0; +WebSocketTracker.prototype._onSocketData = function (data) { + var self = this + if (self.destroyed) return - // a flag to be able to tell if the onwrite cb is called immediately, - // or on a later tick. We set this to true at first, because any - // actions that shouldn't happen until "later" should generally also - // not happen before the first write call. - this.sync = true; + self.expectingResponse = false - // a flag to know if we're processing previously buffered items, which - // may call the _write() callback in the same tick, so that we don't - // end up in an overlapped onwrite situation. - this.bufferProcessing = false; + try { + data = JSON.parse(data) + } catch (err) { + self.client.emit('warning', new Error('Invalid tracker response')) + return + } - // the callback that's passed to _write(chunk,cb) - this.onwrite = function (er) { - onwrite(stream, er); - }; + if (data.action === 'announce') { + self._onAnnounceResponse(data) + } else if (data.action === 'scrape') { + self._onScrapeResponse(data) + } else { + self._onSocketError(new Error('invalid action in WS response: ' + data.action)) + } +} - // the callback that the user supplies to write(chunk,encoding,cb) - this.writecb = null; +WebSocketTracker.prototype._onAnnounceResponse = function (data) { + var self = this - // the amount that is being written when _write is called. - this.writelen = 0; + if (data.info_hash !== self.client._infoHashBinary) { + debug( + 'ignoring websocket data from %s for %s (looking for %s: reused socket)', + self.announceUrl, common.binaryToHex(data.info_hash), self.client.infoHash + ) + return + } - this.bufferedRequest = null; - this.lastBufferedRequest = null; + if (data.peer_id && data.peer_id === self.client._peerIdBinary) { + // ignore offers/answers from this client + return + } - // number of pending user-supplied write callbacks - // this must be 0 before 'finish' can be emitted - this.pendingcb = 0; + debug( + 'received %s from %s for %s', + JSON.stringify(data), self.announceUrl, self.client.infoHash + ) - // emit prefinish if the only thing we're waiting for is _write cbs - // This is relevant for synchronous Transform streams - this.prefinished = false; + var failure = data['failure reason'] + if (failure) return self.client.emit('warning', new Error(failure)) - // True if the error was already emitted and should not be thrown again - this.errorEmitted = false; + var warning = data['warning message'] + if (warning) self.client.emit('warning', new Error(warning)) - // count buffered requests - this.bufferedRequestCount = 0; + var interval = data.interval || data['min interval'] + if (interval) self.setInterval(interval * 1000) - // allocate the first CorkedRequest, there is always - // one allocated and free to use, and we maintain at most two - this.corkedRequestsFree = new CorkedRequest(this); -} + var trackerId = data['tracker id'] + if (trackerId) { + // If absent, do not discard previous trackerId value + self._trackerId = trackerId + } -WritableState.prototype.getBuffer = function getBuffer() { - var current = this.bufferedRequest; - var out = []; - while (current) { - out.push(current); - current = current.next; + if (data.complete != null) { + var response = Object.assign({}, data, { + announce: self.announceUrl, + infoHash: common.binaryToHex(data.info_hash) + }) + self.client.emit('update', response) } - return out; -}; -(function () { - try { - Object.defineProperty(WritableState.prototype, 'buffer', { - get: internalUtil.deprecate(function () { - return this.getBuffer(); - }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') - }); - } catch (_) {} -})(); + var peer + if (data.offer && data.peer_id) { + debug('creating peer (from remote offer)') + peer = self._createPeer() + peer.id = common.binaryToHex(data.peer_id) + peer.once('signal', function (answer) { + var params = { + action: 'announce', + info_hash: self.client._infoHashBinary, + peer_id: self.client._peerIdBinary, + to_peer_id: data.peer_id, + answer: answer, + offer_id: data.offer_id + } + if (self._trackerId) params.trackerid = self._trackerId + self._send(params) + }) + peer.signal(data.offer) + self.client.emit('peer', peer) + } -// Test _writableState for inheritance to account for Duplex streams, -// whose prototype chain only points to Readable. -var realHasInstance; -if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { - realHasInstance = Function.prototype[Symbol.hasInstance]; - Object.defineProperty(Writable, Symbol.hasInstance, { - value: function (object) { - if (realHasInstance.call(this, object)) return true; + if (data.answer && data.peer_id) { + var offerId = common.binaryToHex(data.offer_id) + peer = self.peers[offerId] + if (peer) { + peer.id = common.binaryToHex(data.peer_id) + peer.signal(data.answer) + self.client.emit('peer', peer) - return object && object._writableState instanceof WritableState; + clearTimeout(peer.trackerTimeout) + peer.trackerTimeout = null + delete self.peers[offerId] + } else { + debug('got unexpected answer: ' + JSON.stringify(data.answer)) } - }); -} else { - realHasInstance = function (object) { - return object instanceof this; - }; + } } -function Writable(options) { - Duplex = Duplex || require('./_stream_duplex'); - - // Writable ctor is applied to Duplexes, too. - // `realHasInstance` is necessary because using plain `instanceof` - // would return false, as no `_writableState` property is attached. +WebSocketTracker.prototype._onScrapeResponse = function (data) { + var self = this + data = data.files || {} - // Trying to use the custom `instanceof` for Writable here will also break the - // Node.js LazyTransform implementation, which has a non-trivial getter for - // `_writableState` that would lead to infinite recursion. - if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { - return new Writable(options); + var keys = Object.keys(data) + if (keys.length === 0) { + self.client.emit('warning', new Error('invalid scrape response')) + return } - this._writableState = new WritableState(options, this); - - // legacy. - this.writable = true; - - if (options) { - if (typeof options.write === 'function') this._write = options.write; + keys.forEach(function (infoHash) { + // TODO: optionally handle data.flags.min_request_interval + // (separate from announce interval) + var response = Object.assign(data[infoHash], { + announce: self.announceUrl, + infoHash: common.binaryToHex(infoHash) + }) + self.client.emit('scrape', response) + }) +} - if (typeof options.writev === 'function') this._writev = options.writev; +WebSocketTracker.prototype._onSocketClose = function () { + var self = this + if (self.destroyed) return + self.destroy() + self._startReconnectTimer() +} - if (typeof options.destroy === 'function') this._destroy = options.destroy; +WebSocketTracker.prototype._onSocketError = function (err) { + var self = this + if (self.destroyed) return + self.destroy() + // errors will often happen if a tracker is offline, so don't treat it as fatal + self.client.emit('warning', err) + self._startReconnectTimer() +} - if (typeof options.final === 'function') this._final = options.final; - } +WebSocketTracker.prototype._startReconnectTimer = function () { + var self = this + var ms = Math.floor(Math.random() * RECONNECT_VARIANCE) + Math.min(Math.pow(2, self.retries) * RECONNECT_MINIMUM, RECONNECT_MAXIMUM) - Stream.call(this); -} + self.reconnecting = true + clearTimeout(self.reconnectTimer) + self.reconnectTimer = setTimeout(function () { + self.retries++ + self._openSocket() + }, ms) + if (self.reconnectTimer.unref) self.reconnectTimer.unref() -// Otherwise people can pipe Writable streams, which is just wrong. -Writable.prototype.pipe = function () { - this.emit('error', new Error('Cannot pipe, not readable')); -}; + debug('reconnecting socket in %s ms', ms) +} -function writeAfterEnd(stream, cb) { - var er = new Error('write after end'); - // TODO: defer error events consistently everywhere, not just the cb - stream.emit('error', er); - processNextTick(cb, er); +WebSocketTracker.prototype._send = function (params) { + var self = this + if (self.destroyed) return + self.expectingResponse = true + var message = JSON.stringify(params) + debug('send %s', message) + self.socket.send(message) } -// Checks that a user-supplied chunk is valid, especially for the particular -// mode the stream is in. Currently this means that `null` is never accepted -// and undefined/non-string values are only allowed in object mode. -function validChunk(stream, state, chunk, cb) { - var valid = true; - var er = false; +WebSocketTracker.prototype._generateOffers = function (numwant, cb) { + var self = this + var offers = [] + debug('generating %s offers', numwant) - if (chunk === null) { - er = new TypeError('May not write null values to stream'); - } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { - er = new TypeError('Invalid non-string/buffer chunk'); + for (var i = 0; i < numwant; ++i) { + generateOffer() } - if (er) { - stream.emit('error', er); - processNextTick(cb, er); - valid = false; + checkDone() + + function generateOffer () { + var offerId = randombytes(20).toString('hex') + debug('creating peer (from _generateOffers)') + var peer = self.peers[offerId] = self._createPeer({ initiator: true }) + peer.once('signal', function (offer) { + offers.push({ + offer: offer, + offer_id: common.hexToBinary(offerId) + }) + checkDone() + }) + peer.trackerTimeout = setTimeout(function () { + debug('tracker timeout: destroying peer') + peer.trackerTimeout = null + delete self.peers[offerId] + peer.destroy() + }, OFFER_TIMEOUT) + if (peer.trackerTimeout.unref) peer.trackerTimeout.unref() + } + + function checkDone () { + if (offers.length === numwant) { + debug('generated %s offers', numwant) + cb(offers) + } } - return valid; } -Writable.prototype.write = function (chunk, encoding, cb) { - var state = this._writableState; - var ret = false; - var isBuf = _isUint8Array(chunk) && !state.objectMode; +WebSocketTracker.prototype._createPeer = function (opts) { + var self = this - if (isBuf && !Buffer.isBuffer(chunk)) { - chunk = _uint8ArrayToBuffer(chunk); - } + opts = Object.assign({ + trickle: false, + config: self.client._rtcConfig, + wrtc: self.client._wrtc + }, opts) - if (typeof encoding === 'function') { - cb = encoding; - encoding = null; - } + var peer = new Peer(opts) - if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + peer.once('error', onError) + peer.once('connect', onConnect) - if (typeof cb !== 'function') cb = nop; + return peer - if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { - state.pendingcb++; - ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + // Handle peer 'error' events that are fired *before* the peer is emitted in + // a 'peer' event. + function onError (err) { + self.client.emit('warning', new Error('Connection error: ' + err.message)) + peer.destroy() } - return ret; -}; + // Once the peer is emitted in a 'peer' event, then it's the consumer's + // responsibility to listen for errors, so the listeners are removed here. + function onConnect () { + peer.removeListener('error', onError) + peer.removeListener('connect', onConnect) + } +} -Writable.prototype.cork = function () { - var state = this._writableState; +function noop () {} - state.corked++; -}; +},{"../common":78,"./tracker":76,"debug":87,"inherits":96,"randombytes":122,"simple-peer":141,"simple-websocket":143,"xtend":171}],78:[function(require,module,exports){ +/** + * Functions/constants needed by both the client and server. + */ -Writable.prototype.uncork = function () { - var state = this._writableState; +var Buffer = require('safe-buffer').Buffer +var extend = require('xtend/mutable') - if (state.corked) { - state.corked--; +exports.DEFAULT_ANNOUNCE_PEERS = 50 +exports.MAX_ANNOUNCE_PEERS = 82 - if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); +exports.binaryToHex = function (str) { + if (typeof str !== 'string') { + str = String(str) } -}; - -Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { - // node::ParseEncoding() requires lower case. - if (typeof encoding === 'string') encoding = encoding.toLowerCase(); - if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); - this._writableState.defaultEncoding = encoding; - return this; -}; + return Buffer.from(str, 'binary').toString('hex') +} -function decodeChunk(state, chunk, encoding) { - if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { - chunk = Buffer.from(chunk, encoding); +exports.hexToBinary = function (str) { + if (typeof str !== 'string') { + str = String(str) } - return chunk; + return Buffer.from(str, 'hex').toString('binary') } -// if we're already writing something, then just put this -// in the queue, and wait our turn. Otherwise, call _write -// If we return false, then we need a drain event, so set that flag. -function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { - if (!isBuf) { - var newChunk = decodeChunk(state, chunk, encoding); - if (chunk !== newChunk) { - isBuf = true; - encoding = 'buffer'; - chunk = newChunk; - } - } - var len = state.objectMode ? 1 : chunk.length; +var config = require('./common-node') +extend(exports, config) - state.length += len; +},{"./common-node":3,"safe-buffer":138,"xtend/mutable":172}],79:[function(require,module,exports){ +(function (Buffer){ +/* global Blob, FileReader */ - var ret = state.length < state.highWaterMark; - // we must ensure that previous needDrain will not be reset to false. - if (!ret) state.needDrain = true; +module.exports = function blobToBuffer (blob, cb) { + if (typeof Blob === 'undefined' || !(blob instanceof Blob)) { + throw new Error('first argument must be a Blob') + } + if (typeof cb !== 'function') { + throw new Error('second argument must be a function') + } - if (state.writing || state.corked) { - var last = state.lastBufferedRequest; - state.lastBufferedRequest = { - chunk: chunk, - encoding: encoding, - isBuf: isBuf, - callback: cb, - next: null - }; - if (last) { - last.next = state.lastBufferedRequest; - } else { - state.bufferedRequest = state.lastBufferedRequest; - } - state.bufferedRequestCount += 1; - } else { - doWrite(stream, state, false, len, chunk, encoding, cb); + var reader = new FileReader() + + function onLoadEnd (e) { + reader.removeEventListener('loadend', onLoadEnd, false) + if (e.error) cb(e.error) + else cb(null, new Buffer(reader.result)) } - return ret; + reader.addEventListener('loadend', onLoadEnd, false) + reader.readAsArrayBuffer(blob) } -function doWrite(stream, state, writev, len, chunk, encoding, cb) { - state.writelen = len; - state.writecb = cb; - state.writing = true; - state.sync = true; - if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); - state.sync = false; -} +}).call(this,require("buffer").Buffer) +},{"buffer":4}],80:[function(require,module,exports){ +(function (Buffer){ +var inherits = require('inherits'); +var Transform = require('readable-stream').Transform; +var defined = require('defined'); -function onwriteError(stream, state, sync, er, cb) { - --state.pendingcb; - - if (sync) { - // defer the callback if we are being called synchronously - // to avoid piling up things on the stack - processNextTick(cb, er); - // this can emit finish, and it will always happen - // after error - processNextTick(finishMaybe, stream, state); - stream._writableState.errorEmitted = true; - stream.emit('error', er); - } else { - // the caller expect this to happen before if - // it is async - cb(er); - stream._writableState.errorEmitted = true; - stream.emit('error', er); - // this can emit finish, but finish must - // always follow error - finishMaybe(stream, state); - } -} - -function onwriteStateUpdate(state) { - state.writing = false; - state.writecb = null; - state.length -= state.writelen; - state.writelen = 0; -} - -function onwrite(stream, er) { - var state = stream._writableState; - var sync = state.sync; - var cb = state.writecb; - - onwriteStateUpdate(state); - - if (er) onwriteError(stream, state, sync, er, cb);else { - // Check if we're actually ready to finish, but don't emit yet - var finished = needFinish(state); - - if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { - clearBuffer(stream, state); - } +module.exports = Block; +inherits(Block, Transform); - if (sync) { - /**/ - asyncWrite(afterWrite, stream, state, finished, cb); - /**/ - } else { - afterWrite(stream, state, finished, cb); +function Block (size, opts) { + if (!(this instanceof Block)) return new Block(size, opts); + Transform.call(this); + if (!opts) opts = {}; + if (typeof size === 'object') { + opts = size; + size = opts.size; } - } -} - -function afterWrite(stream, state, finished, cb) { - if (!finished) onwriteDrain(stream, state); - state.pendingcb--; - cb(); - finishMaybe(stream, state); -} - -// Must force callback to be called on nextTick, so that we don't -// emit 'drain' before the write() consumer gets the 'false' return -// value, and has a chance to attach a 'drain' listener. -function onwriteDrain(stream, state) { - if (state.length === 0 && state.needDrain) { - state.needDrain = false; - stream.emit('drain'); - } + this.size = size || 512; + + if (opts.nopad) this._zeroPadding = false; + else this._zeroPadding = defined(opts.zeroPadding, true); + + this._buffered = []; + this._bufferedBytes = 0; } -// if there's something in the buffer waiting, then process it -function clearBuffer(stream, state) { - state.bufferProcessing = true; - var entry = state.bufferedRequest; - - if (stream._writev && entry && entry.next) { - // Fast case, write everything using _writev() - var l = state.bufferedRequestCount; - var buffer = new Array(l); - var holder = state.corkedRequestsFree; - holder.entry = entry; - - var count = 0; - var allBuffers = true; - while (entry) { - buffer[count] = entry; - if (!entry.isBuf) allBuffers = false; - entry = entry.next; - count += 1; +Block.prototype._transform = function (buf, enc, next) { + this._bufferedBytes += buf.length; + this._buffered.push(buf); + + while (this._bufferedBytes >= this.size) { + var b = Buffer.concat(this._buffered); + this._bufferedBytes -= this.size; + this.push(b.slice(0, this.size)); + this._buffered = [ b.slice(this.size, b.length) ]; } - buffer.allBuffers = allBuffers; - - doWrite(stream, state, true, state.length, buffer, '', holder.finish); + next(); +}; - // doWrite is almost always async, defer these to save a bit of time - // as the hot path ends with doWrite - state.pendingcb++; - state.lastBufferedRequest = null; - if (holder.next) { - state.corkedRequestsFree = holder.next; - holder.next = null; - } else { - state.corkedRequestsFree = new CorkedRequest(state); +Block.prototype._flush = function () { + if (this._bufferedBytes && this._zeroPadding) { + var zeroes = new Buffer(this.size - this._bufferedBytes); + zeroes.fill(0); + this._buffered.push(zeroes); + this.push(Buffer.concat(this._buffered)); + this._buffered = null; } - } else { - // Slow case, write chunks one-by-one - while (entry) { - var chunk = entry.chunk; - var encoding = entry.encoding; - var cb = entry.callback; - var len = state.objectMode ? 1 : chunk.length; - - doWrite(stream, state, false, len, chunk, encoding, cb); - entry = entry.next; - // if we didn't call the onwrite immediately, then - // it means that we need to wait until it does. - // also, that means that the chunk and cb are currently - // being processed, so move the buffer counter past them. - if (state.writing) { - break; - } + else if (this._bufferedBytes) { + this.push(Buffer.concat(this._buffered)); + this._buffered = null; } - - if (entry === null) state.lastBufferedRequest = null; - } - - state.bufferedRequestCount = 0; - state.bufferedRequest = entry; - state.bufferProcessing = false; -} - -Writable.prototype._write = function (chunk, encoding, cb) { - cb(new Error('_write() is not implemented')); + this.push(null); }; -Writable.prototype._writev = null; +}).call(this,require("buffer").Buffer) +},{"buffer":4,"defined":89,"inherits":96,"readable-stream":132}],81:[function(require,module,exports){ +/* + * JavaScript MD5 + * https://github.com/blueimp/JavaScript-MD5 + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + * + * Based on + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ -Writable.prototype.end = function (chunk, encoding, cb) { - var state = this._writableState; +/* global define */ - if (typeof chunk === 'function') { - cb = chunk; - chunk = null; - encoding = null; - } else if (typeof encoding === 'function') { - cb = encoding; - encoding = null; +;(function ($) { + 'use strict' + + /* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ + function safeAdd (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF) + var msw = (x >> 16) + (y >> 16) + (lsw >> 16) + return (msw << 16) | (lsw & 0xFFFF) } - if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); + /* + * Bitwise rotate a 32-bit number to the left. + */ + function bitRotateLeft (num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)) + } - // .end() fully uncorks - if (state.corked) { - state.corked = 1; - this.uncork(); + /* + * These functions implement the four basic operations the algorithm uses. + */ + function md5cmn (q, a, b, x, s, t) { + return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b) + } + function md5ff (a, b, c, d, x, s, t) { + return md5cmn((b & c) | ((~b) & d), a, b, x, s, t) + } + function md5gg (a, b, c, d, x, s, t) { + return md5cmn((b & d) | (c & (~d)), a, b, x, s, t) + } + function md5hh (a, b, c, d, x, s, t) { + return md5cmn(b ^ c ^ d, a, b, x, s, t) + } + function md5ii (a, b, c, d, x, s, t) { + return md5cmn(c ^ (b | (~d)), a, b, x, s, t) } - // ignore unnecessary end() calls. - if (!state.ending && !state.finished) endWritable(this, state, cb); -}; + /* + * Calculate the MD5 of an array of little-endian words, and a bit length. + */ + function binlMD5 (x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << (len % 32) + x[(((len + 64) >>> 9) << 4) + 14] = len -function needFinish(state) { - return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; -} -function callFinal(stream, state) { - stream._final(function (err) { - state.pendingcb--; - if (err) { - stream.emit('error', err); - } - state.prefinished = true; - stream.emit('prefinish'); - finishMaybe(stream, state); - }); -} -function prefinish(stream, state) { - if (!state.prefinished && !state.finalCalled) { - if (typeof stream._final === 'function') { - state.pendingcb++; - state.finalCalled = true; - processNextTick(callFinal, stream, state); - } else { - state.prefinished = true; - stream.emit('prefinish'); + var i + var olda + var oldb + var oldc + var oldd + var a = 1732584193 + var b = -271733879 + var c = -1732584194 + var d = 271733878 + + for (i = 0; i < x.length; i += 16) { + olda = a + oldb = b + oldc = c + oldd = d + + a = md5ff(a, b, c, d, x[i], 7, -680876936) + d = md5ff(d, a, b, c, x[i + 1], 12, -389564586) + c = md5ff(c, d, a, b, x[i + 2], 17, 606105819) + b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330) + a = md5ff(a, b, c, d, x[i + 4], 7, -176418897) + d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426) + c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341) + b = md5ff(b, c, d, a, x[i + 7], 22, -45705983) + a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416) + d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417) + c = md5ff(c, d, a, b, x[i + 10], 17, -42063) + b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162) + a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682) + d = md5ff(d, a, b, c, x[i + 13], 12, -40341101) + c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290) + b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329) + + a = md5gg(a, b, c, d, x[i + 1], 5, -165796510) + d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632) + c = md5gg(c, d, a, b, x[i + 11], 14, 643717713) + b = md5gg(b, c, d, a, x[i], 20, -373897302) + a = md5gg(a, b, c, d, x[i + 5], 5, -701558691) + d = md5gg(d, a, b, c, x[i + 10], 9, 38016083) + c = md5gg(c, d, a, b, x[i + 15], 14, -660478335) + b = md5gg(b, c, d, a, x[i + 4], 20, -405537848) + a = md5gg(a, b, c, d, x[i + 9], 5, 568446438) + d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690) + c = md5gg(c, d, a, b, x[i + 3], 14, -187363961) + b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501) + a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467) + d = md5gg(d, a, b, c, x[i + 2], 9, -51403784) + c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473) + b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734) + + a = md5hh(a, b, c, d, x[i + 5], 4, -378558) + d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463) + c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562) + b = md5hh(b, c, d, a, x[i + 14], 23, -35309556) + a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060) + d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353) + c = md5hh(c, d, a, b, x[i + 7], 16, -155497632) + b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640) + a = md5hh(a, b, c, d, x[i + 13], 4, 681279174) + d = md5hh(d, a, b, c, x[i], 11, -358537222) + c = md5hh(c, d, a, b, x[i + 3], 16, -722521979) + b = md5hh(b, c, d, a, x[i + 6], 23, 76029189) + a = md5hh(a, b, c, d, x[i + 9], 4, -640364487) + d = md5hh(d, a, b, c, x[i + 12], 11, -421815835) + c = md5hh(c, d, a, b, x[i + 15], 16, 530742520) + b = md5hh(b, c, d, a, x[i + 2], 23, -995338651) + + a = md5ii(a, b, c, d, x[i], 6, -198630844) + d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415) + c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905) + b = md5ii(b, c, d, a, x[i + 5], 21, -57434055) + a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571) + d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606) + c = md5ii(c, d, a, b, x[i + 10], 15, -1051523) + b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799) + a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359) + d = md5ii(d, a, b, c, x[i + 15], 10, -30611744) + c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380) + b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649) + a = md5ii(a, b, c, d, x[i + 4], 6, -145523070) + d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379) + c = md5ii(c, d, a, b, x[i + 2], 15, 718787259) + b = md5ii(b, c, d, a, x[i + 9], 21, -343485551) + + a = safeAdd(a, olda) + b = safeAdd(b, oldb) + c = safeAdd(c, oldc) + d = safeAdd(d, oldd) } + return [a, b, c, d] } -} -function finishMaybe(stream, state) { - var need = needFinish(state); - if (need) { - prefinish(stream, state); - if (state.pendingcb === 0) { - state.finished = true; - stream.emit('finish'); + /* + * Convert an array of little-endian words to a string + */ + function binl2rstr (input) { + var i + var output = '' + var length32 = input.length * 32 + for (i = 0; i < length32; i += 8) { + output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF) } + return output } - return need; -} -function endWritable(stream, state, cb) { - state.ending = true; - finishMaybe(stream, state); - if (cb) { - if (state.finished) processNextTick(cb);else stream.once('finish', cb); + /* + * Convert a raw string to an array of little-endian words + * Characters >255 have their high-byte silently ignored. + */ + function rstr2binl (input) { + var i + var output = [] + output[(input.length >> 2) - 1] = undefined + for (i = 0; i < output.length; i += 1) { + output[i] = 0 + } + var length8 = input.length * 8 + for (i = 0; i < length8; i += 8) { + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32) + } + return output } - state.ended = true; - stream.writable = false; -} -function onCorkedFinish(corkReq, state, err) { - var entry = corkReq.entry; - corkReq.entry = null; - while (entry) { - var cb = entry.callback; - state.pendingcb--; - cb(err); - entry = entry.next; - } - if (state.corkedRequestsFree) { - state.corkedRequestsFree.next = corkReq; - } else { - state.corkedRequestsFree = corkReq; + /* + * Calculate the MD5 of a raw string + */ + function rstrMD5 (s) { + return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)) } -} -Object.defineProperty(Writable.prototype, 'destroyed', { - get: function () { - if (this._writableState === undefined) { - return false; + /* + * Calculate the HMAC-MD5, of a key and some data (raw strings) + */ + function rstrHMACMD5 (key, data) { + var i + var bkey = rstr2binl(key) + var ipad = [] + var opad = [] + var hash + ipad[15] = opad[15] = undefined + if (bkey.length > 16) { + bkey = binlMD5(bkey, key.length * 8) } - return this._writableState.destroyed; - }, - set: function (value) { - // we ignore the value if the stream - // has not been initialized yet - if (!this._writableState) { - return; + for (i = 0; i < 16; i += 1) { + ipad[i] = bkey[i] ^ 0x36363636 + opad[i] = bkey[i] ^ 0x5C5C5C5C } - - // backward compatibility, the user is explicitly - // managing destroyed - this._writableState.destroyed = value; + hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8) + return binl2rstr(binlMD5(opad.concat(hash), 512 + 128)) } -}); - -Writable.prototype.destroy = destroyImpl.destroy; -Writable.prototype._undestroy = destroyImpl.undestroy; -Writable.prototype._destroy = function (err, cb) { - this.end(); - cb(err); -}; -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./_stream_duplex":84,"./internal/streams/destroy":90,"./internal/streams/stream":91,"_process":170,"core-util-is":49,"inherits":58,"process-nextick-args":79,"safe-buffer":98,"util-deprecate":119}],89:[function(require,module,exports){ -'use strict'; - -/**/ - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -var Buffer = require('safe-buffer').Buffer; -/**/ - -function copyBuffer(src, target, offset) { - src.copy(target, offset); -} + /* + * Convert a raw string to a hex string + */ + function rstr2hex (input) { + var hexTab = '0123456789abcdef' + var output = '' + var x + var i + for (i = 0; i < input.length; i += 1) { + x = input.charCodeAt(i) + output += hexTab.charAt((x >>> 4) & 0x0F) + + hexTab.charAt(x & 0x0F) + } + return output + } -module.exports = function () { - function BufferList() { - _classCallCheck(this, BufferList); + /* + * Encode a string as utf-8 + */ + function str2rstrUTF8 (input) { + return unescape(encodeURIComponent(input)) + } - this.head = null; - this.tail = null; - this.length = 0; + /* + * Take string arguments and return either raw or hex encoded strings + */ + function rawMD5 (s) { + return rstrMD5(str2rstrUTF8(s)) + } + function hexMD5 (s) { + return rstr2hex(rawMD5(s)) + } + function rawHMACMD5 (k, d) { + return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d)) + } + function hexHMACMD5 (k, d) { + return rstr2hex(rawHMACMD5(k, d)) } - BufferList.prototype.push = function push(v) { - var entry = { data: v, next: null }; - if (this.length > 0) this.tail.next = entry;else this.head = entry; - this.tail = entry; - ++this.length; - }; + function md5 (string, key, raw) { + if (!key) { + if (!raw) { + return hexMD5(string) + } + return rawMD5(string) + } + if (!raw) { + return hexHMACMD5(key, string) + } + return rawHMACMD5(key, string) + } - BufferList.prototype.unshift = function unshift(v) { - var entry = { data: v, next: this.head }; - if (this.length === 0) this.tail = entry; - this.head = entry; - ++this.length; - }; + if (typeof define === 'function' && define.amd) { + define(function () { + return md5 + }) + } else if (typeof module === 'object' && module.exports) { + module.exports = md5 + } else { + $.md5 = md5 + } +}(this)) - BufferList.prototype.shift = function shift() { - if (this.length === 0) return; - var ret = this.head.data; - if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; - --this.length; - return ret; - }; +},{}],82:[function(require,module,exports){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ - BufferList.prototype.clear = function clear() { - this.head = this.tail = null; - this.length = 0; - }; +'use strict' - BufferList.prototype.join = function join(s) { - if (this.length === 0) return ''; - var p = this.head; - var ret = '' + p.data; - while (p = p.next) { - ret += s + p.data; - }return ret; - }; +var base64 = require('base64-js') +var ieee754 = require('ieee754') - BufferList.prototype.concat = function concat(n) { - if (this.length === 0) return Buffer.alloc(0); - if (this.length === 1) return this.head.data; - var ret = Buffer.allocUnsafe(n >>> 0); - var p = this.head; - var i = 0; - while (p) { - copyBuffer(p.data, ret, i); - i += p.data.length; - p = p.next; - } - return ret; - }; +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 - return BufferList; -}(); -},{"safe-buffer":98}],90:[function(require,module,exports){ -'use strict'; +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH -/**/ +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an 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+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() -var processNextTick = require('process-nextick-args'); -/**/ +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && + typeof console.error === 'function') { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' + ) +} -// undocumented cb() API, needed for core, not for public API -function destroy(err, cb) { - var _this = this; +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }} + return arr.foo() === 42 + } catch (e) { + return false + } +} - var readableDestroyed = this._readableState && this._readableState.destroyed; - var writableDestroyed = this._writableState && this._writableState.destroyed; +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('Invalid typed array length') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} - if (readableDestroyed || writableDestroyed) { - if (cb) { - cb(err); - } else if (err && (!this._writableState || !this._writableState.errorEmitted)) { - processNextTick(emitErrorNT, this, err); +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new Error( + 'If encoding is specified then the first argument must be a string' + ) } - return; + return allocUnsafe(arg) } + return from(arg, encodingOrOffset, length) +} - // we set destroyed to true before firing error callbacks in order - // to make it re-entrance safe in case destroy() is called within callbacks +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} - if (this._readableState) { - this._readableState.destroyed = true; +Buffer.poolSize = 8192 // not used by this implementation + +function from (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') } - // if this is a duplex stream mark the writable part as destroyed as well - if (this._writableState) { - this._writableState.destroyed = true; + if (value instanceof ArrayBuffer) { + return fromArrayBuffer(value, encodingOrOffset, length) } - this._destroy(err || null, function (err) { - if (!cb && err) { - processNextTick(emitErrorNT, _this, err); - if (_this._writableState) { - _this._writableState.errorEmitted = true; - } - } else if (cb) { - cb(err); - } - }); + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + return fromObject(value) } -function undestroy() { - if (this._readableState) { - this._readableState.destroyed = false; - this._readableState.reading = false; - this._readableState.ended = false; - this._readableState.endEmitted = false; +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be a number') + } else if (size < 0) { + throw new RangeError('"size" argument must not be negative') } +} - if (this._writableState) { - this._writableState.destroyed = false; - this._writableState.ended = false; - this._writableState.ending = false; - this._writableState.finished = false; - this._writableState.errorEmitted = false; +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) } + return createBuffer(size) } -function emitErrorNT(self, err) { - self.emit('error', err); +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) } -module.exports = { - destroy: destroy, - undestroy: undestroy -}; -},{"process-nextick-args":79}],91:[function(require,module,exports){ -module.exports = require('events').EventEmitter; +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} -},{"events":162}],92:[function(require,module,exports){ -exports = module.exports = require('./lib/_stream_readable.js'); -exports.Stream = exports; -exports.Readable = exports; -exports.Writable = require('./lib/_stream_writable.js'); -exports.Duplex = require('./lib/_stream_duplex.js'); -exports.Transform = require('./lib/_stream_transform.js'); -exports.PassThrough = require('./lib/_stream_passthrough.js'); +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} -},{"./lib/_stream_duplex.js":84,"./lib/_stream_passthrough.js":85,"./lib/_stream_readable.js":86,"./lib/_stream_transform.js":87,"./lib/_stream_writable.js":88}],93:[function(require,module,exports){ -exports.render = render -exports.append = append -exports.mime = require('./lib/mime.json') +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } -var debug = require('debug')('render-media') -var isAscii = require('is-ascii') -var MediaElementWrapper = require('mediasource') -var path = require('path') -var streamToBlobURL = require('stream-to-blob-url') -var videostream = require('videostream') + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('"encoding" must be a valid string encoding') + } -var VIDEOSTREAM_EXTS = [ - '.m4a', - '.m4v', - '.mp4' -] + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) -var MEDIASOURCE_VIDEO_EXTS = [ - '.m4v', - '.mkv', - '.mp4', - '.webm' -] + var actual = buf.write(string, encoding) -var MEDIASOURCE_AUDIO_EXTS = [ - '.m4a', - '.mp3' -] + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } -var MEDIASOURCE_EXTS = [].concat( - MEDIASOURCE_VIDEO_EXTS, - MEDIASOURCE_AUDIO_EXTS -) + return buf +} -var AUDIO_EXTS = [ - '.aac', - '.oga', - '.ogg', - '.wav' -] +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} -var IMAGE_EXTS = [ - '.bmp', - '.gif', - '.jpeg', - '.jpg', - '.png', - '.svg' -] +function fromArrayBuffer (array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('\'offset\' is out of bounds') + } -var IFRAME_EXTS = [ - '.css', - '.html', - '.js', - '.md', - '.pdf', - '.txt' -] + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('\'length\' is out of bounds') + } -// Maximum file length for which the Blob URL strategy will be attempted -// See: https://github.com/feross/render-media/issues/18 -var MAX_BLOB_LENGTH = 200 * 1000 * 1000 // 200 MB + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } -var MediaSource = typeof window !== 'undefined' && window.MediaSource + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf +} -function render (file, elem, opts, cb) { - if (typeof opts === 'function') { - cb = opts - opts = {} - } - if (!opts) opts = {} - if (!cb) cb = function () {} +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) - validateFile(file) - parseOpts(opts) + if (buf.length === 0) { + return buf + } - if (typeof elem === 'string') elem = document.querySelector(elem) + obj.copy(buf, 0, 0, len) + return buf + } - renderMedia(file, function (tagName) { - if (elem.nodeName !== tagName.toUpperCase()) { - var extname = path.extname(file.name).toLowerCase() + if (obj) { + if (isArrayBufferView(obj) || 'length' in obj) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } - throw new Error( - 'Cannot render "' + extname + '" inside a "' + - elem.nodeName.toLowerCase() + '" element, expected "' + tagName + '"' - ) + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) } + } - return elem - }, opts, cb) + throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') } -function append (file, rootElem, opts, cb) { - if (typeof opts === 'function') { - cb = opts - opts = {} +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') } - if (!opts) opts = {} - if (!cb) cb = function () {} + return length | 0 +} - validateFile(file) - parseOpts(opts) +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} - if (typeof rootElem === 'string') rootElem = document.querySelector(rootElem) +Buffer.isBuffer = function isBuffer (b) { + return b != null && b._isBuffer === true +} - if (rootElem && (rootElem.nodeName === 'VIDEO' || rootElem.nodeName === 'AUDIO')) { - throw new Error( - 'Invalid video/audio node argument. Argument must be root element that ' + - 'video/audio tag will be appended to.' - ) +Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') } - renderMedia(file, getElem, opts, done) + if (a === b) return 0 - function getElem (tagName) { - if (tagName === 'video' || tagName === 'audio') return createMedia(tagName) - else return createElem(tagName) - } + var x = a.length + var y = b.length - function createMedia (tagName) { - var elem = createElem(tagName) - if (opts.controls) elem.controls = true - if (opts.autoplay) elem.autoplay = true - rootElem.appendChild(elem) - return elem + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } } - function createElem (tagName) { - var elem = document.createElement(tagName) - rootElem.appendChild(elem) - return elem - } + if (x < y) return -1 + if (y < x) return 1 + return 0 +} - function done (err, elem) { - if (err && elem) elem.remove() - cb(err, elem) +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false } } -function renderMedia (file, getElem, opts, cb) { - var extname = path.extname(file.name).toLowerCase() - var currentTime = 0 - var elem +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } - if (MEDIASOURCE_EXTS.indexOf(extname) >= 0) { - renderMediaSource() - } else if (AUDIO_EXTS.indexOf(extname) >= 0) { - renderAudio() - } else if (IMAGE_EXTS.indexOf(extname) >= 0) { - renderImage() - } else if (IFRAME_EXTS.indexOf(extname) >= 0) { - renderIframe() - } else { - tryRenderIframe() + if (list.length === 0) { + return Buffer.alloc(0) } - function renderMediaSource () { - var tagName = MEDIASOURCE_VIDEO_EXTS.indexOf(extname) >= 0 ? 'video' : 'audio' - - if (MediaSource) { - if (VIDEOSTREAM_EXTS.indexOf(extname) >= 0) { - useVideostream() - } else { - useMediaSource() - } - } else { - useBlobURL() - } - - function useVideostream () { - debug('Use `videostream` package for ' + file.name) - prepareElem() - elem.addEventListener('error', fallbackToMediaSource) - elem.addEventListener('loadstart', onLoadStart) - elem.addEventListener('canplay', onCanPlay) - videostream(file, elem) + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length } + } - function useMediaSource () { - debug('Use MediaSource API for ' + file.name) - prepareElem() - elem.addEventListener('error', fallbackToBlobURL) - elem.addEventListener('loadstart', onLoadStart) - elem.addEventListener('canplay', onCanPlay) - - var wrapper = new MediaElementWrapper(elem) - var writable = wrapper.createWriteStream(getCodec(file.name)) - file.createReadStream().pipe(writable) - - if (currentTime) elem.currentTime = currentTime + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} - function useBlobURL () { - debug('Use Blob URL for ' + file.name) - prepareElem() - elem.addEventListener('error', fatalError) - elem.addEventListener('loadstart', onLoadStart) - elem.addEventListener('canplay', onCanPlay) - getBlobURL(file, function (err, url) { - if (err) return fatalError(err) - elem.src = url - if (currentTime) elem.currentTime = currentTime - }) - } +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (isArrayBufferView(string) || string instanceof ArrayBuffer) { + return string.byteLength + } + if (typeof string !== 'string') { + string = '' + string + } - function fallbackToMediaSource (err) { - debug('videostream error: fallback to MediaSource API: %o', err.message || err) - elem.removeEventListener('error', fallbackToMediaSource) - elem.removeEventListener('canplay', onCanPlay) + var len = string.length + if (len === 0) return 0 - useMediaSource() + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + case undefined: + 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 fallbackToBlobURL (err) { - debug('MediaSource API error: fallback to Blob URL: %o', err.message || err) - - if (typeof file.length === 'number' && file.length > opts.maxBlobLength) { - debug( - 'File length too large for Blob URL approach: %d (max: %d)', - file.length, opts.maxBlobLength - ) - return fatalError(new Error( - 'File length too large for Blob URL approach: ' + file.length + - ' (max: ' + opts.maxBlobLength + ')' - )) - } - - elem.removeEventListener('error', fallbackToBlobURL) - elem.removeEventListener('canplay', onCanPlay) - - useBlobURL() - } +function slowToString (encoding, start, end) { + var loweredCase = false - function prepareElem () { - if (!elem) { - elem = getElem(tagName) + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. - elem.addEventListener('progress', function () { - currentTime = elem.currentTime - }) - } - } + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 } - - function renderAudio () { - elem = getElem('audio') - getBlobURL(file, function (err, url) { - if (err) return fatalError(err) - elem.addEventListener('error', fatalError) - elem.addEventListener('loadstart', onLoadStart) - elem.addEventListener('canplay', onCanPlay) - elem.src = url - }) + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' } - function onLoadStart () { - elem.removeEventListener('loadstart', onLoadStart) - if (opts.autoplay) elem.play() + if (end === undefined || end > this.length) { + end = this.length } - function onCanPlay () { - elem.removeEventListener('canplay', onCanPlay) - cb(null, elem) + if (end <= 0) { + return '' } - function renderImage () { - elem = getElem('img') - getBlobURL(file, function (err, url) { - if (err) return fatalError(err) - elem.src = url - elem.alt = file.name - cb(null, elem) - }) + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' } - function renderIframe () { - elem = getElem('iframe') + if (!encoding) encoding = 'utf8' - getBlobURL(file, function (err, url) { - if (err) return fatalError(err) - elem.src = url - if (extname !== '.pdf') elem.sandbox = 'allow-forms allow-scripts' - cb(null, elem) - }) - } + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) - function tryRenderIframe () { - debug('Unknown file extension "%s" - will attempt to render into iframe', extname) + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) - var str = '' - file.createReadStream({ start: 0, end: 1000 }) - .setEncoding('utf8') - .on('data', function (chunk) { - str += chunk - }) - .on('end', done) - .on('error', cb) + case 'ascii': + return asciiSlice(this, start, end) - function done () { - if (isAscii(str)) { - debug('File extension "%s" appears ascii, so will render.', extname) - renderIframe() - } else { - debug('File extension "%s" appears non-ascii, will not render.', extname) - cb(new Error('Unsupported file type "' + extname + '": Cannot append to DOM')) - } - } - } + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) - function fatalError (err) { - err.message = 'Error rendering file "' + file.name + '": ' + err.message - debug(err.message) - cb(err) + 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 + } } } -function getBlobURL (file, cb) { - var extname = path.extname(file.name).toLowerCase() - streamToBlobURL(file.createReadStream(), exports.mime[extname], cb) -} +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true -function validateFile (file) { - if (file == null) { - throw new Error('file cannot be null or undefined') +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') } - if (typeof file.name !== 'string') { - throw new Error('missing or invalid file.name property') + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) } - if (typeof file.createReadStream !== 'function') { - throw new Error('missing or invalid file.createReadStream property') + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) } + return this } -function getCodec (name) { - var extname = path.extname(name).toLowerCase() - return { - '.m4a': 'audio/mp4; codecs="mp4a.40.5"', - '.m4v': 'video/mp4; codecs="avc1.640029, mp4a.40.5"', - '.mkv': 'video/webm; codecs="avc1.640029, mp4a.40.5"', - '.mp3': 'audio/mpeg', - '.mp4': 'video/mp4; codecs="avc1.640029, mp4a.40.5"', - '.webm': 'video/webm; codecs="vorbis, vp8"' - }[extname] +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this } -function parseOpts (opts) { - if (opts.autoplay == null) opts.autoplay = true - if (opts.controls == null) opts.controls = true - if (opts.maxBlobLength == null) opts.maxBlobLength = MAX_BLOB_LENGTH +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) } -},{"./lib/mime.json":94,"debug":27,"is-ascii":59,"mediasource":65,"path":168,"stream-to-blob-url":105,"videostream":121}],94:[function(require,module,exports){ -module.exports={ - ".3gp": "video/3gpp", - ".aac": "audio/aac", - ".aif": "audio/x-aiff", - ".aiff": "audio/x-aiff", - ".atom": "application/atom+xml", - ".avi": "video/x-msvideo", - ".bmp": "image/bmp", - ".bz2": "application/x-bzip2", - ".conf": "text/plain", - ".css": "text/css", - ".csv": "text/plain", - ".diff": "text/x-diff", - ".doc": "application/msword", - ".flv": "video/x-flv", - ".gif": "image/gif", - ".gz": "application/x-gzip", - ".htm": "text/html", - ".html": "text/html", - ".ico": "image/vnd.microsoft.icon", - ".ics": "text/calendar", - ".iso": "application/octet-stream", - ".jar": "application/java-archive", - ".jpeg": "image/jpeg", - ".jpg": "image/jpeg", - ".js": "application/javascript", - ".json": "application/json", - ".less": "text/css", - ".log": "text/plain", - ".m3u": "audio/x-mpegurl", - ".m4a": "audio/mp4", - ".m4v": "video/mp4", - ".manifest": "text/cache-manifest", - ".markdown": "text/x-markdown", - ".mathml": "application/mathml+xml", - ".md": "text/x-markdown", - ".mid": "audio/midi", - ".midi": "audio/midi", - ".mov": "video/quicktime", - ".mp3": "audio/mpeg", - ".mp4": "video/mp4", - ".mp4v": "video/mp4", - ".mpeg": "video/mpeg", - ".mpg": "video/mpeg", - ".odp": "application/vnd.oasis.opendocument.presentation", - ".ods": "application/vnd.oasis.opendocument.spreadsheet", - ".odt": "application/vnd.oasis.opendocument.text", - ".oga": "audio/ogg", - ".ogg": "application/ogg", - ".pdf": "application/pdf", - ".png": "image/png", - ".pps": "application/vnd.ms-powerpoint", - ".ppt": "application/vnd.ms-powerpoint", - ".ps": "application/postscript", - ".psd": "image/vnd.adobe.photoshop", - ".qt": "video/quicktime", - ".rar": "application/x-rar-compressed", - ".rdf": "application/rdf+xml", - ".rss": "application/rss+xml", - ".rtf": "application/rtf", - ".svg": "image/svg+xml", - ".svgz": "image/svg+xml", - ".swf": "application/x-shockwave-flash", - ".tar": "application/x-tar", - ".tbz": "application/x-bzip-compressed-tar", - ".text": "text/plain", - ".tif": "image/tiff", - ".tiff": "image/tiff", - ".torrent": "application/x-bittorrent", - ".ttf": "application/x-font-ttf", - ".txt": "text/plain", - ".wav": "audio/wav", - ".webm": "video/webm", - ".wma": "audio/x-ms-wma", - ".wmv": "video/x-ms-wmv", - ".xls": "application/vnd.ms-excel", - ".xml": "application/xml", - ".yaml": "text/yaml", - ".yml": "text/yaml", - ".zip": "application/zip" +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 } -},{}],95:[function(require,module,exports){ -(function (process){ -module.exports = function (tasks, limit, cb) { - if (typeof limit !== 'number') throw new Error('second argument must be a Number') - var results, len, pending, keys, isErrored - var isSync = true +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 '' +} - if (Array.isArray(tasks)) { - results = [] - pending = len = tasks.length - } else { - keys = Object.keys(tasks) - results = {} - pending = len = keys.length +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (!Buffer.isBuffer(target)) { + throw new TypeError('Argument must be a Buffer') } - function done (err) { - function end () { - if (cb) cb(err, results) - cb = null - } - if (isSync) process.nextTick(end) - else end() + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length } - function each (i, err, result) { - results[i] = result - if (err) isErrored = true - if (--pending === 0 || err) { - done(err) - } else if (!isErrored && next < len) { - var key - if (keys) { - key = keys[next] - next += 1 - tasks[key](function (err, result) { each(key, err, result) }) - } else { - key = next - next += 1 - tasks[key](function (err, result) { each(key, err, result) }) - } - } + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') } - var next = limit - if (!pending) { - // empty - done(null) - } else if (keys) { - // object - keys.some(function (key, i) { - tasks[key](function (err, result) { each(key, err, result) }) - if (i === limit - 1) return true // early return - }) - } else { - // array - tasks.some(function (task, i) { - task(function (err, result) { each(i, err, result) }) - if (i === limit - 1) return true // early return - }) + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 } - isSync = false -} + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 -}).call(this,require('_process')) -},{"_process":170}],96:[function(require,module,exports){ -(function (process){ -module.exports = function (tasks, cb) { - var results, pending, keys - var isSync = true + if (this === target) return 0 - if (Array.isArray(tasks)) { - results = [] - pending = tasks.length - } else { - keys = Object.keys(tasks) - results = {} - pending = keys.length + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } } - function done (err) { - function end () { - if (cb) cb(err, results) - cb = null + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 } - if (isSync) process.nextTick(end) - else end() + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) } - function each (i, err, result) { - results[i] = result - if (--pending === 0 || err) { - done(err) + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 } } - if (!pending) { - // empty - done(null) - } else if (keys) { - // object - keys.forEach(function (key) { - tasks[key](function (err, result) { each(key, err, result) }) - }) + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } } else { - // array - tasks.forEach(function (task, i) { - task(function (err, result) { each(i, err, result) }) - }) + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } } - isSync = false + return -1 } -}).call(this,require('_process')) -},{"_process":170}],97:[function(require,module,exports){ -(function (global){ -(function () { - var /* - * Rusha, a JavaScript implementation of the Secure Hash Algorithm, SHA-1, - * as defined in FIPS PUB 180-1, tuned for high performance with large inputs. - * (http://github.com/srijs/rusha) - * - * Inspired by Paul Johnstons implementation (http://pajhome.org.uk/crypt/md5). - * - * Copyright (c) 2013 Sam Rijs (http://awesam.de). - * Released under the terms of the MIT license as follows: - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - util = { - getDataType: function (data) { - if (typeof data === 'string') { - return 'string'; - } - if (data instanceof Array) { - return 'array'; - } - if (typeof global !== 'undefined' && global.Buffer && global.Buffer.isBuffer(data)) { - return 'buffer'; - } - if (data instanceof ArrayBuffer) { - return 'arraybuffer'; - } - if (data.buffer instanceof ArrayBuffer) { - return 'view'; - } - if (data instanceof Blob) { - return 'blob'; - } - throw new Error('Unsupported data type.'); - } - }; - function Rusha(chunkSize) { - 'use strict'; - var // Private object structure. - self$2 = { fill: 0 }; - var // Calculate the length of buffer that the sha1 routine uses - // including the padding. - padlen = function (len) { - for (len += 9; len % 64 > 0; len += 1); - return len; - }; - var padZeroes = function (bin, len) { - var h8 = new Uint8Array(bin.buffer); - var om = len % 4, align = len - om; - switch (om) { - case 0: - h8[align + 3] = 0; - case 1: - h8[align + 2] = 0; - case 2: - h8[align + 1] = 0; - case 3: - h8[align + 0] = 0; - } - for (var i$2 = (len >> 2) + 1; i$2 < bin.length; i$2++) - bin[i$2] = 0; - }; - var padData = function (bin, chunkLen, msgLen) { - bin[chunkLen >> 2] |= 128 << 24 - (chunkLen % 4 << 3); - // To support msgLen >= 2 GiB, use a float division when computing the - // high 32-bits of the big-endian message length in bits. - bin[((chunkLen >> 2) + 2 & ~15) + 14] = msgLen / (1 << 29) | 0; - bin[((chunkLen >> 2) + 2 & ~15) + 15] = msgLen << 3; - }; - var // Convert a binary string and write it to the heap. - // A binary string is expected to only contain char codes < 256. - convStr = function (H8, H32, start, len, off) { - var str = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm; - switch (om) { - case 0: - H8[off] = str.charCodeAt(start + 3); - case 1: - H8[off + 1 - (om << 1) | 0] = str.charCodeAt(start + 2); - case 2: - H8[off + 2 - (om << 1) | 0] = str.charCodeAt(start + 1); - case 3: - H8[off + 3 - (om << 1) | 0] = str.charCodeAt(start); - } - if (len < lm + om) { - return; - } - for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) { - H32[off + i$2 >> 2] = str.charCodeAt(start + i$2) << 24 | str.charCodeAt(start + i$2 + 1) << 16 | str.charCodeAt(start + i$2 + 2) << 8 | str.charCodeAt(start + i$2 + 3); - } - switch (lm) { - case 3: - H8[off + j + 1 | 0] = str.charCodeAt(start + j + 2); - case 2: - H8[off + j + 2 | 0] = str.charCodeAt(start + j + 1); - case 1: - H8[off + j + 3 | 0] = str.charCodeAt(start + j); - } - }; - var // Convert a buffer or array and write it to the heap. - // The buffer or array is expected to only contain elements < 256. - convBuf = function (H8, H32, start, len, off) { - var buf = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm; - switch (om) { - case 0: - H8[off] = buf[start + 3]; - case 1: - H8[off + 1 - (om << 1) | 0] = buf[start + 2]; - case 2: - H8[off + 2 - (om << 1) | 0] = buf[start + 1]; - case 3: - H8[off + 3 - (om << 1) | 0] = buf[start]; - } - if (len < lm + om) { - return; - } - for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) { - H32[off + i$2 >> 2 | 0] = buf[start + i$2] << 24 | buf[start + i$2 + 1] << 16 | buf[start + i$2 + 2] << 8 | buf[start + i$2 + 3]; - } - switch (lm) { - case 3: - H8[off + j + 1 | 0] = buf[start + j + 2]; - case 2: - H8[off + j + 2 | 0] = buf[start + j + 1]; - case 1: - H8[off + j + 3 | 0] = buf[start + j]; - } - }; - var convBlob = function (H8, H32, start, len, off) { - var blob = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm; - var buf = new Uint8Array(reader.readAsArrayBuffer(blob.slice(start, start + len))); - switch (om) { - case 0: - H8[off] = buf[3]; - case 1: - H8[off + 1 - (om << 1) | 0] = buf[2]; - case 2: - H8[off + 2 - (om << 1) | 0] = buf[1]; - case 3: - H8[off + 3 - (om << 1) | 0] = buf[0]; +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +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 TypeError('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 (numberIsNaN(parsed)) return i + 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 latin1Write (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 + } + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + 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 'latin1': + case 'binary': + return latin1Write(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 } - if (len < lm + om) { - return; + } + 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 } - for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) { - H32[off + i$2 >> 2 | 0] = buf[i$2] << 24 | buf[i$2 + 1] << 16 | buf[i$2 + 2] << 8 | buf[i$2 + 3]; + } + 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 } - switch (lm) { - case 3: - H8[off + j + 1 | 0] = buf[j + 2]; - case 2: - H8[off + j + 2 | 0] = buf[j + 1]; - case 1: - H8[off + j + 3 | 0] = buf[j]; - } - }; - var convFn = function (data) { - switch (util.getDataType(data)) { - case 'string': - return convStr.bind(data); - case 'array': - return convBuf.bind(data); - case 'buffer': - return convBuf.bind(data); - case 'arraybuffer': - return convBuf.bind(new Uint8Array(data)); - case 'view': - return convBuf.bind(new Uint8Array(data.buffer, data.byteOffset, data.byteLength)); - case 'blob': - return convBlob.bind(data); - } - }; - var slice = function (data, offset) { - switch (util.getDataType(data)) { - case 'string': - return data.slice(offset); - case 'array': - return data.slice(offset); - case 'buffer': - return data.slice(offset); - case 'arraybuffer': - return data.slice(offset); - case 'view': - return data.buffer.slice(offset); - } - }; - var // Precompute 00 - ff strings - precomputedHex = new Array(256); - for (var i = 0; i < 256; i++) { - precomputedHex[i] = (i < 16 ? '0' : '') + i.toString(16); - } - var // Convert an ArrayBuffer into its hexadecimal string representation. - hex = function (arrayBuffer) { - var binarray = new Uint8Array(arrayBuffer); - var res = new Array(arrayBuffer.byteLength); - for (var i$2 = 0; i$2 < res.length; i$2++) { - res[i$2] = precomputedHex[binarray[i$2]]; - } - return res.join(''); - }; - var ceilHeapSize = function (v) { - // The asm.js spec says: - // The heap object's byteLength must be either - // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1. - // Also, byteLengths smaller than 2^16 are deprecated. - var p; - if (// If v is smaller than 2^16, the smallest possible solution - // is 2^16. - v <= 65536) - return 65536; - if (// If v < 2^24, we round up to 2^n, - // otherwise we round up to 2^24 * n. - v < 16777216) { - for (p = 1; p < v; p = p << 1); - } else { - for (p = 16777216; p < v; p += 16777216); - } - return p; - }; - var // Initialize the internal data structures to a new capacity. - init = function (size) { - if (size % 64 > 0) { - throw new Error('Chunk size must be a multiple of 128 bit'); - } - self$2.offset = 0; - self$2.maxChunkLen = size; - self$2.padMaxChunkLen = padlen(size); - // The size of the heap is the sum of: - // 1. The padded input message size - // 2. The extended space the algorithm needs (320 byte) - // 3. The 160 bit state the algoritm uses - self$2.heap = new ArrayBuffer(ceilHeapSize(self$2.padMaxChunkLen + 320 + 20)); - self$2.h32 = new Int32Array(self$2.heap); - self$2.h8 = new Int8Array(self$2.heap); - self$2.core = new Rusha._core({ - Int32Array: Int32Array, - DataView: DataView - }, {}, self$2.heap); - self$2.buffer = null; - }; - // Iinitializethe datastructures according - // to a chunk siyze. - init(chunkSize || 64 * 1024); - var initState = function (heap, padMsgLen) { - self$2.offset = 0; - var io = new Int32Array(heap, padMsgLen + 320, 5); - io[0] = 1732584193; - io[1] = -271733879; - io[2] = -1732584194; - io[3] = 271733878; - io[4] = -1009589776; - }; - var padChunk = function (chunkLen, msgLen) { - var padChunkLen = padlen(chunkLen); - var view = new Int32Array(self$2.heap, 0, padChunkLen >> 2); - padZeroes(view, chunkLen); - padData(view, chunkLen, msgLen); - return padChunkLen; - }; - var // Write data to the heap. - write = function (data, chunkOffset, chunkLen, off) { - convFn(data)(self$2.h8, self$2.h32, chunkOffset, chunkLen, off || 0); - }; - var // Initialize and call the RushaCore, - // assuming an input buffer of length len * 4. - coreCall = function (data, chunkOffset, chunkLen, msgLen, finalize) { - var padChunkLen = chunkLen; - write(data, chunkOffset, chunkLen); - if (finalize) { - padChunkLen = padChunk(chunkLen, msgLen); - } - self$2.core.hash(padChunkLen, self$2.padMaxChunkLen); - }; - var getRawDigest = function (heap, padMaxChunkLen) { - var io = new Int32Array(heap, padMaxChunkLen + 320, 5); - var out = new Int32Array(5); - var arr = new DataView(out.buffer); - arr.setInt32(0, io[0], false); - arr.setInt32(4, io[1], false); - arr.setInt32(8, io[2], false); - arr.setInt32(12, io[3], false); - arr.setInt32(16, io[4], false); - return out; - }; - var // Calculate the hash digest as an array of 5 32bit integers. - rawDigest = this.rawDigest = function (str) { - var msgLen = str.byteLength || str.length || str.size || 0; - initState(self$2.heap, self$2.padMaxChunkLen); - var chunkOffset = 0, chunkLen = self$2.maxChunkLen; - for (chunkOffset = 0; msgLen > chunkOffset + chunkLen; chunkOffset += chunkLen) { - coreCall(str, chunkOffset, chunkLen, msgLen, false); - } - coreCall(str, chunkOffset, msgLen - chunkOffset, msgLen, true); - return getRawDigest(self$2.heap, self$2.padMaxChunkLen); - }; - // The digest and digestFrom* interface returns the hash digest - // as a hex string. - this.digest = this.digestFromString = this.digestFromBuffer = this.digestFromArrayBuffer = function (str) { - return hex(rawDigest(str).buffer); - }; - this.resetState = function () { - initState(self$2.heap, self$2.padMaxChunkLen); - return this; - }; - this.append = function (chunk) { - var chunkOffset = 0; - var chunkLen = chunk.byteLength || chunk.length || chunk.size || 0; - var turnOffset = self$2.offset % self$2.maxChunkLen; - var inputLen; - self$2.offset += chunkLen; - while (chunkOffset < chunkLen) { - inputLen = Math.min(chunkLen - chunkOffset, self$2.maxChunkLen - turnOffset); - write(chunk, chunkOffset, inputLen, turnOffset); - turnOffset += inputLen; - chunkOffset += inputLen; - if (turnOffset === self$2.maxChunkLen) { - self$2.core.hash(self$2.maxChunkLen, self$2.padMaxChunkLen); - turnOffset = 0; - } - } - return this; - }; - this.getState = function () { - var turnOffset = self$2.offset % self$2.maxChunkLen; - var heap; - if (!turnOffset) { - var io = new Int32Array(self$2.heap, self$2.padMaxChunkLen + 320, 5); - heap = io.buffer.slice(io.byteOffset, io.byteOffset + io.byteLength); - } else { - heap = self$2.heap.slice(0); - } - return { - offset: self$2.offset, - heap: heap - }; - }; - this.setState = function (state) { - self$2.offset = state.offset; - if (state.heap.byteLength === 20) { - var io = new Int32Array(self$2.heap, self$2.padMaxChunkLen + 320, 5); - io.set(new Int32Array(state.heap)); - } else { - self$2.h32.set(new Int32Array(state.heap)); - } - return this; - }; - var rawEnd = this.rawEnd = function () { - var msgLen = self$2.offset; - var chunkLen = msgLen % self$2.maxChunkLen; - var padChunkLen = padChunk(chunkLen, msgLen); - self$2.core.hash(padChunkLen, self$2.padMaxChunkLen); - var result = getRawDigest(self$2.heap, self$2.padMaxChunkLen); - initState(self$2.heap, self$2.padMaxChunkLen); - return result; - }; - this.end = function () { - return hex(rawEnd().buffer); - }; + } + } } - ; - // The low-level RushCore module provides the heart of Rusha, - // a high-speed sha1 implementation working on an Int32Array heap. - // At first glance, the implementation seems complicated, however - // with the SHA1 spec at hand, it is obvious this almost a textbook - // implementation that has a few functions hand-inlined and a few loops - // hand-unrolled. - Rusha._core = function RushaCore(stdlib, foreign, heap) { - 'use asm'; - var H = new stdlib.Int32Array(heap); - function hash(k, x) { - // k in bytes - k = k | 0; - x = x | 0; - var i = 0, j = 0, y0 = 0, z0 = 0, y1 = 0, z1 = 0, y2 = 0, z2 = 0, y3 = 0, z3 = 0, y4 = 0, z4 = 0, t0 = 0, t1 = 0; - y0 = H[x + 320 >> 2] | 0; - y1 = H[x + 324 >> 2] | 0; - y2 = H[x + 328 >> 2] | 0; - y3 = H[x + 332 >> 2] | 0; - y4 = H[x + 336 >> 2] | 0; - for (i = 0; (i | 0) < (k | 0); i = i + 64 | 0) { - z0 = y0; - z1 = y1; - z2 = y2; - z3 = y3; - z4 = y4; - for (j = 0; (j | 0) < 64; j = j + 4 | 0) { - t1 = H[i + j >> 2] | 0; - t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0; - y4 = y3; - y3 = y2; - y2 = y1 << 30 | y1 >>> 2; - y1 = y0; - y0 = t0; - H[k + j >> 2] = t1; - } - for (j = k + 64 | 0; (j | 0) < (k + 80 | 0); j = j + 4 | 0) { - t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; - t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0; - y4 = y3; - y3 = y2; - y2 = y1 << 30 | y1 >>> 2; - y1 = y0; - y0 = t0; - H[j >> 2] = t1; - } - for (j = k + 80 | 0; (j | 0) < (k + 160 | 0); j = j + 4 | 0) { - t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; - t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) + 1859775393 | 0) | 0; - y4 = y3; - y3 = y2; - y2 = y1 << 30 | y1 >>> 2; - y1 = y0; - y0 = t0; - H[j >> 2] = t1; - } - for (j = k + 160 | 0; (j | 0) < (k + 240 | 0); j = j + 4 | 0) { - t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; - t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | y1 & y3 | y2 & y3) | 0) + ((t1 + y4 | 0) - 1894007588 | 0) | 0; - y4 = y3; - y3 = y2; - y2 = y1 << 30 | y1 >>> 2; - y1 = y0; - y0 = t0; - H[j >> 2] = t1; - } - for (j = k + 240 | 0; (j | 0) < (k + 320 | 0); j = j + 4 | 0) { - t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; - t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) - 899497514 | 0) | 0; - y4 = y3; - y3 = y2; - y2 = y1 << 30 | y1 >>> 2; - y1 = y0; - y0 = t0; - H[j >> 2] = t1; - } - y0 = y0 + z0 | 0; - y1 = y1 + z1 | 0; - y2 = y2 + z2 | 0; - y3 = y3 + z3 | 0; - y4 = y4 + z4 | 0; - } - H[x + 320 >> 2] = y0; - H[x + 324 >> 2] = y1; - H[x + 328 >> 2] = y2; - H[x + 332 >> 2] = y3; - H[x + 336 >> 2] = y4; - } - return { hash: hash }; - }; - if (// If we'e running in Node.JS, export a module. - typeof module !== 'undefined') { - module.exports = Rusha; - } else if (// If we're running in a DOM context, export - // the Rusha object to toplevel. - typeof window !== 'undefined') { - window.Rusha = Rusha; - } - if (// If we're running in a webworker, accept - // messages containing a jobid and a buffer - // or blob object, and return the hash result. - typeof FileReaderSync !== 'undefined') { - var reader = new FileReaderSync(); - var hashData = function hash(hasher, data, cb) { - try { - return cb(null, hasher.digest(data)); - } catch (e) { - return cb(e); - } - }; - var hashFile = function hashArrayBuffer(hasher, readTotal, blockSize, file, cb) { - var reader$2 = new self.FileReader(); - reader$2.onloadend = function onloadend() { - var buffer = reader$2.result; - readTotal += reader$2.result.byteLength; - try { - hasher.append(buffer); - } catch (e) { - cb(e); - return; - } - if (readTotal < file.size) { - hashFile(hasher, readTotal, blockSize, file, cb); - } else { - cb(null, hasher.end()); - } - }; - reader$2.readAsArrayBuffer(file.slice(readTotal, readTotal + blockSize)); - }; - self.onmessage = function onMessage(event) { - var data = event.data.data, file = event.data.file, id = event.data.id; - if (typeof id === 'undefined') - return; - if (!file && !data) - return; - var blockSize = event.data.blockSize || 4 * 1024 * 1024; - var hasher = new Rusha(blockSize); - hasher.resetState(); - var done = function done$2(err, hash) { - if (!err) { - self.postMessage({ - id: id, - hash: hash - }); - } else { - self.postMessage({ - id: id, - error: err.name - }); - } - }; - if (data) - hashData(hasher, data, done); - if (file) - hashFile(hasher, 0, blockSize, file, done); - }; + + 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 } -}()); -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],98:[function(require,module,exports){ -/* eslint-disable node/no-deprecated-api */ -var buffer = require('buffer') -var Buffer = buffer.Buffer -// alternative to using Object.keys for old browsers -function copyProps (src, dst) { - for (var key in src) { - dst[key] = src[key] + res.push(codePoint) + i += bytesPerSequence } -} -if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { - module.exports = buffer -} else { - // Copy properties from require('buffer') - copyProps(buffer, exports) - exports.Buffer = SafeBuffer -} -function SafeBuffer (arg, encodingOrOffset, length) { - return Buffer(arg, encodingOrOffset, length) + return decodeCodePointsArray(res) } -// Copy static methods from Buffer -copyProps(Buffer, SafeBuffer) +// 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 -SafeBuffer.from = function (arg, encodingOrOffset, length) { - if (typeof arg === 'number') { - throw new TypeError('Argument must not be a number') +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() } - return Buffer(arg, encodingOrOffset, length) -} -SafeBuffer.alloc = function (size, fill, encoding) { - if (typeof size !== 'number') { - throw new TypeError('Argument must be a number') - } - var buf = Buffer(size) - if (fill !== undefined) { - if (typeof encoding === 'string') { - buf.fill(fill, encoding) - } else { - buf.fill(fill) - } - } else { - buf.fill(0) + // 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 buf + return res } -SafeBuffer.allocUnsafe = function (size) { - if (typeof size !== 'number') { - throw new TypeError('Argument must be a number') +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 Buffer(size) + return ret } -SafeBuffer.allocUnsafeSlow = function (size) { - if (typeof size !== 'number') { - throw new TypeError('Argument must be a number') +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) } - return buffer.SlowBuffer(size) + return ret } -},{"buffer":159}],99:[function(require,module,exports){ -(function (Buffer){ -module.exports = function (stream, cb) { - var chunks = [] - stream.on('data', function (chunk) { - chunks.push(chunk) - }) - stream.once('end', function () { - if (cb) cb(null, Buffer.concat(chunks)) - cb = null - }) - stream.once('error', function (err) { - if (cb) cb(err) - cb = null - }) -} +function hexSlice (buf, start, end) { + var len = buf.length -}).call(this,require("buffer").Buffer) -},{"buffer":159}],100:[function(require,module,exports){ -(function (Buffer){ -module.exports = simpleGet + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len -var concat = require('simple-concat') -var http = require('http') -var https = require('https') -var once = require('once') -var querystring = require('querystring') -var unzipResponse = require('unzip-response') // excluded from browser build -var url = require('url') + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} -function simpleGet (opts, cb) { - opts = typeof opts === 'string' ? {url: opts} : Object.assign({}, opts) - cb = once(cb) +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 +} - if (opts.url) parseOptsUrl(opts) - if (opts.headers == null) opts.headers = {} - if (opts.maxRedirects == null) opts.maxRedirects = 10 +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end - var body - if (opts.form) body = typeof opts.form === 'string' ? opts.form : querystring.stringify(opts.form) - if (opts.body) body = opts.json ? JSON.stringify(opts.body) : opts.body - - if (opts.json) opts.headers.accept = 'application/json' - if (opts.json && body) opts.headers['content-type'] = 'application/json' - if (opts.form) opts.headers['content-type'] = 'application/x-www-form-urlencoded' - if (body && !isStream(body)) opts.headers['content-length'] = Buffer.byteLength(body) - delete opts.body - delete opts.form - - if (body && !opts.method) opts.method = 'POST' - if (opts.method) opts.method = opts.method.toUpperCase() - - // Request gzip/deflate - var customAcceptEncoding = Object.keys(opts.headers).some(function (h) { - return h.toLowerCase() === 'accept-encoding' - }) - if (!customAcceptEncoding) opts.headers['accept-encoding'] = 'gzip, deflate' - - // Support http/https urls - var protocol = opts.protocol === 'https:' ? https : http - var req = protocol.request(opts, function (res) { - // Follow 3xx redirects - if (res.statusCode >= 300 && res.statusCode < 400 && 'location' in res.headers) { - opts.url = res.headers.location - res.resume() // Discard response - - if (opts.maxRedirects > 0) { - opts.maxRedirects -= 1 - simpleGet(opts, cb) - } else { - cb(new Error('too many redirects')) - } - return - } + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } - var tryUnzip = typeof unzipResponse === 'function' && opts.method !== 'HEAD' - cb(null, tryUnzip ? unzipResponse(res) : res) - }) - req.on('timeout', function () { - req.abort() - cb(new Error('Request timed out')) - }) - req.on('error', cb) + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } - if (body && isStream(body)) body.on('error', cb).pipe(req) - else req.end(body) + if (end < start) end = start - return req + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf } -simpleGet.concat = function (opts, cb) { - return simpleGet(opts, function (err, res) { - if (err) return cb(err) - concat(res, function (err, data) { - if (err) return cb(err) - if (opts.json) { - try { - data = JSON.parse(data.toString()) - } catch (err) { - return cb(err, res, data) - } - } - cb(null, res, data) - }) - }) +/* + * 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') } -;['get', 'post', 'put', 'patch', 'head', 'delete'].forEach(function (method) { - simpleGet[method] = function (opts, cb) { - if (typeof opts === 'string') opts = {url: opts} - opts.method = method.toUpperCase() - return simpleGet(opts, cb) +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 } -}) -function parseOptsUrl (opts) { - var loc = url.parse(opts.url) - if (loc.hostname) opts.hostname = loc.hostname - if (loc.port) opts.port = loc.port - if (loc.protocol) opts.protocol = loc.protocol - if (loc.auth) opts.auth = loc.auth - opts.path = loc.path - delete opts.url + return val } -function isStream (obj) { return typeof obj.pipe === 'function' } +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } -}).call(this,require("buffer").Buffer) -},{"buffer":159,"http":185,"https":163,"once":75,"querystring":174,"simple-concat":99,"unzip-response":158,"url":191}],101:[function(require,module,exports){ -(function (Buffer){ -module.exports = Peer + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } -var debug = require('debug')('simple-peer') -var getBrowserRTC = require('get-browser-rtc') -var inherits = require('inherits') -var randombytes = require('randombytes') -var stream = require('readable-stream') + return val +} -var MAX_BUFFERED_AMOUNT = 64 * 1024 +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} -inherits(Peer, stream.Duplex) +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} -/** - * WebRTC peer connection. Same API as node core `net.Socket`, plus a few extra methods. - * Duplex stream. - * @param {Object} opts - */ -function Peer (opts) { - var self = this - if (!(self instanceof Peer)) return new Peer(opts) +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} - self._id = randombytes(4).toString('hex').slice(0, 7) - self._debug('new peer %o', opts) +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) - opts = Object.assign({ - allowHalfOpen: false - }, opts) + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} - stream.Duplex.call(self, opts) +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) - self.channelName = opts.initiator - ? opts.channelName || randombytes(20).toString('hex') - : null + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} - // Needed by _transformConstraints, so set this early - self._isChromium = typeof window !== 'undefined' && !!window.webkitRTCPeerConnection +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) - self.initiator = opts.initiator || false - self.channelConfig = opts.channelConfig || Peer.channelConfig - self.config = opts.config || Peer.config - self.constraints = self._transformConstraints(opts.constraints || Peer.constraints) - self.offerConstraints = self._transformConstraints(opts.offerConstraints || {}) - self.answerConstraints = self._transformConstraints(opts.answerConstraints || {}) - self.reconnectTimer = opts.reconnectTimer || false - self.sdpTransform = opts.sdpTransform || function (sdp) { return sdp } - self.stream = opts.stream || false - self.trickle = opts.trickle !== undefined ? opts.trickle : true - self._earlyMessage = null + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 - self.destroyed = false - self.connected = false + if (val >= mul) val -= Math.pow(2, 8 * byteLength) - self.remoteAddress = undefined - self.remoteFamily = undefined - self.remotePort = undefined - self.localAddress = undefined - self.localPort = undefined + return val +} - self._wrtc = (opts.wrtc && typeof opts.wrtc === 'object') - ? opts.wrtc - : getBrowserRTC() +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) - if (!self._wrtc) { - if (typeof window === 'undefined') { - throw new Error('No WebRTC support: Specify `opts.wrtc` option in this environment') - } else { - throw new Error('No WebRTC support: Not a supported browser') - } + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul } + mul *= 0x80 - self._pcReady = false - self._channelReady = false - self._iceComplete = false // ice candidate trickle done (got null candidate) - self._channel = null - self._pendingCandidates = [] - self._previousStreams = [] + if (val >= mul) val -= Math.pow(2, 8 * byteLength) - self._chunk = null - self._cb = null - self._interval = null - self._reconnectTimeout = null + return val +} - self._pc = new (self._wrtc.RTCPeerConnection)(self.config, self.constraints) +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} - // We prefer feature detection whenever possible, but sometimes that's not - // possible for certain implementations. - self._isWrtc = Array.isArray(self._pc.RTCIceConnectionStates) - self._isReactNativeWebrtc = typeof self._pc._peerConnectionId === 'number' - - self._pc.oniceconnectionstatechange = function () { - self._onIceStateChange() - } - self._pc.onicegatheringstatechange = function () { - self._onIceStateChange() - } - self._pc.onsignalingstatechange = function () { - self._onSignalingStateChange() - } - self._pc.onicecandidate = function (event) { - self._onIceCandidate(event) - } +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} - // Other spec events, unused by this implementation: - // - onconnectionstatechange - // - onicecandidateerror - // - onfingerprintfailure +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} - if (self.initiator) { - var createdOffer = false - self._pc.onnegotiationneeded = function () { - if (!createdOffer) self._createOffer() - createdOffer = true - } +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) - self._setupData({ - channel: self._pc.createDataChannel(self.channelName, self.channelConfig) - }) - } else { - self._pc.ondatachannel = function (event) { - self._setupData(event) - } - } + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} - if ('addTrack' in self._pc) { - // WebRTC Spec, Firefox - if (self.stream) { - self.stream.getTracks().forEach(function (track) { - self._pc.addTrack(track, self.stream) - }) - } - self._pc.ontrack = function (event) { - self._onTrack(event) - } - } else { - // Chrome, etc. This can be removed once all browsers support `ontrack` - if (self.stream) self._pc.addStream(self.stream) - self._pc.onaddstream = function (event) { - self._onAddStream(event) - } - } +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) - // HACK: wrtc doesn't fire the 'negotionneeded' event - if (self.initiator && self._isWrtc) { - self._pc.onnegotiationneeded() - } + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} - self._onFinishBound = function () { - self._onFinish() - } - self.once('finish', self._onFinishBound) +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) } -Peer.WEBRTC_SUPPORT = !!getBrowserRTC() +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} -/** - * Expose config, constraints, and data channel config for overriding all Peer - * instances. Otherwise, just set opts.config, opts.constraints, or opts.channelConfig - * when constructing a Peer. - */ -Peer.config = { - iceServers: [ - { - urls: 'stun:stun.l.google.com:19302' - }, - { - urls: 'stun:global.stun.twilio.com:3478?transport=udp' - } - ] +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) } -Peer.constraints = {} -Peer.channelConfig = {} -Object.defineProperty(Peer.prototype, 'bufferSize', { - get: function () { - var self = this - return (self._channel && self._channel.bufferedAmount) || 0 - } -}) +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} -Peer.prototype.address = function () { - var self = this - return { port: self.localPort, family: 'IPv4', address: self.localAddress } +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') } -Peer.prototype.signal = function (data) { - var self = this - if (self.destroyed) throw new Error('cannot signal after peer is destroyed') - if (typeof data === 'string') { - try { - data = JSON.parse(data) - } catch (err) { - data = {} - } +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) } - self._debug('signal()') - if (data.candidate) { - if (self._pc.remoteDescription) self._addIceCandidate(data.candidate) - else self._pendingCandidates.push(data.candidate) + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF } - if (data.sdp) { - self._pc.setRemoteDescription(new (self._wrtc.RTCSessionDescription)(data), function () { - if (self.destroyed) return - - self._pendingCandidates.forEach(function (candidate) { - self._addIceCandidate(candidate) - }) - self._pendingCandidates = [] - if (self._pc.remoteDescription.type === 'offer') self._createAnswer() - }, function (err) { self._destroy(err) }) - } - if (!data.sdp && !data.candidate) { - self._destroy(new Error('signal() called with invalid signal data')) - } + return offset + byteLength } -Peer.prototype._addIceCandidate = function (candidate) { - var self = this - try { - self._pc.addIceCandidate( - new self._wrtc.RTCIceCandidate(candidate), - noop, - function (err) { self._destroy(err) } - ) - } catch (err) { - self._destroy(new Error('error adding candidate: ' + err.message)) +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) } -} - -/** - * Send text/binary data to the remote peer. - * @param {TypedArrayView|ArrayBuffer|Buffer|string|Blob|Object} chunk - */ -Peer.prototype.send = function (chunk) { - var self = this - // HACK: `wrtc` module crashes on Node.js Buffer, so convert to Uint8Array - // See: https://github.com/feross/simple-peer/issues/60 - if (self._isWrtc && Buffer.isBuffer(chunk)) { - chunk = new Uint8Array(chunk) + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF } - self._channel.send(chunk) + return offset + byteLength } -Peer.prototype.destroy = function (onclose) { - var self = this - self._destroy(null, onclose) +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 } -Peer.prototype._destroy = function (err, onclose) { - var self = this - if (self.destroyed) return - if (onclose) self.once('close', onclose) - - self._debug('destroy (error: %s)', err && (err.message || err)) +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} - self.readable = self.writable = false +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} - if (!self._readableState.ended) self.push(null) - if (!self._writableState.finished) self.end() +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 +} - self.destroyed = true - self.connected = false - self._pcReady = false - self._channelReady = false - self._previousStreams = null - self._earlyMessage = null - - clearInterval(self._interval) - clearTimeout(self._reconnectTimeout) - self._interval = null - self._reconnectTimeout = null - self._chunk = null - self._cb = null - - if (self._onFinishBound) self.removeListener('finish', self._onFinishBound) - self._onFinishBound = null +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} - if (self._pc) { - try { - self._pc.close() - } catch (err) {} +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) - self._pc.oniceconnectionstatechange = null - self._pc.onicegatheringstatechange = null - self._pc.onsignalingstatechange = null - self._pc.onicecandidate = null - if ('addTrack' in self._pc) { - self._pc.ontrack = null - } else { - self._pc.onaddstream = null - } - self._pc.onnegotiationneeded = null - self._pc.ondatachannel = null + checkInt(this, value, offset, byteLength, limit - 1, -limit) } - if (self._channel) { - try { - self._channel.close() - } catch (err) {} - - self._channel.onmessage = null - self._channel.onopen = null - self._channel.onclose = null - self._channel.onerror = null + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF } - self._pc = null - self._channel = null - if (err) self.emit('error', err) - self.emit('close') + return offset + byteLength } -Peer.prototype._setupData = function (event) { - var self = this - if (!event.channel) { - // In some situations `pc.createDataChannel()` returns `undefined` (in wrtc), - // which is invalid behavior. Handle it gracefully. - // See: https://github.com/feross/simple-peer/issues/163 - return self._destroy(new Error('Data channel event is missing `channel` property')) - } - - self._channel = event.channel - self._channel.binaryType = 'arraybuffer' +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) - if (typeof self._channel.bufferedAmountLowThreshold === 'number') { - self._channel.bufferedAmountLowThreshold = MAX_BUFFERED_AMOUNT + checkInt(this, value, offset, byteLength, limit - 1, -limit) } - self.channelName = self._channel.label - - self._channel.onmessage = function (event) { - if (!self._channelReady) { // HACK: Workaround for Chrome not firing "open" between tabs - self._earlyMessage = event - self._onChannelOpen() - } else { - self._onChannelMessage(event) + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF } - self._channel.onbufferedamountlow = function () { - self._onChannelBufferedAmountLow() - } - self._channel.onopen = function () { - if (!self._channelReady) self._onChannelOpen() - } - self._channel.onclose = function () { - self._onChannelClose() - } - self._channel.onerror = function (err) { - self._destroy(err) - } + + return offset + byteLength } -Peer.prototype._read = function () {} +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} -Peer.prototype._write = function (chunk, encoding, cb) { - var self = this - if (self.destroyed) return cb(new Error('cannot write after peer is destroyed')) +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} - if (self.connected) { - try { - self.send(chunk) - } catch (err) { - return self._destroy(err) - } - if (self._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) { - self._debug('start backpressure: bufferedAmount %d', self._channel.bufferedAmount) - self._cb = cb - } else { - cb(null) - } - } else { - self._debug('write before connect') - self._chunk = chunk - self._cb = cb - } +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 } -// When stream finishes writing, close socket. Half open connections are not -// supported. -Peer.prototype._onFinish = function () { - var self = this - if (self.destroyed) return +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} - if (self.connected) { - destroySoon() - } else { - self.once('connect', destroySoon) - } +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 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} - // Wait a bit before destroying so the socket flushes. - // TODO: is there a more reliable way to accomplish this? - function destroySoon () { - setTimeout(function () { - self._destroy() - }, 1000) +function checkIEEE754 (buf, value, offset, ext, max, min) { + 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) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 } -Peer.prototype._createOffer = function () { - var self = this - if (self.destroyed) return +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} - self._pc.createOffer(function (offer) { - if (self.destroyed) return - offer.sdp = self.sdpTransform(offer.sdp) - self._pc.setLocalDescription(offer, onSuccess, onError) +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} - function onSuccess () { - if (self.destroyed) return - if (self.trickle || self._iceComplete) sendOffer() - else self.once('_iceComplete', sendOffer) // wait for candidates - } +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} - function onError (err) { - self._destroy(err) - } +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} - function sendOffer () { - var signal = self._pc.localDescription || offer - self._debug('signal') - self.emit('signal', { - type: signal.type, - sdp: signal.sdp - }) - } - }, function (err) { self._destroy(err) }, self.offerConstraints) +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) } -Peer.prototype._createAnswer = function () { - var self = this - if (self.destroyed) return +// 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 - self._pc.createAnswer(function (answer) { - if (self.destroyed) return - answer.sdp = self.sdpTransform(answer.sdp) - self._pc.setLocalDescription(answer, onSuccess, onError) + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 - function onSuccess () { - if (self.destroyed) return - if (self.trickle || self._iceComplete) sendAnswer() - else self.once('_iceComplete', sendAnswer) - } + // 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') - function onError (err) { - self._destroy(err) - } + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } - function sendAnswer () { - var signal = self._pc.localDescription || answer - self._debug('signal') - self.emit('signal', { - type: signal.type, - sdp: signal.sdp - }) - } - }, function (err) { self._destroy(err) }, self.answerConstraints) -} + var len = end - start + var i -Peer.prototype._onIceStateChange = function () { - var self = this - if (self.destroyed) return - var iceConnectionState = self._pc.iceConnectionState - var iceGatheringState = self._pc.iceGatheringState + 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) { + // ascending copy from start + for (i = 0; i < len; ++i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, start + len), + targetStart + ) + } - self._debug( - 'iceStateChange (connection: %s) (gathering: %s)', - iceConnectionState, - iceGatheringState - ) - self.emit('iceStateChange', iceConnectionState, iceGatheringState) + return len +} - if (iceConnectionState === 'connected' || iceConnectionState === 'completed') { - clearTimeout(self._reconnectTimeout) - self._pcReady = true - self._maybeReady() - } - if (iceConnectionState === 'disconnected') { - if (self.reconnectTimer) { - // If user has set `opt.reconnectTimer`, allow time for ICE to attempt a reconnect - clearTimeout(self._reconnectTimeout) - self._reconnectTimeout = setTimeout(function () { - self._destroy() - }, self.reconnectTimer) - } else { - self._destroy() +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if (code < 256) { + val = code + } + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + } else if (typeof val === 'number') { + val = val & 255 } - if (iceConnectionState === 'failed') { - self._destroy(new Error('Ice connection failed.')) + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') } - if (iceConnectionState === 'closed') { - self._destroy() + + if (end <= start) { + return this } -} -Peer.prototype.getStats = function (cb) { - var self = this + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 - // Promise-based getStats() (standard) - if (self._pc.getStats.length === 0) { - self._pc.getStats().then(function (res) { - var reports = [] - res.forEach(function (report) { - reports.push(report) - }) - cb(null, reports) - }, function (err) { cb(err) }) + if (!val) val = 0 - // Two-parameter callback-based getStats() (deprecated, former standard) - } else if (self._isReactNativeWebrtc) { - self._pc.getStats(null, function (res) { - var reports = [] - res.forEach(function (report) { - reports.push(report) - }) - cb(null, reports) - }, function (err) { cb(err) }) + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : new Buffer(val, encoding) + var len = bytes.length + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } - // Single-parameter callback-based getStats() (non-standard) - } else if (self._pc.getStats.length > 0) { - self._pc.getStats(function (res) { - var reports = [] - res.result().forEach(function (result) { - var report = {} - result.names().forEach(function (name) { - report[name] = result.stat(name) - }) - report.id = result.id - report.type = result.type - report.timestamp = result.timestamp - reports.push(report) - }) - cb(null, reports) - }, function (err) { cb(err) }) + return this +} - // Unknown browser, skip getStats() since it's anyone's guess which style of - // getStats() they implement. - } else { - cb(null, []) +// HELPER FUNCTIONS +// ================ + +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 = str.trim().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 } -Peer.prototype._maybeReady = function () { - var self = this - self._debug('maybeReady pc %s channel %s', self._pcReady, self._channelReady) - if (self.connected || self._connecting || !self._pcReady || !self._channelReady) return - self._connecting = true +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} - self.getStats(function (err, items) { - if (self.destroyed) return +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] - // Treat getStats error as non-fatal. It's not essential. - if (err) items = [] + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) - self._connecting = false - self.connected = true + // 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 + } - var remoteCandidates = {} - var localCandidates = {} - var candidatePairs = {} + // valid lead + leadSurrogate = codePoint - items.forEach(function (item) { - // TODO: Once all browsers support the hyphenated stats report types, remove - // the non-hypenated ones - if (item.type === 'remotecandidate' || item.type === 'remote-candidate') { - remoteCandidates[item.id] = item - } - if (item.type === 'localcandidate' || item.type === 'local-candidate') { - localCandidates[item.id] = item - } - if (item.type === 'candidatepair' || item.type === 'candidate-pair') { - candidatePairs[item.id] = item + continue } - }) - - items.forEach(function (item) { - // Spec-compliant - if (item.type === 'transport') { - setSelectedCandidatePair(candidatePairs[item.selectedCandidatePairId]) - } - - // Old implementations - if ( - (item.type === 'googCandidatePair' && item.googActiveConnection === 'true') || - ((item.type === 'candidatepair' || item.type === 'candidate-pair') && item.selected) - ) { - setSelectedCandidatePair(item) - } - }) - - function setSelectedCandidatePair (selectedCandidatePair) { - var local = localCandidates[selectedCandidatePair.localCandidateId] - - if (local && local.ip) { - // Spec - self.localAddress = local.ip - self.localPort = Number(local.port) - } else if (local && local.ipAddress) { - // Firefox - self.localAddress = local.ipAddress - self.localPort = Number(local.portNumber) - } else if (typeof selectedCandidatePair.googLocalAddress === 'string') { - // TODO: remove this once Chrome 58 is released - local = selectedCandidatePair.googLocalAddress.split(':') - self.localAddress = local[0] - self.localPort = Number(local[1]) - } - - var remote = remoteCandidates[selectedCandidatePair.remoteCandidateId] - if (remote && remote.ip) { - // Spec - self.remoteAddress = remote.ip - self.remotePort = Number(remote.port) - } else if (remote && remote.ipAddress) { - // Firefox - self.remoteAddress = remote.ipAddress - self.remotePort = Number(remote.portNumber) - } else if (typeof selectedCandidatePair.googRemoteAddress === 'string') { - // TODO: remove this once Chrome 58 is released - remote = selectedCandidatePair.googRemoteAddress.split(':') - self.remoteAddress = remote[0] - self.remotePort = Number(remote[1]) + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue } - self.remoteFamily = 'IPv4' - self._debug( - 'connect local: %s:%s remote: %s:%s', - self.localAddress, self.localPort, self.remoteAddress, self.remotePort - ) + // 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) } - if (self._chunk) { - try { - self.send(self._chunk) - } catch (err) { - return self._destroy(err) - } - self._chunk = null - self._debug('sent chunk from "write before connect"') - - var cb = self._cb - self._cb = null - cb(null) - } + leadSurrogate = null - // If `bufferedAmountLowThreshold` and 'onbufferedamountlow' are unsupported, - // fallback to using setInterval to implement backpressure. - if (typeof self._channel.bufferedAmountLowThreshold !== 'number') { - self._interval = setInterval(function () { self._onInterval() }, 150) - if (self._interval.unref) self._interval.unref() + // 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') } + } - self._debug('connect') - self.emit('connect') - if (self._earlyMessage) { // HACK: Workaround for Chrome not firing "open" between tabs - self._onChannelMessage(self._earlyMessage) - self._earlyMessage = null - } - }) + return bytes } -Peer.prototype._onInterval = function () { - if (!this._cb || !this._channel || this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) { - return +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) } - this._onChannelBufferedAmountLow() + return byteArray } -Peer.prototype._onSignalingStateChange = function () { - var self = this - if (self.destroyed) return - self._debug('signalingStateChange %s', self._pc.signalingState) - self.emit('signalingStateChange', self._pc.signalingState) -} +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break -Peer.prototype._onIceCandidate = function (event) { - var self = this - if (self.destroyed) return - if (event.candidate && self.trickle) { - self.emit('signal', { - candidate: { - candidate: event.candidate.candidate, - sdpMLineIndex: event.candidate.sdpMLineIndex, - sdpMid: event.candidate.sdpMid - } - }) - } else if (!event.candidate) { - self._iceComplete = true - self.emit('_iceComplete') + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) } -} -Peer.prototype._onChannelMessage = function (event) { - var self = this - if (self.destroyed) return - var data = event.data - if (data instanceof ArrayBuffer) data = Buffer.from(data) - self.push(data) + return byteArray } -Peer.prototype._onChannelBufferedAmountLow = function () { - var self = this - if (self.destroyed || !self._cb) return - self._debug('ending backpressure: bufferedAmount %d', self._channel.bufferedAmount) - var cb = self._cb - self._cb = null - cb(null) +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) } -Peer.prototype._onChannelOpen = function () { - var self = this - if (self.connected || self.destroyed) return - self._debug('on channel open') - self._channelReady = true - self._maybeReady() +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 } -Peer.prototype._onChannelClose = function () { - var self = this - if (self.destroyed) return - self._debug('on channel close') - self._destroy() +// Node 0.10 supports `ArrayBuffer` but lacks `ArrayBuffer.isView` +function isArrayBufferView (obj) { + return (typeof ArrayBuffer.isView === 'function') && ArrayBuffer.isView(obj) } -Peer.prototype._onAddStream = function (event) { - var self = this - if (self.destroyed) return - self._debug('on add stream') - self.emit('stream', event.stream) +function numberIsNaN (obj) { + return obj !== obj // eslint-disable-line no-self-compare } -Peer.prototype._onTrack = function (event) { - var self = this - if (self.destroyed) return - self._debug('on track') - var id = event.streams[0].id - if (self._previousStreams.indexOf(id) !== -1) return // Only fire one 'stream' event, even though there may be multiple tracks per stream - self._previousStreams.push(id) - self.emit('stream', event.streams[0]) -} +},{"base64-js":68,"ieee754":94}],83:[function(require,module,exports){ +module.exports = ChunkStoreWriteStream -Peer.prototype._debug = function () { - var self = this - var args = [].slice.call(arguments) - args[0] = '[' + self._id + '] ' + args[0] - debug.apply(null, args) -} +var BlockStream = require('block-stream2') +var inherits = require('inherits') +var stream = require('readable-stream') -// Transform constraints objects into the new format (unless Chromium) -// TODO: This can be removed when Chromium supports the new format -Peer.prototype._transformConstraints = function (constraints) { - var self = this +inherits(ChunkStoreWriteStream, stream.Writable) - if (Object.keys(constraints).length === 0) { - return constraints +function ChunkStoreWriteStream (store, chunkLength, opts) { + var self = this + if (!(self instanceof ChunkStoreWriteStream)) { + return new ChunkStoreWriteStream(store, chunkLength, opts) } + stream.Writable.call(self, opts) + if (!opts) opts = {} - if ((constraints.mandatory || constraints.optional) && !self._isChromium) { - // convert to new format + if (!store || !store.put || !store.get) { + throw new Error('First argument must be an abstract-chunk-store compliant store') + } + chunkLength = Number(chunkLength) + if (!chunkLength) throw new Error('Second argument must be a chunk length') - // Merge mandatory and optional objects, prioritizing mandatory - var newConstraints = Object.assign({}, constraints.optional, constraints.mandatory) + self._blockstream = new BlockStream(chunkLength, { zeroPadding: false }) - // fix casing - if (newConstraints.OfferToReceiveVideo !== undefined) { - newConstraints.offerToReceiveVideo = newConstraints.OfferToReceiveVideo - delete newConstraints['OfferToReceiveVideo'] - } + self._blockstream + .on('data', onData) + .on('error', function (err) { self.destroy(err) }) - if (newConstraints.OfferToReceiveAudio !== undefined) { - newConstraints.offerToReceiveAudio = newConstraints.OfferToReceiveAudio - delete newConstraints['OfferToReceiveAudio'] - } - - return newConstraints - } else if (!constraints.mandatory && !constraints.optional && self._isChromium) { - // convert to old format + var index = 0 + function onData (chunk) { + if (self.destroyed) return + store.put(index, chunk) + index += 1 + } - // fix casing - if (constraints.offerToReceiveVideo !== undefined) { - constraints.OfferToReceiveVideo = constraints.offerToReceiveVideo - delete constraints['offerToReceiveVideo'] - } + self.on('finish', function () { this._blockstream.end() }) +} - if (constraints.offerToReceiveAudio !== undefined) { - constraints.OfferToReceiveAudio = constraints.offerToReceiveAudio - delete constraints['offerToReceiveAudio'] - } +ChunkStoreWriteStream.prototype._write = function (chunk, encoding, callback) { + this._blockstream.write(chunk, encoding, callback) +} - return { - mandatory: constraints // NOTE: All constraints are upgraded to mandatory - } - } +ChunkStoreWriteStream.prototype.destroy = function (err) { + if (this.destroyed) return + this.destroyed = true - return constraints + if (err) this.emit('error', err) + this.emit('close') } -function noop () {} - -}).call(this,require("buffer").Buffer) -},{"buffer":159,"debug":27,"get-browser-rtc":55,"inherits":58,"randombytes":82,"readable-stream":92}],102:[function(require,module,exports){ -var Rusha = require('rusha') +},{"block-stream2":80,"inherits":96,"readable-stream":132}],84:[function(require,module,exports){ +var abs = Math.abs -var rusha = new Rusha -var scope = typeof window !== 'undefined' ? window : self -var crypto = scope.crypto || scope.msCrypto || {} -var subtle = crypto.subtle || crypto.webkitSubtle +module.exports = closest -function sha1sync (buf) { - return rusha.digest(buf) +function closest (n, arr, rndx) { + var i, ndx, diff, best = Infinity + var low = 0, high = arr.length - 1 + while (low <= high) { + i = low + (high - low >> 1) + diff = arr[i] - n + diff < 0 ? low = i + 1 : + diff > 0 ? high = i - 1 : void 0 + diff = abs(diff) + if (diff < best) best = diff, ndx = i + if (arr[i] === n) break + } + return rndx ? ndx : arr[ndx] } -// Browsers throw if they lack support for an algorithm. -// Promise will be rejected on non-secure origins. (http://goo.gl/lq4gCo) -try { - subtle.digest({ name: 'sha-1' }, new Uint8Array).catch(function () { - subtle = false - }) -} catch (err) { subtle = false } +},{}],85:[function(require,module,exports){ +(function (Buffer){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -function sha1 (buf, cb) { - if (!subtle) { - // Use Rusha - setTimeout(cb, 0, sha1sync(buf)) - return - } +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. - if (typeof buf === 'string') { - buf = uint8array(buf) +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); } - - subtle.digest({ name: 'sha-1' }, buf) - .then(function succeed (result) { - cb(hex(new Uint8Array(result))) - }, - function fail (error) { - cb(sha1sync(buf)) - }) + return objectToString(arg) === '[object Array]'; } +exports.isArray = isArray; -function uint8array (s) { - var l = s.length - var array = new Uint8Array(l) - for (var i = 0; i < l; i++) { - array[i] = s.charCodeAt(i) - } - return array +function isBoolean(arg) { + return typeof arg === 'boolean'; } +exports.isBoolean = isBoolean; -function hex (buf) { - var l = buf.length - var chars = [] - for (var i = 0; i < l; i++) { - var bite = buf[i] - chars.push((bite >>> 4).toString(16)) - chars.push((bite & 0x0f).toString(16)) - } - return chars.join('') +function isNull(arg) { + return arg === null; } +exports.isNull = isNull; -module.exports = sha1 -module.exports.sync = sha1sync +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; -},{"rusha":97}],103:[function(require,module,exports){ -(function (process){ -/* global WebSocket */ +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; -module.exports = Socket +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; -var Buffer = require('safe-buffer').Buffer -var debug = require('debug')('simple-websocket') -var inherits = require('inherits') -var randombytes = require('randombytes') -var stream = require('readable-stream') -var ws = require('ws') // websockets in node - will be empty object in browser +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; -var _WebSocket = typeof ws !== 'function' ? WebSocket : ws +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; -var MAX_BUFFERED_AMOUNT = 64 * 1024 +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; -inherits(Socket, stream.Duplex) +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; -/** - * WebSocket. Same API as node core `net.Socket`. Duplex stream. - * @param {Object} opts - * @param {string=} opts.url websocket server url - * @param {string=} opts.socket raw websocket instance to wrap - */ -function Socket (opts) { - var self = this - if (!(self instanceof Socket)) return new Socket(opts) - if (!opts) opts = {} +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; - // Support simple usage: `new Socket(url)` - if (typeof opts === 'string') { - opts = { url: opts } - } +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; - if (opts.url == null && opts.socket == null) { - throw new Error('Missing required `url` or `socket` option') - } - if (opts.url != null && opts.socket != null) { - throw new Error('Must specify either `url` or `socket` option, not both') - } +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; - self._id = randombytes(4).toString('hex').slice(0, 7) - self._debug('new websocket: %o', opts) +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; - opts = Object.assign({ - allowHalfOpen: false - }, opts) +exports.isBuffer = Buffer.isBuffer; - stream.Duplex.call(self, opts) +function objectToString(o) { + return Object.prototype.toString.call(o); +} - self.connected = false - self.destroyed = false +}).call(this,{"isBuffer":require("C:/Users/Administrator/AppData/Roaming/npm/node_modules/browserify/node_modules/is-buffer/index.js")}) +},{"C:/Users/Administrator/AppData/Roaming/npm/node_modules/browserify/node_modules/is-buffer/index.js":11}],86:[function(require,module,exports){ +(function (process,global,Buffer){ +module.exports = createTorrent +module.exports.parseInput = parseInput - self._chunk = null - self._cb = null - self._interval = null +module.exports.announceList = [ + [ 'udp://tracker.leechers-paradise.org:6969' ], + [ 'udp://tracker.coppersurfer.tk:6969' ], + [ 'udp://tracker.opentrackr.org:1337' ], + [ 'udp://explodie.org:6969' ], + [ 'udp://tracker.empire-js.us:1337' ], + [ 'wss://tracker.btorrent.xyz' ], + [ 'wss://tracker.openwebtorrent.com' ], + [ 'wss://tracker.fastcast.nz' ] +] - if (opts.socket) { - self.url = opts.socket.url - self._ws = opts.socket - } else { - self.url = opts.url - try { - if (typeof ws === 'function') { - // `ws` package accepts options - self._ws = new _WebSocket(opts.url, opts) - } else { - self._ws = new _WebSocket(opts.url) - } - } catch (err) { - process.nextTick(function () { - self._destroy(err) - }) - return - } - } +var bencode = require('bencode') +var BlockStream = require('block-stream2') +var calcPieceLength = require('piece-length') +var corePath = require('path') +var extend = require('xtend') +var FileReadStream = require('filestream/read') +var flatten = require('flatten') +var fs = require('fs') +var isFile = require('is-file') +var junk = require('junk') +var MultiStream = require('multistream') +var once = require('once') +var parallel = require('run-parallel') +var sha1 = require('simple-sha1') +var stream = require('readable-stream') - self._ws.binaryType = 'arraybuffer' - self._ws.onopen = function () { - self._onOpen() - } - self._ws.onmessage = function (event) { - self._onMessage(event) - } - self._ws.onclose = function () { - self._onClose() - } - self._ws.onerror = function () { - self._destroy(new Error('connection error to ' + self.url)) - } +/** + * Create a torrent. + * @param {string|File|FileList|Buffer|Stream|Array.} input + * @param {Object} opts + * @param {string=} opts.name + * @param {Date=} opts.creationDate + * @param {string=} opts.comment + * @param {string=} opts.createdBy + * @param {boolean|number=} opts.private + * @param {number=} opts.pieceLength + * @param {Array.>=} opts.announceList + * @param {Array.=} opts.urlList + * @param {function} cb + * @return {Buffer} buffer of .torrent file data + */ +function createTorrent (input, opts, cb) { + if (typeof opts === 'function') return createTorrent(input, null, opts) + opts = opts ? extend(opts) : {} - self._onFinishBound = function () { - self._onFinish() - } - self.once('finish', self._onFinishBound) + _parseInput(input, opts, function (err, files, singleFileTorrent) { + if (err) return cb(err) + opts.singleFileTorrent = singleFileTorrent + onFiles(files, opts, cb) + }) } -Socket.WEBSOCKET_SUPPORT = !!_WebSocket +function parseInput (input, opts, cb) { + if (typeof opts === 'function') return parseInput(input, null, opts) + opts = opts ? extend(opts) : {} + _parseInput(input, opts, cb) +} /** - * Send text/binary data to the WebSocket server. - * @param {TypedArrayView|ArrayBuffer|Buffer|string|Blob|Object} chunk + * Parse input file and return file information. */ -Socket.prototype.send = function (chunk) { - this._ws.send(chunk) -} +function _parseInput (input, opts, cb) { + if (Array.isArray(input) && input.length === 0) throw new Error('invalid input type') -Socket.prototype.destroy = function (onclose) { - this._destroy(null, onclose) -} + if (isFileList(input)) input = Array.prototype.slice.call(input) + if (!Array.isArray(input)) input = [ input ] -Socket.prototype._destroy = function (err, onclose) { - var self = this - if (self.destroyed) return - if (onclose) self.once('close', onclose) + // In Electron, use the true file path + input = input.map(function (item) { + if (isBlob(item) && typeof item.path === 'string' && typeof fs.stat === 'function') return item.path + return item + }) - self._debug('destroy (error: %s)', err && (err.message || err)) + // If there's just one file, allow the name to be set by `opts.name` + if (input.length === 1 && typeof input[0] !== 'string' && !input[0].name) input[0].name = opts.name - self.readable = self.writable = false - if (!self._readableState.ended) self.push(null) - if (!self._writableState.finished) self.end() + var commonPrefix = null + input.forEach(function (item, i) { + if (typeof item === 'string') { + return + } - self.connected = false - self.destroyed = true + var path = item.fullPath || item.name + if (!path) { + path = 'Unknown File ' + (i + 1) + item.unknownName = true + } - clearInterval(self._interval) - self._interval = null - self._chunk = null - self._cb = null + item.path = path.split('/') - if (self._onFinishBound) self.removeListener('finish', self._onFinishBound) - self._onFinishBound = null + // Remove initial slash + if (!item.path[0]) { + item.path.shift() + } - if (self._ws) { - var ws = self._ws - var onClose = function () { - ws.onclose = null + if (item.path.length < 2) { // No real prefix + commonPrefix = null + } else if (i === 0 && input.length > 1) { // The first file has a prefix + commonPrefix = item.path[0] + } else if (item.path[0] !== commonPrefix) { // The prefix doesn't match + commonPrefix = null } - if (ws.readyState === _WebSocket.CLOSED) { - onClose() - } else { - try { - ws.onclose = onClose - ws.close() - } catch (err) { - onClose() - } + }) + + // remove junk files + input = input.filter(function (item) { + if (typeof item === 'string') { + return true } + var filename = item.path[item.path.length - 1] + return notHidden(filename) && junk.not(filename) + }) - ws.onopen = null - ws.onmessage = null - ws.onerror = null + if (commonPrefix) { + input.forEach(function (item) { + var pathless = (Buffer.isBuffer(item) || isReadable(item)) && !item.path + if (typeof item === 'string' || pathless) return + item.path.shift() + }) } - self._ws = null - - if (err) self.emit('error', err) - self.emit('close') -} - -Socket.prototype._read = function () {} - -Socket.prototype._write = function (chunk, encoding, cb) { - if (this.destroyed) return cb(new Error('cannot write after socket is destroyed')) - if (this.connected) { - try { - this.send(chunk) - } catch (err) { - return this._destroy(err) - } - if (typeof ws !== 'function' && this._ws.bufferedAmount > MAX_BUFFERED_AMOUNT) { - this._debug('start backpressure: bufferedAmount %d', this._ws.bufferedAmount) - this._cb = cb - } else { - cb(null) - } - } else { - this._debug('write before connect') - this._chunk = chunk - this._cb = cb + if (!opts.name && commonPrefix) { + opts.name = commonPrefix } -} - -// When stream finishes writing, close socket. Half open connections are not -// supported. -Socket.prototype._onFinish = function () { - var self = this - if (self.destroyed) return - if (self.connected) { - destroySoon() - } else { - self.once('connect', destroySoon) + if (!opts.name) { + // use first user-set file name + input.some(function (item) { + if (typeof item === 'string') { + opts.name = corePath.basename(item) + return true + } else if (!item.unknownName) { + opts.name = item.path[item.path.length - 1] + return true + } + }) } - // Wait a bit before destroying so the socket flushes. - // TODO: is there a more reliable way to accomplish this? - function destroySoon () { - setTimeout(function () { - self._destroy() - }, 1000) + if (!opts.name) { + opts.name = 'Unnamed Torrent ' + Date.now() } -} -Socket.prototype._onMessage = function (event) { - if (this.destroyed) return - var data = event.data - if (data instanceof ArrayBuffer) data = Buffer.from(data) - this.push(data) -} + var numPaths = input.reduce(function (sum, item) { + return sum + Number(typeof item === 'string') + }, 0) -Socket.prototype._onOpen = function () { - var self = this - if (self.connected || self.destroyed) return - self.connected = true + var isSingleFileTorrent = (input.length === 1) - if (self._chunk) { - try { - self.send(self._chunk) - } catch (err) { - return self._destroy(err) + if (input.length === 1 && typeof input[0] === 'string') { + if (typeof fs.stat !== 'function') { + throw new Error('filesystem paths do not work in the browser') } - self._chunk = null - self._debug('sent chunk from "write before connect"') - - var cb = self._cb - self._cb = null - cb(null) + // If there's a single path, verify it's a file before deciding this is a single + // file torrent + isFile(input[0], function (err, pathIsFile) { + if (err) return cb(err) + isSingleFileTorrent = pathIsFile + processInput() + }) + } else { + process.nextTick(function () { + processInput() + }) } - // Backpressure is not implemented in Node.js. The `ws` module has a buggy - // `bufferedAmount` property. See: https://github.com/websockets/ws/issues/492 - if (typeof ws !== 'function') { - self._interval = setInterval(function () { - self._onInterval() - }, 150) - if (self._interval.unref) self._interval.unref() - } - - self._debug('connect') - self.emit('connect') -} + function processInput () { + parallel(input.map(function (item) { + return function (cb) { + var file = {} -Socket.prototype._onInterval = function () { - if (!this._cb || !this._ws || this._ws.bufferedAmount > MAX_BUFFERED_AMOUNT) { - return + if (isBlob(item)) { + file.getStream = getBlobStream(item) + file.length = item.size + } else if (Buffer.isBuffer(item)) { + file.getStream = getBufferStream(item) + file.length = item.length + } else if (isReadable(item)) { + file.getStream = getStreamStream(item, file) + file.length = 0 + } else if (typeof item === 'string') { + if (typeof fs.stat !== 'function') { + throw new Error('filesystem paths do not work in the browser') + } + var keepRoot = numPaths > 1 || isSingleFileTorrent + getFiles(item, keepRoot, cb) + return // early return! + } else { + throw new Error('invalid input type') + } + file.path = item.path + cb(null, file) + } + }), function (err, files) { + if (err) return cb(err) + files = flatten(files) + cb(null, files, isSingleFileTorrent) + }) } - this._debug('ending backpressure: bufferedAmount %d', this._ws.bufferedAmount) - var cb = this._cb - this._cb = null - cb(null) } -Socket.prototype._onClose = function () { - if (this.destroyed) return - this._debug('on close') - this._destroy() +function getFiles (path, keepRoot, cb) { + traversePath(path, getFileInfo, function (err, files) { + if (err) return cb(err) + + if (Array.isArray(files)) files = flatten(files) + else files = [ files ] + + path = corePath.normalize(path) + if (keepRoot) { + path = path.slice(0, path.lastIndexOf(corePath.sep) + 1) + } + if (path[path.length - 1] !== corePath.sep) path += corePath.sep + + files.forEach(function (file) { + file.getStream = getFilePathStream(file.path) + file.path = file.path.replace(path, '').split(corePath.sep) + }) + cb(null, files) + }) } -Socket.prototype._debug = function () { - var args = [].slice.call(arguments) - args[0] = '[' + this._id + '] ' + args[0] - debug.apply(null, args) +function getFileInfo (path, cb) { + cb = once(cb) + fs.stat(path, function (err, stat) { + if (err) return cb(err) + var info = { + length: stat.size, + path: path + } + cb(null, info) + }) } -}).call(this,require('_process')) -},{"_process":170,"debug":27,"inherits":58,"randombytes":82,"readable-stream":92,"safe-buffer":98,"ws":158}],104:[function(require,module,exports){ -var tick = 1 -var maxTick = 65535 -var resolution = 4 -var inc = function () { - tick = (tick + 1) & maxTick +function traversePath (path, fn, cb) { + fs.stat(path, function (err, stats) { + if (err) return cb(err) + if (stats.isDirectory()) { + fs.readdir(path, function (err, entries) { + if (err) return cb(err) + parallel(entries.filter(notHidden).filter(junk.not).map(function (entry) { + return function (cb) { + traversePath(corePath.join(path, entry), fn, cb) + } + }), cb) + }) + } else if (stats.isFile()) { + fn(path, cb) + } + // Ignore other types (not a file or directory) + }) } -var timer = setInterval(inc, (1000 / resolution) | 0) -if (timer.unref) timer.unref() +function notHidden (file) { + return file[0] !== '.' +} -module.exports = function (seconds) { - var size = resolution * (seconds || 5) - var buffer = [0] - var pointer = 1 - var last = (tick - 1) & maxTick +function getPieceList (files, pieceLength, cb) { + cb = once(cb) + var pieces = [] + var length = 0 - return function (delta) { - var dist = (tick - last) & maxTick - if (dist > size) dist = size - last = tick + var streams = files.map(function (file) { + return file.getStream + }) - while (dist--) { - if (pointer === size) pointer = 0 - buffer[pointer] = buffer[pointer === 0 ? size - 1 : pointer - 1] - pointer++ - } + var remainingHashes = 0 + var pieceNum = 0 + var ended = false - if (delta) buffer[pointer - 1] += delta + var multistream = new MultiStream(streams) + var blockstream = new BlockStream(pieceLength, { zeroPadding: false }) - var top = buffer[pointer - 1] - var btm = buffer.length < size ? 0 : buffer[pointer === size ? 0 : pointer] + multistream.on('error', onError) - return buffer.length < resolution ? top : (top - btm) * resolution / buffer.length - } -} + multistream + .pipe(blockstream) + .on('data', onData) + .on('end', onEnd) + .on('error', onError) -},{}],105:[function(require,module,exports){ -/* global URL */ + function onData (chunk) { + length += chunk.length -var getBlob = require('stream-to-blob') + var i = pieceNum + sha1(chunk, function (hash) { + pieces[i] = hash + remainingHashes -= 1 + maybeDone() + }) + remainingHashes += 1 + pieceNum += 1 + } -module.exports = function getBlobURL (stream, mimeType, cb) { - if (typeof mimeType === 'function') return getBlobURL(stream, null, mimeType) - getBlob(stream, mimeType, function (err, blob) { - if (err) return cb(err) - var url = URL.createObjectURL(blob) - cb(null, url) - }) -} + function onEnd () { + ended = true + maybeDone() + } -},{"stream-to-blob":106}],106:[function(require,module,exports){ -/* global Blob */ + function onError (err) { + cleanup() + cb(err) + } -var once = require('once') + function cleanup () { + multistream.removeListener('error', onError) + blockstream.removeListener('data', onData) + blockstream.removeListener('end', onEnd) + blockstream.removeListener('error', onError) + } -module.exports = function getBlob (stream, mimeType, cb) { - if (typeof mimeType === 'function') return getBlob(stream, null, mimeType) - cb = once(cb) - var chunks = [] - stream - .on('data', function (chunk) { - chunks.push(chunk) - }) - .on('end', function () { - var blob = mimeType - ? new Blob(chunks, { type: mimeType }) - : new Blob(chunks) - cb(null, blob) - }) - .on('error', cb) + function maybeDone () { + if (ended && remainingHashes === 0) { + cleanup() + cb(null, Buffer.from(pieces.join(''), 'hex'), length) + } + } } -},{"once":75}],107:[function(require,module,exports){ -(function (Buffer){ -var once = require('once') +function onFiles (files, opts, cb) { + var announceList = opts.announceList -module.exports = function getBuffer (stream, length, cb) { - cb = once(cb) - var buf = new Buffer(length) - var offset = 0 - stream - .on('data', function (chunk) { - chunk.copy(buf, offset) - offset += chunk.length - }) - .on('end', function () { cb(null, buf) }) - .on('error', cb) -} + if (!announceList) { + if (typeof opts.announce === 'string') announceList = [ [ opts.announce ] ] + else if (Array.isArray(opts.announce)) { + announceList = opts.announce.map(function (u) { return [ u ] }) + } + } -}).call(this,require("buffer").Buffer) -},{"buffer":159,"once":75}],108:[function(require,module,exports){ -'use strict'; + if (!announceList) announceList = [] -var Buffer = require('safe-buffer').Buffer; + if (global.WEBTORRENT_ANNOUNCE) { + if (typeof global.WEBTORRENT_ANNOUNCE === 'string') { + announceList.push([ [ global.WEBTORRENT_ANNOUNCE ] ]) + } else if (Array.isArray(global.WEBTORRENT_ANNOUNCE)) { + announceList = announceList.concat(global.WEBTORRENT_ANNOUNCE.map(function (u) { + return [ u ] + })) + } + } -var isEncoding = Buffer.isEncoding || function (encoding) { - encoding = '' + encoding; - switch (encoding && encoding.toLowerCase()) { - case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': - return true; - default: - return false; + // When no trackers specified, use some reasonable defaults + if (opts.announce === undefined && opts.announceList === undefined) { + announceList = announceList.concat(module.exports.announceList) } -}; -function _normalizeEncoding(enc) { - if (!enc) return 'utf8'; - var retried; - while (true) { - switch (enc) { - case 'utf8': - case 'utf-8': - return 'utf8'; - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - return 'utf16le'; - case 'latin1': - case 'binary': - return 'latin1'; - case 'base64': - case 'ascii': - case 'hex': - return enc; - default: - if (retried) return; // undefined - enc = ('' + enc).toLowerCase(); - retried = true; - } - } -}; - -// Do not cache `Buffer.isEncoding` when checking encoding names as some -// modules monkey-patch it to support additional encodings -function normalizeEncoding(enc) { - var nenc = _normalizeEncoding(enc); - if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); - return nenc || enc; -} + if (typeof opts.urlList === 'string') opts.urlList = [ opts.urlList ] -// StringDecoder provides an interface for efficiently splitting a series of -// buffers into a series of JS strings without breaking apart multi-byte -// characters. -exports.StringDecoder = StringDecoder; -function StringDecoder(encoding) { - this.encoding = normalizeEncoding(encoding); - var nb; - switch (this.encoding) { - case 'utf16le': - this.text = utf16Text; - this.end = utf16End; - nb = 4; - break; - case 'utf8': - this.fillLast = utf8FillLast; - nb = 4; - break; - case 'base64': - this.text = base64Text; - this.end = base64End; - nb = 3; - break; - default: - this.write = simpleWrite; - this.end = simpleEnd; - return; + var torrent = { + info: { + name: opts.name + }, + 'creation date': Math.ceil((Number(opts.creationDate) || Date.now()) / 1000), + encoding: 'UTF-8' } - this.lastNeed = 0; - this.lastTotal = 0; - this.lastChar = Buffer.allocUnsafe(nb); -} -StringDecoder.prototype.write = function (buf) { - if (buf.length === 0) return ''; - var r; - var i; - if (this.lastNeed) { - r = this.fillLast(buf); - if (r === undefined) return ''; - i = this.lastNeed; - this.lastNeed = 0; - } else { - i = 0; + if (announceList.length !== 0) { + torrent.announce = announceList[0][0] + torrent['announce-list'] = announceList } - if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); - return r || ''; -}; -StringDecoder.prototype.end = utf8End; + if (opts.comment !== undefined) torrent.comment = opts.comment -// Returns only complete characters in a Buffer -StringDecoder.prototype.text = utf8Text; + if (opts.createdBy !== undefined) torrent['created by'] = opts.createdBy -// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer -StringDecoder.prototype.fillLast = function (buf) { - if (this.lastNeed <= buf.length) { - buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); - return this.lastChar.toString(this.encoding, 0, this.lastTotal); - } - buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); - this.lastNeed -= buf.length; -}; + if (opts.private !== undefined) torrent.info.private = Number(opts.private) -// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a -// continuation byte. -function utf8CheckByte(byte) { - if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; - return -1; -} + // "ssl-cert" key is for SSL torrents, see: + // - http://blog.libtorrent.org/2012/01/bittorrent-over-ssl/ + // - http://www.libtorrent.org/manual-ref.html#ssl-torrents + // - http://www.libtorrent.org/reference-Create_Torrents.html + if (opts.sslCert !== undefined) torrent.info['ssl-cert'] = opts.sslCert -// Checks at most 3 bytes at the end of a Buffer in order to detect an -// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) -// needed to complete the UTF-8 character (if applicable) are returned. -function utf8CheckIncomplete(self, buf, i) { - var j = buf.length - 1; - if (j < i) return 0; - var nb = utf8CheckByte(buf[j]); - if (nb >= 0) { - if (nb > 0) self.lastNeed = nb - 1; - return nb; - } - if (--j < i) return 0; - nb = utf8CheckByte(buf[j]); - if (nb >= 0) { - if (nb > 0) self.lastNeed = nb - 2; - return nb; - } - if (--j < i) return 0; - nb = utf8CheckByte(buf[j]); - if (nb >= 0) { - if (nb > 0) { - if (nb === 2) nb = 0;else self.lastNeed = nb - 3; - } - return nb; - } - return 0; -} + if (opts.urlList !== undefined) torrent['url-list'] = opts.urlList -// Validates as many continuation bytes for a multi-byte UTF-8 character as -// needed or are available. If we see a non-continuation byte where we expect -// one, we "replace" the validated continuation bytes we've seen so far with -// UTF-8 replacement characters ('\ufffd'), to match v8's UTF-8 decoding -// behavior. The continuation byte check is included three times in the case -// where all of the continuation bytes for a character exist in the same buffer. -// It is also done this way as a slight performance increase instead of using a -// loop. -function utf8CheckExtraBytes(self, buf, p) { - if ((buf[0] & 0xC0) !== 0x80) { - self.lastNeed = 0; - return '\ufffd'.repeat(p); - } - if (self.lastNeed > 1 && buf.length > 1) { - if ((buf[1] & 0xC0) !== 0x80) { - self.lastNeed = 1; - return '\ufffd'.repeat(p + 1); - } - if (self.lastNeed > 2 && buf.length > 2) { - if ((buf[2] & 0xC0) !== 0x80) { - self.lastNeed = 2; - return '\ufffd'.repeat(p + 2); - } + var pieceLength = opts.pieceLength || calcPieceLength(files.reduce(sumLength, 0)) + torrent.info['piece length'] = pieceLength + + getPieceList(files, pieceLength, function (err, pieces, torrentLength) { + if (err) return cb(err) + torrent.info.pieces = pieces + + files.forEach(function (file) { + delete file.getStream + }) + + if (opts.singleFileTorrent) { + torrent.info.length = torrentLength + } else { + torrent.info.files = files } - } -} -// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. -function utf8FillLast(buf) { - var p = this.lastTotal - this.lastNeed; - var r = utf8CheckExtraBytes(this, buf, p); - if (r !== undefined) return r; - if (this.lastNeed <= buf.length) { - buf.copy(this.lastChar, p, 0, this.lastNeed); - return this.lastChar.toString(this.encoding, 0, this.lastTotal); - } - buf.copy(this.lastChar, p, 0, buf.length); - this.lastNeed -= buf.length; + cb(null, bencode.encode(torrent)) + }) } -// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a -// partial character, the character's bytes are buffered until the required -// number of bytes are available. -function utf8Text(buf, i) { - var total = utf8CheckIncomplete(this, buf, i); - if (!this.lastNeed) return buf.toString('utf8', i); - this.lastTotal = total; - var end = buf.length - (total - this.lastNeed); - buf.copy(this.lastChar, 0, end); - return buf.toString('utf8', i, end); +/** + * Accumulator to sum file lengths + * @param {number} sum + * @param {Object} file + * @return {number} + */ +function sumLength (sum, file) { + return sum + file.length } -// For UTF-8, a replacement character for each buffered byte of a (partial) -// character needs to be added to the output. -function utf8End(buf) { - var r = buf && buf.length ? this.write(buf) : ''; - if (this.lastNeed) return r + '\ufffd'.repeat(this.lastTotal - this.lastNeed); - return r; +/** + * Check if `obj` is a W3C `Blob` object (which `File` inherits from) + * @param {*} obj + * @return {boolean} + */ +function isBlob (obj) { + return typeof Blob !== 'undefined' && obj instanceof Blob } -// UTF-16LE typically needs two bytes per character, but even if we have an even -// number of bytes available, we need to check if we end on a leading/high -// surrogate. In that case, we need to wait for the next two bytes in order to -// decode the last character properly. -function utf16Text(buf, i) { - if ((buf.length - i) % 2 === 0) { - var r = buf.toString('utf16le', i); - if (r) { - var c = r.charCodeAt(r.length - 1); - if (c >= 0xD800 && c <= 0xDBFF) { - this.lastNeed = 2; - this.lastTotal = 4; - this.lastChar[0] = buf[buf.length - 2]; - this.lastChar[1] = buf[buf.length - 1]; - return r.slice(0, -1); - } - } - return r; - } - this.lastNeed = 1; - this.lastTotal = 2; - this.lastChar[0] = buf[buf.length - 1]; - return buf.toString('utf16le', i, buf.length - 1); +/** + * Check if `obj` is a W3C `FileList` object + * @param {*} obj + * @return {boolean} + */ +function isFileList (obj) { + return typeof FileList !== 'undefined' && obj instanceof FileList } -// For UTF-16LE we do not explicitly append special replacement characters if we -// end on a partial character, we simply let v8 handle that. -function utf16End(buf) { - var r = buf && buf.length ? this.write(buf) : ''; - if (this.lastNeed) { - var end = this.lastTotal - this.lastNeed; - return r + this.lastChar.toString('utf16le', 0, end); - } - return r; +/** + * Check if `obj` is a node Readable stream + * @param {*} obj + * @return {boolean} + */ +function isReadable (obj) { + return typeof obj === 'object' && obj != null && typeof obj.pipe === 'function' } -function base64Text(buf, i) { - var n = (buf.length - i) % 3; - if (n === 0) return buf.toString('base64', i); - this.lastNeed = 3 - n; - this.lastTotal = 3; - if (n === 1) { - this.lastChar[0] = buf[buf.length - 1]; - } else { - this.lastChar[0] = buf[buf.length - 2]; - this.lastChar[1] = buf[buf.length - 1]; +/** + * Convert a `File` to a lazy readable stream. + * @param {File|Blob} file + * @return {function} + */ +function getBlobStream (file) { + return function () { + return new FileReadStream(file) } - return buf.toString('base64', i, buf.length - n); } -function base64End(buf) { - var r = buf && buf.length ? this.write(buf) : ''; - if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); - return r; +/** + * Convert a `Buffer` to a lazy readable stream. + * @param {Buffer} buffer + * @return {function} + */ +function getBufferStream (buffer) { + return function () { + var s = new stream.PassThrough() + s.end(buffer) + return s + } } -// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) -function simpleWrite(buf) { - return buf.toString(this.encoding); +/** + * Convert a file path to a lazy readable stream. + * @param {string} path + * @return {function} + */ +function getFilePathStream (path) { + return function () { + return fs.createReadStream(path) + } } -function simpleEnd(buf) { - return buf && buf.length ? this.write(buf) : ''; +/** + * Convert a readable stream to a lazy readable stream. Adds instrumentation to track + * the number of bytes in the stream and set `file.length`. + * + * @param {Stream} stream + * @param {Object} file + * @return {function} + */ +function getStreamStream (readable, file) { + return function () { + var counter = new stream.Transform() + counter._transform = function (buf, enc, done) { + file.length += buf.length + this.push(buf) + done() + } + readable.pipe(counter) + return counter + } } -},{"safe-buffer":98}],109:[function(require,module,exports){ -/* -Copyright (c) 2011, Chris Umbel -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) +},{"_process":15,"bencode":71,"block-stream2":80,"buffer":4,"filestream/read":91,"flatten":92,"fs":1,"is-file":99,"junk":102,"multistream":113,"once":115,"path":13,"piece-length":118,"readable-stream":132,"run-parallel":136,"simple-sha1":142,"xtend":171}],87:[function(require,module,exports){ +(function (process){ +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +exports = module.exports = require('./debug'); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ +/** + * Colors. + */ -var base32 = require('./thirty-two'); +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; -exports.encode = base32.encode; -exports.decode = base32.decode; +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ -},{"./thirty-two":110}],110:[function(require,module,exports){ -(function (Buffer){ -/* -Copyright (c) 2011, Chris Umbel +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { + return true; + } -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + // is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -'use strict'; +exports.formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (err) { + return '[UnexpectedJSONParseError]: ' + err.message; + } +}; -var charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; -var byteTable = [ - 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff -]; -function quintetCount(buff) { - var quintets = Math.floor(buff.length / 5); - return buff.length % 5 === 0 ? quintets: quintets + 1; -} +/** + * Colorize log arguments if enabled. + * + * @api public + */ -exports.encode = function(plain) { - if(!Buffer.isBuffer(plain)){ - plain = new Buffer(plain); - } - var i = 0; - var j = 0; - var shiftIndex = 0; - var digit = 0; - var encoded = new Buffer(quintetCount(plain) * 8); +function formatArgs(args) { + var useColors = this.useColors; - /* byte by byte isn't as pretty as quintet by quintet but tests a bit - faster. will have to revisit. */ - while(i < plain.length) { - var current = plain[i]; + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); - if(shiftIndex > 3) { - digit = current & (0xff >> shiftIndex); - shiftIndex = (shiftIndex + 5) % 8; - digit = (digit << shiftIndex) | ((i + 1 < plain.length) ? - plain[i + 1] : 0) >> (8 - shiftIndex); - i++; - } else { - digit = (current >> (8 - (shiftIndex + 5))) & 0x1f; - shiftIndex = (shiftIndex + 5) % 8; - if(shiftIndex === 0) i++; - } + if (!useColors) return; - encoded[j] = charTable.charCodeAt(digit); - j++; - } + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit') - for(i = j; i < encoded.length; i++) { - encoded[i] = 0x3d; //'='.charCodeAt(0) + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; } + }); - return encoded; -}; + args.splice(lastC, 0, c); +} -exports.decode = function(encoded) { - var shiftIndex = 0; - var plainDigit = 0; - var plainChar; - var plainPos = 0; - if(!Buffer.isBuffer(encoded)){ - encoded = new Buffer(encoded); - } - var decoded = new Buffer(Math.ceil(encoded.length * 5 / 8)); +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ - /* byte by byte isn't as pretty as octet by octet but tests a bit - faster. will have to revisit. */ - for(var i = 0; i < encoded.length; i++) { - if(encoded[i] === 0x3d){ //'=' - break; - } +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); +} - var encodedByte = encoded[i] - 0x30; +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ - if(encodedByte < byteTable.length) { - plainDigit = byteTable[encodedByte]; +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} - if(shiftIndex <= 3) { - shiftIndex = (shiftIndex + 5) % 8; +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ - if(shiftIndex === 0) { - plainChar |= plainDigit; - decoded[plainPos] = plainChar; - plainPos++; - plainChar = 0; - } else { - plainChar |= 0xff & (plainDigit << (8 - shiftIndex)); - } - } else { - shiftIndex = (shiftIndex + 5) % 8; - plainChar |= 0xff & (plainDigit >>> shiftIndex); - decoded[plainPos] = plainChar; - plainPos++; +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} - plainChar = 0xff & (plainDigit << (8 - shiftIndex)); - } - } else { - throw new Error('Invalid input - it is not base32 encoded string'); - } - } + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } - return decoded.slice(0, plainPos); -}; + return r; +} -}).call(this,require("buffer").Buffer) -},{"buffer":159}],111:[function(require,module,exports){ -var Buffer = require('buffer').Buffer +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ -module.exports = function (buf) { - // If the buffer is backed by a Uint8Array, a faster version will work - if (buf instanceof Uint8Array) { - // If the buffer isn't a subarray, return the underlying ArrayBuffer - if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) { - return buf.buffer - } else if (typeof buf.buffer.slice === 'function') { - // Otherwise we need to get a proper copy - return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) - } - } +exports.enable(load()); - if (Buffer.isBuffer(buf)) { - // This is the slow version that will work with any Buffer - // implementation (even in old browsers) - var arrayCopy = new Uint8Array(buf.length) - var len = buf.length - for (var i = 0; i < len; i++) { - arrayCopy[i] = buf[i] - } - return arrayCopy.buffer - } else { - throw new Error('Argument must be a Buffer') - } +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + return window.localStorage; + } catch (e) {} } -},{"buffer":159}],112:[function(require,module,exports){ -(function (process){ -module.exports = Discovery +}).call(this,require('_process')) +},{"./debug":88,"_process":15}],88:[function(require,module,exports){ -var debug = require('debug')('torrent-discovery') -var DHT = require('bittorrent-dht/client') // empty object in browser -var EventEmitter = require('events').EventEmitter -var extend = require('xtend') -var inherits = require('inherits') -var parallel = require('run-parallel') -var Tracker = require('bittorrent-tracker/client') +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ -inherits(Discovery, EventEmitter) +exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = require('ms'); -function Discovery (opts) { - var self = this - if (!(self instanceof Discovery)) return new Discovery(opts) - EventEmitter.call(self) +/** + * The currently active debug mode names, and names to skip. + */ - if (!opts.peerId) throw new Error('Option `peerId` is required') - if (!opts.infoHash) throw new Error('Option `infoHash` is required') - if (!process.browser && !opts.port) throw new Error('Option `port` is required') +exports.names = []; +exports.skips = []; - self.peerId = typeof opts.peerId === 'string' - ? opts.peerId - : opts.peerId.toString('hex') - self.infoHash = typeof opts.infoHash === 'string' - ? opts.infoHash - : opts.infoHash.toString('hex') - self._port = opts.port // torrent port - self._userAgent = opts.userAgent // User-Agent header for http requests +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ - self.destroyed = false +exports.formatters = {}; - self._announce = opts.announce || [] - self._intervalMs = opts.intervalMs || (15 * 60 * 1000) - self._trackerOpts = null - self._dhtAnnouncing = false - self._dhtTimeout = false - self._internalDHT = false // is the DHT created internally? +/** + * Previous log timestamp. + */ - self._onWarning = function (err) { - self.emit('warning', err) - } - self._onError = function (err) { - self.emit('error', err) - } - self._onDHTPeer = function (peer, infoHash) { - if (infoHash.toString('hex') !== self.infoHash) return - self.emit('peer', peer.host + ':' + peer.port, 'dht') - } - self._onTrackerPeer = function (peer) { - self.emit('peer', peer, 'tracker') - } - self._onTrackerAnnounce = function () { - self.emit('trackerAnnounce') - } +var prevTime; - if (opts.tracker === false) { - self.tracker = null - } else if (opts.tracker && typeof opts.tracker === 'object') { - self._trackerOpts = extend(opts.tracker) - self.tracker = self._createTracker() - } else { - self.tracker = self._createTracker() - } +/** + * Select a color. + * @param {String} namespace + * @return {Number} + * @api private + */ - if (opts.dht === false || typeof DHT !== 'function') { - self.dht = null - } else if (opts.dht && typeof opts.dht.addNode === 'function') { - self.dht = opts.dht - } else if (opts.dht && typeof opts.dht === 'object') { - self.dht = createDHT(opts.dhtPort, opts.dht) - } else { - self.dht = createDHT(opts.dhtPort) - } +function selectColor(namespace) { + var hash = 0, i; - if (self.dht) { - self.dht.on('peer', self._onDHTPeer) - self._dhtAnnounce() + for (i in namespace) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer } - function createDHT (port, opts) { - var dht = new DHT(opts) - dht.on('warning', self._onWarning) - dht.on('error', self._onError) - dht.listen(port) - self._internalDHT = true - return dht - } + return exports.colors[Math.abs(hash) % exports.colors.length]; } -Discovery.prototype.updatePort = function (port) { - var self = this - if (port === self._port) return - self._port = port - - if (self.dht) self._dhtAnnounce() - - if (self.tracker) { - self.tracker.stop() - self.tracker.destroy(function () { - self.tracker = self._createTracker() - }) - } -} - -Discovery.prototype.complete = function (opts) { - if (this.tracker) { - this.tracker.complete(opts) - } -} - -Discovery.prototype.destroy = function (cb) { - var self = this - if (self.destroyed) return - self.destroyed = true - - clearTimeout(self._dhtTimeout) - - var tasks = [] +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ - if (self.tracker) { - self.tracker.stop() - self.tracker.removeListener('warning', self._onWarning) - self.tracker.removeListener('error', self._onError) - self.tracker.removeListener('peer', self._onTrackerPeer) - self.tracker.removeListener('update', self._onTrackerAnnounce) - tasks.push(function (cb) { - self.tracker.destroy(cb) - }) - } +function createDebug(namespace) { - if (self.dht) { - self.dht.removeListener('peer', self._onDHTPeer) - } + function debug() { + // disabled? + if (!debug.enabled) return; - if (self._internalDHT) { - self.dht.removeListener('warning', self._onWarning) - self.dht.removeListener('error', self._onError) - tasks.push(function (cb) { - self.dht.destroy(cb) - }) - } + var self = debug; - parallel(tasks, cb) + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; - // cleanup - self.dht = null - self.tracker = null - self._announce = null -} + // turn the `arguments` into a proper Array + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } -Discovery.prototype._createTracker = function () { - var opts = extend(this._trackerOpts, { - infoHash: this.infoHash, - announce: this._announce, - peerId: this.peerId, - port: this._port, - userAgent: this._userAgent - }) + args[0] = exports.coerce(args[0]); - var tracker = new Tracker(opts) - tracker.on('warning', this._onWarning) - tracker.on('error', this._onError) - tracker.on('peer', this._onTrackerPeer) - tracker.on('update', this._onTrackerAnnounce) - tracker.setInterval(this._intervalMs) - tracker.start() - return tracker -} + if ('string' !== typeof args[0]) { + // anything else let's inspect with %O + args.unshift('%O'); + } -Discovery.prototype._dhtAnnounce = function () { - var self = this - if (self._dhtAnnouncing) return - debug('dht announce') + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); - self._dhtAnnouncing = true - clearTimeout(self._dhtTimeout) + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); - self.dht.announce(self.infoHash, self._port, function (err) { - self._dhtAnnouncing = false - debug('dht announce complete') + // apply env-specific formatting (colors, etc.) + exports.formatArgs.call(self, args); - if (err) self.emit('warning', err) - self.emit('dhtAnnounce') + var logFn = debug.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } - if (!self.destroyed) { - self._dhtTimeout = setTimeout(function () { - self._dhtAnnounce() - }, getRandomTimeout()) - if (self._dhtTimeout.unref) self._dhtTimeout.unref() - } - }) + debug.namespace = namespace; + debug.enabled = exports.enabled(namespace); + debug.useColors = exports.useColors(); + debug.color = selectColor(namespace); - // Returns timeout interval, with some random jitter - function getRandomTimeout () { - return self._intervalMs + Math.floor(Math.random() * self._intervalMs / 5) + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); } + + return debug; } -}).call(this,require('_process')) -},{"_process":170,"bittorrent-dht/client":158,"bittorrent-tracker/client":39,"debug":27,"events":162,"inherits":58,"run-parallel":96,"xtend":131}],113:[function(require,module,exports){ -(function (Buffer){ -module.exports = Piece +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ -var BLOCK_LENGTH = 1 << 14 +function enable(namespaces) { + exports.save(namespaces); -function Piece (length) { - if (!(this instanceof Piece)) return new Piece(length) + exports.names = []; + exports.skips = []; - this.length = length - this.missing = length - this.sources = null + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; - this._chunks = Math.ceil(length / BLOCK_LENGTH) - this._remainder = (length % BLOCK_LENGTH) || BLOCK_LENGTH - this._buffered = 0 - this._buffer = null - this._cancellations = null - this._reservations = 0 - this._flushed = false + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } } -Piece.BLOCK_LENGTH = BLOCK_LENGTH +/** + * Disable debug output. + * + * @api public + */ -Piece.prototype.chunkLength = function (i) { - return i === this._chunks - 1 ? this._remainder : BLOCK_LENGTH +function disable() { + exports.enable(''); } -Piece.prototype.chunkLengthRemaining = function (i) { - return this.length - (i * BLOCK_LENGTH) -} +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ -Piece.prototype.chunkOffset = function (i) { - return i * BLOCK_LENGTH +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; } -Piece.prototype.reserve = function () { - if (!this.init()) return -1 - if (this._cancellations.length) return this._cancellations.pop() - if (this._reservations < this._chunks) return this._reservations++ - return -1 -} +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ -Piece.prototype.reserveRemaining = function () { - if (!this.init()) return -1 - if (this._reservations < this._chunks) { - var min = this._reservations - this._reservations = this._chunks - return min - } - return -1 +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; } -Piece.prototype.cancel = function (i) { - if (!this.init()) return - this._cancellations.push(i) -} +},{"ms":112}],89:[function(require,module,exports){ +module.exports = function () { + for (var i = 0; i < arguments.length; i++) { + if (arguments[i] !== undefined) return arguments[i]; + } +}; -Piece.prototype.cancelRemaining = function (i) { - if (!this.init()) return - this._reservations = i -} +},{}],90:[function(require,module,exports){ +var once = require('once'); -Piece.prototype.get = function (i) { - if (!this.init()) return null - return this._buffer[i] -} +var noop = function() {}; -Piece.prototype.set = function (i, data, source) { - if (!this.init()) return false - var len = data.length - var blocks = Math.ceil(len / BLOCK_LENGTH) - for (var j = 0; j < blocks; j++) { - if (!this._buffer[i + j]) { - var offset = j * BLOCK_LENGTH - var splitData = data.slice(offset, offset + BLOCK_LENGTH) - this._buffered++ - this._buffer[i + j] = splitData - this.missing -= splitData.length - if (this.sources.indexOf(source) === -1) { - this.sources.push(source) - } - } - } - return this._buffered === this._chunks -} +var isRequest = function(stream) { + return stream.setHeader && typeof stream.abort === 'function'; +}; -Piece.prototype.flush = function () { - if (!this._buffer || this._chunks !== this._buffered) return null - var buffer = Buffer.concat(this._buffer, this.length) - this._buffer = null - this._cancellations = null - this.sources = null - this._flushed = true - return buffer -} +var isChildProcess = function(stream) { + return stream.stdio && Array.isArray(stream.stdio) && stream.stdio.length === 3 +}; -Piece.prototype.init = function () { - if (this._flushed) return false - if (this._buffer) return true - this._buffer = new Array(this._chunks) - this._cancellations = [] - this.sources = [] - return true -} +var eos = function(stream, opts, callback) { + if (typeof opts === 'function') return eos(stream, null, opts); + if (!opts) opts = {}; -}).call(this,require("buffer").Buffer) -},{"buffer":159}],114:[function(require,module,exports){ -(function (Buffer){ -/** - * Convert a typed array to a Buffer without a copy - * - * Author: Feross Aboukhadijeh - * License: MIT - * - * `npm install typedarray-to-buffer` - */ + callback = once(callback || noop); -var isTypedArray = require('is-typedarray').strict + var ws = stream._writableState; + var rs = stream._readableState; + var readable = opts.readable || (opts.readable !== false && stream.readable); + var writable = opts.writable || (opts.writable !== false && stream.writable); -module.exports = function typedarrayToBuffer (arr) { - if (isTypedArray(arr)) { - // To avoid a copy, use the typed array's underlying ArrayBuffer to back new Buffer - var buf = new Buffer(arr.buffer) - if (arr.byteLength !== arr.buffer.byteLength) { - // Respect the "view", i.e. byteOffset and byteLength, without doing a copy - buf = buf.slice(arr.byteOffset, arr.byteOffset + arr.byteLength) - } - return buf - } else { - // Pass through all other types to the `Buffer` constructor - return new Buffer(arr) - } -} + var onlegacyfinish = function() { + if (!stream.writable) onfinish(); + }; -}).call(this,require("buffer").Buffer) -},{"buffer":159,"is-typedarray":61}],115:[function(require,module,exports){ -(function (Buffer){ -var UINT_32_MAX = 0xffffffff + var onfinish = function() { + writable = false; + if (!readable) callback.call(stream); + }; -exports.encodingLength = function () { - return 8 -} + var onend = function() { + readable = false; + if (!writable) callback.call(stream); + }; -exports.encode = function (num, buf, offset) { - if (!buf) buf = new Buffer(8) - if (!offset) offset = 0 + var onexit = function(exitCode) { + callback.call(stream, exitCode ? new Error('exited with error code: ' + exitCode) : null); + }; - var top = Math.floor(num / UINT_32_MAX) - var rem = num - top * UINT_32_MAX + var onclose = function() { + if (readable && !(rs && rs.ended)) return callback.call(stream, new Error('premature close')); + if (writable && !(ws && ws.ended)) return callback.call(stream, new Error('premature close')); + }; - buf.writeUInt32BE(top, offset) - buf.writeUInt32BE(rem, offset + 4) - return buf -} + var onrequest = function() { + stream.req.on('finish', onfinish); + }; -exports.decode = function (buf, offset) { - if (!offset) offset = 0 + if (isRequest(stream)) { + stream.on('complete', onfinish); + stream.on('abort', onclose); + if (stream.req) onrequest(); + else stream.on('request', onrequest); + } else if (writable && !ws) { // legacy streams + stream.on('end', onlegacyfinish); + stream.on('close', onlegacyfinish); + } - if (!buf) buf = new Buffer(4) - if (!offset) offset = 0 + if (isChildProcess(stream)) stream.on('exit', onexit); - var top = buf.readUInt32BE(offset) - var rem = buf.readUInt32BE(offset + 4) + stream.on('end', onend); + stream.on('finish', onfinish); + if (opts.error !== false) stream.on('error', callback); + stream.on('close', onclose); - return top * UINT_32_MAX + rem -} + return function() { + stream.removeListener('complete', onfinish); + stream.removeListener('abort', onclose); + stream.removeListener('request', onrequest); + if (stream.req) stream.req.removeListener('finish', onfinish); + stream.removeListener('end', onlegacyfinish); + stream.removeListener('close', onlegacyfinish); + stream.removeListener('finish', onfinish); + stream.removeListener('exit', onexit); + stream.removeListener('end', onend); + stream.removeListener('error', callback); + stream.removeListener('close', onclose); + }; +}; -exports.encode.bytes = 8 -exports.decode.bytes = 8 +module.exports = eos; -}).call(this,require("buffer").Buffer) -},{"buffer":159}],116:[function(require,module,exports){ -"use strict" +},{"once":115}],91:[function(require,module,exports){ +var Readable = require('readable-stream').Readable; +var inherits = require('inherits'); +var reExtension = /^.*\.(\w+)$/; +var toBuffer = require('typedarray-to-buffer'); -function unique_pred(list, compare) { - var ptr = 1 - , len = list.length - , a=list[0], b=list[0] - for(var i=1; i= arr.length || i < 0) return - var last = arr.pop() - if (i < arr.length) { - var tmp = arr[i] - arr[i] = last - return tmp +FileReadStream.prototype._read = function() { + if (!this._ready) { + this.once('_ready', this._read.bind(this)); + return; } - return last -} + var readStream = this; + var reader = this.reader; -},{}],118:[function(require,module,exports){ -var bencode = require('bencode') -var BitField = require('bitfield') -var Buffer = require('safe-buffer').Buffer -var debug = require('debug')('ut_metadata') -var EventEmitter = require('events').EventEmitter -var inherits = require('inherits') -var sha1 = require('simple-sha1') + var startOffset = this._offset; + var endOffset = this._offset + this._chunkSize; + if (endOffset > this._size) endOffset = this._size; -var MAX_METADATA_SIZE = 10000000 // 10MB -var BITFIELD_GROW = 1000 -var PIECE_LENGTH = 16 * 1024 + if (startOffset === this._size) { + this.destroy(); + this.push(null); + return; + } -module.exports = function (metadata) { - inherits(utMetadata, EventEmitter) + reader.onload = function() { + // update the stream offset + readStream._offset = endOffset; - function utMetadata (wire) { - EventEmitter.call(this) + // get the data chunk + readStream.push(toBuffer(reader.result)); + } + reader.onerror = function() { + readStream.emit('error', reader.error); + } - this._wire = wire + reader.readAsArrayBuffer(this._file.slice(startOffset, endOffset)); +}; - this._metadataComplete = false - this._metadataSize = null - this._remainingRejects = null // how many reject messages to tolerate before quitting - this._fetching = false +FileReadStream.prototype.destroy = function() { + this._file = null; + if (this.reader) { + this.reader.onload = null; + this.reader.onerror = null; + try { this.reader.abort(); } catch (e) {}; + } + this.reader = null; +} - // The largest .torrent file that I know of is ~1-2MB, which is ~100 pieces. - // Therefore, cap the bitfield to 10x that (1000 pieces) so a malicious peer can't - // make it grow to fill all memory. - this._bitfield = new BitField(0, { grow: BITFIELD_GROW }) +},{"inherits":96,"readable-stream":132,"typedarray-to-buffer":154}],92:[function(require,module,exports){ +module.exports = function flatten(list, depth) { + depth = (typeof depth == 'number') ? depth : Infinity; - if (Buffer.isBuffer(metadata)) { - this.setMetadata(metadata) + if (!depth) { + if (Array.isArray(list)) { + return list.map(function(i) { return i; }); } + return list; } - // Name of the bittorrent-protocol extension - utMetadata.prototype.name = 'ut_metadata' + return _flatten(list, 1); - utMetadata.prototype.onHandshake = function (infoHash, peerId, extensions) { - this._infoHash = infoHash + function _flatten(list, d) { + return list.reduce(function (acc, item) { + if (Array.isArray(item) && d < depth) { + return acc.concat(_flatten(item, d + 1)); + } + else { + return acc.concat(item); + } + }, []); } +}; - utMetadata.prototype.onExtendedHandshake = function (handshake) { - if (!handshake.m || !handshake.m.ut_metadata) { - return this.emit('warning', new Error('Peer does not support ut_metadata')) - } - if (!handshake.metadata_size) { - return this.emit('warning', new Error('Peer does not have metadata')) - } - if (typeof handshake.metadata_size !== 'number' || - MAX_METADATA_SIZE < handshake.metadata_size || - handshake.metadata_size <= 0) { - return this.emit('warning', new Error('Peer gave invalid metadata size')) - } - - this._metadataSize = handshake.metadata_size - this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH) - this._remainingRejects = this._numPieces * 2 +},{}],93:[function(require,module,exports){ +// originally pulled out of simple-peer - if (this._fetching) { - this._requestPieces() - } +module.exports = function getBrowserRTC () { + if (typeof window === 'undefined') return null + var wrtc = { + RTCPeerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection || + window.webkitRTCPeerConnection, + RTCSessionDescription: window.RTCSessionDescription || + window.mozRTCSessionDescription || window.webkitRTCSessionDescription, + RTCIceCandidate: window.RTCIceCandidate || window.mozRTCIceCandidate || + window.webkitRTCIceCandidate } + if (!wrtc.RTCPeerConnection) return null + return wrtc +} - utMetadata.prototype.onMessage = function (buf) { - var dict, trailer - try { - var str = buf.toString() - var trailerIndex = str.indexOf('ee') + 2 - dict = bencode.decode(str.substring(0, trailerIndex)) - trailer = buf.slice(trailerIndex) - } catch (err) { - // drop invalid messages - return - } +},{}],94:[function(require,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] - switch (dict.msg_type) { - case 0: - // ut_metadata request (from peer) - // example: { 'msg_type': 0, 'piece': 0 } - this._onRequest(dict.piece) - break - case 1: - // ut_metadata data (in response to our request) - // example: { 'msg_type': 1, 'piece': 0, 'total_size': 3425 } - this._onData(dict.piece, trailer, dict.total_size) - break - case 2: - // ut_metadata reject (peer doesn't have piece we requested) - // { 'msg_type': 2, 'piece': 0 } - this._onReject(dict.piece) - break - } + 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) +} - /** - * Ask the peer to send metadata. - * @public - */ - utMetadata.prototype.fetch = function () { - if (this._metadataComplete) { - return +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 } - this._fetching = true - if (this._metadataSize) { - this._requestPieces() + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 } - } - /** - * Stop asking the peer to send metadata. - * @public - */ - utMetadata.prototype.cancel = function () { - this._fetching = false + 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 + } } - utMetadata.prototype.setMetadata = function (metadata) { - if (this._metadataComplete) return true - debug('set metadata') + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} - // if full torrent dictionary was passed in, pull out just `info` key - try { - var info = bencode.decode(metadata).info - if (info) { - metadata = bencode.encode(info) - } - } catch (err) {} + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} - // check hash - if (this._infoHash && this._infoHash !== sha1.sync(metadata)) { - return false - } + buffer[offset + i - d] |= s * 128 +} - this.cancel() +},{}],95:[function(require,module,exports){ +(function (process){ +module.exports = ImmediateStore - this.metadata = metadata - this._metadataComplete = true - this._metadataSize = this.metadata.length - this._wire.extendedHandshake.metadata_size = this._metadataSize +function ImmediateStore (store) { + if (!(this instanceof ImmediateStore)) return new ImmediateStore(store) - this.emit('metadata', bencode.encode({ info: bencode.decode(this.metadata) })) + this.store = store + this.chunkLength = store.chunkLength - return true - } - - utMetadata.prototype._send = function (dict, trailer) { - var buf = bencode.encode(dict) - if (Buffer.isBuffer(trailer)) { - buf = Buffer.concat([buf, trailer]) - } - this._wire.extended('ut_metadata', buf) - } - - utMetadata.prototype._request = function (piece) { - this._send({ msg_type: 0, piece: piece }) - } - - utMetadata.prototype._data = function (piece, buf, totalSize) { - var msg = { msg_type: 1, piece: piece } - if (typeof totalSize === 'number') { - msg.total_size = totalSize - } - this._send(msg, buf) - } - - utMetadata.prototype._reject = function (piece) { - this._send({ msg_type: 2, piece: piece }) - } - - utMetadata.prototype._onRequest = function (piece) { - if (!this._metadataComplete) { - this._reject(piece) - return - } - var start = piece * PIECE_LENGTH - var end = start + PIECE_LENGTH - if (end > this._metadataSize) { - end = this._metadataSize - } - var buf = this.metadata.slice(start, end) - this._data(piece, buf, this._metadataSize) - } - - utMetadata.prototype._onData = function (piece, buf, totalSize) { - if (buf.length > PIECE_LENGTH) { - return - } - buf.copy(this.metadata, piece * PIECE_LENGTH) - this._bitfield.set(piece) - this._checkDone() + if (!this.store || !this.store.get || !this.store.put) { + throw new Error('First argument must be abstract-chunk-store compliant') } - utMetadata.prototype._onReject = function (piece) { - if (this._remainingRejects > 0 && this._fetching) { - // If we haven't been rejected too much, then try to request the piece again - this._request(piece) - this._remainingRejects -= 1 - } else { - this.emit('warning', new Error('Peer sent "reject" too much')) - } - } + this.mem = [] +} - utMetadata.prototype._requestPieces = function () { - this.metadata = Buffer.alloc(this._metadataSize) - for (var piece = 0; piece < this._numPieces; piece++) { - this._request(piece) - } - } +ImmediateStore.prototype.put = function (index, buf, cb) { + var self = this + self.mem[index] = buf + self.store.put(index, buf, function (err) { + self.mem[index] = null + if (cb) cb(err) + }) +} - utMetadata.prototype._checkDone = function () { - var done = true - for (var piece = 0; piece < this._numPieces; piece++) { - if (!this._bitfield.get(piece)) { - done = false - break - } - } - if (!done) return +ImmediateStore.prototype.get = function (index, opts, cb) { + if (typeof opts === 'function') return this.get(index, null, opts) - // attempt to set metadata -- may fail sha1 check - var success = this.setMetadata(this.metadata) + var start = (opts && opts.offset) || 0 + var end = opts && opts.length && (start + opts.length) - if (!success) { - this._failedMetadata() - } - } + var buf = this.mem[index] + if (buf) return nextTick(cb, null, opts ? buf.slice(start, end) : buf) - utMetadata.prototype._failedMetadata = function () { - // reset bitfield & try again - this._bitfield = new BitField(0, { grow: BITFIELD_GROW }) - this._remainingRejects -= this._numPieces - if (this._remainingRejects > 0) { - this._requestPieces() - } else { - this.emit('warning', new Error('Peer sent invalid metadata')) - } - } + this.store.get(index, opts, cb) +} - return utMetadata +ImmediateStore.prototype.close = function (cb) { + this.store.close(cb) } -},{"bencode":35,"bitfield":37,"debug":27,"events":162,"inherits":58,"safe-buffer":98,"simple-sha1":102}],119:[function(require,module,exports){ -(function (global){ +ImmediateStore.prototype.destroy = function (cb) { + this.store.destroy(cb) +} -/** - * Module exports. - */ +function nextTick (cb, err, val) { + process.nextTick(function () { + if (cb) cb(err, val) + }) +} -module.exports = deprecate; +}).call(this,require('_process')) +},{"_process":15}],96:[function(require,module,exports){ +arguments[4][10][0].apply(exports,arguments) +},{"dup":10}],97:[function(require,module,exports){ +/* (c) 2016 Ari Porad (@ariporad) . License: ariporad.mit-license.org */ -/** - * Mark that a method should not be used. - * Returns a modified function which warns once by default. - * - * If `localStorage.noDeprecation = true` is set, then it is a no-op. - * - * If `localStorage.throwDeprecation = true` is set, then deprecated functions - * will throw an Error when invoked. - * - * If `localStorage.traceDeprecation = true` is set, then deprecated functions - * will invoke `console.trace()` instead of `console.error()`. - * - * @param {Function} fn - the function to deprecate - * @param {String} msg - the string to print to the console when `fn` is invoked - * @returns {Function} a new "deprecated" version of `fn` - * @api public - */ +// Partially from http://stackoverflow.com/a/94049/1928484, and from another SO answer, which told me that the highest +// char code that's ascii is 127, but I can't find the link for. Sorry. -function deprecate (fn, msg) { - if (config('noDeprecation')) { - return fn; - } +var MAX_ASCII_CHAR_CODE = 127; - var warned = false; - function deprecated() { - if (!warned) { - if (config('throwDeprecation')) { - throw new Error(msg); - } else if (config('traceDeprecation')) { - console.trace(msg); - } else { - console.warn(msg); - } - warned = true; - } - return fn.apply(this, arguments); +module.exports = function isAscii(str) { + for (var i = 0, strLen = str.length; i < strLen; ++i) { + if (str.charCodeAt(i) > MAX_ASCII_CHAR_CODE) return false; } + return true; +}; - return deprecated; -} - -/** - * Checks `localStorage` for boolean values for the given `name`. +},{}],98:[function(require,module,exports){ +/*! + * Determine if an object is a Buffer * - * @param {String} name - * @returns {Boolean} - * @api private + * @author Feross Aboukhadijeh + * @license MIT */ -function config (name) { - // accessing global.localStorage can trigger a DOMException in sandboxed iframes - try { - if (!global.localStorage) return false; - } catch (_) { - return false; - } - var val = global.localStorage[name]; - if (null == val) return false; - return String(val).toLowerCase() === 'true'; +// The _isBuffer check is for Safari 5-7 support, because it's missing +// Object.prototype.constructor. Remove this eventually +module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) } -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],120:[function(require,module,exports){ -(function (Buffer){ -var bs = require('binary-search') -var EventEmitter = require('events').EventEmitter -var inherits = require('inherits') -var mp4 = require('mp4-stream') -var Box = require('mp4-box-encoding') -var RangeSliceStream = require('range-slice-stream') - -module.exports = MP4Remuxer +function isBuffer (obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} -function MP4Remuxer (file) { - var self = this - EventEmitter.call(self) - self._tracks = [] - self._fragmentSequence = 1 - self._file = file - self._decoder = null - self._findMoov(0) +// For Node v0.10 support. Remove this eventually. +function isSlowBuffer (obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) } -inherits(MP4Remuxer, EventEmitter) +},{}],99:[function(require,module,exports){ +'use strict'; -MP4Remuxer.prototype._findMoov = function (offset) { - var self = this +var fs = require('fs'); - if (self._decoder) { - self._decoder.destroy() - } +module.exports = function isFile(path, cb){ + if(!cb)return isFileSync(path); - self._decoder = mp4.decode() - var fileStream = self._file.createReadStream({ - start: offset - }) - fileStream.pipe(self._decoder) + fs.stat(path, function(err, stats){ + if(err)return cb(err); + return cb(null, stats.isFile()); + }); +}; - self._decoder.once('box', function (headers) { - if (headers.type === 'moov') { - self._decoder.decode(function (moov) { - fileStream.destroy() - try { - self._processMoov(moov) - } catch (err) { - err.message = 'Cannot parse mp4 file: ' + err.message - self.emit('error', err) - } - }) - } else { - fileStream.destroy() - self._findMoov(offset + headers.length) - } - }) -} +module.exports.sync = isFileSync; -function RunLengthIndex (entries, countName) { - var self = this - self._entries = entries - self._countName = countName || 'count' - self._index = 0 - self._offset = 0 - - self.value = self._entries[0] +function isFileSync(path){ + return fs.existsSync(path) && fs.statSync(path).isFile(); } -RunLengthIndex.prototype.inc = function () { - var self = this - self._offset++ - if (self._offset >= self._entries[self._index][self._countName]) { - self._index++ - self._offset = 0 - } +},{"fs":1}],100:[function(require,module,exports){ +module.exports = isTypedArray +isTypedArray.strict = isStrictTypedArray +isTypedArray.loose = isLooseTypedArray - self.value = self._entries[self._index] +var toString = Object.prototype.toString +var names = { + '[object Int8Array]': true + , '[object Int16Array]': true + , '[object Int32Array]': true + , '[object Uint8Array]': true + , '[object Uint8ClampedArray]': true + , '[object Uint16Array]': true + , '[object Uint32Array]': true + , '[object Float32Array]': true + , '[object Float64Array]': true } -MP4Remuxer.prototype._processMoov = function (moov) { - var self = this +function isTypedArray(arr) { + return ( + isStrictTypedArray(arr) + || isLooseTypedArray(arr) + ) +} - var traks = moov.traks - self._tracks = [] - self._hasVideo = false - self._hasAudio = false - for (var i = 0; i < traks.length; i++) { - var trak = traks[i] - var stbl = trak.mdia.minf.stbl - var stsdEntry = stbl.stsd.entries[0] - var handlerType = trak.mdia.hdlr.handlerType - var codec - var mime - if (handlerType === 'vide' && stsdEntry.type === 'avc1') { - if (self._hasVideo) { - continue - } - self._hasVideo = true - codec = 'avc1' - if (stsdEntry.avcC) { - codec += '.' + stsdEntry.avcC.mimeCodec - } - mime = 'video/mp4; codecs="' + codec + '"' - } else if (handlerType === 'soun' && stsdEntry.type === 'mp4a') { - if (self._hasAudio) { - continue - } - self._hasAudio = true - codec = 'mp4a' - if (stsdEntry.esds && stsdEntry.esds.mimeCodec) { - codec += '.' + stsdEntry.esds.mimeCodec - } - mime = 'audio/mp4; codecs="' + codec + '"' - } else { - continue - } +function isStrictTypedArray(arr) { + return ( + arr instanceof Int8Array + || arr instanceof Int16Array + || arr instanceof Int32Array + || arr instanceof Uint8Array + || arr instanceof Uint8ClampedArray + || arr instanceof Uint16Array + || arr instanceof Uint32Array + || arr instanceof Float32Array + || arr instanceof Float64Array + ) +} - var samples = [] - var sample = 0 +function isLooseTypedArray(arr) { + return names[toString.call(arr)] +} - // Chunk/position data - var sampleInChunk = 0 - var chunk = 0 - var offsetInChunk = 0 - var sampleToChunkIndex = 0 +},{}],101:[function(require,module,exports){ +arguments[4][12][0].apply(exports,arguments) +},{"dup":12}],102:[function(require,module,exports){ +'use strict'; - // Time data - var dts = 0 - var decodingTimeEntry = new RunLengthIndex(stbl.stts.entries) - var presentationOffsetEntry = null - if (stbl.ctts) { - presentationOffsetEntry = new RunLengthIndex(stbl.ctts.entries) - } +// # All +// /^npm-debug\.log$/, // npm error log +// /^\..*\.swp$/, // Vim state - // Sync table index - var syncSampleIndex = 0 +// # macOS +// /^\.DS_Store$/, // Stores custom folder attributes +// /^\.AppleDouble$/, // Stores additional file resources +// /^\.LSOverride$/, // Contains the absolute path to the app to be used +// /^Icon\r$/, // Custom Finder icon: http://superuser.com/questions/298785/icon-file-on-os-x-desktop +// /^\._.*/, // Thumbnail +// /^\.Spotlight-V100(?:$|\/)/, // Directory that might appear on external disk +// /\.Trashes/, // File that might appear on external disk +// /^__MACOSX$/, // Resource fork - while (true) { - var currChunkEntry = stbl.stsc.entries[sampleToChunkIndex] +// # Linux +// /~$/, // Backup file - // Compute size - var size = stbl.stsz.entries[sample] +// # Windows +// /^Thumbs\.db$/, // Image file cache +// /^ehthumbs\.db$/, // Folder config file +// /^Desktop\.ini$/ // Stores custom folder attributes +// /^@eaDir$/ // Synology Diskstation "hidden" folder where the server stores thumbnails - // Compute time data - var duration = decodingTimeEntry.value.duration - var presentationOffset = presentationOffsetEntry ? presentationOffsetEntry.value.compositionOffset : 0 +exports.regex = exports.re = /^npm-debug\.log$|^\..*\.swp$|^\.DS_Store$|^\.AppleDouble$|^\.LSOverride$|^Icon\r$|^\._.*|^\.Spotlight-V100(?:$|\/)|\.Trashes|^__MACOSX$|~$|^Thumbs\.db$|^ehthumbs\.db$|^Desktop\.ini$|^@eaDir$/; - // Compute sync - var sync = true - if (stbl.stss) { - sync = stbl.stss.entries[syncSampleIndex] === sample + 1 - } +exports.is = filename => exports.re.test(filename); - // Create new sample entry - samples.push({ - size: size, - duration: duration, - dts: dts, - presentationOffset: presentationOffset, - sync: sync, - offset: offsetInChunk + stbl.stco.entries[chunk] - }) +exports.not = filename => !exports.is(filename); - // Go to next sample - sample++ - if (sample >= stbl.stsz.entries.length) { - break - } +},{}],103:[function(require,module,exports){ +module.exports = magnetURIDecode +module.exports.decode = magnetURIDecode +module.exports.encode = magnetURIEncode - // Move position/chunk - sampleInChunk++ - offsetInChunk += size - if (sampleInChunk >= currChunkEntry.samplesPerChunk) { - // Move to new chunk - sampleInChunk = 0 - offsetInChunk = 0 - chunk++ - // Move sample to chunk box index - var nextChunkEntry = stbl.stsc.entries[sampleToChunkIndex + 1] - if (nextChunkEntry && chunk + 1 >= nextChunkEntry.firstChunk) { - sampleToChunkIndex++ - } - } +var base32 = require('thirty-two') +var Buffer = require('safe-buffer').Buffer +var extend = require('xtend') +var uniq = require('uniq') - // Move time forward - dts += duration - decodingTimeEntry.inc() - presentationOffsetEntry && presentationOffsetEntry.inc() +/** + * Parse a magnet URI and return an object of keys/values + * + * @param {string} uri + * @return {Object} parsed uri + */ +function magnetURIDecode (uri) { + var result = {} - // Move sync table index - if (sync) { - syncSampleIndex++ - } - } + // Support 'magnet:' and 'stream-magnet:' uris + var data = uri.split('magnet:?')[1] - trak.mdia.mdhd.duration = 0 - trak.tkhd.duration = 0 + var params = (data && data.length >= 0) + ? data.split('&') + : [] - var defaultSampleDescriptionIndex = currChunkEntry.sampleDescriptionId + params.forEach(function (param) { + var keyval = param.split('=') - var trackMoov = { - type: 'moov', - mvhd: moov.mvhd, - traks: [{ - tkhd: trak.tkhd, - mdia: { - mdhd: trak.mdia.mdhd, - hdlr: trak.mdia.hdlr, - elng: trak.mdia.elng, - minf: { - vmhd: trak.mdia.minf.vmhd, - smhd: trak.mdia.minf.smhd, - dinf: trak.mdia.minf.dinf, - stbl: { - stsd: stbl.stsd, - stts: empty(), - ctts: empty(), - stsc: empty(), - stsz: empty(), - stco: empty(), - stss: empty() - } - } - } - }], - mvex: { - mehd: { - fragmentDuration: moov.mvhd.duration - }, - trexs: [{ - trackId: trak.tkhd.trackId, - defaultSampleDescriptionIndex: defaultSampleDescriptionIndex, - defaultSampleDuration: 0, - defaultSampleSize: 0, - defaultSampleFlags: 0 - }] - } - } + // This keyval is invalid, skip it + if (keyval.length !== 2) return - self._tracks.push({ - trackId: trak.tkhd.trackId, - timeScale: trak.mdia.mdhd.timeScale, - samples: samples, - currSample: null, - currTime: null, - moov: trackMoov, - mime: mime - }) - } + var key = keyval[0] + var val = keyval[1] - if (self._tracks.length === 0) { - self.emit('error', new Error('no playable tracks')) - return - } + // Clean up torrent name + if (key === 'dn') val = decodeURIComponent(val).replace(/\+/g, ' ') - // Must be set last since this is used above - moov.mvhd.duration = 0 + // Address tracker (tr), exact source (xs), and acceptable source (as) are encoded + // URIs, so decode them + if (key === 'tr' || key === 'xs' || key === 'as' || key === 'ws') { + val = decodeURIComponent(val) + } - self._ftyp = { - type: 'ftyp', - brand: 'iso5', - brandVersion: 0, - compatibleBrands: [ - 'iso5' - ] - } + // Return keywords as an array + if (key === 'kt') val = decodeURIComponent(val).split('+') - var ftypBuf = Box.encode(self._ftyp) - var data = self._tracks.map(function (track) { - var moovBuf = Box.encode(track.moov) - return { - mime: track.mime, - init: Buffer.concat([ftypBuf, moovBuf]) - } - }) + // Cast file index (ix) to a number + if (key === 'ix') val = Number(val) - self.emit('ready', data) -} + // If there are repeated parameters, return an array of values + if (result[key]) { + if (Array.isArray(result[key])) { + result[key].push(val) + } else { + var old = result[key] + result[key] = [old, val] + } + } else { + result[key] = val + } + }) -function empty () { - return { - version: 0, - flags: 0, - entries: [] - } -} + // Convenience properties for parity with `parse-torrent-file` module + var m + if (result.xt) { + var xts = Array.isArray(result.xt) ? result.xt : [ result.xt ] + xts.forEach(function (xt) { + if ((m = xt.match(/^urn:btih:(.{40})/))) { + result.infoHash = m[1].toLowerCase() + } else if ((m = xt.match(/^urn:btih:(.{32})/))) { + var decodedStr = base32.decode(m[1]) + result.infoHash = Buffer.from(decodedStr, 'binary').toString('hex') + } + }) + } + if (result.infoHash) result.infoHashBuffer = Buffer.from(result.infoHash, 'hex') -MP4Remuxer.prototype.seek = function (time) { - var self = this - if (!self._tracks) { - throw new Error('Not ready yet; wait for \'ready\' event') - } + if (result.dn) result.name = result.dn + if (result.kt) result.keywords = result.kt - if (self._fileStream) { - self._fileStream.destroy() - self._fileStream = null - } + if (typeof result.tr === 'string') result.announce = [ result.tr ] + else if (Array.isArray(result.tr)) result.announce = result.tr + else result.announce = [] - var startOffset = -1 - self._tracks.map(function (track, i) { - // find the keyframe before the time - // stream from there - if (track.outStream) { - track.outStream.destroy() - } - if (track.inStream) { - track.inStream.destroy() - track.inStream = null - } - var outStream = track.outStream = mp4.encode() - var fragment = self._generateFragment(i, time) - if (!fragment) { - return outStream.finalize() - } + result.urlList = [] + if (typeof result.as === 'string' || Array.isArray(result.as)) { + result.urlList = result.urlList.concat(result.as) + } + if (typeof result.ws === 'string' || Array.isArray(result.ws)) { + result.urlList = result.urlList.concat(result.ws) + } - if (startOffset === -1 || fragment.ranges[0].start < startOffset) { - startOffset = fragment.ranges[0].start - } + uniq(result.announce) + uniq(result.urlList) - writeFragment(fragment) + return result +} - function writeFragment (frag) { - if (outStream.destroyed) return - outStream.box(frag.moof, function (err) { - if (err) return self.emit('error', err) - if (outStream.destroyed) return - var slicedStream = track.inStream.slice(frag.ranges) - slicedStream.pipe(outStream.mediaData(frag.length, function (err) { - if (err) return self.emit('error', err) - if (outStream.destroyed) return - var nextFrag = self._generateFragment(i) - if (!nextFrag) { - return outStream.finalize() - } - writeFragment(nextFrag) - })) - }) - } - }) +function magnetURIEncode (obj) { + obj = extend(obj) // clone obj, so we can mutate it - if (startOffset >= 0) { - var fileStream = self._fileStream = self._file.createReadStream({ - start: startOffset - }) + // support using convenience names, in addition to spec names + // (example: `infoHash` for `xt`, `name` for `dn`) + if (obj.infoHashBuffer) obj.xt = 'urn:btih:' + obj.infoHashBuffer.toString('hex') + if (obj.infoHash) obj.xt = 'urn:btih:' + obj.infoHash + if (obj.name) obj.dn = obj.name + if (obj.keywords) obj.kt = obj.keywords + if (obj.announce) obj.tr = obj.announce + if (obj.urlList) { + obj.ws = obj.urlList + delete obj.as + } - self._tracks.forEach(function (track) { - track.inStream = new RangeSliceStream(startOffset, { - // Allow up to a 10MB offset between audio and video, - // which should be fine for any reasonable interleaving - // interval and bitrate - highWaterMark: 10000000 - }) - fileStream.pipe(track.inStream) - }) - } + var result = 'magnet:?' + Object.keys(obj) + .filter(function (key) { + return key.length === 2 + }) + .forEach(function (key, i) { + var values = Array.isArray(obj[key]) ? obj[key] : [ obj[key] ] + values.forEach(function (val, j) { + if ((i > 0 || j > 0) && (key !== 'kt' || j === 0)) result += '&' - return self._tracks.map(function (track) { - return track.outStream - }) -} + if (key === 'dn') val = encodeURIComponent(val).replace(/%20/g, '+') + if (key === 'tr' || key === 'xs' || key === 'as' || key === 'ws') { + val = encodeURIComponent(val) + } + if (key === 'kt') val = encodeURIComponent(val) -MP4Remuxer.prototype._findSampleBefore = function (trackInd, time) { - var self = this + if (key === 'kt' && j > 0) result += '+' + val + else result += key + '=' + val + }) + }) - var track = self._tracks[trackInd] - var scaledTime = Math.floor(track.timeScale * time) - var sample = bs(track.samples, scaledTime, function (sample, t) { - var pts = sample.dts + sample.presentationOffset// - track.editShift - return pts - t - }) - if (sample === -1) { - sample = 0 - } else if (sample < 0) { - sample = -sample - 2 - } - // sample is now the last sample with dts <= time - // Find the preceeding sync sample - while (!track.samples[sample].sync) { - sample-- - } - return sample + return result } -var MIN_FRAGMENT_DURATION = 1 // second +},{"safe-buffer":138,"thirty-two":149,"uniq":156,"xtend":171}],104:[function(require,module,exports){ +module.exports = MediaElementWrapper -MP4Remuxer.prototype._generateFragment = function (track, time) { - var self = this - /* - 1. Find correct sample - 2. Process backward until sync sample found - 3. Process forward until next sync sample after MIN_FRAGMENT_DURATION found - */ - var currTrack = self._tracks[track] - var firstSample - if (time !== undefined) { - firstSample = self._findSampleBefore(track, time) - } else { - firstSample = currTrack.currSample - } +var inherits = require('inherits') +var stream = require('readable-stream') +var toArrayBuffer = require('to-arraybuffer') - if (firstSample >= currTrack.samples.length) - return null +var MediaSource = typeof window !== 'undefined' && window.MediaSource - var startDts = currTrack.samples[firstSample].dts +var DEFAULT_BUFFER_DURATION = 60 // seconds - var totalLen = 0 - var ranges = [] - for (var currSample = firstSample; currSample < currTrack.samples.length; currSample++) { - var sample = currTrack.samples[currSample] - if (sample.sync && sample.dts - startDts >= currTrack.timeScale * MIN_FRAGMENT_DURATION) { - break // This is a reasonable place to end the fragment - } +function MediaElementWrapper (elem, opts) { + var self = this + if (!(self instanceof MediaElementWrapper)) return new MediaElementWrapper(elem, opts) - totalLen += sample.size - var currRange = ranges.length - 1 - if (currRange < 0 || ranges[currRange].end !== sample.offset) { - // Push a new range - ranges.push({ - start: sample.offset, - end: sample.offset + sample.size - }) - } else { - ranges[currRange].end += sample.size - } - } + if (!MediaSource) throw new Error('web browser lacks MediaSource support') - currTrack.currSample = currSample + if (!opts) opts = {} + self._bufferDuration = opts.bufferDuration || DEFAULT_BUFFER_DURATION + self._elem = elem + self._mediaSource = new MediaSource() + self._streams = [] + self.detailedError = null - return { - moof: self._generateMoof(track, firstSample, currSample), - ranges: ranges, - length: totalLen - } -} - -MP4Remuxer.prototype._generateMoof = function (track, firstSample, lastSample) { - var self = this - - var currTrack = self._tracks[track] - - var entries = [] - for (var j = firstSample; j < lastSample; j++) { - var currSample = currTrack.samples[j] - entries.push({ - sampleDuration: currSample.duration, - sampleSize: currSample.size, - sampleFlags: currSample.sync ? 0x2000000 : 0x1010000, - sampleCompositionTimeOffset: currSample.presentationOffset - }) - } + self._errorHandler = function () { + self._elem.removeEventListener('error', self._errorHandler) + var streams = self._streams.slice() + streams.forEach(function (stream) { + stream.destroy(self._elem.error) + }) + } + self._elem.addEventListener('error', self._errorHandler) - var moof = { - type: 'moof', - mfhd: { - sequenceNumber: self._fragmentSequence++ - }, - trafs: [{ - tfhd: { - flags: 0x20000, // default-base-is-moof - trackId: currTrack.trackId - }, - tfdt: { - baseMediaDecodeTime: currTrack.samples[firstSample].dts - }, - trun: { - flags: 0xf01, - dataOffset: 8, // The moof size has to be added to this later as well - entries: entries - } - }] - } + self._elem.src = window.URL.createObjectURL(self._mediaSource) +} - // Update the offset - moof.trafs[0].trun.dataOffset += Box.encodingLength(moof) +/* + * `obj` can be a previous value returned by this function + * or a string + */ +MediaElementWrapper.prototype.createWriteStream = function (obj) { + var self = this - return moof + return new MediaSourceStream(self, obj) } -}).call(this,require("buffer").Buffer) -},{"binary-search":36,"buffer":159,"events":162,"inherits":58,"mp4-box-encoding":69,"mp4-stream":72,"range-slice-stream":83}],121:[function(require,module,exports){ -var MediaElementWrapper = require('mediasource') -var pump = require('pump') +/* + * Use to trigger an error on the underlying media element + */ +MediaElementWrapper.prototype.error = function (err) { + var self = this -var MP4Remuxer = require('./mp4-remuxer') + // be careful not to overwrite any existing detailedError values + if (!self.detailedError) { + self.detailedError = err + } + try { + self._mediaSource.endOfStream('decode') + } catch (err) {} +} -module.exports = VideoStream +inherits(MediaSourceStream, stream.Writable) -function VideoStream (file, mediaElem, opts) { - var self = this - if (!(this instanceof VideoStream)) return new VideoStream(file, mediaElem, opts) - opts = opts || {} +function MediaSourceStream (wrapper, obj) { + var self = this + stream.Writable.call(self) - self.detailedError = null + self._wrapper = wrapper + self._elem = wrapper._elem + self._mediaSource = wrapper._mediaSource + self._allStreams = wrapper._streams + self._allStreams.push(self) + self._bufferDuration = wrapper._bufferDuration + self._sourceBuffer = null - self._elem = mediaElem - self._elemWrapper = new MediaElementWrapper(mediaElem) - self._waitingFired = false - self._trackMeta = null - self._file = file - self._tracks = null - if (self._elem.preload !== 'none') { - self._createMuxer() - } + self._openHandler = function () { + self._onSourceOpen() + } + self._flowHandler = function () { + self._flow() + } - self._onError = function (err) { - self.detailedError = self._elemWrapper.detailedError - self.destroy() // don't pass err though so the user doesn't need to listen for errors - } - self._onWaiting = function () { - self._waitingFired = true - if (!self._muxer) { - self._createMuxer() - } else if (self._tracks) { - self._pump() - } - } - self._elem.addEventListener('waiting', self._onWaiting) - self._elem.addEventListener('error', self._onError) -} + if (typeof obj === 'string') { + self._type = obj + // Need to create a new sourceBuffer + if (self._mediaSource.readyState === 'open') { + self._createSourceBuffer() + } else { + self._mediaSource.addEventListener('sourceopen', self._openHandler) + } + } else if (obj._sourceBuffer === null) { + obj.destroy() + self._type = obj._type // The old stream was created but hasn't finished initializing + self._mediaSource.addEventListener('sourceopen', self._openHandler) + } else if (obj._sourceBuffer) { + obj.destroy() + self._type = obj._type + self._sourceBuffer = obj._sourceBuffer // Copy over the old sourceBuffer + self._sourceBuffer.addEventListener('updateend', self._flowHandler) + } else { + throw new Error('The argument to MediaElementWrapper.createWriteStream must be a string or a previous stream returned from that function') + } -VideoStream.prototype._createMuxer = function () { - var self = this - self._muxer = new MP4Remuxer(self._file) - self._muxer.on('ready', function (data) { - self._tracks = data.map(function (trackData) { - var mediaSource = self._elemWrapper.createWriteStream(trackData.mime) - mediaSource.on('error', function (err) { - self._elemWrapper.error(err) - }) - var track = { - muxed: null, - mediaSource: mediaSource, - initFlushed: false, - onInitFlushed: null - } - mediaSource.write(trackData.init, function (err) { - track.initFlushed = true - if (track.onInitFlushed) { - track.onInitFlushed(err) - } - }) - return track - }) + self._elem.addEventListener('timeupdate', self._flowHandler) - if (self._waitingFired || self._elem.preload === 'auto') { - self._pump() - } - }) + self.on('error', function (err) { + self._wrapper.error(err) + }) - self._muxer.on('error', function (err) { - self._elemWrapper.error(err) - }) + self.on('finish', function () { + if (self.destroyed) return + self._finished = true + if (self._allStreams.every(function (other) { return other._finished })) { + try { + self._mediaSource.endOfStream() + } catch (err) {} + } + }) } -VideoStream.prototype._pump = function () { - var self = this - - var muxed = self._muxer.seek(self._elem.currentTime, !self._tracks) +MediaSourceStream.prototype._onSourceOpen = function () { + var self = this + if (self.destroyed) return - self._tracks.forEach(function (track, i) { - var pumpTrack = function () { - if (track.muxed) { - track.muxed.destroy() - track.mediaSource = self._elemWrapper.createWriteStream(track.mediaSource) - track.mediaSource.on('error', function (err) { - self._elemWrapper.error(err) - }) - } - track.muxed = muxed[i] - pump(track.muxed, track.mediaSource) - } - if (!track.initFlushed) { - track.onInitFlushed = function (err) { - if (err) { - self._elemWrapper.error(err) - return - } - pumpTrack() - } - } else { - pumpTrack() - } - }) + self._mediaSource.removeEventListener('sourceopen', self._openHandler) + self._createSourceBuffer() } -VideoStream.prototype.destroy = function () { - var self = this - if (self.destroyed) { - return - } - self.destroyed = true +MediaSourceStream.prototype.destroy = function (err) { + var self = this + if (self.destroyed) return + self.destroyed = true - self._elem.removeEventListener('waiting', self._onWaiting) - self._elem.removeEventListener('error', self._onError) + // Remove from allStreams + self._allStreams.splice(self._allStreams.indexOf(self), 1) - if (self._tracks) { - self._tracks.forEach(function (track) { - track.muxed.destroy() - }) - } + self._mediaSource.removeEventListener('sourceopen', self._openHandler) + self._elem.removeEventListener('timeupdate', self._flowHandler) + if (self._sourceBuffer) { + self._sourceBuffer.removeEventListener('updateend', self._flowHandler) + if (self._mediaSource.readyState === 'open') { + self._sourceBuffer.abort() + } + } - self._elem.src = '' + if (err) self.emit('error', err) + self.emit('close') } -},{"./mp4-remuxer":120,"mediasource":65,"pump":80}],122:[function(require,module,exports){ -(function (process,global){ -/* global FileList */ - -module.exports = WebTorrent +MediaSourceStream.prototype._createSourceBuffer = function () { + var self = this + if (self.destroyed) return -var Buffer = require('safe-buffer').Buffer -var concat = require('simple-concat') -var createTorrent = require('create-torrent') -var debug = require('debug')('webtorrent') -var DHT = require('bittorrent-dht/client') // browser exclude -var EventEmitter = require('events').EventEmitter -var extend = require('xtend') -var inherits = require('inherits') -var loadIPSet = require('load-ip-set') // browser exclude -var parallel = require('run-parallel') -var parseTorrent = require('parse-torrent') -var path = require('path') -var Peer = require('simple-peer') -var randombytes = require('randombytes') -var speedometer = require('speedometer') -var zeroFill = require('zero-fill') - -var TCPPool = require('./lib/tcp-pool') // browser exclude -var Torrent = require('./lib/torrent') - -/** - * WebTorrent version. - */ -var VERSION = require('./package.json').version + if (MediaSource.isTypeSupported(self._type)) { + self._sourceBuffer = self._mediaSource.addSourceBuffer(self._type) + self._sourceBuffer.addEventListener('updateend', self._flowHandler) + if (self._cb) { + var cb = self._cb + self._cb = null + cb() + } + } else { + self.destroy(new Error('The provided type is not supported')) + } +} -/** - * Version number in Azureus-style. Generated from major and minor semver version. - * For example: - * '0.16.1' -> '0016' - * '1.2.5' -> '0102' - */ -var VERSION_STR = VERSION.match(/([0-9]+)/g) - .slice(0, 2) - .map(function (v) { return zeroFill(2, v) }) - .join('') +MediaSourceStream.prototype._write = function (chunk, encoding, cb) { + var self = this + if (self.destroyed) return + if (!self._sourceBuffer) { + self._cb = function (err) { + if (err) return cb(err) + self._write(chunk, encoding, cb) + } + return + } -/** - * Version prefix string (used in peer ID). WebTorrent uses the Azureus-style - * encoding: '-', two characters for client id ('WW'), four ascii digits for version - * number, '-', followed by random numbers. - * For example: - * '-WW0102-'... - */ -var VERSION_PREFIX = '-WW' + VERSION_STR + '-' + if (self._sourceBuffer.updating) { + return cb(new Error('Cannot append buffer while source buffer updating')) + } -inherits(WebTorrent, EventEmitter) + try { + self._sourceBuffer.appendBuffer(toArrayBuffer(chunk)) + } catch (err) { + // appendBuffer can throw for a number of reasons, most notably when the data + // being appended is invalid or if appendBuffer is called after another error + // already occurred on the media element. In Chrome, there may be useful debugging + // info in chrome://media-internals + self.destroy(err) + return + } + self._cb = cb +} -/** - * WebTorrent Client - * @param {Object=} opts - */ -function WebTorrent (opts) { +MediaSourceStream.prototype._flow = function () { var self = this - if (!(self instanceof WebTorrent)) return new WebTorrent(opts) - EventEmitter.call(self) - if (!opts) opts = {} + if (self.destroyed || !self._sourceBuffer || self._sourceBuffer.updating) { + return + } - if (typeof opts.peerId === 'string') { - self.peerId = opts.peerId - } else if (Buffer.isBuffer(opts.peerId)) { - self.peerId = opts.peerId.toString('hex') - } else { - self.peerId = Buffer.from(VERSION_PREFIX + randombytes(9).toString('base64')).toString('hex') + if (self._mediaSource.readyState === 'open') { + // check buffer size + if (self._getBufferDuration() > self._bufferDuration) { + return + } } - self.peerIdBuffer = Buffer.from(self.peerId, 'hex') - if (typeof opts.nodeId === 'string') { - self.nodeId = opts.nodeId - } else if (Buffer.isBuffer(opts.nodeId)) { - self.nodeId = opts.nodeId.toString('hex') - } else { - self.nodeId = randombytes(20).toString('hex') + if (self._cb) { + var cb = self._cb + self._cb = null + cb() } - self.nodeIdBuffer = Buffer.from(self.nodeId, 'hex') +} - self._debugId = self.peerId.toString('hex').substring(0, 7) +// TODO: if zero actually works in all browsers, remove the logic associated with this below +var EPSILON = 0 - self.destroyed = false - self.listening = false - self.torrentPort = opts.torrentPort || 0 - self.dhtPort = opts.dhtPort || 0 - self.tracker = opts.tracker !== undefined ? opts.tracker : {} - self.torrents = [] - self.maxConns = Number(opts.maxConns) || 55 +MediaSourceStream.prototype._getBufferDuration = function () { + var self = this - self._debug( - 'new webtorrent (peerId %s, nodeId %s, port %s)', - self.peerId, self.nodeId, self.torrentPort - ) + var buffered = self._sourceBuffer.buffered + var currentTime = self._elem.currentTime + var bufferEnd = -1 // end of the buffer + // This is a little over complex because some browsers seem to separate the + // buffered region into multiple sections with slight gaps. + for (var i = 0; i < buffered.length; i++) { + var start = buffered.start(i) + var end = buffered.end(i) + EPSILON - if (self.tracker) { - if (typeof self.tracker !== 'object') self.tracker = {} - if (opts.rtcConfig) { - // TODO: remove in v1 - console.warn('WebTorrent: opts.rtcConfig is deprecated. Use opts.tracker.rtcConfig instead') - self.tracker.rtcConfig = opts.rtcConfig - } - if (opts.wrtc) { - // TODO: remove in v1 - console.warn('WebTorrent: opts.wrtc is deprecated. Use opts.tracker.wrtc instead') - self.tracker.wrtc = opts.wrtc - } - if (global.WRTC && !self.tracker.wrtc) { - self.tracker.wrtc = global.WRTC + if (start > currentTime) { + // Reached past the joined buffer + break + } else if (bufferEnd >= 0 || currentTime <= end) { + // Found the start/continuation of the joined buffer + bufferEnd = end } } - if (typeof TCPPool === 'function') { - self._tcpPool = new TCPPool(self) - } else { - process.nextTick(function () { - self._onListening() - }) + var bufferedTime = bufferEnd - currentTime + if (bufferedTime < 0) { + bufferedTime = 0 } - // stats - self._downloadSpeed = speedometer() - self._uploadSpeed = speedometer() + return bufferedTime +} - if (opts.dht !== false && typeof DHT === 'function' /* browser exclude */) { - // use a single DHT instance for all torrents, so the routing table can be reused - self.dht = new DHT(extend({ nodeId: self.nodeId }, opts.dht)) +},{"inherits":96,"readable-stream":132,"to-arraybuffer":151}],105:[function(require,module,exports){ +(function (process){ +module.exports = Storage - self.dht.once('error', function (err) { - self._destroy(err) - }) +function Storage (chunkLength, opts) { + if (!(this instanceof Storage)) return new Storage(chunkLength, opts) + if (!opts) opts = {} - self.dht.once('listening', function () { - var address = self.dht.address() - if (address) self.dhtPort = address.port - }) + this.chunkLength = Number(chunkLength) + if (!this.chunkLength) throw new Error('First argument must be a chunk length') - // Ignore warning when there are > 10 torrents in the client - self.dht.setMaxListeners(0) + this.chunks = [] + this.closed = false + this.length = Number(opts.length) || Infinity - self.dht.listen(self.dhtPort) - } else { - self.dht = false + if (this.length !== Infinity) { + this.lastChunkLength = (this.length % this.chunkLength) || this.chunkLength + this.lastChunkIndex = Math.ceil(this.length / this.chunkLength) - 1 } +} - // Enable or disable BEP19 (Web Seeds). Enabled by default: - self.enableWebSeeds = opts.webSeeds !== false +Storage.prototype.put = function (index, buf, cb) { + if (this.closed) return nextTick(cb, new Error('Storage is closed')) - if (typeof loadIPSet === 'function' && opts.blocklist != null) { - loadIPSet(opts.blocklist, { - headers: { - 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' - } - }, function (err, ipSet) { - if (err) return self.error('Failed to load blocklist: ' + err.message) - self.blocked = ipSet - ready() - }) - } else { - process.nextTick(ready) + var isLastChunk = (index === this.lastChunkIndex) + if (isLastChunk && buf.length !== this.lastChunkLength) { + return nextTick(cb, new Error('Last chunk length must be ' + this.lastChunkLength)) } - - function ready () { - if (self.destroyed) return - self.ready = true - self.emit('ready') + if (!isLastChunk && buf.length !== this.chunkLength) { + return nextTick(cb, new Error('Chunk length must be ' + this.chunkLength)) } + this.chunks[index] = buf + nextTick(cb, null) } -WebTorrent.WEBRTC_SUPPORT = Peer.WEBRTC_SUPPORT - -Object.defineProperty(WebTorrent.prototype, 'downloadSpeed', { - get: function () { return this._downloadSpeed() } -}) +Storage.prototype.get = function (index, opts, cb) { + if (typeof opts === 'function') return this.get(index, null, opts) + if (this.closed) return nextTick(cb, new Error('Storage is closed')) + var buf = this.chunks[index] + if (!buf) return nextTick(cb, new Error('Chunk not found')) + if (!opts) return nextTick(cb, null, buf) + var offset = opts.offset || 0 + var len = opts.length || (buf.length - offset) + nextTick(cb, null, buf.slice(offset, len + offset)) +} -Object.defineProperty(WebTorrent.prototype, 'uploadSpeed', { - get: function () { return this._uploadSpeed() } -}) +Storage.prototype.close = Storage.prototype.destroy = function (cb) { + if (this.closed) return nextTick(cb, new Error('Storage is closed')) + this.closed = true + this.chunks = null + nextTick(cb, null) +} -Object.defineProperty(WebTorrent.prototype, 'progress', { - get: function () { - var torrents = this.torrents.filter(function (torrent) { - return torrent.progress !== 1 - }) - var downloaded = torrents.reduce(function (total, torrent) { - return total + torrent.downloaded - }, 0) - var length = torrents.reduce(function (total, torrent) { - return total + (torrent.length || 0) - }, 0) || 1 - return downloaded / length - } -}) +function nextTick (cb, err, val) { + process.nextTick(function () { + if (cb) cb(err, val) + }) +} -Object.defineProperty(WebTorrent.prototype, 'ratio', { - get: function () { - var uploaded = this.torrents.reduce(function (total, torrent) { - return total + torrent.uploaded - }, 0) - var received = this.torrents.reduce(function (total, torrent) { - return total + torrent.received - }, 0) || 1 - return uploaded / received - } -}) +}).call(this,require('_process')) +},{"_process":15}],106:[function(require,module,exports){ +(function (Buffer){ +// This is an intentionally recursive require. I don't like it either. +var Box = require('./index') +var Descriptor = require('./descriptor') -/** - * Returns the torrent with the given `torrentId`. Convenience method. Easier than - * searching through the `client.torrents` array. Returns `null` if no matching torrent - * found. - * - * @param {string|Buffer|Object|Torrent} torrentId - * @return {Torrent|null} - */ -WebTorrent.prototype.get = function (torrentId) { - var self = this - var i, torrent - var len = self.torrents.length +var TIME_OFFSET = 2082844800000 - if (torrentId instanceof Torrent) { - for (i = 0; i < len; i++) { - torrent = self.torrents[i] - if (torrent === torrentId) return torrent - } - } else { - var parsed - try { parsed = parseTorrent(torrentId) } catch (err) {} +/* +TODO: +test these +add new box versions +*/ - if (!parsed) return null - if (!parsed.infoHash) throw new Error('Invalid torrent identifier') +// These have 'version' and 'flags' fields in the headers +exports.fullBoxes = {} +var fullBoxes = [ + 'mvhd', + 'tkhd', + 'mdhd', + 'vmhd', + 'smhd', + 'stsd', + 'esds', + 'stsz', + 'stco', + 'stss', + 'stts', + 'ctts', + 'stsc', + 'dref', + 'elst', + 'hdlr', + 'mehd', + 'trex', + 'mfhd', + 'tfhd', + 'tfdt', + 'trun' +] +fullBoxes.forEach(function (type) { + exports.fullBoxes[type] = true +}) - for (i = 0; i < len; i++) { - torrent = self.torrents[i] - if (torrent.infoHash === parsed.infoHash) return torrent - } +exports.ftyp = {} +exports.ftyp.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(exports.ftyp.encodingLength(box)) + var brands = box.compatibleBrands || [] + buf.write(box.brand, 0, 4, 'ascii') + buf.writeUInt32BE(box.brandVersion, 4) + for (var i = 0; i < brands.length; i++) buf.write(brands[i], 8 + (i * 4), 4, 'ascii') + exports.ftyp.encode.bytes = 8 + brands.length * 4 + return buf +} +exports.ftyp.decode = function (buf, offset) { + buf = buf.slice(offset) + var brand = buf.toString('ascii', 0, 4) + var version = buf.readUInt32BE(4) + var compatibleBrands = [] + for (var i = 8; i < buf.length; i += 4) compatibleBrands.push(buf.toString('ascii', i, i + 4)) + return { + brand: brand, + brandVersion: version, + compatibleBrands: compatibleBrands } - return null } - -// TODO: remove in v1 -WebTorrent.prototype.download = function (torrentId, opts, ontorrent) { - console.warn('WebTorrent: client.download() is deprecated. Use client.add() instead') - return this.add(torrentId, opts, ontorrent) +exports.ftyp.encodingLength = function (box) { + return 8 + (box.compatibleBrands || []).length * 4 } -/** - * Start downloading a new torrent. Aliased as `client.download`. - * @param {string|Buffer|Object} torrentId - * @param {Object} opts torrent-specific options - * @param {function=} ontorrent called when the torrent is ready (has metadata) - */ -WebTorrent.prototype.add = function (torrentId, opts, ontorrent) { - var self = this - if (self.destroyed) throw new Error('client is destroyed') - if (typeof opts === 'function') return self.add(torrentId, null, opts) - - self._debug('add') - opts = opts ? extend(opts) : {} - - var torrent = new Torrent(torrentId, self, opts) - self.torrents.push(torrent) - - torrent.once('_infoHash', onInfoHash) - torrent.once('ready', onReady) - torrent.once('close', onClose) - - function onInfoHash () { - if (self.destroyed) return - for (var i = 0, len = self.torrents.length; i < len; i++) { - var t = self.torrents[i] - if (t.infoHash === torrent.infoHash && t !== torrent) { - torrent._destroy(new Error('Cannot add duplicate torrent ' + torrent.infoHash)) - return - } - } - } - - function onReady () { - if (self.destroyed) return - if (typeof ontorrent === 'function') ontorrent(torrent) - self.emit('torrent', torrent) +exports.mvhd = {} +exports.mvhd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(96) + writeDate(box.ctime || new Date(), buf, 0) + writeDate(box.mtime || new Date(), buf, 4) + buf.writeUInt32BE(box.timeScale || 0, 8) + buf.writeUInt32BE(box.duration || 0, 12) + writeFixed32(box.preferredRate || 0, buf, 16) + writeFixed16(box.preferredVolume || 0, buf, 20) + writeReserved(buf, 22, 32) + writeMatrix(box.matrix, buf, 32) + buf.writeUInt32BE(box.previewTime || 0, 68) + buf.writeUInt32BE(box.previewDuration || 0, 72) + buf.writeUInt32BE(box.posterTime || 0, 76) + buf.writeUInt32BE(box.selectionTime || 0, 80) + buf.writeUInt32BE(box.selectionDuration || 0, 84) + buf.writeUInt32BE(box.currentTime || 0, 88) + buf.writeUInt32BE(box.nextTrackId || 0, 92) + exports.mvhd.encode.bytes = 96 + return buf +} +exports.mvhd.decode = function (buf, offset) { + buf = buf.slice(offset) + return { + ctime: readDate(buf, 0), + mtime: readDate(buf, 4), + timeScale: buf.readUInt32BE(8), + duration: buf.readUInt32BE(12), + preferredRate: readFixed32(buf, 16), + preferredVolume: readFixed16(buf, 20), + matrix: readMatrix(buf.slice(32, 68)), + previewTime: buf.readUInt32BE(68), + previewDuration: buf.readUInt32BE(72), + posterTime: buf.readUInt32BE(76), + selectionTime: buf.readUInt32BE(80), + selectionDuration: buf.readUInt32BE(84), + currentTime: buf.readUInt32BE(88), + nextTrackId: buf.readUInt32BE(92) } +} +exports.mvhd.encodingLength = function (box) { + return 96 +} - function onClose () { - torrent.removeListener('_infoHash', onInfoHash) - torrent.removeListener('ready', onReady) - torrent.removeListener('close', onClose) +exports.tkhd = {} +exports.tkhd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(80) + writeDate(box.ctime || new Date(), buf, 0) + writeDate(box.mtime || new Date(), buf, 4) + buf.writeUInt32BE(box.trackId || 0, 8) + writeReserved(buf, 12, 16) + buf.writeUInt32BE(box.duration || 0, 16) + writeReserved(buf, 20, 28) + buf.writeUInt16BE(box.layer || 0, 28) + buf.writeUInt16BE(box.alternateGroup || 0, 30) + buf.writeUInt16BE(box.volume || 0, 32) + writeMatrix(box.matrix, buf, 36) + buf.writeUInt32BE(box.trackWidth || 0, 72) + buf.writeUInt32BE(box.trackHeight || 0, 76) + exports.tkhd.encode.bytes = 80 + return buf +} +exports.tkhd.decode = function (buf, offset) { + buf = buf.slice(offset) + return { + ctime: readDate(buf, 0), + mtime: readDate(buf, 4), + trackId: buf.readUInt32BE(8), + duration: buf.readUInt32BE(16), + layer: buf.readUInt16BE(28), + alternateGroup: buf.readUInt16BE(30), + volume: buf.readUInt16BE(32), + matrix: readMatrix(buf.slice(36, 72)), + trackWidth: buf.readUInt32BE(72), + trackHeight: buf.readUInt32BE(76) } - - return torrent +} +exports.tkhd.encodingLength = function (box) { + return 80 } -/** - * Start seeding a new file/folder. - * @param {string|File|FileList|Buffer|Array.} input - * @param {Object=} opts - * @param {function=} onseed called when torrent is seeding - */ -WebTorrent.prototype.seed = function (input, opts, onseed) { - var self = this - if (self.destroyed) throw new Error('client is destroyed') - if (typeof opts === 'function') return self.seed(input, null, opts) - - self._debug('seed') - opts = opts ? extend(opts) : {} - - // When seeding from fs path, initialize store from that path to avoid a copy - if (typeof input === 'string') opts.path = path.dirname(input) - if (!opts.createdBy) opts.createdBy = 'WebTorrent/' + VERSION_STR - - var torrent = self.add(null, opts, onTorrent) - var streams - - if (isFileList(input)) input = Array.prototype.slice.call(input) - if (!Array.isArray(input)) input = [ input ] - - parallel(input.map(function (item) { - return function (cb) { - if (isReadable(item)) concat(item, cb) - else cb(null, item) - } - }), function (err, input) { - if (self.destroyed) return - if (err) return torrent._destroy(err) - - createTorrent.parseInput(input, opts, function (err, files) { - if (self.destroyed) return - if (err) return torrent._destroy(err) - - streams = files.map(function (file) { - return file.getStream - }) - - createTorrent(input, opts, function (err, torrentBuf) { - if (self.destroyed) return - if (err) return torrent._destroy(err) - - var existingTorrent = self.get(torrentBuf) - if (existingTorrent) { - torrent._destroy(new Error('Cannot add duplicate torrent ' + existingTorrent.infoHash)) - } else { - torrent._onTorrentId(torrentBuf) - } - }) - }) - }) - - function onTorrent (torrent) { - var tasks = [ - function (cb) { - torrent.load(streams, cb) - } - ] - if (self.dht) { - tasks.push(function (cb) { - torrent.once('dhtAnnounce', cb) - }) - } - parallel(tasks, function (err) { - if (self.destroyed) return - if (err) return torrent._destroy(err) - _onseed(torrent) - }) +exports.mdhd = {} +exports.mdhd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(20) + writeDate(box.ctime || new Date(), buf, 0) + writeDate(box.mtime || new Date(), buf, 4) + buf.writeUInt32BE(box.timeScale || 0, 8) + buf.writeUInt32BE(box.duration || 0, 12) + buf.writeUInt16BE(box.language || 0, 16) + buf.writeUInt16BE(box.quality || 0, 18) + exports.mdhd.encode.bytes = 20 + return buf +} +exports.mdhd.decode = function (buf, offset) { + buf = buf.slice(offset) + return { + ctime: readDate(buf, 0), + mtime: readDate(buf, 4), + timeScale: buf.readUInt32BE(8), + duration: buf.readUInt32BE(12), + language: buf.readUInt16BE(16), + quality: buf.readUInt16BE(18) } +} +exports.mdhd.encodingLength = function (box) { + return 20 +} - function _onseed (torrent) { - self._debug('on seed') - if (typeof onseed === 'function') onseed(torrent) - torrent.emit('seed') - self.emit('seed', torrent) +exports.vmhd = {} +exports.vmhd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(8) + buf.writeUInt16BE(box.graphicsMode || 0, 0) + var opcolor = box.opcolor || [0, 0, 0] + buf.writeUInt16BE(opcolor[0], 2) + buf.writeUInt16BE(opcolor[1], 4) + buf.writeUInt16BE(opcolor[2], 6) + exports.vmhd.encode.bytes = 8 + return buf +} +exports.vmhd.decode = function (buf, offset) { + buf = buf.slice(offset) + return { + graphicsMode: buf.readUInt16BE(0), + opcolor: [buf.readUInt16BE(2), buf.readUInt16BE(4), buf.readUInt16BE(6)] } - - return torrent } - -/** - * Remove a torrent from the client. - * @param {string|Buffer|Torrent} torrentId - * @param {function} cb - */ -WebTorrent.prototype.remove = function (torrentId, cb) { - this._debug('remove') - var torrent = this.get(torrentId) - if (!torrent) throw new Error('No torrent with id ' + torrentId) - this._remove(torrentId, cb) +exports.vmhd.encodingLength = function (box) { + return 8 } -WebTorrent.prototype._remove = function (torrentId, cb) { - var torrent = this.get(torrentId) - if (!torrent) return - this.torrents.splice(this.torrents.indexOf(torrent), 1) - torrent.destroy(cb) +exports.smhd = {} +exports.smhd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(4) + buf.writeUInt16BE(box.balance || 0, 0) + writeReserved(buf, 2, 4) + exports.smhd.encode.bytes = 4 + return buf } - -WebTorrent.prototype.address = function () { - if (!this.listening) return null - return this._tcpPool - ? this._tcpPool.server.address() - : { address: '0.0.0.0', family: 'IPv4', port: 0 } +exports.smhd.decode = function (buf, offset) { + buf = buf.slice(offset) + return { + balance: buf.readUInt16BE(0) + } } - -/** - * Destroy the client, including all torrents and connections to peers. - * @param {function} cb - */ -WebTorrent.prototype.destroy = function (cb) { - if (this.destroyed) throw new Error('client already destroyed') - this._destroy(null, cb) +exports.smhd.encodingLength = function (box) { + return 4 } -WebTorrent.prototype._destroy = function (err, cb) { - var self = this - self._debug('client destroy') - self.destroyed = true - - var tasks = self.torrents.map(function (torrent) { - return function (cb) { - torrent.destroy(cb) - } - }) +exports.stsd = {} +exports.stsd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(exports.stsd.encodingLength(box)) + var entries = box.entries || [] - if (self._tcpPool) { - tasks.push(function (cb) { - self._tcpPool.destroy(cb) - }) - } + buf.writeUInt32BE(entries.length, 0) - if (self.dht) { - tasks.push(function (cb) { - self.dht.destroy(cb) - }) + var ptr = 4 + for (var i = 0; i < entries.length; i++) { + var entry = entries[i] + Box.encode(entry, buf, ptr) + ptr += Box.encode.bytes } - parallel(tasks, cb) - - if (err) self.emit('error', err) - - self.torrents = [] - self._tcpPool = null - self.dht = null + exports.stsd.encode.bytes = ptr + return buf } +exports.stsd.decode = function (buf, offset, end) { + buf = buf.slice(offset) + var num = buf.readUInt32BE(0) + var entries = new Array(num) + var ptr = 4 -WebTorrent.prototype._onListening = function () { - this._debug('listening') - this.listening = true - - if (this._tcpPool) { - // Sometimes server.address() returns `null` in Docker. - var address = this._tcpPool.server.address() - if (address) this.torrentPort = address.port + for (var i = 0; i < num; i++) { + var entry = Box.decode(buf, ptr, end) + entries[i] = entry + ptr += entry.length } - this.emit('listening') + return { + entries: entries + } } - -WebTorrent.prototype._debug = function () { - var args = [].slice.call(arguments) - args[0] = '[' + this._debugId + '] ' + args[0] - debug.apply(null, args) +exports.stsd.encodingLength = function (box) { + var totalSize = 4 + if (!box.entries) return totalSize + for (var i = 0; i < box.entries.length; i++) { + totalSize += Box.encodingLength(box.entries[i]) + } + return totalSize } -/** - * Check if `obj` is a node Readable stream - * @param {*} obj - * @return {boolean} - */ -function isReadable (obj) { - return typeof obj === 'object' && obj != null && typeof obj.pipe === 'function' -} +exports.avc1 = exports.VisualSampleEntry = {} +exports.VisualSampleEntry.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(exports.VisualSampleEntry.encodingLength(box)) -/** - * Check if `obj` is a W3C `FileList` object - * @param {*} obj - * @return {boolean} - */ -function isFileList (obj) { - return typeof FileList !== 'undefined' && obj instanceof FileList -} + writeReserved(buf, 0, 6) + buf.writeUInt16BE(box.dataReferenceIndex || 0, 6) + writeReserved(buf, 8, 24) + buf.writeUInt16BE(box.width || 0, 24) + buf.writeUInt16BE(box.height || 0, 26) + buf.writeUInt32BE(box.hResolution || 0x480000, 28) + buf.writeUInt32BE(box.vResolution || 0x480000, 32) + writeReserved(buf, 36, 40) + buf.writeUInt16BE(box.frameCount || 1, 40) + var compressorName = box.compressorName || '' + var nameLen = Math.min(compressorName.length, 31) + buf.writeUInt8(nameLen, 42) + buf.write(compressorName, 43, nameLen, 'utf8') + buf.writeUInt16BE(box.depth || 0x18, 74) + buf.writeInt16BE(-1, 76) -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./lib/tcp-pool":158,"./lib/torrent":127,"./package.json":129,"_process":170,"bittorrent-dht/client":158,"create-torrent":50,"debug":27,"events":162,"inherits":58,"load-ip-set":158,"parse-torrent":77,"path":168,"randombytes":82,"run-parallel":96,"safe-buffer":98,"simple-concat":99,"simple-peer":101,"speedometer":104,"xtend":131,"zero-fill":133}],123:[function(require,module,exports){ -module.exports = FileStream + var ptr = 78 + var children = box.children || [] + children.forEach(function (child) { + Box.encode(child, buf, ptr) + ptr += Box.encode.bytes + }) + exports.VisualSampleEntry.encode.bytes = ptr +} +exports.VisualSampleEntry.decode = function (buf, offset, end) { + buf = buf.slice(offset) + var length = end - offset + var nameLen = Math.min(buf.readUInt8(42), 31) + var box = { + dataReferenceIndex: buf.readUInt16BE(6), + width: buf.readUInt16BE(24), + height: buf.readUInt16BE(26), + hResolution: buf.readUInt32BE(28), + vResolution: buf.readUInt32BE(32), + frameCount: buf.readUInt16BE(40), + compressorName: buf.toString('utf8', 43, 43 + nameLen), + depth: buf.readUInt16BE(74), + children: [] + } -var debug = require('debug')('webtorrent:file-stream') -var inherits = require('inherits') -var stream = require('readable-stream') + var ptr = 78 + while (length - ptr >= 8) { + var child = Box.decode(buf, ptr, length) + box.children.push(child) + box[child.type] = child + ptr += child.length + } -inherits(FileStream, stream.Readable) + return box +} +exports.VisualSampleEntry.encodingLength = function (box) { + var len = 78 + var children = box.children || [] + children.forEach(function (child) { + len += Box.encodingLength(child) + }) + return len +} -/** - * Readable stream of a torrent file - * - * @param {File} file - * @param {Object} opts - * @param {number} opts.start stream slice of file, starting from this byte (inclusive) - * @param {number} opts.end stream slice of file, ending with this byte (inclusive) - */ -function FileStream (file, opts) { - stream.Readable.call(this, opts) +exports.avcC = {} +exports.avcC.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : Buffer(box.buffer.length) - this.destroyed = false - this._torrent = file._torrent + box.buffer.copy(buf) + exports.avcC.encode.bytes = box.buffer.length +} +exports.avcC.decode = function (buf, offset, end) { + buf = buf.slice(offset, end) - var start = (opts && opts.start) || 0 - var end = (opts && opts.end && opts.end < file.length) - ? opts.end - : file.length - 1 + return { + mimeCodec: buf.toString('hex', 1, 4), + buffer: new Buffer(buf) + } +} +exports.avcC.encodingLength = function (box) { + return box.buffer.length +} - var pieceLength = file._torrent.pieceLength +exports.mp4a = exports.AudioSampleEntry = {} +exports.AudioSampleEntry.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(exports.AudioSampleEntry.encodingLength(box)) - this._startPiece = (start + file.offset) / pieceLength | 0 - this._endPiece = (end + file.offset) / pieceLength | 0 + writeReserved(buf, 0, 6) + buf.writeUInt16BE(box.dataReferenceIndex || 0, 6) + writeReserved(buf, 8, 16) + buf.writeUInt16BE(box.channelCount || 2, 16) + buf.writeUInt16BE(box.sampleSize || 16, 18) + writeReserved(buf, 20, 24) + buf.writeUInt32BE(box.sampleRate || 0, 24) - this._piece = this._startPiece - this._offset = (start + file.offset) - (this._startPiece * pieceLength) + var ptr = 28 + var children = box.children || [] + children.forEach(function (child) { + Box.encode(child, buf, ptr) + ptr += Box.encode.bytes + }) + exports.AudioSampleEntry.encode.bytes = ptr +} +exports.AudioSampleEntry.decode = function (buf, offset, end) { + buf = buf.slice(offset, end) + var length = end - offset + var box = { + dataReferenceIndex: buf.readUInt16BE(6), + channelCount: buf.readUInt16BE(16), + sampleSize: buf.readUInt16BE(18), + sampleRate: buf.readUInt32BE(24), + children: [] + } - this._missing = end - start + 1 - this._reading = false - this._notifying = false - this._criticalLength = Math.min((1024 * 1024 / pieceLength) | 0, 2) + var ptr = 28 + while (length - ptr >= 8) { + var child = Box.decode(buf, ptr, length) + box.children.push(child) + box[child.type] = child + ptr += child.length + } + + return box +} +exports.AudioSampleEntry.encodingLength = function (box) { + var len = 28 + var children = box.children || [] + children.forEach(function (child) { + len += Box.encodingLength(child) + }) + return len } -FileStream.prototype._read = function () { - if (this._reading) return - this._reading = true - this._notify() +exports.esds = {} +exports.esds.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : Buffer(box.buffer.length) + + box.buffer.copy(buf, 0) + exports.esds.encode.bytes = box.buffer.length } +exports.esds.decode = function (buf, offset, end) { + buf = buf.slice(offset, end) -FileStream.prototype._notify = function () { - var self = this + var desc = Descriptor.Descriptor.decode(buf, 0, buf.length) + var esd = (desc.tagName === 'ESDescriptor') ? desc : {} + var dcd = esd.DecoderConfigDescriptor || {} + var oti = dcd.oti || 0 + var dsi = dcd.DecoderSpecificInfo + var audioConfig = dsi ? (dsi.buffer.readUInt8(0) & 0xf8) >> 3 : 0 - if (!self._reading || self._missing === 0) return - if (!self._torrent.bitfield.get(self._piece)) { - return self._torrent.critical(self._piece, self._piece + self._criticalLength) + var mimeCodec = null + if (oti) { + mimeCodec = oti.toString(16) + if (audioConfig) { + mimeCodec += '.' + audioConfig + } } - if (self._notifying) return - self._notifying = true - - var p = self._piece - self._torrent.store.get(p, function (err, buffer) { - self._notifying = false - if (self.destroyed) return - if (err) return self._destroy(err) - debug('read %s (length %s) (err %s)', p, buffer.length, err && err.message) + return { + mimeCodec: mimeCodec, + buffer: new Buffer(buf.slice(0)) + } +} +exports.esds.encodingLength = function (box) { + return box.buffer.length +} - if (self._offset) { - buffer = buffer.slice(self._offset) - self._offset = 0 - } +// TODO: integrate the two versions in a saner way +exports.stsz = {} +exports.stsz.encode = function (box, buf, offset) { + var entries = box.entries || [] + buf = buf ? buf.slice(offset) : Buffer(exports.stsz.encodingLength(box)) - if (self._missing < buffer.length) { - buffer = buffer.slice(0, self._missing) - } - self._missing -= buffer.length + buf.writeUInt32BE(0, 0) + buf.writeUInt32BE(entries.length, 4) - debug('pushing buffer of length %s', buffer.length) - self._reading = false - self.push(buffer) + for (var i = 0; i < entries.length; i++) { + buf.writeUInt32BE(entries[i], i * 4 + 8) + } - if (self._missing === 0) self.push(null) - }) - self._piece += 1 + exports.stsz.encode.bytes = 8 + entries.length * 4 + return buf } +exports.stsz.decode = function (buf, offset) { + buf = buf.slice(offset) + var size = buf.readUInt32BE(0) + var num = buf.readUInt32BE(4) + var entries = new Array(num) -FileStream.prototype.destroy = function (onclose) { - this._destroy(null, onclose) + for (var i = 0; i < num; i++) { + if (size === 0) { + entries[i] = buf.readUInt32BE(i * 4 + 8) + } else { + entries[i] = size + } + } + + return { + entries: entries + } +} +exports.stsz.encodingLength = function (box) { + return 8 + box.entries.length * 4 } -FileStream.prototype._destroy = function (err, onclose) { - if (this.destroyed) return - this.destroyed = true +exports.stss = +exports.stco = {} +exports.stco.encode = function (box, buf, offset) { + var entries = box.entries || [] + buf = buf ? buf.slice(offset) : new Buffer(exports.stco.encodingLength(box)) - if (!this._torrent.destroyed) { - this._torrent.deselect(this._startPiece, this._endPiece, true) + buf.writeUInt32BE(entries.length, 0) + + for (var i = 0; i < entries.length; i++) { + buf.writeUInt32BE(entries[i], i * 4 + 4) } - if (err) this.emit('error', err) - this.emit('close') - if (onclose) onclose() + exports.stco.encode.bytes = 4 + entries.length * 4 + return buf } +exports.stco.decode = function (buf, offset) { + buf = buf.slice(offset) + var num = buf.readUInt32BE(0) + var entries = new Array(num) -},{"debug":27,"inherits":58,"readable-stream":92}],124:[function(require,module,exports){ -(function (process){ -module.exports = File + for (var i = 0; i < num; i++) { + entries[i] = buf.readUInt32BE(i * 4 + 4) + } -var eos = require('end-of-stream') -var EventEmitter = require('events').EventEmitter -var FileStream = require('./file-stream') -var inherits = require('inherits') -var path = require('path') -var render = require('render-media') -var stream = require('readable-stream') -var streamToBlob = require('stream-to-blob') -var streamToBlobURL = require('stream-to-blob-url') -var streamToBuffer = require('stream-with-known-length-to-buffer') - -inherits(File, EventEmitter) - -function File (torrent, file) { - EventEmitter.call(this) + return { + entries: entries + } +} +exports.stco.encodingLength = function (box) { + return 4 + box.entries.length * 4 +} - this._torrent = torrent - this._destroyed = false +exports.stts = {} +exports.stts.encode = function (box, buf, offset) { + var entries = box.entries || [] + buf = buf ? buf.slice(offset) : new Buffer(exports.stts.encodingLength(box)) - this.name = file.name - this.path = file.path - this.length = file.length - this.offset = file.offset + buf.writeUInt32BE(entries.length, 0) - this.done = false + for (var i = 0; i < entries.length; i++) { + var ptr = i * 8 + 4 + buf.writeUInt32BE(entries[i].count || 0, ptr) + buf.writeUInt32BE(entries[i].duration || 0, ptr + 4) + } - var start = file.offset - var end = start + file.length - 1 + exports.stts.encode.bytes = 4 + box.entries.length * 8 + return buf +} +exports.stts.decode = function (buf, offset) { + buf = buf.slice(offset) + var num = buf.readUInt32BE(0) + var entries = new Array(num) - this._startPiece = start / this._torrent.pieceLength | 0 - this._endPiece = end / this._torrent.pieceLength | 0 + for (var i = 0; i < num; i++) { + var ptr = i * 8 + 4 + entries[i] = { + count: buf.readUInt32BE(ptr), + duration: buf.readUInt32BE(ptr + 4) + } + } - if (this.length === 0) { - this.done = true - this.emit('done') + return { + entries: entries } } +exports.stts.encodingLength = function (box) { + return 4 + box.entries.length * 8 +} -Object.defineProperty(File.prototype, 'downloaded', { - get: function () { - if (!this._torrent.bitfield) return 0 - var downloaded = 0 - for (var index = this._startPiece; index <= this._endPiece; ++index) { - if (this._torrent.bitfield.get(index)) { - // verified data - downloaded += this._torrent.pieceLength - } else { - // "in progress" data - var piece = this._torrent.pieces[index] - downloaded += (piece.length - piece.missing) - } - } - return downloaded - } -}) +exports.ctts = {} +exports.ctts.encode = function (box, buf, offset) { + var entries = box.entries || [] + buf = buf ? buf.slice(offset) : new Buffer(exports.ctts.encodingLength(box)) -Object.defineProperty(File.prototype, 'progress', { - get: function () { return this.length ? this.downloaded / this.length : 0 } -}) + buf.writeUInt32BE(entries.length, 0) -File.prototype.select = function (priority) { - if (this.length === 0) return - this._torrent.select(this._startPiece, this._endPiece, priority) -} + for (var i = 0; i < entries.length; i++) { + var ptr = i * 8 + 4 + buf.writeUInt32BE(entries[i].count || 0, ptr) + buf.writeUInt32BE(entries[i].compositionOffset || 0, ptr + 4) + } -File.prototype.deselect = function () { - if (this.length === 0) return - this._torrent.deselect(this._startPiece, this._endPiece, false) + exports.ctts.encode.bytes = 4 + entries.length * 8 + return buf } +exports.ctts.decode = function (buf, offset) { + buf = buf.slice(offset) + var num = buf.readUInt32BE(0) + var entries = new Array(num) -File.prototype.createReadStream = function (opts) { - var self = this - if (this.length === 0) { - var empty = new stream.PassThrough() - process.nextTick(function () { - empty.end() - }) - return empty + for (var i = 0; i < num; i++) { + var ptr = i * 8 + 4 + entries[i] = { + count: buf.readUInt32BE(ptr), + compositionOffset: buf.readInt32BE(ptr + 4) + } } - var fileStream = new FileStream(self, opts) - self._torrent.select(fileStream._startPiece, fileStream._endPiece, true, function () { - fileStream._notify() - }) - eos(fileStream, function () { - if (self._destroyed) return - if (!self._torrent.destroyed) { - self._torrent.deselect(fileStream._startPiece, fileStream._endPiece, true) - } - }) - return fileStream + return { + entries: entries + } } - -File.prototype.getBuffer = function (cb) { - streamToBuffer(this.createReadStream(), this.length, cb) +exports.ctts.encodingLength = function (box) { + return 4 + box.entries.length * 8 } -File.prototype.getBlob = function (cb) { - if (typeof window === 'undefined') throw new Error('browser-only method') - streamToBlob(this.createReadStream(), this._getMimeType(), cb) -} +exports.stsc = {} +exports.stsc.encode = function (box, buf, offset) { + var entries = box.entries || [] + buf = buf ? buf.slice(offset) : new Buffer(exports.stsc.encodingLength(box)) -File.prototype.getBlobURL = function (cb) { - if (typeof window === 'undefined') throw new Error('browser-only method') - streamToBlobURL(this.createReadStream(), this._getMimeType(), cb) -} + buf.writeUInt32BE(entries.length, 0) -File.prototype.appendTo = function (elem, opts, cb) { - if (typeof window === 'undefined') throw new Error('browser-only method') - render.append(this, elem, opts, cb) -} + for (var i = 0; i < entries.length; i++) { + var ptr = i * 12 + 4 + buf.writeUInt32BE(entries[i].firstChunk || 0, ptr) + buf.writeUInt32BE(entries[i].samplesPerChunk || 0, ptr + 4) + buf.writeUInt32BE(entries[i].sampleDescriptionId || 0, ptr + 8) + } -File.prototype.renderTo = function (elem, opts, cb) { - if (typeof window === 'undefined') throw new Error('browser-only method') - render.render(this, elem, opts, cb) + exports.stsc.encode.bytes = 4 + entries.length * 12 + return buf } +exports.stsc.decode = function (buf, offset) { + buf = buf.slice(offset) + var num = buf.readUInt32BE(0) + var entries = new Array(num) -File.prototype._getMimeType = function () { - return render.mime[path.extname(this.name).toLowerCase()] -} + for (var i = 0; i < num; i++) { + var ptr = i * 12 + 4 + entries[i] = { + firstChunk: buf.readUInt32BE(ptr), + samplesPerChunk: buf.readUInt32BE(ptr + 4), + sampleDescriptionId: buf.readUInt32BE(ptr + 8) + } + } -File.prototype._destroy = function () { - this._destroyed = true - this._torrent = null + return { + entries: entries + } +} +exports.stsc.encodingLength = function (box) { + return 4 + box.entries.length * 12 } -}).call(this,require('_process')) -},{"./file-stream":123,"_process":170,"end-of-stream":52,"events":162,"inherits":58,"path":168,"readable-stream":92,"render-media":93,"stream-to-blob":106,"stream-to-blob-url":105,"stream-with-known-length-to-buffer":107}],125:[function(require,module,exports){ -var arrayRemove = require('unordered-array-remove') -var debug = require('debug')('webtorrent:peer') -var Wire = require('bittorrent-protocol') +exports.dref = {} +exports.dref.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(exports.dref.encodingLength(box)) + var entries = box.entries || [] -var WebConn = require('./webconn') + buf.writeUInt32BE(entries.length, 0) -var CONNECT_TIMEOUT_TCP = 5000 -var CONNECT_TIMEOUT_WEBRTC = 25000 -var HANDSHAKE_TIMEOUT = 25000 + var ptr = 4 + for (var i = 0; i < entries.length; i++) { + var entry = entries[i] + var size = (entry.buf ? entry.buf.length : 0) + 4 + 4 -/** - * WebRTC peer connections start out connected, because WebRTC peers require an - * "introduction" (i.e. WebRTC signaling), and there's no equivalent to an IP address - * that lets you refer to a WebRTC endpoint. - */ -exports.createWebRTCPeer = function (conn, swarm) { - var peer = new Peer(conn.id, 'webrtc') - peer.conn = conn - peer.swarm = swarm + buf.writeUInt32BE(size, ptr) + ptr += 4 - if (peer.conn.connected) { - peer.onConnect() - } else { - peer.conn.once('connect', function () { peer.onConnect() }) - peer.conn.once('error', function (err) { peer.destroy(err) }) - peer.startConnectTimeout() + buf.write(entry.type, ptr, 4, 'ascii') + ptr += 4 + + if (entry.buf) { + entry.buf.copy(buf, ptr) + ptr += entry.buf.length + } } - return peer + exports.dref.encode.bytes = ptr + return buf } +exports.dref.decode = function (buf, offset) { + buf = buf.slice(offset) + var num = buf.readUInt32BE(0) + var entries = new Array(num) + var ptr = 4 -/** - * Incoming TCP peers start out connected, because the remote peer connected to the - * listening port of the TCP server. Until the remote peer sends a handshake, we don't - * know what swarm the connection is intended for. - */ -exports.createTCPIncomingPeer = function (conn) { - var addr = conn.remoteAddress + ':' + conn.remotePort - var peer = new Peer(addr, 'tcpIncoming') - peer.conn = conn - peer.addr = addr - - peer.onConnect() + for (var i = 0; i < num; i++) { + var size = buf.readUInt32BE(ptr) + var type = buf.toString('ascii', ptr + 4, ptr + 8) + var tmp = buf.slice(ptr + 8, ptr + size) + ptr += size - return peer -} - -/** - * Outgoing TCP peers start out with just an IP address. At some point (when there is an - * available connection), the client can attempt to connect to the address. - */ -exports.createTCPOutgoingPeer = function (addr, swarm) { - var peer = new Peer(addr, 'tcpOutgoing') - peer.addr = addr - peer.swarm = swarm + entries[i] = { + type: type, + buf: tmp + } + } - return peer + return { + entries: entries + } } - -/** - * Peer that represents a Web Seed (BEP17 / BEP19). - */ -exports.createWebSeedPeer = function (url, swarm) { - var peer = new Peer(url, 'webSeed') - peer.swarm = swarm - peer.conn = new WebConn(url, swarm) - - peer.onConnect() - - return peer +exports.dref.encodingLength = function (box) { + var totalSize = 4 + if (!box.entries) return totalSize + for (var i = 0; i < box.entries.length; i++) { + var buf = box.entries[i].buf + totalSize += (buf ? buf.length : 0) + 4 + 4 + } + return totalSize } -/** - * Peer. Represents a peer in the torrent swarm. - * - * @param {string} id "ip:port" string, peer id (for WebRTC peers), or url (for Web Seeds) - * @param {string} type the type of the peer - */ -function Peer (id, type) { - var self = this - self.id = id - self.type = type - - debug('new Peer %s', id) +exports.elst = {} +exports.elst.encode = function (box, buf, offset) { + var entries = box.entries || [] + buf = buf ? buf.slice(offset) : new Buffer(exports.elst.encodingLength(box)) - self.addr = null - self.conn = null - self.swarm = null - self.wire = null + buf.writeUInt32BE(entries.length, 0) - self.connected = false - self.destroyed = false - self.timeout = null // handshake timeout - self.retries = 0 // outgoing TCP connection retry count + for (var i = 0; i < entries.length; i++) { + var ptr = i * 12 + 4 + buf.writeUInt32BE(entries[i].trackDuration || 0, ptr) + buf.writeUInt32BE(entries[i].mediaTime || 0, ptr + 4) + writeFixed32(entries[i].mediaRate || 0, buf, ptr + 8) + } - self.sentHandshake = false + exports.elst.encode.bytes = 4 + entries.length * 12 + return buf } +exports.elst.decode = function (buf, offset) { + buf = buf.slice(offset) + var num = buf.readUInt32BE(0) + var entries = new Array(num) -/** - * Called once the peer is connected (i.e. fired 'connect' event) - * @param {Socket} conn - */ -Peer.prototype.onConnect = function () { - var self = this - if (self.destroyed) return - self.connected = true - - debug('Peer %s connected', self.id) + for (var i = 0; i < num; i++) { + var ptr = i * 12 + 4 + entries[i] = { + trackDuration: buf.readUInt32BE(ptr), + mediaTime: buf.readInt32BE(ptr + 4), + mediaRate: readFixed32(buf, ptr + 8) + } + } - clearTimeout(self.connectTimeout) + return { + entries: entries + } +} +exports.elst.encodingLength = function (box) { + return 4 + box.entries.length * 12 +} - var conn = self.conn - conn.once('end', function () { - self.destroy() - }) - conn.once('close', function () { - self.destroy() - }) - conn.once('finish', function () { - self.destroy() - }) - conn.once('error', function (err) { - self.destroy(err) - }) +exports.hdlr = {} +exports.hdlr.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(exports.hdlr.encodingLength(box)) - var wire = self.wire = new Wire() - wire.type = self.type - wire.once('end', function () { - self.destroy() - }) - wire.once('close', function () { - self.destroy() - }) - wire.once('finish', function () { - self.destroy() - }) - wire.once('error', function (err) { - self.destroy(err) - }) + var len = 21 + (box.name || '').length + buf.fill(0, 0, len) - wire.once('handshake', function (infoHash, peerId) { - self.onHandshake(infoHash, peerId) - }) - self.startHandshakeTimeout() + buf.write(box.handlerType || '', 4, 4, 'ascii') + writeString(box.name || '', buf, 20) - conn.pipe(wire).pipe(conn) - if (self.swarm && !self.sentHandshake) self.handshake() + exports.hdlr.encode.bytes = len + return buf } - -/** - * Called when handshake is received from remote peer. - * @param {string} infoHash - * @param {string} peerId - */ -Peer.prototype.onHandshake = function (infoHash, peerId) { - var self = this - if (!self.swarm) return // `self.swarm` not set yet, so do nothing - if (self.destroyed) return - - if (self.swarm.destroyed) { - return self.destroy(new Error('swarm already destroyed')) - } - if (infoHash !== self.swarm.infoHash) { - return self.destroy(new Error('unexpected handshake info hash for this swarm')) - } - if (peerId === self.swarm.peerId) { - return self.destroy(new Error('refusing to connect to ourselves')) +exports.hdlr.decode = function (buf, offset, end) { + buf = buf.slice(offset) + return { + handlerType: buf.toString('ascii', 4, 8), + name: readString(buf, 20, end) } +} +exports.hdlr.encodingLength = function (box) { + return 21 + (box.name || '').length +} - debug('Peer %s got handshake %s', self.id, infoHash) +exports.mehd = {} +exports.mehd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(4) - clearTimeout(self.handshakeTimeout) + buf.writeUInt32BE(box.fragmentDuration || 0, 0) + exports.mehd.encode.bytes = 4 + return buf +} +exports.mehd.decode = function (buf, offset) { + buf = buf.slice(offset) + return { + fragmentDuration: buf.readUInt32BE(0) + } +} +exports.mehd.encodingLength = function (box) { + return 4 +} - self.retries = 0 +exports.trex = {} +exports.trex.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(20) - var addr = self.addr - if (!addr && self.conn.remoteAddress) { - addr = self.conn.remoteAddress + ':' + self.conn.remotePort + buf.writeUInt32BE(box.trackId || 0, 0) + buf.writeUInt32BE(box.defaultSampleDescriptionIndex || 0, 4) + buf.writeUInt32BE(box.defaultSampleDuration || 0, 8) + buf.writeUInt32BE(box.defaultSampleSize || 0, 12) + buf.writeUInt32BE(box.defaultSampleFlags || 0, 16) + exports.trex.encode.bytes = 20 + return buf +} +exports.trex.decode = function (buf, offset) { + buf = buf.slice(offset) + return { + trackId: buf.readUInt32BE(0), + defaultSampleDescriptionIndex: buf.readUInt32BE(4), + defaultSampleDuration: buf.readUInt32BE(8), + defaultSampleSize: buf.readUInt32BE(12), + defaultSampleFlags: buf.readUInt32BE(16) } - self.swarm._onWire(self.wire, addr) +} +exports.trex.encodingLength = function (box) { + return 20 +} - // swarm could be destroyed in user's 'wire' event handler - if (!self.swarm || self.swarm.destroyed) return +exports.mfhd = {} +exports.mfhd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(4) - if (!self.sentHandshake) self.handshake() + buf.writeUInt32BE(box.sequenceNumber || 0, 0) + exports.mfhd.encode.bytes = 4 + return buf } - -Peer.prototype.handshake = function () { - var self = this - var opts = { - dht: self.swarm.private ? false : !!self.swarm.client.dht +exports.mfhd.decode = function (buf, offset) { + return { + sequenceNumber: buf.readUint32BE(0) } - self.wire.handshake(self.swarm.infoHash, self.swarm.client.peerId, opts) - self.sentHandshake = true } - -Peer.prototype.startConnectTimeout = function () { - var self = this - clearTimeout(self.connectTimeout) - self.connectTimeout = setTimeout(function () { - self.destroy(new Error('connect timeout')) - }, self.type === 'webrtc' ? CONNECT_TIMEOUT_WEBRTC : CONNECT_TIMEOUT_TCP) - if (self.connectTimeout.unref) self.connectTimeout.unref() +exports.mfhd.encodingLength = function (box) { + return 4 } -Peer.prototype.startHandshakeTimeout = function () { - var self = this - clearTimeout(self.handshakeTimeout) - self.handshakeTimeout = setTimeout(function () { - self.destroy(new Error('handshake timeout')) - }, HANDSHAKE_TIMEOUT) - if (self.handshakeTimeout.unref) self.handshakeTimeout.unref() -} - -Peer.prototype.destroy = function (err) { - var self = this - if (self.destroyed) return - self.destroyed = true - self.connected = false - - debug('destroy %s (error: %s)', self.id, err && (err.message || err)) - - clearTimeout(self.connectTimeout) - clearTimeout(self.handshakeTimeout) - - var swarm = self.swarm - var conn = self.conn - var wire = self.wire +exports.tfhd = {} +exports.tfhd.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(4) + buf.writeUInt32BE(box.trackId, 0) + exports.tfhd.encode.bytes = 4 + return buf +} +exports.tfhd.decode = function (buf, offset) { + // TODO: this +} +exports.tfhd.encodingLength = function (box) { + // TODO: this is wrong! + return 4 +} - self.swarm = null - self.conn = null - self.wire = null +exports.tfdt = {} +exports.tfdt.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(4) - if (swarm && wire) { - arrayRemove(swarm.wires, swarm.wires.indexOf(wire)) - } - if (conn) { - conn.on('error', noop) - conn.destroy() - } - if (wire) wire.destroy() - if (swarm) swarm.removePeer(self.id) + buf.writeUInt32BE(box.baseMediaDecodeTime || 0, 0) + exports.tfdt.encode.bytes = 4 + return buf +} +exports.tfdt.decode = function (buf, offset) { + // TODO: this +} +exports.tfdt.encodingLength = function (box) { + return 4 } -function noop () {} +exports.trun = {} +exports.trun.encode = function (box, buf, offset) { + buf = buf ? buf.slice(offset) : new Buffer(8 + box.entries.length * 16) -},{"./webconn":128,"bittorrent-protocol":38,"debug":27,"unordered-array-remove":117}],126:[function(require,module,exports){ -module.exports = RarityMap + // TODO: this is wrong + buf.writeUInt32BE(box.entries.length, 0) + buf.writeInt32BE(box.dataOffset, 4) + var ptr = 8 + for (var i = 0; i < box.entries.length; i++) { + var entry = box.entries[i] + buf.writeUInt32BE(entry.sampleDuration, ptr) + ptr += 4 -/** - * Mapping of torrent pieces to their respective availability in the torrent swarm. Used - * by the torrent manager for implementing the rarest piece first selection strategy. - */ -function RarityMap (torrent) { - var self = this + buf.writeUInt32BE(entry.sampleSize, ptr) + ptr += 4 - self._torrent = torrent - self._numPieces = torrent.pieces.length - self._pieces = [] + buf.writeUInt32BE(entry.sampleFlags, ptr) + ptr += 4 - self._onWire = function (wire) { - self.recalculate() - self._initWire(wire) + buf.writeUInt32BE(entry.sampleCompositionTimeOffset, ptr) + ptr += 4 } - self._onWireHave = function (index) { - self._pieces[index] += 1 + exports.trun.encode.bytes = ptr +} +exports.trun.decode = function (buf, offset) { + // TODO: this +} +exports.trun.encodingLength = function (box) { + // TODO: this is wrong + return 8 + box.entries.length * 16 +} + +exports.mdat = {} +exports.mdat.encode = function (box, buf, offset) { + if (box.buffer) { + box.buffer.copy(buf, offset) + exports.mdat.encode.bytes = box.buffer.length + } else { + exports.mdat.encode.bytes = exports.mdat.encodingLength(box) } - self._onWireBitfield = function () { - self.recalculate() +} +exports.mdat.decode = function (buf, start, end) { + return { + buffer: new Buffer(buf.slice(start, end)) } - - self._torrent.wires.forEach(function (wire) { - self._initWire(wire) - }) - self._torrent.on('wire', self._onWire) - self.recalculate() +} +exports.mdat.encodingLength = function (box) { + return box.buffer ? box.buffer.length : box.contentLength } -/** - * Get the index of the rarest piece. Optionally, pass a filter function to exclude - * certain pieces (for instance, those that we already have). - * - * @param {function} pieceFilterFunc - * @return {number} index of rarest piece, or -1 - */ -RarityMap.prototype.getRarestPiece = function (pieceFilterFunc) { - if (!pieceFilterFunc) pieceFilterFunc = trueFn +function writeReserved (buf, offset, end) { + for (var i = offset; i < end; i++) buf[i] = 0 +} - var candidates = [] - var min = Infinity +function writeDate (date, buf, offset) { + buf.writeUInt32BE(Math.floor((date.getTime() + TIME_OFFSET) / 1000), offset) +} - for (var i = 0; i < this._numPieces; ++i) { - if (!pieceFilterFunc(i)) continue +// TODO: think something is wrong here +function writeFixed32 (num, buf, offset) { + buf.writeUInt16BE(Math.floor(num) % (256 * 256), offset) + buf.writeUInt16BE(Math.floor(num * 256 * 256) % (256 * 256), offset + 2) +} - var availability = this._pieces[i] - if (availability === min) { - candidates.push(i) - } else if (availability < min) { - candidates = [ i ] - min = availability - } - } +function writeFixed16 (num, buf, offset) { + buf[offset] = Math.floor(num) % 256 + buf[offset + 1] = Math.floor(num * 256) % 256 +} - if (candidates.length > 0) { - // if there are multiple pieces with the same availability, choose one randomly - return candidates[Math.random() * candidates.length | 0] - } else { - return -1 +function writeMatrix (list, buf, offset) { + if (!list) list = [0, 0, 0, 0, 0, 0, 0, 0, 0] + for (var i = 0; i < list.length; i++) { + writeFixed32(list[i], buf, offset + i * 4) } } -RarityMap.prototype.destroy = function () { - var self = this - self._torrent.removeListener('wire', self._onWire) - self._torrent.wires.forEach(function (wire) { - self._cleanupWireEvents(wire) - }) - self._torrent = null - self._pieces = null +function writeString (str, buf, offset) { + var strBuffer = new Buffer(str, 'utf8') + strBuffer.copy(buf, offset) + buf[offset + strBuffer.length] = 0 +} - self._onWire = null - self._onWireHave = null - self._onWireBitfield = null +function readMatrix (buf) { + var list = new Array(buf.length / 4) + for (var i = 0; i < list.length; i++) list[i] = readFixed32(buf, i * 4) + return list } -RarityMap.prototype._initWire = function (wire) { - var self = this +function readDate (buf, offset) { + return new Date(buf.readUInt32BE(offset) * 1000 - TIME_OFFSET) +} - wire._onClose = function () { - self._cleanupWireEvents(wire) - for (var i = 0; i < this._numPieces; ++i) { - self._pieces[i] -= wire.peerPieces.get(i) - } - } +function readFixed32 (buf, offset) { + return buf.readUInt16BE(offset) + buf.readUInt16BE(offset + 2) / (256 * 256) +} - wire.on('have', self._onWireHave) - wire.on('bitfield', self._onWireBitfield) - wire.once('close', wire._onClose) +function readFixed16 (buf, offset) { + return buf[offset] + buf[offset + 1] / 256 } -/** - * Recalculates piece availability across all peers in the torrent. - */ -RarityMap.prototype.recalculate = function () { +function readString (buf, offset, length) { var i - for (i = 0; i < this._numPieces; ++i) { - this._pieces[i] = 0 - } - - var numWires = this._torrent.wires.length - for (i = 0; i < numWires; ++i) { - var wire = this._torrent.wires[i] - for (var j = 0; j < this._numPieces; ++j) { - this._pieces[j] += wire.peerPieces.get(j) + for (i = 0; i < length; i++) { + if (buf[offset + i] === 0) { + break } } + return buf.toString('utf8', offset, offset + i) } -RarityMap.prototype._cleanupWireEvents = function (wire) { - wire.removeListener('have', this._onWireHave) - wire.removeListener('bitfield', this._onWireBitfield) - if (wire._onClose) wire.removeListener('close', wire._onClose) - wire._onClose = null +}).call(this,require("buffer").Buffer) +},{"./descriptor":107,"./index":108,"buffer":4}],107:[function(require,module,exports){ +(function (Buffer){ +var tagToName = { + 0x03: 'ESDescriptor', + 0x04: 'DecoderConfigDescriptor', + 0x05: 'DecoderSpecificInfo', + 0x06: 'SLConfigDescriptor' } -function trueFn () { - return true -} +exports.Descriptor = {} +exports.Descriptor.decode = function (buf, start, end) { + var tag = buf.readUInt8(start) + var ptr = start + 1 + var lenByte + var len = 0 + do { + lenByte = buf.readUInt8(ptr++) + len = (len << 7) | (lenByte & 0x7f) + } while (lenByte & 0x80) -},{}],127:[function(require,module,exports){ -(function (process,global){ -/* global URL, Blob */ + var obj + var tagName = tagToName[tag] // May be undefined; that's ok + if (exports[tagName]) { + obj = exports[tagName].decode(buf, ptr, end) + } else { + obj = { + buffer: new Buffer(buf.slice(ptr, ptr + len)) + } + } -module.exports = Torrent - -var addrToIPPort = require('addr-to-ip-port') -var BitField = require('bitfield') -var ChunkStoreWriteStream = require('chunk-store-stream/write') -var debug = require('debug')('webtorrent:torrent') -var Discovery = require('torrent-discovery') -var EventEmitter = require('events').EventEmitter -var extend = require('xtend') -var extendMutable = require('xtend/mutable') -var fs = require('fs') -var FSChunkStore = require('fs-chunk-store') // browser: `memory-chunk-store` -var get = require('simple-get') -var ImmediateChunkStore = require('immediate-chunk-store') -var inherits = require('inherits') -var MultiStream = require('multistream') -var net = require('net') // browser exclude -var os = require('os') // browser exclude -var parallel = require('run-parallel') -var parallelLimit = require('run-parallel-limit') -var parseTorrent = require('parse-torrent') -var path = require('path') -var Piece = require('torrent-piece') -var pump = require('pump') -var randomIterate = require('random-iterate') -var sha1 = require('simple-sha1') -var speedometer = require('speedometer') -var uniq = require('uniq') -var utMetadata = require('ut_metadata') -var utPex = require('ut_pex') // browser exclude + obj.tag = tag + obj.tagName = tagName + obj.length = (ptr - start) + len + obj.contentsLen = len + return obj +} -var File = require('./file') -var Peer = require('./peer') -var RarityMap = require('./rarity-map') -var Server = require('./server') // browser exclude +exports.DescriptorArray = {} +exports.DescriptorArray.decode = function (buf, start, end) { + var ptr = start + var obj = {} + while (ptr + 2 <= end) { + var descriptor = exports.Descriptor.decode(buf, ptr, end) + ptr += descriptor.length + var tagName = tagToName[descriptor.tag] || ('Descriptor' + descriptor.tag) + obj[tagName] = descriptor + } + return obj +} -var MAX_BLOCK_LENGTH = 128 * 1024 -var PIECE_TIMEOUT = 30000 -var CHOKE_TIMEOUT = 5000 -var SPEED_THRESHOLD = 3 * Piece.BLOCK_LENGTH +exports.ESDescriptor = {} +exports.ESDescriptor.decode = function (buf, start, end) { + var flags = buf.readUInt8(start + 2) + var ptr = start + 3 + if (flags & 0x80) { + ptr += 2 + } + if (flags & 0x40) { + var len = buf.readUInt8(ptr) + ptr += len + 1 + } + if (flags & 0x20) { + ptr += 2 + } + return exports.DescriptorArray.decode(buf, ptr, end) +} -var PIPELINE_MIN_DURATION = 0.5 -var PIPELINE_MAX_DURATION = 1 +exports.DecoderConfigDescriptor = {} +exports.DecoderConfigDescriptor.decode = function (buf, start, end) { + var oti = buf.readUInt8(start) + var obj = exports.DescriptorArray.decode(buf, start + 13, end) + obj.oti = oti + return obj +} -var RECHOKE_INTERVAL = 10000 // 10 seconds -var RECHOKE_OPTIMISTIC_DURATION = 2 // 30 seconds +}).call(this,require("buffer").Buffer) +},{"buffer":4}],108:[function(require,module,exports){ +(function (Buffer){ +// var assert = require('assert') +var uint64be = require('uint64be') -var FILESYSTEM_CONCURRENCY = 2 +var boxes = require('./boxes') -var RECONNECT_WAIT = [ 1000, 5000, 15000 ] +var UINT32_MAX = 4294967295 -var VERSION = require('../package.json').version -var USER_AGENT = 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' +var Box = exports -var TMP -try { - TMP = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent') -} catch (err) { - TMP = path.join(typeof os.tmpdir === 'function' ? os.tmpdir() : '/', 'webtorrent') +/* + * Lists the proper order for boxes inside containers. + * Five-character names ending in 's' indicate arrays instead of single elements. + */ +var containers = exports.containers = { + 'moov': ['mvhd', 'meta', 'traks', 'mvex'], + 'trak': ['tkhd', 'tref', 'trgr', 'edts', 'meta', 'mdia', 'udta'], + 'edts': ['elst'], + 'mdia': ['mdhd', 'hdlr', 'elng', 'minf'], + 'minf': ['vmhd', 'smhd', 'hmhd', 'sthd', 'nmhd', 'dinf', 'stbl'], + 'dinf': ['dref'], + 'stbl': ['stsd', 'stts', 'ctts', 'cslg', 'stsc', 'stsz', 'stz2', 'stco', 'co64', 'stss', 'stsh', 'padb', 'stdp', 'sdtp', 'sbgps', 'sgpds', 'subss', 'saizs', 'saios'], + 'mvex': ['mehd', 'trexs', 'leva'], + 'moof': ['mfhd', 'meta', 'trafs'], + 'traf': ['tfhd', 'trun', 'sbgps', 'sgpds', 'subss', 'saizs', 'saios', 'tfdt', 'meta'] } -inherits(Torrent, EventEmitter) - -function Torrent (torrentId, client, opts) { - EventEmitter.call(this) +Box.encode = function (obj, buffer, offset) { + Box.encodingLength(obj) // sets every level appropriately + offset = offset || 0 + buffer = buffer || new Buffer(obj.length) + return Box._encode(obj, buffer, offset) +} - this._debugId = 'unknown infohash' - this.client = client +Box._encode = function (obj, buffer, offset) { + var type = obj.type + var len = obj.length + if (len > UINT32_MAX) { + len = 1 + } + buffer.writeUInt32BE(len, offset) + buffer.write(obj.type, offset + 4, 4, 'ascii') + var ptr = offset + 8 + if (len === 1) { + uint64be.encode(obj.length, buffer, ptr) + ptr += 8 + } + if (boxes.fullBoxes[type]) { + buffer.writeUInt32BE(obj.flags || 0, ptr) + buffer.writeUInt8(obj.version || 0, ptr) + ptr += 4 + } - this.announce = opts.announce - this.urlList = opts.urlList + if (containers[type]) { + var contents = containers[type] + contents.forEach(function (childType) { + if (childType.length === 5) { + var entry = obj[childType] || [] + childType = childType.substr(0, 4) + entry.forEach(function (child) { + Box._encode(child, buffer, ptr) + ptr += Box.encode.bytes + }) + } else if (obj[childType]) { + Box._encode(obj[childType], buffer, ptr) + ptr += Box.encode.bytes + } + }) + if (obj.otherBoxes) { + obj.otherBoxes.forEach(function (child) { + Box._encode(child, buffer, ptr) + ptr += Box.encode.bytes + }) + } + } else if (boxes[type]) { + var encode = boxes[type].encode + encode(obj, buffer, ptr) + ptr += encode.bytes + } else if (obj.buffer) { + var buf = obj.buffer + buf.copy(buffer, ptr) + ptr += obj.buffer.length + } else { + throw new Error('Either `type` must be set to a known type (not\'' + type + '\') or `buffer` must be set') + } - this.path = opts.path - this._store = opts.store || FSChunkStore - this._getAnnounceOpts = opts.getAnnounceOpts + Box.encode.bytes = ptr - offset + // assert.equal(ptr - offset, obj.length, 'Error encoding \'' + type + '\': wrote ' + ptr - offset + ' bytes, expecting ' + obj.length) + return buffer +} - this.strategy = opts.strategy || 'sequential' +/* + * Returns an object with `type` and `size` fields, + * or if there isn't enough data, returns the total + * number of bytes needed to read the headers + */ +Box.readHeaders = function (buffer, start, end) { + start = start || 0 + end = end || buffer.length + if (end - start < 8) { + return 8 + } - this.maxWebConns = opts.maxWebConns || 4 + var len = buffer.readUInt32BE(start) + var type = buffer.toString('ascii', start + 4, start + 8) + var ptr = start + 8 - this._rechokeNumSlots = (opts.uploads === false || opts.uploads === 0) - ? 0 - : (+opts.uploads || 10) - this._rechokeOptimisticWire = null - this._rechokeOptimisticTime = 0 - this._rechokeIntervalId = null + if (len === 1) { + if (end - start < 16) { + return 16 + } - this.ready = false - this.destroyed = false - this.paused = false - this.done = false + len = uint64be.decode(buffer, ptr) + ptr += 8 + } - this.metadata = null - this.store = null - this.files = [] - this.pieces = [] + var version + var flags + if (boxes.fullBoxes[type]) { + version = buffer.readUInt8(ptr) + flags = buffer.readUInt32BE(ptr) & 0xffffff + ptr += 4 + } - this._amInterested = false - this._selections = [] - this._critical = [] + return { + length: len, + headersLen: ptr - start, + contentLen: len - (ptr - start), + type: type, + version: version, + flags: flags + } +} - this.wires = [] // open wires (added *after* handshake) +Box.decode = function (buffer, start, end) { + start = start || 0 + end = end || buffer.length + var headers = Box.readHeaders(buffer, start, end) + if (!headers || headers.length > end - start) { + throw new Error('Data too short') + } - this._queue = [] // queue of outgoing tcp peers to connect to - this._peers = {} // connected peers (addr/peerId -> Peer) - this._peersLength = 0 // number of elements in `this._peers` (cache, for perf) + return Box.decodeWithoutHeaders(headers, buffer, start + headers.headersLen, start + headers.length) +} - // stats - this.received = 0 - this.uploaded = 0 - this._downloadSpeed = speedometer() - this._uploadSpeed = speedometer() - - // for cleanup - this._servers = [] - this._xsRequests = [] - - // TODO: remove this and expose a hook instead - // optimization: don't recheck every file if it hasn't changed - this._fileModtimes = opts.fileModtimes - - if (torrentId !== null) this._onTorrentId(torrentId) +Box.decodeWithoutHeaders = function (headers, buffer, start, end) { + start = start || 0 + end = end || buffer.length + var type = headers.type + var obj = {} + if (containers[type]) { + obj.otherBoxes = [] + var contents = containers[type] + var ptr = start + while (end - ptr >= 8) { + var child = Box.decode(buffer, ptr, end) + ptr += child.length + if (contents.indexOf(child.type) >= 0) { + obj[child.type] = child + } else if (contents.indexOf(child.type + 's') >= 0) { + var childType = child.type + 's' + var entry = obj[childType] = obj[childType] || [] + entry.push(child) + } else { + obj.otherBoxes.push(child) + } + } + } else if (boxes[type]) { + var decode = boxes[type].decode + obj = decode(buffer, start, end) + } else { + obj.buffer = new Buffer(buffer.slice(start, end)) + } - this._debug('new torrent') + obj.length = headers.length + obj.contentLen = headers.contentLen + obj.type = headers.type + obj.version = headers.version + obj.flags = headers.flags + return obj } -Object.defineProperty(Torrent.prototype, 'timeRemaining', { - get: function () { - if (this.done) return 0 - if (this.downloadSpeed === 0) return Infinity - return ((this.length - this.downloaded) / this.downloadSpeed) * 1000 +Box.encodingLength = function (obj) { + var type = obj.type + + var len = 8 + if (boxes.fullBoxes[type]) { + len += 4 } -}) -Object.defineProperty(Torrent.prototype, 'downloaded', { - get: function () { - if (!this.bitfield) return 0 - var downloaded = 0 - for (var index = 0, len = this.pieces.length; index < len; ++index) { - if (this.bitfield.get(index)) { // verified data - downloaded += (index === len - 1) ? this.lastPieceLength : this.pieceLength - } else { // "in progress" data - var piece = this.pieces[index] - downloaded += (piece.length - piece.missing) + if (containers[type]) { + var contents = containers[type] + contents.forEach(function (childType) { + if (childType.length === 5) { + var entry = obj[childType] || [] + childType = childType.substr(0, 4) + entry.forEach(function (child) { + child.type = childType + len += Box.encodingLength(child) + }) + } else if (obj[childType]) { + var child = obj[childType] + child.type = childType + len += Box.encodingLength(child) } + }) + if (obj.otherBoxes) { + obj.otherBoxes.forEach(function (child) { + len += Box.encodingLength(child) + }) } - return downloaded + } else if (boxes[type]) { + len += boxes[type].encodingLength(obj) + } else if (obj.buffer) { + len += obj.buffer.length + } else { + throw new Error('Either `type` must be set to a known type (not\'' + type + '\') or `buffer` must be set') } -}) - -// TODO: re-enable this. The number of missing pieces. Used to implement 'end game' mode. -// Object.defineProperty(Storage.prototype, 'numMissing', { -// get: function () { -// var self = this -// var numMissing = self.pieces.length -// for (var index = 0, len = self.pieces.length; index < len; index++) { -// numMissing -= self.bitfield.get(index) -// } -// return numMissing -// } -// }) -Object.defineProperty(Torrent.prototype, 'downloadSpeed', { - get: function () { return this._downloadSpeed() } -}) + if (len > UINT32_MAX) { + len += 8 + } -Object.defineProperty(Torrent.prototype, 'uploadSpeed', { - get: function () { return this._uploadSpeed() } -}) + obj.length = len + return len +} -Object.defineProperty(Torrent.prototype, 'progress', { - get: function () { return this.length ? this.downloaded / this.length : 0 } -}) +}).call(this,require("buffer").Buffer) +},{"./boxes":106,"buffer":4,"uint64be":155}],109:[function(require,module,exports){ +(function (Buffer){ +var stream = require('readable-stream') +var inherits = require('inherits') +var nextEvent = require('next-event') +var Box = require('mp4-box-encoding') -Object.defineProperty(Torrent.prototype, 'ratio', { - get: function () { return this.uploaded / (this.received || 1) } -}) +var EMPTY = new Buffer(0) -Object.defineProperty(Torrent.prototype, 'numPeers', { - get: function () { return this.wires.length } -}) +module.exports = Decoder -Object.defineProperty(Torrent.prototype, 'torrentFileBlobURL', { - get: function () { - if (typeof window === 'undefined') throw new Error('browser-only property') - if (!this.torrentFile) return null - return URL.createObjectURL( - new Blob([ this.torrentFile ], { type: 'application/x-bittorrent' }) - ) - } -}) +function Decoder () { + if (!(this instanceof Decoder)) return new Decoder() + stream.Writable.call(this) -Object.defineProperty(Torrent.prototype, '_numQueued', { - get: function () { - return this._queue.length + (this._peersLength - this._numConns) - } -}) + this.destroyed = false -Object.defineProperty(Torrent.prototype, '_numConns', { - get: function () { - var self = this - var numConns = 0 - for (var id in self._peers) { - if (self._peers[id].connected) numConns += 1 - } - return numConns - } -}) + this._pending = 0 + this._missing = 0 + this._buf = null + this._str = null + this._cb = null + this._ondrain = null + this._writeBuffer = null + this._writeCb = null -// TODO: remove in v1 -Object.defineProperty(Torrent.prototype, 'swarm', { - get: function () { - console.warn('WebTorrent: `torrent.swarm` is deprecated. Use `torrent` directly instead.') - return this - } -}) + this._ondrain = null + this._kick() +} -Torrent.prototype._onTorrentId = function (torrentId) { - var self = this - if (self.destroyed) return +inherits(Decoder, stream.Writable) - var parsedTorrent - try { parsedTorrent = parseTorrent(torrentId) } catch (err) {} - if (parsedTorrent) { - // Attempt to set infoHash property synchronously - self.infoHash = parsedTorrent.infoHash - self._debugId = parsedTorrent.infoHash.toString('hex').substring(0, 7) - process.nextTick(function () { - if (self.destroyed) return - self._onParsedTorrent(parsedTorrent) - }) - } else { - // If torrentId failed to parse, it could be in a form that requires an async - // operation, i.e. http/https link, filesystem path, or Blob. - parseTorrent.remote(torrentId, function (err, parsedTorrent) { - if (self.destroyed) return - if (err) return self._destroy(err) - self._onParsedTorrent(parsedTorrent) - }) - } +Decoder.prototype.destroy = function (err) { + if (this.destroyed) return + this.destroyed = true + if (err) this.emit('error', err) + this.emit('close') } -Torrent.prototype._onParsedTorrent = function (parsedTorrent) { - var self = this - if (self.destroyed) return +Decoder.prototype._write = function (data, enc, next) { + if (this.destroyed) return + var drained = !this._str || !this._str._writableState.needDrain - self._processParsedTorrent(parsedTorrent) + while (data.length && !this.destroyed) { + if (!this._missing) { + this._writeBuffer = data + this._writeCb = next + return + } - if (!self.infoHash) { - return self._destroy(new Error('Malformed torrent data: No info hash')) - } + var consumed = data.length < this._missing ? data.length : this._missing + if (this._buf) data.copy(this._buf, this._buf.length - this._missing) + else if (this._str) drained = this._str.write(consumed === data.length ? data : data.slice(0, consumed)) - if (!self.path) self.path = path.join(TMP, self.infoHash) + this._missing -= consumed - self._rechokeIntervalId = setInterval(function () { - self._rechoke() - }, RECHOKE_INTERVAL) - if (self._rechokeIntervalId.unref) self._rechokeIntervalId.unref() + if (!this._missing) { + var buf = this._buf + var cb = this._cb + var stream = this._str - // Private 'infoHash' event allows client.add to check for duplicate torrents and - // destroy them before the normal 'infoHash' event is emitted. Prevents user - // applications from needing to deal with duplicate 'infoHash' events. - self.emit('_infoHash', self.infoHash) - if (self.destroyed) return + this._buf = this._cb = this._str = this._ondrain = null + drained = true - self.emit('infoHash', self.infoHash) - if (self.destroyed) return // user might destroy torrent in event handler + if (stream) stream.end() + if (cb) cb(buf) + } - if (self.client.listening) { - self._onListening() - } else { - self.client.once('listening', function () { - self._onListening() - }) - } -} - -Torrent.prototype._processParsedTorrent = function (parsedTorrent) { - this._debugId = parsedTorrent.infoHash.toString('hex').substring(0, 7) - - if (this.announce) { - // Allow specifying trackers via `opts` parameter - parsedTorrent.announce = parsedTorrent.announce.concat(this.announce) - } - - if (this.client.tracker && global.WEBTORRENT_ANNOUNCE && !this.private) { - // So `webtorrent-hybrid` can force specific trackers to be used - parsedTorrent.announce = parsedTorrent.announce.concat(global.WEBTORRENT_ANNOUNCE) + data = consumed === data.length ? EMPTY : data.slice(consumed) } - if (this.urlList) { - // Allow specifying web seeds via `opts` parameter - parsedTorrent.urlList = parsedTorrent.urlList.concat(this.urlList) + if (this._pending && !this._missing) { + this._writeBuffer = data + this._writeCb = next + return } - uniq(parsedTorrent.announce) - uniq(parsedTorrent.urlList) + if (drained) next() + else this._ondrain(next) +} - extendMutable(this, parsedTorrent) +Decoder.prototype._buffer = function (size, cb) { + this._missing = size + this._buf = new Buffer(size) + this._cb = cb +} - this.magnetURI = parseTorrent.toMagnetURI(parsedTorrent) - this.torrentFile = parseTorrent.toTorrentFile(parsedTorrent) +Decoder.prototype._stream = function (size, cb) { + var self = this + this._missing = size + this._str = new MediaData(this) + this._ondrain = nextEvent(this._str, 'drain') + this._pending++ + this._str.on('end', function () { + self._pending-- + self._kick() + }) + this._cb = cb + return this._str } -Torrent.prototype._onListening = function () { +Decoder.prototype._readBox = function () { var self = this - if (self.discovery || self.destroyed) return + bufferHeaders(8) - var trackerOpts = self.client.tracker - if (trackerOpts) { - trackerOpts = extend(self.client.tracker, { - getAnnounceOpts: function () { - var opts = { - uploaded: self.uploaded, - downloaded: self.downloaded, - left: Math.max(self.length - self.downloaded, 0) - } - if (self.client.tracker.getAnnounceOpts) { - extendMutable(opts, self.client.tracker.getAnnounceOpts()) - } - if (self._getAnnounceOpts) { - // TODO: consider deprecating this, as it's redundant with the former case - extendMutable(opts, self._getAnnounceOpts()) - } - return opts + function bufferHeaders (len, buf) { + self._buffer(len, function (additionalBuf) { + if (buf) { + buf = Buffer.concat([buf, additionalBuf]) + } else { + buf = additionalBuf + } + var headers = Box.readHeaders(buf) + if (typeof headers === 'number') { + bufferHeaders(headers - buf.length, buf) + } else { + self._pending++ + self._headers = headers + self.emit('box', headers) } }) } +} - // begin discovering peers via DHT and trackers - self.discovery = new Discovery({ - infoHash: self.infoHash, - announce: self.announce, - peerId: self.client.peerId, - dht: !self.private && self.client.dht, - tracker: trackerOpts, - port: self.client.torrentPort, - userAgent: USER_AGENT - }) +Decoder.prototype.stream = function () { + var self = this + if (!self._headers) throw new Error('this function can only be called once after \'box\' is emitted') + var headers = self._headers + self._headers = null - self.discovery.on('error', onError) - self.discovery.on('peer', onPeer) - self.discovery.on('trackerAnnounce', onTrackerAnnounce) - self.discovery.on('dhtAnnounce', onDHTAnnounce) - self.discovery.on('warning', onWarning) + return self._stream(headers.contentLen, null) +} - function onError (err) { - self._destroy(err) - } +Decoder.prototype.decode = function (cb) { + var self = this + if (!self._headers) throw new Error('this function can only be called once after \'box\' is emitted') + var headers = self._headers + self._headers = null - function onPeer (peer) { - // Don't create new outgoing TCP connections when torrent is done - if (typeof peer === 'string' && self.done) return - self.addPeer(peer) - } + self._buffer(headers.contentLen, function (buf) { + var box = Box.decodeWithoutHeaders(headers, buf) + cb(box) + self._pending-- + self._kick() + }) +} - function onTrackerAnnounce () { - self.emit('trackerAnnounce') - if (self.numPeers === 0) self.emit('noPeers', 'tracker') - } +Decoder.prototype.ignore = function () { + var self = this + if (!self._headers) throw new Error('this function can only be called once after \'box\' is emitted') + var headers = self._headers + self._headers = null - function onDHTAnnounce () { - self.emit('dhtAnnounce') - if (self.numPeers === 0) self.emit('noPeers', 'dht') + this._missing = headers.contentLen + this._cb = function () { + self._pending-- + self._kick() } +} - function onWarning (err) { - self.emit('warning', err) +Decoder.prototype._kick = function () { + if (this._pending) return + if (!this._buf && !this._str) this._readBox() + if (this._writeBuffer) { + var next = this._writeCb + var buffer = this._writeBuffer + this._writeBuffer = null + this._writeCb = null + this._write(buffer, null, next) } +} - if (self.info) { - // if full metadata was included in initial torrent id, use it immediately. Otherwise, - // wait for torrent-discovery to find peers and ut_metadata to get the metadata. - self._onMetadata(self) - } else if (self.xs) { - self._getMetadataFromServer() - } +function MediaData (parent) { + this._parent = parent + this.destroyed = false + stream.PassThrough.call(this) } -Torrent.prototype._getMetadataFromServer = function () { - var self = this - var urls = Array.isArray(self.xs) ? self.xs : [ self.xs ] +inherits(MediaData, stream.PassThrough) - var tasks = urls.map(function (url) { - return function (cb) { - getMetadataFromURL(url, cb) - } - }) - parallel(tasks) +MediaData.prototype.destroy = function (err) { + if (this.destroyed) return + this.destroyed = true + this._parent.destroy(err) + if (err) this.emit('error', err) + this.emit('close') +} - function getMetadataFromURL (url, cb) { - if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { - self.emit('warning', new Error('skipping non-http xs param: ' + url)) - return cb(null) - } +}).call(this,require("buffer").Buffer) +},{"buffer":4,"inherits":96,"mp4-box-encoding":108,"next-event":114,"readable-stream":132}],110:[function(require,module,exports){ +(function (process,Buffer){ +var stream = require('readable-stream') +var inherits = require('inherits') +var Box = require('mp4-box-encoding') - var opts = { - url: url, - method: 'GET', - headers: { - 'user-agent': USER_AGENT - } - } - var req - try { - req = get.concat(opts, onResponse) - } catch (err) { - self.emit('warning', new Error('skipping invalid url xs param: ' + url)) - return cb(null) - } +module.exports = Encoder - self._xsRequests.push(req) +function noop () {} - function onResponse (err, res, torrent) { - if (self.destroyed) return cb(null) - if (self.metadata) return cb(null) +function Encoder () { + if (!(this instanceof Encoder)) return new Encoder() + stream.Readable.call(this) - if (err) { - self.emit('warning', new Error('http error from xs param: ' + url)) - return cb(null) - } - if (res.statusCode !== 200) { - self.emit('warning', new Error('non-200 status code ' + res.statusCode + ' from xs param: ' + url)) - return cb(null) - } + this.destroyed = false - var parsedTorrent - try { - parsedTorrent = parseTorrent(torrent) - } catch (err) {} + this._reading = false + this._stream = null + this._drain = null + this._want = false + this._onreadable = onreadable + this._onend = onend - if (!parsedTorrent) { - self.emit('warning', new Error('got invalid torrent file from xs param: ' + url)) - return cb(null) - } + var self = this - if (parsedTorrent.infoHash !== self.infoHash) { - self.emit('warning', new Error('got torrent file with incorrect info hash from xs param: ' + url)) - return cb(null) - } + function onreadable () { + if (!self._want) return + self._want = false + self._read() + } - self._onMetadata(parsedTorrent) - cb(null) - } + function onend () { + self._stream = null } } -/** - * Called when the full torrent metadata is received. - */ -Torrent.prototype._onMetadata = function (metadata) { - var self = this - if (self.metadata || self.destroyed) return - self._debug('got metadata') - - self._xsRequests.forEach(function (req) { - req.abort() - }) - self._xsRequests = [] +inherits(Encoder, stream.Readable) - var parsedTorrent - if (metadata && metadata.infoHash) { - // `metadata` is a parsed torrent (from parse-torrent module) - parsedTorrent = metadata - } else { - try { - parsedTorrent = parseTorrent(metadata) - } catch (err) { - return self._destroy(err) - } - } +Encoder.prototype.mediaData = +Encoder.prototype.mdat = function (size, cb) { + var stream = new MediaData(this) + this.box({type: 'mdat', contentLength: size, encodeBufferLen: 8, stream: stream}, cb) + return stream +} - self._processParsedTorrent(parsedTorrent) - self.metadata = self.torrentFile +Encoder.prototype.box = function (box, cb) { + if (!cb) cb = noop + if (this.destroyed) return cb(new Error('Encoder is destroyed')) - // add web seed urls (BEP19) - if (self.client.enableWebSeeds) { - self.urlList.forEach(function (url) { - self.addWebSeed(url) - }) + var buf + if (box.encodeBufferLen) { + buf = new Buffer(box.encodeBufferLen) } - - // start off selecting the entire torrent with low priority - if (self.pieces.length !== 0) { - self.select(0, self.pieces.length - 1, false) + if (box.stream) { + box.buffer = null + buf = Box.encode(box, buf) + this.push(buf) + this._stream = box.stream + this._stream.on('readable', this._onreadable) + this._stream.on('end', this._onend) + this._stream.on('end', cb) + this._forward() + } else { + buf = Box.encode(box, buf) + var drained = this.push(buf) + if (drained) return process.nextTick(cb) + this._drain = cb } +} - self._rarityMap = new RarityMap(self) - - self.store = new ImmediateChunkStore( - new self._store(self.pieceLength, { - torrent: { - infoHash: self.infoHash - }, - files: self.files.map(function (file) { - return { - path: path.join(self.path, file.path), - length: file.length, - offset: file.offset - } - }), - length: self.length - }) - ) - - self.files = self.files.map(function (file) { - return new File(self, file) - }) - - self._hashes = self.pieces - - self.pieces = self.pieces.map(function (hash, i) { - var pieceLength = (i === self.pieces.length - 1) - ? self.lastPieceLength - : self.pieceLength - return new Piece(pieceLength) - }) +Encoder.prototype.destroy = function (err) { + if (this.destroyed) return + this.destroyed = true + if (this._stream && this._stream.destroy) this._stream.destroy() + this._stream = null + if (this._drain) { + var cb = this._drain + this._drain = null + cb(err) + } + if (err) this.emit('error', err) + this.emit('close') +} - self._reservations = self.pieces.map(function () { - return [] - }) +Encoder.prototype.finalize = function () { + this.push(null) +} - self.bitfield = new BitField(self.pieces.length) +Encoder.prototype._forward = function () { + if (!this._stream) return - self.wires.forEach(function (wire) { - // If we didn't have the metadata at the time ut_metadata was initialized for this - // wire, we still want to make it available to the peer in case they request it. - if (wire.ut_metadata) wire.ut_metadata.setMetadata(self.metadata) + while (!this.destroyed) { + var buf = this._stream.read() - self._onWireWithMetadata(wire) - }) + if (!buf) { + this._want = !!this._stream + return + } - self._debug('verifying existing torrent data') - if (self._fileModtimes && self._store === FSChunkStore) { - // don't verify if the files haven't been modified since we last checked - self.getFileModtimes(function (err, fileModtimes) { - if (err) return self._destroy(err) + if (!this.push(buf)) return + } +} - var unchanged = self.files.map(function (_, index) { - return fileModtimes[index] === self._fileModtimes[index] - }).every(function (x) { - return x - }) +Encoder.prototype._read = function () { + if (this._reading || this.destroyed) return + this._reading = true - if (unchanged) { - for (var index = 0; index < self.pieces.length; index++) { - self._markVerified(index) - } - self._onStore() - } else { - self._verifyPieces() - } - }) - } else { - self._verifyPieces() + if (this._stream) this._forward() + if (this._drain) { + var drain = this._drain + this._drain = null + drain() } - self.emit('metadata') + this._reading = false } -/* - * TODO: remove this - * Gets the last modified time of every file on disk for this torrent. - * Only valid in Node, not in the browser. - */ -Torrent.prototype.getFileModtimes = function (cb) { - var self = this - var ret = [] - parallelLimit(self.files.map(function (file, index) { - return function (cb) { - fs.stat(path.join(self.path, file.path), function (err, stat) { - if (err && err.code !== 'ENOENT') return cb(err) - ret[index] = stat && stat.mtime.getTime() - cb(null) - }) - } - }), FILESYSTEM_CONCURRENCY, function (err) { - self._debug('done getting file modtimes') - cb(err, ret) - }) +function MediaData (parent) { + this._parent = parent + this.destroyed = false + stream.PassThrough.call(this) } -Torrent.prototype._verifyPieces = function () { - var self = this - parallelLimit(self.pieces.map(function (_, index) { - return function (cb) { - if (self.destroyed) return cb(new Error('torrent is destroyed')) - - self.store.get(index, function (err, buf) { - if (self.destroyed) return cb(new Error('torrent is destroyed')) - - if (err) return process.nextTick(cb, null) // ignore error - sha1(buf, function (hash) { - if (self.destroyed) return cb(new Error('torrent is destroyed')) +inherits(MediaData, stream.PassThrough) - if (hash === self._hashes[index]) { - if (!self.pieces[index]) return - self._debug('piece verified %s', index) - self._markVerified(index) - } else { - self._debug('piece invalid %s', index) - } - cb(null) - }) - }) - } - }), FILESYSTEM_CONCURRENCY, function (err) { - if (err) return self._destroy(err) - self._debug('done verifying') - self._onStore() - }) +MediaData.prototype.destroy = function (err) { + if (this.destroyed) return + this.destroyed = true + this._parent.destroy(err) + if (err) this.emit('error', err) + this.emit('close') } -Torrent.prototype._markVerified = function (index) { - this.pieces[index] = null - this._reservations[index] = null - this.bitfield.set(index, true) -} +}).call(this,require('_process'),require("buffer").Buffer) +},{"_process":15,"buffer":4,"inherits":96,"mp4-box-encoding":108,"readable-stream":132}],111:[function(require,module,exports){ +exports.decode = require('./decode') +exports.encode = require('./encode') +},{"./decode":109,"./encode":110}],112:[function(require,module,exports){ /** - * Called when the metadata, listening server, and underlying chunk store is initialized. + * Helpers. */ -Torrent.prototype._onStore = function () { - var self = this - if (self.destroyed) return - self._debug('on store') - self.ready = true - self.emit('ready') +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; - // Files may start out done if the file was already in the store - self._checkDone() +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ - // In case any selections were made before torrent was ready - self._updateSelections() -} +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; -Torrent.prototype.destroy = function (cb) { - var self = this - self._destroy(null, cb) +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } } -Torrent.prototype._destroy = function (err, cb) { - var self = this - if (self.destroyed) return - self.destroyed = true - self._debug('destroy') +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ - self.client._remove(self) +function fmtShort(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd'; + } + if (ms >= h) { + return Math.round(ms / h) + 'h'; + } + if (ms >= m) { + return Math.round(ms / m) + 'm'; + } + if (ms >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} - clearInterval(self._rechokeIntervalId) +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ - self._xsRequests.forEach(function (req) { - req.abort() - }) +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms'; +} - if (self._rarityMap) { - self._rarityMap.destroy() - } +/** + * Pluralization helper. + */ - for (var id in self._peers) { - self.removePeer(id) +function plural(ms, n, name) { + if (ms < n) { + return; + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name; } + return Math.ceil(ms / n) + ' ' + name + 's'; +} - self.files.forEach(function (file) { - if (file instanceof File) file._destroy() - }) +},{}],113:[function(require,module,exports){ +module.exports = MultiStream - var tasks = self._servers.map(function (server) { - return function (cb) { - server.destroy(cb) - } - }) +var inherits = require('inherits') +var stream = require('readable-stream') - if (self.discovery) { - tasks.push(function (cb) { - self.discovery.destroy(cb) - }) - } +inherits(MultiStream, stream.Readable) - if (self.store) { - tasks.push(function (cb) { - self.store.close(cb) - }) - } +function MultiStream (streams, opts) { + var self = this + if (!(self instanceof MultiStream)) return new MultiStream(streams, opts) + stream.Readable.call(self, opts) - parallel(tasks, cb) + self.destroyed = false - if (err) { - // Torrent errors are emitted at `torrent.on('error')`. If there are no 'error' - // event handlers on the torrent instance, then the error will be emitted at - // `client.on('error')`. This prevents throwing an uncaught exception - // (unhandled 'error' event), but it makes it impossible to distinguish client - // errors versus torrent errors. Torrent errors are not fatal, and the client - // is still usable afterwards. Therefore, always listen for errors in both - // places (`client.on('error')` and `torrent.on('error')`). - if (self.listenerCount('error') === 0) { - self.client.emit('error', err) - } else { - self.emit('error', err) - } + self._drained = false + self._forwarding = false + self._current = null + + if (typeof streams === 'function') { + self._queue = streams + } else { + self._queue = streams.map(toStreams2) + self._queue.forEach(function (stream) { + if (typeof stream !== 'function') self._attachErrorListener(stream) + }) } - self.emit('close') + self._next() +} - self.client = null - self.files = [] - self.discovery = null - self.store = null - self._rarityMap = null - self._peers = null - self._servers = null - self._xsRequests = null +MultiStream.obj = function (streams) { + return new MultiStream(streams, { objectMode: true, highWaterMark: 16 }) } -Torrent.prototype.addPeer = function (peer) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') - if (!self.infoHash) throw new Error('addPeer() must not be called before the `infoHash` event') +MultiStream.prototype._read = function () { + this._drained = true + this._forward() +} - if (self.client.blocked) { - var host - if (typeof peer === 'string') { - var parts - try { - parts = addrToIPPort(peer) - } catch (e) { - self._debug('ignoring peer: invalid %s', peer) - self.emit('invalidPeer', peer) - return false - } - host = parts[0] - } else if (typeof peer.remoteAddress === 'string') { - host = peer.remoteAddress - } +MultiStream.prototype._forward = function () { + if (this._forwarding || !this._drained || !this._current) return + this._forwarding = true - if (host && self.client.blocked.contains(host)) { - self._debug('ignoring peer: blocked %s', peer) - if (typeof peer !== 'string') peer.destroy() - self.emit('blockedPeer', peer) - return false - } + var chunk + while ((chunk = this._current.read()) !== null) { + this._drained = this.push(chunk) } - var wasAdded = !!self._addPeer(peer) - if (wasAdded) { - self.emit('peer', peer) - } else { - self.emit('invalidPeer', peer) - } - return wasAdded + this._forwarding = false } -Torrent.prototype._addPeer = function (peer) { - var self = this - if (self.destroyed) { - if (typeof peer !== 'string') peer.destroy() - return null - } - if (typeof peer === 'string' && !self._validAddr(peer)) { - self._debug('ignoring peer: invalid %s', peer) - return null - } +MultiStream.prototype.destroy = function (err) { + if (this.destroyed) return + this.destroyed = true - var id = (peer && peer.id) || peer - if (self._peers[id]) { - self._debug('ignoring peer: duplicate (%s)', id) - if (typeof peer !== 'string') peer.destroy() - return null + if (this._current && this._current.destroy) this._current.destroy() + if (typeof this._queue !== 'function') { + this._queue.forEach(function (stream) { + if (stream.destroy) stream.destroy() + }) } - if (self.paused) { - self._debug('ignoring peer: torrent is paused') - if (typeof peer !== 'string') peer.destroy() - return null - } + if (err) this.emit('error', err) + this.emit('close') +} - self._debug('add peer %s', id) +MultiStream.prototype._next = function () { + var self = this + self._current = null - var newPeer - if (typeof peer === 'string') { - // `peer` is an addr ("ip:port" string) - newPeer = Peer.createTCPOutgoingPeer(peer, self) + if (typeof self._queue === 'function') { + self._queue(function (err, stream) { + if (err) return self.destroy(err) + stream = toStreams2(stream) + self._attachErrorListener(stream) + self._gotNextStream(stream) + }) } else { - // `peer` is a WebRTC connection (simple-peer) - newPeer = Peer.createWebRTCPeer(peer, self) + var stream = self._queue.shift() + if (typeof stream === 'function') { + stream = toStreams2(stream()) + self._attachErrorListener(stream) + } + self._gotNextStream(stream) } +} - self._peers[newPeer.id] = newPeer - self._peersLength += 1 +MultiStream.prototype._gotNextStream = function (stream) { + var self = this - if (typeof peer === 'string') { - // `peer` is an addr ("ip:port" string) - self._queue.push(newPeer) - self._drain() + if (!stream) { + self.push(null) + self.destroy() + return } - return newPeer -} + self._current = stream + self._forward() -Torrent.prototype.addWebSeed = function (url) { - if (this.destroyed) throw new Error('torrent is destroyed') + stream.on('readable', onReadable) + stream.once('end', onEnd) + stream.once('close', onClose) - if (!/^https?:\/\/.+/.test(url)) { - this.emit('warning', new Error('ignoring invalid web seed: ' + url)) - this.emit('invalidPeer', url) - return + function onReadable () { + self._forward() } - if (this._peers[url]) { - this.emit('warning', new Error('ignoring duplicate web seed: ' + url)) - this.emit('invalidPeer', url) - return + function onClose () { + if (!stream._readableState.ended) { + self.destroy() + } } - this._debug('add web seed %s', url) - - var newPeer = Peer.createWebSeedPeer(url, this) - this._peers[newPeer.id] = newPeer - this._peersLength += 1 - - this.emit('peer', url) + function onEnd () { + self._current = null + stream.removeListener('readable', onReadable) + stream.removeListener('end', onEnd) + stream.removeListener('close', onClose) + self._next() + } } -/** - * Called whenever a new incoming TCP peer connects to this torrent swarm. Called with a - * peer that has already sent a handshake. - */ -Torrent.prototype._addIncomingPeer = function (peer) { +MultiStream.prototype._attachErrorListener = function (stream) { var self = this - if (self.destroyed) return peer.destroy(new Error('torrent is destroyed')) - if (self.paused) return peer.destroy(new Error('torrent is paused')) + if (!stream) return - this._debug('add incoming peer %s', peer.id) + stream.once('error', onError) - self._peers[peer.id] = peer - self._peersLength += 1 + function onError (err) { + stream.removeListener('error', onError) + self.destroy(err) + } } -Torrent.prototype.removePeer = function (peer) { - var self = this - var id = (peer && peer.id) || peer - peer = self._peers[id] - - if (!peer) return - - this._debug('removePeer %s', id) - - delete self._peers[id] - self._peersLength -= 1 - - peer.destroy() +function toStreams2 (s) { + if (!s || typeof s === 'function' || s._readableState) return s - // If torrent swarm was at capacity before, try to open a new connection now - self._drain() + var wrap = new stream.Readable().wrap(s) + if (s.destroy) { + wrap.destroy = s.destroy.bind(s) + } + return wrap } -Torrent.prototype.select = function (start, end, priority, notify) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') +},{"inherits":96,"readable-stream":132}],114:[function(require,module,exports){ +module.exports = nextEvent - if (start < 0 || end < start || self.pieces.length <= end) { - throw new Error('invalid selection ', start, ':', end) +function nextEvent (emitter, name) { + var next = null + emitter.on(name, function (data) { + if (!next) return + var fn = next + next = null + fn(data) + }) + + return function (once) { + next = once } - priority = Number(priority) || 0 +} - self._debug('select %s-%s (priority %s)', start, end, priority) +},{}],115:[function(require,module,exports){ +var wrappy = require('wrappy') +module.exports = wrappy(once) +module.exports.strict = wrappy(onceStrict) - self._selections.push({ - from: start, - to: end, - offset: 0, - priority: priority, - notify: notify || noop +once.proto = once(function () { + Object.defineProperty(Function.prototype, 'once', { + value: function () { + return once(this) + }, + configurable: true }) - self._selections.sort(function (a, b) { - return b.priority - a.priority + Object.defineProperty(Function.prototype, 'onceStrict', { + value: function () { + return onceStrict(this) + }, + configurable: true }) +}) - self._updateSelections() +function once (fn) { + var f = function () { + if (f.called) return f.value + f.called = true + return f.value = fn.apply(this, arguments) + } + f.called = false + return f } -Torrent.prototype.deselect = function (start, end, priority) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') - - priority = Number(priority) || 0 - self._debug('deselect %s-%s (priority %s)', start, end, priority) - - for (var i = 0; i < self._selections.length; ++i) { - var s = self._selections[i] - if (s.from === start && s.to === end && s.priority === priority) { - self._selections.splice(i, 1) - break - } +function onceStrict (fn) { + var f = function () { + if (f.called) + throw new Error(f.onceError) + f.called = true + return f.value = fn.apply(this, arguments) } - - self._updateSelections() + var name = fn.name || 'Function wrapped with `once`' + f.onceError = name + " shouldn't be called more than once" + f.called = false + return f } -Torrent.prototype.critical = function (start, end) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') +},{"wrappy":170}],116:[function(require,module,exports){ +(function (Buffer){ +module.exports = decodeTorrentFile +module.exports.decode = decodeTorrentFile +module.exports.encode = encodeTorrentFile - self._debug('critical %s-%s', start, end) +var bencode = require('bencode') +var path = require('path') +var sha1 = require('simple-sha1') +var uniq = require('uniq') - for (var i = start; i <= end; ++i) { - self._critical[i] = true +/** + * Parse a torrent. Throws an exception if the torrent is missing required fields. + * @param {Buffer|Object} torrent + * @return {Object} parsed torrent + */ +function decodeTorrentFile (torrent) { + if (Buffer.isBuffer(torrent)) { + torrent = bencode.decode(torrent) } - self._updateSelections() -} + // sanity check + ensure(torrent.info, 'info') + ensure(torrent.info['name.utf-8'] || torrent.info.name, 'info.name') + ensure(torrent.info['piece length'], 'info[\'piece length\']') + ensure(torrent.info.pieces, 'info.pieces') -Torrent.prototype._onWire = function (wire, addr) { - var self = this - self._debug('got wire %s (%s)', wire._debugId, addr || 'Unknown') + if (torrent.info.files) { + torrent.info.files.forEach(function (file) { + ensure(typeof file.length === 'number', 'info.files[0].length') + ensure(file['path.utf-8'] || file.path, 'info.files[0].path') + }) + } else { + ensure(typeof torrent.info.length === 'number', 'info.length') + } - wire.on('download', function (downloaded) { - if (self.destroyed) return - self.received += downloaded - self._downloadSpeed(downloaded) - self.client._downloadSpeed(downloaded) - self.emit('download', downloaded) - self.client.emit('download', downloaded) - }) + var result = {} + result.info = torrent.info + result.infoBuffer = bencode.encode(torrent.info) + result.infoHash = sha1.sync(result.infoBuffer) + result.infoHashBuffer = Buffer.from(result.infoHash, 'hex') - wire.on('upload', function (uploaded) { - if (self.destroyed) return - self.uploaded += uploaded - self._uploadSpeed(uploaded) - self.client._uploadSpeed(uploaded) - self.emit('upload', uploaded) - self.client.emit('upload', uploaded) - }) + result.name = (torrent.info['name.utf-8'] || torrent.info.name).toString() - self.wires.push(wire) + if (torrent.info.private !== undefined) result.private = !!torrent.info.private - if (addr) { - // Sometimes RTCPeerConnection.getStats() doesn't return an ip:port for peers - var parts = addrToIPPort(addr) - wire.remoteAddress = parts[0] - wire.remotePort = parts[1] - } + if (torrent['creation date']) result.created = new Date(torrent['creation date'] * 1000) + if (torrent['created by']) result.createdBy = torrent['created by'].toString() - // When peer sends PORT message, add that DHT node to routing table - if (self.client.dht && self.client.dht.listening) { - wire.on('port', function (port) { - if (self.destroyed || self.client.dht.destroyed) { - return - } - if (!wire.remoteAddress) { - return self._debug('ignoring PORT from peer with no address') - } - if (port === 0 || port > 65536) { - return self._debug('ignoring invalid PORT from peer') - } + if (Buffer.isBuffer(torrent.comment)) result.comment = torrent.comment.toString() - self._debug('port: %s (from %s)', port, addr) - self.client.dht.addNode({ host: wire.remoteAddress, port: port }) + // announce and announce-list will be missing if metadata fetched via ut_metadata + result.announce = [] + if (torrent['announce-list'] && torrent['announce-list'].length) { + torrent['announce-list'].forEach(function (urls) { + urls.forEach(function (url) { + result.announce.push(url.toString()) + }) }) + } else if (torrent.announce) { + result.announce.push(torrent.announce.toString()) } - wire.on('timeout', function () { - self._debug('wire timeout (%s)', addr) - // TODO: this might be destroying wires too eagerly - wire.destroy() + // handle url-list (BEP19 / web seeding) + if (Buffer.isBuffer(torrent['url-list'])) { + // some clients set url-list to empty string + torrent['url-list'] = torrent['url-list'].length > 0 + ? [ torrent['url-list'] ] + : [] + } + result.urlList = (torrent['url-list'] || []).map(function (url) { + return url.toString() }) - // Timeout for piece requests to this peer - wire.setTimeout(PIECE_TIMEOUT, true) - - // Send KEEP-ALIVE (every 60s) so peers will not disconnect the wire - wire.setKeepAlive(true) - - // use ut_metadata extension - wire.use(utMetadata(self.metadata)) - - wire.ut_metadata.on('warning', function (err) { - self._debug('ut_metadata warning: %s', err.message) - }) + uniq(result.announce) + uniq(result.urlList) - if (!self.metadata) { - wire.ut_metadata.on('metadata', function (metadata) { - self._debug('got metadata via ut_metadata') - self._onMetadata(metadata) + var files = torrent.info.files || [ torrent.info ] + result.files = files.map(function (file, i) { + var parts = [].concat(result.name, file['path.utf-8'] || file.path || []).map(function (p) { + return p.toString() }) - wire.ut_metadata.fetch() - } - - // use ut_pex extension if the torrent is not flagged as private - if (typeof utPex === 'function' && !self.private) { - wire.use(utPex()) + return { + path: path.join.apply(null, [path.sep].concat(parts)).slice(1), + name: parts[parts.length - 1], + length: file.length, + offset: files.slice(0, i).reduce(sumLength, 0) + } + }) - wire.ut_pex.on('peer', function (peer) { - // Only add potential new peers when we're not seeding - if (self.done) return - self._debug('ut_pex: got peer: %s (from %s)', peer, addr) - self.addPeer(peer) - }) + result.length = files.reduce(sumLength, 0) - wire.ut_pex.on('dropped', function (peer) { - // the remote peer believes a given peer has been dropped from the torrent swarm. - // if we're not currently connected to it, then remove it from the queue. - var peerObj = self._peers[peer] - if (peerObj && !peerObj.connected) { - self._debug('ut_pex: dropped peer: %s (from %s)', peer, addr) - self.removePeer(peer) - } - }) + var lastFile = result.files[result.files.length - 1] - wire.once('close', function () { - // Stop sending updates to remote peer - wire.ut_pex.reset() - }) - } + result.pieceLength = torrent.info['piece length'] + result.lastPieceLength = ((lastFile.offset + lastFile.length) % result.pieceLength) || result.pieceLength + result.pieces = splitPieces(torrent.info.pieces) - // Hook to allow user-defined `bittorrent-protocol` extensions - // More info: https://github.com/webtorrent/bittorrent-protocol#extension-api - self.emit('wire', wire, addr) + return result +} - if (self.metadata) { - process.nextTick(function () { - // This allows wire.handshake() to be called (by Peer.onHandshake) before any - // messages get sent on the wire - self._onWireWithMetadata(wire) - }) +/** + * Convert a parsed torrent object back into a .torrent file buffer. + * @param {Object} parsed parsed torrent + * @return {Buffer} + */ +function encodeTorrentFile (parsed) { + var torrent = { + info: parsed.info } -} -Torrent.prototype._onWireWithMetadata = function (wire) { - var self = this - var timeoutId = null + torrent['announce-list'] = (parsed.announce || []).map(function (url) { + if (!torrent.announce) torrent.announce = url + url = Buffer.from(url, 'utf8') + return [ url ] + }) - function onChokeTimeout () { - if (self.destroyed || wire.destroyed) return + torrent['url-list'] = parsed.urlList || [] - if (self._numQueued > 2 * (self._numConns - self.numPeers) && - wire.amInterested) { - wire.destroy() - } else { - timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) - if (timeoutId.unref) timeoutId.unref() - } + if (parsed.created) { + torrent['creation date'] = (parsed.created.getTime() / 1000) | 0 } - var i - function updateSeedStatus () { - if (wire.peerPieces.buffer.length !== self.bitfield.buffer.length) return - for (i = 0; i < self.pieces.length; ++i) { - if (!wire.peerPieces.get(i)) return - } - wire.isSeeder = true - wire.choke() // always choke seeders + if (parsed.createdBy) { + torrent['created by'] = parsed.createdBy } - wire.on('bitfield', function () { - updateSeedStatus() - self._update() - }) - - wire.on('have', function () { - updateSeedStatus() - self._update() - }) - - wire.once('interested', function () { - wire.unchoke() - }) + if (parsed.comment) { + torrent.comment = parsed.comment + } - wire.once('close', function () { - clearTimeout(timeoutId) - }) + return bencode.encode(torrent) +} - wire.on('choke', function () { - clearTimeout(timeoutId) - timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) - if (timeoutId.unref) timeoutId.unref() - }) +function sumLength (sum, file) { + return sum + file.length +} - wire.on('unchoke', function () { - clearTimeout(timeoutId) - self._update() - }) +function splitPieces (buf) { + var pieces = [] + for (var i = 0; i < buf.length; i += 20) { + pieces.push(buf.slice(i, i + 20).toString('hex')) + } + return pieces +} - wire.on('request', function (index, offset, length, cb) { - if (length > MAX_BLOCK_LENGTH) { - // Per spec, disconnect from peers that request >128KB - return wire.destroy() - } - if (self.pieces[index]) return - self.store.get(index, { offset: offset, length: length }, cb) - }) +function ensure (bool, fieldName) { + if (!bool) throw new Error('Torrent is missing required field: ' + fieldName) +} - wire.bitfield(self.bitfield) // always send bitfield (required) - wire.interested() // always start out interested +}).call(this,require("buffer").Buffer) +},{"bencode":71,"buffer":4,"path":13,"simple-sha1":142,"uniq":156}],117:[function(require,module,exports){ +(function (process,Buffer){ +/* global Blob */ - // Send PORT message to peers that support DHT - if (wire.peerExtensions.dht && self.client.dht && self.client.dht.listening) { - wire.port(self.client.dht.address().port) - } +module.exports = parseTorrent +module.exports.remote = parseTorrentRemote - if (wire.type !== 'webSeed') { // do not choke on webseeds - timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) - if (timeoutId.unref) timeoutId.unref() - } +var blobToBuffer = require('blob-to-buffer') +var fs = require('fs') // browser exclude +var get = require('simple-get') +var magnet = require('magnet-uri') +var parseTorrentFile = require('parse-torrent-file') - wire.isSeeder = false - updateSeedStatus() -} +module.exports.toMagnetURI = magnet.encode +module.exports.toTorrentFile = parseTorrentFile.encode /** - * Called on selection changes. + * Parse a torrent identifier (magnet uri, .torrent file, info hash) + * @param {string|Buffer|Object} torrentId + * @return {Object} */ -Torrent.prototype._updateSelections = function () { - var self = this - if (!self.ready || self.destroyed) return +function parseTorrent (torrentId) { + if (typeof torrentId === 'string' && /^(stream-)?magnet:/.test(torrentId)) { + // magnet uri (string) + return magnet(torrentId) + } else if (typeof torrentId === 'string' && (/^[a-f0-9]{40}$/i.test(torrentId) || /^[a-z2-7]{32}$/i.test(torrentId))) { + // info hash (hex/base-32 string) + return magnet('magnet:?xt=urn:btih:' + torrentId) + } else if (Buffer.isBuffer(torrentId) && torrentId.length === 20) { + // info hash (buffer) + return magnet('magnet:?xt=urn:btih:' + torrentId.toString('hex')) + } else if (Buffer.isBuffer(torrentId)) { + // .torrent file (buffer) + return parseTorrentFile(torrentId) // might throw + } else if (torrentId && torrentId.infoHash) { + // parsed torrent (from `parse-torrent`, `parse-torrent-file`, or `magnet-uri`) + if (!torrentId.announce) torrentId.announce = [] + if (typeof torrentId.announce === 'string') { + torrentId.announce = [ torrentId.announce ] + } + if (!torrentId.urlList) torrentId.urlList = [] + return torrentId + } else { + throw new Error('Invalid torrent identifier') + } +} - process.nextTick(function () { - self._gcSelections() - }) - self._updateInterest() - self._update() +function parseTorrentRemote (torrentId, cb) { + var parsedTorrent + if (typeof cb !== 'function') throw new Error('second argument must be a Function') + + try { + parsedTorrent = parseTorrent(torrentId) + } catch (err) { + // If torrent fails to parse, it could be a Blob, http/https URL or + // filesystem path, so don't consider it an error yet. + } + + if (parsedTorrent && parsedTorrent.infoHash) { + process.nextTick(function () { + cb(null, parsedTorrent) + }) + } else if (isBlob(torrentId)) { + blobToBuffer(torrentId, function (err, torrentBuf) { + if (err) return cb(new Error('Error converting Blob: ' + err.message)) + parseOrThrow(torrentBuf) + }) + } else if (typeof get === 'function' && /^https?:/.test(torrentId)) { + // http, or https url to torrent file + get.concat({ + url: torrentId, + timeout: 30 * 1000, + headers: { 'user-agent': 'WebTorrent (http://webtorrent.io)' } + }, function (err, res, torrentBuf) { + if (err) return cb(new Error('Error downloading torrent: ' + err.message)) + parseOrThrow(torrentBuf) + }) + } else if (typeof fs.readFile === 'function' && typeof torrentId === 'string') { + // assume it's a filesystem path + fs.readFile(torrentId, function (err, torrentBuf) { + if (err) return cb(new Error('Invalid torrent identifier')) + parseOrThrow(torrentBuf) + }) + } else { + process.nextTick(function () { + cb(new Error('Invalid torrent identifier')) + }) + } + + function parseOrThrow (torrentBuf) { + try { + parsedTorrent = parseTorrent(torrentBuf) + } catch (err) { + return cb(err) + } + if (parsedTorrent && parsedTorrent.infoHash) cb(null, parsedTorrent) + else cb(new Error('Invalid torrent identifier')) + } } /** - * Garbage collect selections with respect to the store's current state. + * Check if `obj` is a W3C `Blob` or `File` object + * @param {*} obj + * @return {boolean} */ -Torrent.prototype._gcSelections = function () { - var self = this +function isBlob (obj) { + return typeof Blob !== 'undefined' && obj instanceof Blob +} - for (var i = 0; i < self._selections.length; ++i) { - var s = self._selections[i] - var oldOffset = s.offset +// Workaround Browserify v13 bug +// https://github.com/substack/node-browserify/issues/1483 +;(function () { Buffer.alloc(0) })() - // check for newly downloaded pieces in selection - while (self.bitfield.get(s.from + s.offset) && s.from + s.offset < s.to) { - s.offset += 1 - } +}).call(this,require('_process'),require("buffer").Buffer) +},{"_process":15,"blob-to-buffer":79,"buffer":4,"fs":1,"magnet-uri":103,"parse-torrent-file":116,"simple-get":140}],118:[function(require,module,exports){ +var closest = require('closest-to') +var kB = Math.pow(2, 10) - if (oldOffset !== s.offset) s.notify() - if (s.to !== s.from + s.offset) continue - if (!self.bitfield.get(s.from + s.offset)) continue +// Create a range from 16kb–4mb +var p = 13, range = [] +while (p++ < 22) range.push(Math.pow(2, p)) - self._selections.splice(i, 1) // remove fully downloaded selection - i -= 1 // decrement i to offset splice +module.exports = function (bytes) { + return closest(bytes / kB, range) +} - s.notify() - self._updateInterest() - } +},{"closest-to":84}],119:[function(require,module,exports){ +(function (process){ +'use strict'; - if (!self._selections.length) self.emit('idle') +if (!process.version || + process.version.indexOf('v0.') === 0 || + process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { + module.exports = nextTick; +} else { + module.exports = process.nextTick; } -/** - * Update interested status for all peers. - */ -Torrent.prototype._updateInterest = function () { - var self = this +function nextTick(fn, arg1, arg2, arg3) { + if (typeof fn !== 'function') { + throw new TypeError('"callback" argument must be a function'); + } + var len = arguments.length; + var args, i; + switch (len) { + case 0: + case 1: + return process.nextTick(fn); + case 2: + return process.nextTick(function afterTickOne() { + fn.call(null, arg1); + }); + case 3: + return process.nextTick(function afterTickTwo() { + fn.call(null, arg1, arg2); + }); + case 4: + return process.nextTick(function afterTickThree() { + fn.call(null, arg1, arg2, arg3); + }); + default: + args = new Array(len - 1); + i = 0; + while (i < args.length) { + args[i++] = arguments[i]; + } + return process.nextTick(function afterTick() { + fn.apply(null, args); + }); + } +} - var prev = self._amInterested - self._amInterested = !!self._selections.length +}).call(this,require('_process')) +},{"_process":15}],120:[function(require,module,exports){ +var once = require('once') +var eos = require('end-of-stream') +var fs = require('fs') // we only need fs to get the ReadStream and WriteStream prototypes - self.wires.forEach(function (wire) { - // TODO: only call wire.interested if the wire has at least one piece we need - if (self._amInterested) wire.interested() - else wire.uninterested() - }) +var noop = function () {} - if (prev === self._amInterested) return - if (self._amInterested) self.emit('interested') - else self.emit('uninterested') +var isFn = function (fn) { + return typeof fn === 'function' } -/** - * Heartbeat to update all peers and their requests. - */ -Torrent.prototype._update = function () { - var self = this - if (self.destroyed) return +var isFS = function (stream) { + if (!fs) return false // browser + return (stream instanceof (fs.ReadStream || noop) || stream instanceof (fs.WriteStream || noop)) && isFn(stream.close) +} - // update wires in random order for better request distribution - var ite = randomIterate(self.wires) - var wire - while ((wire = ite())) { - self._updateWire(wire) - } +var isRequest = function (stream) { + return stream.setHeader && isFn(stream.abort) } -/** - * Attempts to update a peer's requests - */ -Torrent.prototype._updateWire = function (wire) { - var self = this +var destroyer = function (stream, reading, writing, callback) { + callback = once(callback) - if (wire.peerChoking) return - if (!wire.downloaded) return validateWire() + var closed = false + stream.on('close', function () { + closed = true + }) - var minOutstandingRequests = getBlockPipelineLength(wire, PIPELINE_MIN_DURATION) - if (wire.requests.length >= minOutstandingRequests) return - var maxOutstandingRequests = getBlockPipelineLength(wire, PIPELINE_MAX_DURATION) + eos(stream, {readable: reading, writable: writing}, function (err) { + if (err) return callback(err) + closed = true + callback() + }) - trySelectWire(false) || trySelectWire(true) + var destroyed = false + return function (err) { + if (closed) return + if (destroyed) return + destroyed = true - function genPieceFilterFunc (start, end, tried, rank) { - return function (i) { - return i >= start && i <= end && !(i in tried) && wire.peerPieces.get(i) && (!rank || rank(i)) - } - } + if (isFS(stream)) return stream.close() // use close for fs streams to avoid fd leaks + if (isRequest(stream)) return stream.abort() // request.destroy just do .end - .abort is what we want - // TODO: Do we need both validateWire and trySelectWire? - function validateWire () { - if (wire.requests.length) return + if (isFn(stream.destroy)) return stream.destroy() - var i = self._selections.length - while (i--) { - var next = self._selections[i] - var piece - if (self.strategy === 'rarest') { - var start = next.from + next.offset - var end = next.to - var len = end - start + 1 - var tried = {} - var tries = 0 - var filter = genPieceFilterFunc(start, end, tried) + callback(err || new Error('stream was destroyed')) + } +} - while (tries < len) { - piece = self._rarityMap.getRarestPiece(filter) - if (piece < 0) break - if (self._request(wire, piece, false)) return - tried[piece] = true - tries += 1 - } - } else { - for (piece = next.to; piece >= next.from + next.offset; --piece) { - if (!wire.peerPieces.get(piece)) continue - if (self._request(wire, piece, false)) return - } - } - } +var call = function (fn) { + fn() +} - // TODO: wire failed to validate as useful; should we close it? - // probably not, since 'have' and 'bitfield' messages might be coming - } +var pipe = function (from, to) { + return from.pipe(to) +} - function speedRanker () { - var speed = wire.downloadSpeed() || 1 - if (speed > SPEED_THRESHOLD) return function () { return true } +var pump = function () { + var streams = Array.prototype.slice.call(arguments) + var callback = isFn(streams[streams.length - 1] || noop) && streams.pop() || noop - var secs = Math.max(1, wire.requests.length) * Piece.BLOCK_LENGTH / speed - var tries = 10 - var ptr = 0 + if (Array.isArray(streams[0])) streams = streams[0] + if (streams.length < 2) throw new Error('pump requires two streams per minimum') - return function (index) { - if (!tries || self.bitfield.get(index)) return true + var error + var destroys = streams.map(function (stream, i) { + var reading = i < streams.length - 1 + var writing = i > 0 + return destroyer(stream, reading, writing, function (err) { + if (!error) error = err + if (err) destroys.forEach(call) + if (reading) return + destroys.forEach(call) + callback(error) + }) + }) - var missing = self.pieces[index].missing + return streams.reduce(pipe) +} - for (; ptr < self.wires.length; ptr++) { - var otherWire = self.wires[ptr] - var otherSpeed = otherWire.downloadSpeed() +module.exports = pump - if (otherSpeed < SPEED_THRESHOLD) continue - if (otherSpeed <= speed) continue - if (!otherWire.peerPieces.get(index)) continue - if ((missing -= otherSpeed * secs) > 0) continue +},{"end-of-stream":90,"fs":3,"once":115}],121:[function(require,module,exports){ +var iterate = function (list) { + var offset = 0 + return function () { + if (offset === list.length) return null - tries-- - return false - } + var len = list.length - offset + var i = (Math.random() * len) | 0 + var el = list[offset + i] - return true - } - } + var tmp = list[offset] + list[offset] = el + list[offset + i] = tmp + offset++ - function shufflePriority (i) { - var last = i - for (var j = i; j < self._selections.length && self._selections[j].priority; j++) { - last = j - } - var tmp = self._selections[i] - self._selections[i] = self._selections[last] - self._selections[last] = tmp + return el } +} - function trySelectWire (hotswap) { - if (wire.requests.length >= maxOutstandingRequests) return true - var rank = speedRanker() - - for (var i = 0; i < self._selections.length; i++) { - var next = self._selections[i] - - var piece - if (self.strategy === 'rarest') { - var start = next.from + next.offset - var end = next.to - var len = end - start + 1 - var tried = {} - var tries = 0 - var filter = genPieceFilterFunc(start, end, tried, rank) +module.exports = iterate - while (tries < len) { - piece = self._rarityMap.getRarestPiece(filter) - if (piece < 0) break +},{}],122:[function(require,module,exports){ +(function (process,global){ +'use strict' - // request all non-reserved blocks in this piece - while (self._request(wire, piece, self._critical[piece] || hotswap)) {} +function oldBrowser () { + throw new Error('secure random number generation not supported by this browser\nuse chrome, FireFox or Internet Explorer 11') +} - if (wire.requests.length < maxOutstandingRequests) { - tried[piece] = true - tries++ - continue - } +var Buffer = require('safe-buffer').Buffer +var crypto = global.crypto || global.msCrypto - if (next.priority) shufflePriority(i) - return true - } - } else { - for (piece = next.from + next.offset; piece <= next.to; piece++) { - if (!wire.peerPieces.get(piece) || !rank(piece)) continue +if (crypto && crypto.getRandomValues) { + module.exports = randomBytes +} else { + module.exports = oldBrowser +} - // request all non-reserved blocks in piece - while (self._request(wire, piece, self._critical[piece] || hotswap)) {} +function randomBytes (size, cb) { + // phantomjs needs to throw + if (size > 65536) throw new Error('requested too many random bytes') + // in case browserify isn't using the Uint8Array version + var rawBytes = new global.Uint8Array(size) - if (wire.requests.length < maxOutstandingRequests) continue + // This will not work in older browsers. + // See https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues + if (size > 0) { // getRandomValues fails on IE if size == 0 + crypto.getRandomValues(rawBytes) + } - if (next.priority) shufflePriority(i) - return true - } - } - } + // XXX: phantomjs doesn't like a buffer being passed here + var bytes = Buffer.from(rawBytes.buffer) - return false + if (typeof cb === 'function') { + return process.nextTick(function () { + cb(null, bytes) + }) } -} -/** - * Called periodically to update the choked status of all peers, handling optimistic - * unchoking as described in BEP3. - */ -Torrent.prototype._rechoke = function () { - var self = this - if (!self.ready) return + return bytes +} - if (self._rechokeOptimisticTime > 0) self._rechokeOptimisticTime -= 1 - else self._rechokeOptimisticWire = null +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"_process":15,"safe-buffer":138}],123:[function(require,module,exports){ +/* +Instance of writable stream. - var peers = [] +call .get(length) or .discard(length) to get a stream (relative to the last end) - self.wires.forEach(function (wire) { - if (!wire.isSeeder && wire !== self._rechokeOptimisticWire) { - peers.push({ - wire: wire, - downloadSpeed: wire.downloadSpeed(), - uploadSpeed: wire.uploadSpeed(), - salt: Math.random(), - isChoked: true - }) - } - }) +emits 'stalled' once everything is written - peers.sort(rechokeSort) - var unchokeInterested = 0 - var i = 0 - for (; i < peers.length && unchokeInterested < self._rechokeNumSlots; ++i) { - peers[i].isChoked = false - if (peers[i].wire.peerInterested) unchokeInterested += 1 - } +*/ +var inherits = require('inherits') +var stream = require('readable-stream') - // Optimistically unchoke a peer - if (!self._rechokeOptimisticWire && i < peers.length && self._rechokeNumSlots) { - var candidates = peers.slice(i).filter(function (peer) { return peer.wire.peerInterested }) - var optimistic = candidates[randomInt(candidates.length)] +module.exports = RangeSliceStream - if (optimistic) { - optimistic.isChoked = false - self._rechokeOptimisticWire = optimistic.wire - self._rechokeOptimisticTime = RECHOKE_OPTIMISTIC_DURATION - } - } +inherits(RangeSliceStream, stream.Writable) - // Unchoke best peers - peers.forEach(function (peer) { - if (peer.wire.amChoking !== peer.isChoked) { - if (peer.isChoked) peer.wire.choke() - else peer.wire.unchoke() - } - }) +function RangeSliceStream (offset, opts) { + var self = this + if (!(self instanceof RangeSliceStream)) return new RangeSliceStream(offset) + stream.Writable.call(self, opts) - function rechokeSort (peerA, peerB) { - // Prefer higher download speed - if (peerA.downloadSpeed !== peerB.downloadSpeed) { - return peerB.downloadSpeed - peerA.downloadSpeed - } + self.destroyed = false + self._queue = [] + self._position = offset || 0 + self._cb = null + self._buffer = null + self._out = null +} - // Prefer higher upload speed - if (peerA.uploadSpeed !== peerB.uploadSpeed) { - return peerB.uploadSpeed - peerA.uploadSpeed - } +RangeSliceStream.prototype._write = function (chunk, encoding, cb) { + var self = this - // Prefer unchoked - if (peerA.wire.amChoking !== peerB.wire.amChoking) { - return peerA.wire.amChoking ? 1 : -1 - } + var drained = true - // Random order - return peerA.salt - peerB.salt - } -} + while (true) { + if (self.destroyed) { + return + } -/** - * Attempts to cancel a slow block request from another wire such that the - * given wire may effectively swap out the request for one of its own. - */ -Torrent.prototype._hotswap = function (wire, index) { - var self = this + // Wait for more queue entries + if (self._queue.length === 0) { + self._buffer = chunk + self._cb = cb + return + } - var speed = wire.downloadSpeed() - if (speed < Piece.BLOCK_LENGTH) return false - if (!self._reservations[index]) return false + self._buffer = null + var currRange = self._queue[0] + // Relative to the start of chunk, what data do we need? + var writeStart = Math.max(currRange.start - self._position, 0) + var writeEnd = currRange.end - self._position - var r = self._reservations[index] - if (!r) { - return false - } + // Check if we need to throw it all away + if (writeStart >= chunk.length) { + self._position += chunk.length + return cb(null) + } - var minSpeed = Infinity - var minWire + // Check if we need to use it all + var toWrite + if (writeEnd > chunk.length) { + self._position += chunk.length + if (writeStart === 0) { + toWrite = chunk + } else { + toWrite = chunk.slice(writeStart) + } + drained = currRange.stream.write(toWrite) && drained + break + } - var i - for (i = 0; i < r.length; i++) { - var otherWire = r[i] - if (!otherWire || otherWire === wire) continue + self._position += writeEnd + if (writeStart === 0 && writeEnd === chunk.length) { + toWrite = chunk + } else { + toWrite = chunk.slice(writeStart, writeEnd) + } + drained = currRange.stream.write(toWrite) && drained + if (currRange.last) { + currRange.stream.end() + } + chunk = chunk.slice(writeEnd) + self._queue.shift() + } - var otherSpeed = otherWire.downloadSpeed() - if (otherSpeed >= SPEED_THRESHOLD) continue - if (2 * otherSpeed > speed || otherSpeed > minSpeed) continue + if (drained) { + cb(null) + } else { + currRange.stream.once('drain', cb.bind(null, null)) + } +} - minWire = otherWire - minSpeed = otherSpeed - } +RangeSliceStream.prototype.slice = function (ranges) { + var self = this - if (!minWire) return false + if (self.destroyed) return null - for (i = 0; i < r.length; i++) { - if (r[i] === minWire) r[i] = null - } + if (!(ranges instanceof Array)) { + ranges = [ranges] + } - for (i = 0; i < minWire.requests.length; i++) { - var req = minWire.requests[i] - if (req.piece !== index) continue + var str = new stream.PassThrough() - self.pieces[index].cancel((req.offset / Piece.BLOCK_LENGTH) | 0) - } + ranges.forEach(function (range, i) { + self._queue.push({ + start: range.start, + end: range.end, + stream: str, + last: i === (ranges.length - 1) + }) + }) + if (self._buffer) { + self._write(self._buffer, null, self._cb) + } - self.emit('hotswap', minWire, wire, index) - return true + return str } -/** - * Attempts to request a block from the given wire. - */ -Torrent.prototype._request = function (wire, index, hotswap) { - var self = this - var numRequests = wire.requests.length - var isWebSeed = wire.type === 'webSeed' - - if (self.bitfield.get(index)) return false - - var maxOutstandingRequests = isWebSeed - ? Math.min( - getPiecePipelineLength(wire, PIPELINE_MAX_DURATION, self.pieceLength), - self.maxWebConns - ) - : getBlockPipelineLength(wire, PIPELINE_MAX_DURATION) - - if (numRequests >= maxOutstandingRequests) return false - // var endGame = (wire.requests.length === 0 && self.store.numMissing < 30) - - var piece = self.pieces[index] - var reservation = isWebSeed ? piece.reserveRemaining() : piece.reserve() +RangeSliceStream.prototype.destroy = function (err) { + var self = this + if (self.destroyed) return + self.destroyed = true - if (reservation === -1 && hotswap && self._hotswap(wire, index)) { - reservation = isWebSeed ? piece.reserveRemaining() : piece.reserve() - } - if (reservation === -1) return false + if (err) self.emit('error', err) +} - var r = self._reservations[index] - if (!r) r = self._reservations[index] = [] - var i = r.indexOf(null) - if (i === -1) i = r.length - r[i] = wire +},{"inherits":96,"readable-stream":132}],124:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. - var chunkOffset = piece.chunkOffset(reservation) - var chunkLength = isWebSeed ? piece.chunkLengthRemaining(reservation) : piece.chunkLength(reservation) +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. - wire.request(index, chunkOffset, chunkLength, function onChunk (err, chunk) { - if (self.destroyed) return +'use strict'; - // TODO: what is this for? - if (!self.ready) return self.once('ready', function () { onChunk(err, chunk) }) +/**/ - if (r[i] === wire) r[i] = null +var processNextTick = require('process-nextick-args'); +/**/ - if (piece !== self.pieces[index]) return onUpdateTick() +/**/ +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ - if (err) { - self._debug( - 'error getting piece %s (offset: %s length: %s) from %s: %s', - index, chunkOffset, chunkLength, wire.remoteAddress + ':' + wire.remotePort, - err.message - ) - isWebSeed ? piece.cancelRemaining(reservation) : piece.cancel(reservation) - onUpdateTick() - return - } +module.exports = Duplex; - self._debug( - 'got piece %s (offset: %s length: %s) from %s', - index, chunkOffset, chunkLength, wire.remoteAddress + ':' + wire.remotePort - ) +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ - if (!piece.set(reservation, chunk, wire)) return onUpdateTick() +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); - var buf = piece.flush() +util.inherits(Duplex, Readable); - // TODO: might need to set self.pieces[index] = null here since sha1 is async +var keys = objectKeys(Writable.prototype); +for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; +} - sha1(buf, function (hash) { - if (self.destroyed) return +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); - if (hash === self._hashes[index]) { - if (!self.pieces[index]) return - self._debug('piece verified %s', index) + Readable.call(this, options); + Writable.call(this, options); - self.pieces[index] = null - self._reservations[index] = null - self.bitfield.set(index, true) + if (options && options.readable === false) this.readable = false; - self.store.put(index, buf) + if (options && options.writable === false) this.writable = false; - self.wires.forEach(function (wire) { - wire.have(index) - }) + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; - // We also check `self.destroyed` since `torrent.destroy()` could have been - // called in the `torrent.on('done')` handler, triggered by `_checkDone()`. - if (self._checkDone() && !self.destroyed) self.discovery.complete() - } else { - self.pieces[index] = new Piece(piece.length) - self.emit('warning', new Error('Piece ' + index + ' failed verification')) - } - onUpdateTick() - }) - }) + this.once('end', onend); +} - function onUpdateTick () { - process.nextTick(function () { self._update() }) - } +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) return; - return true + // no more data can be written. + // But allow more writes to happen in this tick. + processNextTick(onEndNT, this); } -Torrent.prototype._checkDone = function () { - var self = this - if (self.destroyed) return +function onEndNT(self) { + self.end(); +} - // are any new files done? - self.files.forEach(function (file) { - if (file.done) return - for (var i = file._startPiece; i <= file._endPiece; ++i) { - if (!self.bitfield.get(i)) return +Object.defineProperty(Duplex.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined || this._writableState === undefined) { + return false; } - file.done = true - file.emit('done') - self._debug('file done: ' + file.name) - }) - - // is the torrent done? (if all current selections are satisfied, or there are - // no selections, then torrent is done) - var done = true - for (var i = 0; i < self._selections.length; i++) { - var selection = self._selections[i] - for (var piece = selection.from; piece <= selection.to; piece++) { - if (!self.bitfield.get(piece)) { - done = false - break - } + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; } - if (!done) break - } - if (!self.done && done) { - self.done = true - self._debug('torrent done: ' + self.infoHash) - self.emit('done') - } - self._gcSelections() - - return done -} -Torrent.prototype.load = function (streams, cb) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') - if (!self.ready) return self.once('ready', function () { self.load(streams, cb) }) + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); - if (!Array.isArray(streams)) streams = [ streams ] - if (!cb) cb = noop +Duplex.prototype._destroy = function (err, cb) { + this.push(null); + this.end(); - var readable = new MultiStream(streams) - var writable = new ChunkStoreWriteStream(self.store, self.pieceLength) + processNextTick(cb, err); +}; - pump(readable, writable, function (err) { - if (err) return cb(err) - self.pieces.forEach(function (piece, index) { - self.pieces[index] = null - self._reservations[index] = null - self.bitfield.set(index, true) - }) - self._checkDone() - cb(null) - }) +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } } +},{"./_stream_readable":126,"./_stream_writable":128,"core-util-is":85,"inherits":96,"process-nextick-args":119}],125:[function(require,module,exports){ +arguments[4][21][0].apply(exports,arguments) +},{"./_stream_transform":127,"core-util-is":85,"dup":21,"inherits":96}],126:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -Torrent.prototype.createServer = function (requestListener) { - if (typeof Server !== 'function') throw new Error('node.js-only method') - if (this.destroyed) throw new Error('torrent is destroyed') - var server = new Server(this, requestListener) - this._servers.push(server) - return server -} +'use strict'; -Torrent.prototype.pause = function () { - if (this.destroyed) return - this._debug('pause') - this.paused = true -} +/**/ -Torrent.prototype.resume = function () { - if (this.destroyed) return - this._debug('resume') - this.paused = false - this._drain() -} +var processNextTick = require('process-nextick-args'); +/**/ -Torrent.prototype._debug = function () { - var args = [].slice.call(arguments) - args[0] = '[' + this.client._debugId + '] [' + this._debugId + '] ' + args[0] - debug.apply(null, args) -} +module.exports = Readable; -/** - * Pop a peer off the FIFO queue and connect to it. When _drain() gets called, - * the queue will usually have only one peer in it, except when there are too - * many peers (over `this.maxConns`) in which case they will just sit in the - * queue until another connection closes. - */ -Torrent.prototype._drain = function () { - var self = this - this._debug('_drain numConns %s maxConns %s', self._numConns, self.client.maxConns) - if (typeof net.connect !== 'function' || self.destroyed || self.paused || - self._numConns >= self.client.maxConns) { - return - } - this._debug('drain (%s queued, %s/%s peers)', self._numQueued, self.numPeers, self.client.maxConns) +/**/ +var isArray = require('isarray'); +/**/ - var peer = self._queue.shift() - if (!peer) return // queue could be empty +/**/ +var Duplex; +/**/ - this._debug('tcp connect attempt to %s', peer.addr) +Readable.ReadableState = ReadableState; - var parts = addrToIPPort(peer.addr) - var opts = { - host: parts[0], - port: parts[1] - } +/**/ +var EE = require('events').EventEmitter; - var conn = peer.conn = net.connect(opts) +var EElistenerCount = function (emitter, type) { + return emitter.listeners(type).length; +}; +/**/ - conn.once('connect', function () { peer.onConnect() }) - conn.once('error', function (err) { peer.destroy(err) }) - peer.startConnectTimeout() +/**/ +var Stream = require('./internal/streams/stream'); +/**/ - // When connection closes, attempt reconnect after timeout (with exponential backoff) - conn.on('close', function () { - if (self.destroyed) return +// TODO(bmeurer): Change this back to const once hole checks are +// properly optimized away early in Ignition+TurboFan. +/**/ +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} +/**/ - // TODO: If torrent is done, do not try to reconnect after a timeout +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ - if (peer.retries >= RECONNECT_WAIT.length) { - self._debug( - 'conn %s closed: will not re-add (max %s attempts)', - peer.addr, RECONNECT_WAIT.length - ) - return - } +/**/ +var debugUtil = require('util'); +var debug = void 0; +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function () {}; +} +/**/ - var ms = RECONNECT_WAIT[peer.retries] - self._debug( - 'conn %s closed: will re-add to queue in %sms (attempt %s)', - peer.addr, ms, peer.retries + 1 - ) +var BufferList = require('./internal/streams/BufferList'); +var destroyImpl = require('./internal/streams/destroy'); +var StringDecoder; - var reconnectTimeout = setTimeout(function reconnectTimeout () { - var newPeer = self._addPeer(peer.addr) - if (newPeer) newPeer.retries = peer.retries + 1 - }, ms) - if (reconnectTimeout.unref) reconnectTimeout.unref() - }) -} +util.inherits(Readable, Stream); -/** - * Returns `true` if string is valid IPv4/6 address. - * @param {string} addr - * @return {boolean} - */ -Torrent.prototype._validAddr = function (addr) { - var parts - try { - parts = addrToIPPort(addr) - } catch (e) { - return false +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') { + return emitter.prependListener(event, fn); + } else { + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; } - var host = parts[0] - var port = parts[1] - return port > 0 && port < 65535 && - !(host === '127.0.0.1' && port === this.client.torrentPort) } -function getBlockPipelineLength (wire, duration) { - return 2 + Math.ceil(duration * wire.downloadSpeed() / Piece.BLOCK_LENGTH) -} +function ReadableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); -function getPiecePipelineLength (wire, duration, pieceLength) { - return 1 + Math.ceil(duration * wire.downloadSpeed() / pieceLength) -} + options = options || {}; -/** - * Returns a random integer in [0,high) - */ -function randomInt (high) { - return Math.random() * high | 0 -} + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; -function noop () {} + if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.readableObjectMode; -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"../package.json":129,"./file":124,"./peer":125,"./rarity-map":126,"./server":158,"_process":170,"addr-to-ip-port":31,"bitfield":37,"chunk-store-stream/write":47,"debug":27,"events":162,"fs":156,"fs-chunk-store":66,"immediate-chunk-store":57,"inherits":58,"multistream":73,"net":158,"os":158,"parse-torrent":77,"path":168,"pump":80,"random-iterate":81,"run-parallel":96,"run-parallel-limit":95,"simple-get":100,"simple-sha1":102,"speedometer":104,"torrent-discovery":112,"torrent-piece":113,"uniq":116,"ut_metadata":118,"ut_pex":158,"xtend":131,"xtend/mutable":132}],128:[function(require,module,exports){ -module.exports = WebConn + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; -var BitField = require('bitfield') -var Buffer = require('safe-buffer').Buffer -var debug = require('debug')('webtorrent:webconn') -var get = require('simple-get') -var inherits = require('inherits') -var sha1 = require('simple-sha1') -var Wire = require('bittorrent-protocol') + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); -var VERSION = require('../package.json').version + // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; -inherits(WebConn, Wire) + // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + this.sync = true; -/** - * Converts requests for torrent blocks into http range requests. - * @param {string} url web seed url - * @param {Object} torrent - */ -function WebConn (url, torrent) { - Wire.call(this) + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; - this.url = url - this.webPeerId = sha1.sync(url) - this._torrent = torrent + // has it been destroyed + this.destroyed = false; - this._init() + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } } -WebConn.prototype._init = function () { - var self = this - self.setKeepAlive(true) +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); - self.once('handshake', function (infoHash, peerId) { - if (self.destroyed) return - self.handshake(infoHash, self.webPeerId) - var numPieces = self._torrent.pieces.length - var bitfield = new BitField(numPieces) - for (var i = 0; i <= numPieces; i++) { - bitfield.set(i, true) - } - self.bitfield(bitfield) - }) + if (!(this instanceof Readable)) return new Readable(options); - self.once('interested', function () { - debug('interested') - self.unchoke() - }) + this._readableState = new ReadableState(options, this); - self.on('uninterested', function () { debug('uninterested') }) - self.on('choke', function () { debug('choke') }) - self.on('unchoke', function () { debug('unchoke') }) - self.on('bitfield', function () { debug('bitfield') }) + // legacy + this.readable = true; - self.on('request', function (pieceIndex, offset, length, callback) { - debug('request pieceIndex=%d offset=%d length=%d', pieceIndex, offset, length) - self.httpRequest(pieceIndex, offset, length, callback) - }) -} + if (options) { + if (typeof options.read === 'function') this._read = options.read; -WebConn.prototype.httpRequest = function (pieceIndex, offset, length, cb) { - var self = this - var pieceOffset = pieceIndex * self._torrent.pieceLength - var rangeStart = pieceOffset + offset /* offset within whole torrent */ - var rangeEnd = rangeStart + length - 1 + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } - // Web seed URL format: - // For single-file torrents, make HTTP range requests directly to the web seed URL - // For multi-file torrents, add the torrent folder and file name to the URL - var files = self._torrent.files - var requests - if (files.length <= 1) { - requests = [{ - url: self.url, - start: rangeStart, - end: rangeEnd - }] - } else { - var requestedFiles = files.filter(function (file) { - return file.offset <= rangeEnd && (file.offset + file.length) > rangeStart - }) - if (requestedFiles.length < 1) { - return cb(new Error('Could not find file corresponnding to web seed range request')) + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined) { + return false; + } + return this._readableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; } - requests = requestedFiles.map(function (requestedFile) { - var fileEnd = requestedFile.offset + requestedFile.length - 1 - var url = self.url + - (self.url[self.url.length - 1] === '/' ? '' : '/') + - requestedFile.path - return { - url: url, - fileOffsetInRange: Math.max(requestedFile.offset - rangeStart, 0), - start: Math.max(rangeStart - requestedFile.offset, 0), - end: Math.min(fileEnd, rangeEnd - requestedFile.offset) - } - }) + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; } +}); - // Now make all the HTTP requests we need in order to load this piece - // Usually that's one requests, but sometimes it will be multiple - // Send requests in parallel and wait for them all to come back - var numRequestsSucceeded = 0 - var hasError = false +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; +Readable.prototype._destroy = function (err, cb) { + this.push(null); + cb(err); +}; - var ret - if (requests.length > 1) { - ret = Buffer.alloc(length) - } +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; - requests.forEach(function (request) { - var url = request.url - var start = request.start - var end = request.end - debug( - 'Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d', - url, pieceIndex, offset, length, start, end - ) - var opts = { - url: url, - method: 'GET', - headers: { - 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)', - range: 'bytes=' + start + '-' + end + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; } + skipChunkCheck = true; } - function onResponse (res, data) { - if (res.statusCode < 200 || res.statusCode >= 300) { - hasError = true - return cb(new Error('Unexpected HTTP status code ' + res.statusCode)) + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + var state = stream._readableState; + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); } - debug('Got data of length %d', data.length) - if (requests.length === 1) { - // Common case: fetch piece in a single HTTP request, return directly - cb(null, data) + if (addToFront) { + if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); + } else if (state.ended) { + stream.emit('error', new Error('stream.push() after EOF')); } else { - // Rare case: reconstruct multiple HTTP requests across 2+ files into one - // piece buffer - data.copy(ret, request.fileOffsetInRange) - if (++numRequestsSucceeded === requests.length) { - cb(null, ret) + state.reading = false; + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); } } + } else if (!addToFront) { + state.reading = false; } - get.concat(opts, function (err, res, data) { - if (hasError) return - if (err) { - // Browsers allow HTTP redirects for simple cross-origin - // requests but not for requests that require preflight. - // Use a simple request to unravel any redirects and get the - // final URL. Retry the original request with the new URL if - // it's different. - // - // This test is imperfect but it's simple and good for common - // cases. It catches all cross-origin cases but matches a few - // same-origin cases too. - if (typeof window === 'undefined' || url.startsWith(window.location.origin + '/')) { - hasError = true - return cb(err) - } + } - return get.head(url, function (errHead, res) { - if (hasError) return - if (errHead) { - hasError = true - return cb(errHead) - } - if (res.statusCode < 200 || res.statusCode >= 300) { - hasError = true - return cb(new Error('Unexpected HTTP status code ' + res.statusCode)) - } - if (res.url === url) { - hasError = true - return cb(err) - } + return needMoreData(state); +} - opts.url = res.url - get.concat(opts, function (err, res, data) { - if (hasError) return - if (err) { - hasError = true - return cb(err) - } - onResponse(res, data) - }) - }) - } - onResponse(res, data) - }) - }) +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + + if (state.needReadable) emitReadable(stream); + } + maybeReadMore(stream, state); } -WebConn.prototype.destroy = function () { - Wire.prototype.destroy.call(this) - this._torrent = null +function chunkInvalid(state, chunk) { + var er; + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; } -},{"../package.json":129,"bitfield":37,"bittorrent-protocol":38,"debug":27,"inherits":58,"safe-buffer":98,"simple-get":100,"simple-sha1":102}],129:[function(require,module,exports){ -module.exports={ - "version": "0.98.19" +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); } -},{}],130:[function(require,module,exports){ -// Returns a wrapper function that returns a wrapped callback -// The wrapper function should do some stuff, and return a -// presumably different callback function. -// This makes sure that own properties are retained, so that -// decorations and such are not lost along the way. -module.exports = wrappy -function wrappy (fn, cb) { - if (fn && cb) return wrappy(fn)(cb) - if (typeof fn !== 'function') - throw new TypeError('need wrapper function') +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; - Object.keys(fn).forEach(function (k) { - wrapper[k] = fn[k] - }) +// backwards compatibility. +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; - return wrapper +// Don't raise the hwm > 8MB +var MAX_HWM = 0x800000; +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + return n; +} - function wrapper() { - var args = new Array(arguments.length) - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i] - } - var ret = fn.apply(this, args) - var cb = args[args.length-1] - if (typeof ret === 'function' && ret !== cb) { - Object.keys(cb).forEach(function (k) { - ret[k] = cb[k] - }) - } - return ret +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; } + // If we're asking for more than the current hwm, then raise the hwm. + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; + // Don't have enough + if (!state.ended) { + state.needReadable = true; + return 0; + } + return state.length; } -},{}],131:[function(require,module,exports){ -module.exports = extend +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; -var hasOwnProperty = Object.prototype.hasOwnProperty; + if (n !== 0) state.emittedReadable = false; -function extend() { - var target = {} + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } - for (var i = 0; i < arguments.length; i++) { - var source = arguments[i] + n = howMuchToRead(n, state); - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - target[key] = source[key] - } - } - } + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } - return target -} + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. -},{}],132:[function(require,module,exports){ -module.exports = extend + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); -var hasOwnProperty = Object.prototype.hasOwnProperty; + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } -function extend(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i] + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (!state.reading) n = howMuchToRead(nOrig, state); + } - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - target[key] = source[key] - } - } - } + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; - return target -} + if (ret === null) { + state.needReadable = true; + n = 0; + } else { + state.length -= n; + } -},{}],133:[function(require,module,exports){ -/** - * Given a number, return a zero-filled string. - * From http://stackoverflow.com/questions/1267283/ - * @param {number} width - * @param {number} number - * @return {string} - */ -module.exports = function zeroFill (width, number, pad) { - if (number === undefined) { - return function (number, pad) { - return zeroFill(width, number, pad) + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + + return ret; +}; + +function onEofChunk(stream, state) { + if (state.ended) return; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; } } - if (pad === undefined) pad = '0' - width -= number.toString().length - if (width > 0) return new Array(width + (/\./.test(number) ? 2 : 1)).join(pad) + number - return number + '' + state.ended = true; + + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); } -},{}],134:[function(require,module,exports){ -module.exports={ - "name": "pearplayer", - "version": "2.4.11", - "description": "", - "main": "./dist/pear-player.js", - "dependencies": { - "axios": "^0.17.1", - "babili": "^0.1.4", - "blueimp-md5": "^2.7.0", - "buffer": "^5.0.6", - "end-of-stream": "^1.4.0", - "events": "^1.1.1", - "fs-chunk-store": "^1.6.5", - "inherits": "^2.0.3", - "jquery": "^3.2.1", - "mp4box": "^0.3.15", - "readable-stream": "^2.2.9", - "render-media": "^2.10.0", - "webtorrent": "^0.98.16" - }, - "devDependencies": { - "copy": "^0.1.2", - "del": "^2.0.2" - }, - "scripts": { - "build-player": "browserify index.player.js -s PearPlayer > ./dist/pear-player.js", - "uglify-player": "browserify -s PearPlayer -e ./index.player.js | babili > ./dist/pear-player.min.js", - "pull-from-github": "git pull", - "push-to-github": "git add . && git commit -m 'update' && git push", - "npm-publish": "npm publish" - }, - "author": "Xie Ting Pear Limited", - "license": "MIT", - "homepage": "https://pear.hk", - "keywords": [ - "WebRTC", - "video", - "player", - "p2p", - "peer-to-peer", - "peers", - "streaming", - "multiple source", - "torrent", - "web torrent", - "webrtc data channel", - "webtorrent" - ] +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) processNextTick(emitReadable_, stream);else emitReadable_(stream); + } } -},{}],135:[function(require,module,exports){ -(function (process){ +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} -/* - 该模块用于调度HttpDownloader和RTCDownloader - - config:{ - - initialDownloaders: [], //初始的httpdownloader数组,必须 - chunkSize: number, //每个chunk的大小,默认1M - fileSize: number, //下载文件的总大小,必须 - interval: number, //滑动窗口的时间间隔,单位毫秒,默认5s - auto: boolean, //true为连续下载buffer - useMonitor: boolean, //开启监控器,默认关闭 - scheduler: function //节点调度算法 - sequencial: boolean //是否有序下载,默认false - maxLoaders: number //push算法中同时下载的节点数量 - algorithm: string //下载采用的算法 - } - */ -module.exports = Dispatcher; +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + processNextTick(maybeReadMore_, stream, state); + } +} -var debug = require('debug')('pear:dispatcher'); -var BitField = require('bitfield'); -var EventEmitter = require('events').EventEmitter; -var inherits = require('inherits'); -var FSChunkStore = require('fs-chunk-store'); -var ImmediateChunkStore = require('immediate-chunk-store'); - -inherits(Dispatcher, EventEmitter); - -function Dispatcher(config) { - EventEmitter.call(this); - - var self = this; - - if (!(config.initialDownloaders && config.fileSize && config.scheduler)) throw new Error('config is not completed'); - self.fileSize = config.fileSize; - self.initialDownloaders = config.initialDownloaders; - self.pieceLength = config.chunkSize || 1*1024*512; - self.interval = config.interval || 5000; - self.auto = config.auto || false; - // self.auto = true; - self.useMonitor = config.useMonitor || false; - self.downloaded = 0; - self.fogDownloaded = 0; //通过data channel下载的字节数 - self._windowOffset = 0; - // self.noDataChannel = false; //是否没有datachannel - self.ready = false; - self.done = false; //是否已完成下载 - self.destroyed = false; - - self.chunks = (config.fileSize % self.pieceLength)>0 ? Math.floor((config.fileSize / self.pieceLength)) +1: - (config.fileSize / self.pieceLength); - - // self._startPiece = 0; - // self._endPiece = (self.fileSize-1)/self.pieceLength; - - self._selections = []; //下载队列 - self._store = FSChunkStore; - self.path = ''; - - self.bufferSources = new Array(self.chunks); //记录每个buffer下载的方式 - self.slide = null; - self.noMoreNodes = false; //是否已没有新的节点可获取 - - //monitor - self.startTime = (new Date()).getTime(); //用于计算平均速度 - self.fogRatio = 0.0; - - //firstaid参数自适应 - self.sequencial = config.sequencial || false; - - var pushLength = self.initialDownloaders.filter(function (node) { //能力值超过平均值得节点个数 - return node.ability > 5; - }).length; - self.maxLoaders = (pushLength > 5 && pushLength <= self.maxLoaders) ? pushLength : config.maxLoaders; - self._windowLength = self.initialDownloaders.length >= 10 ? 10 : self.initialDownloaders.length; - // self._windowLength = 5; - // self._colddown = self._windowLength; //窗口滑动的冷却时间 - self._colddown = 5; //窗口滑动的冷却时间 - self.downloaders = []; - - //webtorrent - self.torrent = null; - - //scheduler - self.scheduler = config.scheduler; - self._windowEnd = 0; //当前窗口的end - - self.algorithm = config.algorithm; +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break;else len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function (n) { + this.emit('error', new Error('_read() is not implemented')); }; -Dispatcher.prototype._init = function () { - var self = this; +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; - self.downloaders = self.initialDownloaders.map(function (item){ + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); - self._setupHttp(item); - return item; - }); + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; - self.store = new ImmediateChunkStore( - new self._store(self.pieceLength, { - path: self.path, - length: self.fileSize - }) - ); - // debug('self.path:'+self.path); - self.bitfield = new BitField(self.chunks); //记录哪个块已经下好 + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) processNextTick(endFn);else src.once('end', endFn); - if (self.algorithm === 'push') { - while (self._windowEnd !== self.chunks) { - self._createPushStream(); - } - self._windowEnd = 0; + dest.on('unpipe', onunpipe); + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } } - // self._checkDone(); + } - // self._slide(); - if (self.auto) { - self.select(0, self.chunks-1, true); - self.autoSlide(); - self.slide = noop; - } else { - // self.slide = this._throttle(this._slide, this); - } + function onend() { + debug('onend'); + dest.end(); + } - //初始化buffersources - for (var k=0;k Introduce a guard on increasing awaitDrain. + var increasedAwaitDrain = false; + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + increasedAwaitDrain = false; + var ret = dest.write(chunk); + if (false === ret && !increasedAwaitDrain) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', src._readableState.awaitDrain); + src._readableState.awaitDrain++; + increasedAwaitDrain = true; + } + src.pause(); } - priority = Number(priority) || 0; + } - debug('select %s-%s (priority %s)', start, end, priority); + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); + } - self._selections.push({ - from: start, - to: end, - offset: 0, - priority: priority, - notify: notify || noop - }); + // Make sure our error handler is attached before userland ones. + prependListener(dest, 'error', onerror); - self._selections.sort(function (a, b) { //从小到大排序 - return a.priority - b.priority - }) + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); - self._updateSelections() -}; + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } -Dispatcher.prototype.deselect = function (start, end, priority) { - var self = this; - if (self.destroyed) throw new Error('dispatcher is destroyed'); - - priority = Number(priority) || 0; - debug('deselect %s-%s (priority %s)', start, end, priority); - // self._clearAllQueues(); - for (var i = 0; i < self._selections.length; ++i) { - var s = self._selections[i]; - if (s.from === start && s.to === end && s.priority === priority) { - self._selections.splice(i, 1); - break - } - } + // tell the dest that it's being piped to + dest.emit('pipe', src); - self._updateSelections() -}; + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } -Dispatcher.prototype._slide = function () { - var self = this; - if (self.done) return; - debug('[dispatcher] slide window downloader length:'+self.downloaders.length); - self._fillWindow(); + return dest; }; -/** - * Called on selection changes. - */ -Dispatcher.prototype._updateSelections = function () { - var self = this; - if (!self.ready || self.destroyed) return; +function pipeOnDrain(src) { + return function () { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} - process.nextTick(function () { - self._gcSelections() - }); - // debug('Dispatcher _updateSelections'); - //此处开始下载 - self._update(); -}; +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { hasUnpiped: false }; -/** - * Garbage collect selections with respect to the store's current state. - */ -Dispatcher.prototype._gcSelections = function () { - var self = this; + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) return this; - for (var i = 0; i < self._selections.length; ++i) { - var s = self._selections[i]; - var oldOffset = s.offset; + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; - // check for newly downloaded pieces in selection - while (self.bitfield.get(s.from + s.offset) && s.from + s.offset < s.to) { - s.offset += 1 - } - self._windowOffset = s.from + s.offset; - if (oldOffset !== s.offset) s.notify(); - if (s.to !== s.from + s.offset) continue; - if (!self.bitfield.get(s.from + s.offset)) continue; + if (!dest) dest = state.pipes; - self._selections.splice(i, 1); // remove fully downloaded selection - i -= 1; // decrement i to offset splice + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } - s.notify(); - } + // slow case. multiple pipe destinations. - // for (var i = 0; i < self._selections.length; ++i) { - //test - // var length = self._selections.length; - // if (!length) return; - // var s = self._selections[self._selections.length-1]; - // var oldOffset = s.offset; - // - // // check for newly downloaded pieces in selection - // while (self.bitfield.get(s.from + s.offset) && s.from + s.offset < s.to) { - // s.offset += 1 - // } - // self._windowOffset = s.from + s.offset; - // - // if (oldOffset !== s.offset) s.notify(); - // // if (s.to !== s.from + s.offset) continue; - // // if (!self.bitfield.get(s.from + s.offset)) continue; - // // - // // self._selections.splice(i, 1); // remove fully downloaded selection - // // i -= 1; // decrement i to offset splice - // - // s.notify(); - // } + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; - // self._windowOffset = s.from + s.offset; - // debug('current _windowOffset:' + self._windowOffset); + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, unpipeInfo); + }return this; + } - if (!self._selections.length) self.emit('idle') -}; + // try to find the right one. + var index = indexOf(state.pipes, dest); + if (index === -1) return this; -Dispatcher.prototype._update = function () { - var self = this; - if (self.destroyed) return; - // debug('Dispatcher _update'); - var length = self._selections.length; - // debug('_selections.length:'+self._selections.length); - if (length > 0) { - - // debug('_update self._selections:'+JSON.stringify(self._selections)); - // var s = self._selections[length-1]; - var s = self._selections[0]; - var start = s.from + s.offset; - var end = s.to; - // self._windowOffset = start; - debug('current _windowOffset:' + self._windowOffset); - self._slide(); - // self.slide(); - // self._throttle(self.slide,self); - } -}; + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; -Dispatcher.prototype._resetWindow = function () { + dest.emit('unpipe', this, unpipeInfo); - if (!this.done) { - for (var piece = 0; piece < this.chunks; piece++) { - if (!this.bitfield.get(piece)) { - this._windowEnd = piece; - break; - } - } - } + return this; }; -Dispatcher.prototype._checkDone = function () { - var self = this; - if (self.destroyed) return; - // is the torrent done? (if all current selections are satisfied, or there are - // no selections, then torrent is done) - var done = true; - // debug('_selections.length:'+self._selections.length); - // for (var i = 0; i < self._selections.length; i++) { - // var selection = self._selections[i]; - // for (var piece = selection.from; piece <= selection.to; piece++) { - // if (!self.bitfield.get(piece)) { - // done = false; - // break - // } - // } - // if (!done) break - // } - for (var i = 0; i < self.chunks; i++) { - if (!self.bitfield.get(i)) { - // self._windowOffset = i; - done = false; - break - } - } - // debug('_checkDone self.done:'+self.done+' done:'+done); - if (!self.done && done) { - self.done = true; - // debug('dispatcher done'); - self.emit('done'); - if (self.useMonitor) { - self.emit('downloaded', 1.0); - } - for (var k=0;k= self.chunks){ - break; - } - // debug('index:'+index); - if (count >= sortedNodes.length) break; - - if (!self.bitfield.get(index)) { - - var pair = self._calRange(index); - // var node = self._getNodes(count); - // node.select(pair[0],pair[1]); - var node = sortedNodes[count % sortedNodes.length]; - // var node = sortedNodes[count]; - node.select(pair[0],pair[1]); - count ++; - } else { +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function (stream) { + var state = this._readableState; + var paused = false; - } - index ++; + var self = this; + stream.on('end', function () { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) self.push(chunk); } - self._windowEnd = index; - debug('_fillWindow _windowEnd:'+self._windowEnd); -}; -Dispatcher.prototype._createPushStream = function () { - var self = this; + self.push(null); + }); - var sortedNodes = this.downloaders; + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); - if (sortedNodes.length === 0) return; + // don't skip over falsy values in objectMode + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; - var count = 0; + var ret = self.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); - var index = self._windowEnd; + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function (method) { + return function () { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } - while (count !== self.maxLoaders){ - // debug('_fillWindow _windowLength:'+self._windowLength + ' downloadersLength:' + self.downloaders.length); - if (index >= self.chunks){ - break; - } - // debug('index:'+index); - // if (count >= sortedNodes.length) break; - - if (!self.bitfield.get(index)) { - - var pair = self._calRange(index); - // var node = self._getNodes(count); - // node.select(pair[0],pair[1]); - var node = sortedNodes[count % sortedNodes.length]; - // var node = sortedNodes[count]; - node.select(pair[0],pair[1]); - count ++; - } else { + // proxy certain important events. + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], self.emit.bind(self, kProxyEvents[n])); + } - } - index ++; + // when we try to consume some more bytes, simply unpause the + // underlying stream. + self._read = function (n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); } - self._windowEnd = index; + }; + + return self; }; -Dispatcher.prototype._setupHttp = function (hd) { - var self = this; +// exposed for testing purposes only. +Readable._fromList = fromList; - hd.once('start',function () { +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; - }); - hd.once('done',function () { + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = fromListPartial(n, state.buffer, state.decoder); + } - // debug('httpDownloader ondone'); + return ret; +} - }); - hd.once('error', function (error) { +// Extracts only enough buffered data to satisfy the amount requested. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromListPartial(n, list, hasStrings) { + var ret; + if (n < list.head.data.length) { + // slice is the same for buffers and strings + ret = list.head.data.slice(0, n); + list.head.data = list.head.data.slice(n); + } else if (n === list.head.data.length) { + // first chunk is a perfect match + ret = list.shift(); + } else { + // result spans more than one buffer + ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); + } + return ret; +} - console.warn('http' + hd.uri + 'error!'); +// Copies a specified amount of characters from the list of buffered data +// chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBufferString(n, list) { + var p = list.head; + var c = 1; + var ret = p.data; + n -= ret.length; + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = str.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} - if (self.downloaders.length > self._windowLength) { - self.downloaders.removeObj(hd); - if (self._windowLength > 3) self._windowLength --; - } - self.checkoutDownloaders(); - }); - hd.on('data',function (buffer, start, end, speed) { - - var index = self._calIndex(start); - debug('httpDownloader' + hd.uri +' ondata range:'+start+'-'+end+' at index:'+index+' speed:'+hd.meanSpeed); - var size = end - start + 1; - if (!self.bitfield.get(index)){ - self.bitfield.set(index,true); - - self.store.put(index, buffer); - - - - self._checkDone(); - if (self.useMonitor) { - self.downloaded += size; - self.emit('downloaded', self.downloaded/self.fileSize); - // hd.downloaded += size; - self.emit('traffic', hd.mac, size, hd.type === 1 ? 'HTTP_Node' : 'HTTP_Server', hd.meanSpeed); - debug('ondata hd.type:' + hd.type +' index:' + index); - if (hd.type === 1) { //node - self.fogDownloaded += size; - var fogRatio = self.fogDownloaded/self.downloaded; - if (fogRatio >= self.fogRatio) { - self.emit('fograte', fogRatio); - } - self.emit('fogspeed', self.downloaders.getCurrentSpeed([1])); - // hd.type === 1 ? self.bufferSources[index] = 'n' : self.bufferSources[index] = 'b'; - hd.type === 1 ? self.bufferSources[index] = hd.id : self.bufferSources[index] = 'b'; //test - } else { - self.emit('cloudspeed', self.downloaders.getCurrentSpeed([0])); - // self.bufferSources[index] = 's' - self.bufferSources[index] = hd.id; //test - } - self.emit('buffersources', self.bufferSources); - self.emit('sourcemap', hd.type === 1 ? 'n' : 's', index); - } - // debug('bufferSources:'+self.bufferSources); - } else { - debug('重复下载'); +// Copies a specified amount of bytes from the list of buffered data chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBuffer(n, list) { + var ret = Buffer.allocUnsafe(n); + var p = list.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = buf.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} - } - }); +function endReadable(stream) { + var state = stream._readableState; - return hd; -}; + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); -Dispatcher.prototype._setupDC = function (jd) { - var self = this; + if (!state.endEmitted) { + state.ended = true; + processNextTick(endReadableNT, state, stream); + } +} - jd.once('start',function () { - // debug('DC start downloading'); - }); +function endReadableNT(state, stream) { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } +} - jd.on('data',function (buffer, start, end, speed) { - - var index = self._calIndex(start); - debug('pear_webrtc '+jd.dc_id+' ondata range:'+start+'-'+end+' at index:'+index+' speed:'+jd.meanSpeed); - var size = end - start + 1; - if (!self.bitfield.get(index)){ - self.bitfield.set(index,true); - - self.store.put(index, buffer); - - self._checkDone(); - if (self.useMonitor) { - self.downloaded += size; - self.fogDownloaded += size; - debug('downloaded:'+self.downloaded+' fogDownloaded:'+self.fogDownloaded); - self.emit('downloaded', self.downloaded/self.fileSize); - var fogRatio = self.fogDownloaded/self.downloaded; - if (fogRatio >= self.fogRatio) { - self.emit('fograte', fogRatio); - } - self.emit('fogspeed', self.downloaders.getCurrentSpeed([2])); - self.bufferSources[index] = 'd'; - self.emit('buffersources', self.bufferSources); - self.emit('sourcemap', 'd', index); - // jd.downloaded += size; - self.emit('traffic', jd.mac, size, 'WebRTC_Node', jd.meanSpeed); - } - } else { - debug('重复下载'); - for (var k=0;k= 2) { - this.emit('needsource'); - } - } -}; +var Duplex = require('./_stream_duplex'); -Dispatcher.prototype.addTorrent = function (torrent) { - var self = this; - // debug('torrent.pieces.length:'+torrent.pieces.length+' chunks:'+this.chunks); - if (torrent.pieces.length !== this.chunks) return; - this.torrent = torrent; - torrent.pear_downloaded = 0; - debug('addTorrent _windowOffset:' + self._windowOffset); - if (self._windowOffset + self._windowLength < torrent.pieces.length-1) { - debug('torrent.select:%d to %d', self._windowOffset+self._windowLength, torrent.pieces.length-1); - torrent.select(self._windowOffset+self._windowLength, torrent.pieces.length-1, 1000); - } - torrent.on('piecefromtorrent', function (index) { - - debug('piecefromtorrent:'+index); - if (self.useMonitor) { - self.downloaded += self.pieceLength; - self.fogDownloaded += self.pieceLength; - torrent.pear_downloaded += self.pieceLength; - self.emit('downloaded', self.downloaded/self.fileSize); - var fogRatio = self.fogDownloaded/self.downloaded; - if (fogRatio >= self.fogRatio) { - self.emit('fograte', fogRatio); - } - // debug('torrent.downloadSpeed:'+torrent.downloadSpeed/1024); - self.emit('fogspeed', torrent.downloadSpeed/1024); - self.bufferSources[index] = 'b'; - self.emit('buffersources', self.bufferSources); - self.emit('sourcemap', 'b', index); - self.emit('traffic', 'Webtorrent', self.pieceLength, 'WebRTC_Browser'); - } - }); +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ - torrent.on('done', function () { - debug('torrent done'); - }); -}; +util.inherits(Transform, Duplex); -Dispatcher.prototype.addDataChannel = function (dc) { +function TransformState(stream) { + this.afterTransform = function (er, data) { + return afterTransform(stream, er, data); + }; - // this.downloaders.push(dc); - this.downloaders.splice(this._windowLength-1,0,dc); - // if (this._windowLength < 8 && this.downloaders.length > this._windowLength) { - // this._windowLength ++; - // } - this._setupDC(dc); - if (!this.sequencial && this._windowLength < 10) this._windowLength ++; // -}; + this.needTransform = false; + this.transforming = false; + this.writecb = null; + this.writechunk = null; + this.writeencoding = null; +} -Dispatcher.prototype.addNode = function (node) { //node是httpdownloader对象 +function afterTransform(stream, er, data) { + var ts = stream._transformState; + ts.transforming = false; - this._setupHttp(node); - this.downloaders.push(node); - debug('dispatcher add node: '+node.uri); - if (!this.sequencial && this._windowLength < 10) this._windowLength ++; -}; + var cb = ts.writecb; -Dispatcher.prototype.requestMoreNodes = function () { + if (!cb) { + return stream.emit('error', new Error('write callback called multiple times')); + } - if (this.downloaders.length > 0) { //节点不够,重新请求 - this.emit('needmorenodes'); - } else { - this.emit('error'); - } -}; + ts.writechunk = null; + ts.writecb = null; -Dispatcher.prototype.requestMoreDataChannels = function () { + if (data !== null && data !== undefined) stream.push(data); - if (this.downloaders.length > 0) { //节点不够,重新请求 - this.emit('needmoredatachannels'); - } else { - this.emit('error'); - } -}; + cb(er); -Dispatcher.prototype.destroy = function () { - var self = this; - if (self.destroyed) return; - self.destroyed = true; + var rs = stream._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + stream._read(rs.highWaterMark); + } +} - for (var k=0;k= 0) { - sum+=this[i].meanSpeed; - length ++; - } - } - } else { - for (var i = 0; i < this.length; i++) { - sum+=this[i].meanSpeed; - length ++; - } - } - return Math.floor(sum/length); -}; +Transform.prototype._destroy = function (err, cb) { + var _this = this; -Array.prototype.getCurrentSpeed = function (typeArr) { //根据传输的类型(不传则计算所有节点)来计算瞬时速度 - var sum = 0; - var length = 0; - if (typeArr) { - for (var i = 0; i < this.length; i++) { - if (typeArr.indexOf(this[i].type) >= 0) { - sum+=this[i].speed; - } - } - } else { - for (var i = 0; i < this.length; i++) { - sum+=this[i].speed; - } - } - return Math.floor(sum); + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + _this.emit('close'); + }); }; +function done(stream, er, data) { + if (er) return stream.emit('error', er); -}).call(this,require('_process')) -},{"_process":170,"bitfield":37,"debug":27,"events":162,"fs-chunk-store":66,"immediate-chunk-store":57,"inherits":58}],136:[function(require,module,exports){ -module.exports = FileStream; + if (data !== null && data !== undefined) stream.push(data); -var debug = require('debug')('pear:file-stream'); -var inherits = require('inherits'); -var stream = require('readable-stream'); + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + var ws = stream._writableState; + var ts = stream._transformState; -inherits(FileStream, stream.Readable); + if (ws.length) throw new Error('Calling transform done when ws.length != 0'); -/** - * Readable stream of a torrent file - * - * @param {File} file - * @param {Object} opts - * @param {number} opts.start stream slice of file, starting from this byte (inclusive) - * @param {number} opts.end stream slice of file, ending with this byte (inclusive) - */ -function FileStream (file, opts) { - stream.Readable.call(this, opts); + if (ts.transforming) throw new Error('Calling transform done when still transforming'); + + return stream.push(null); +} +},{"./_stream_duplex":124,"core-util-is":85,"inherits":96}],128:[function(require,module,exports){ +(function (process,global,setImmediate){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. - this.destroyed = false; - this._dispatcher = file._dispatcher; +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. - var start = (opts && opts.start) || 0; - var end = (opts && opts.end && opts.end < file.length) - ? opts.end - : file.length - 1; +'use strict'; - var pieceLength = file._dispatcher.pieceLength; - this._startPiece = (start + file.offset) / pieceLength | 0; //start和end的单位应该是byte - this._endPiece = (end + file.offset) / pieceLength | 0; +/**/ - this._piece = this._startPiece; - this._offset = (start + file.offset) - (this._startPiece * pieceLength); +var processNextTick = require('process-nextick-args'); +/**/ - this._missing = end - start + 1; - this._reading = false; - this._notifying = false; - this._criticalLength = Math.min((1024 * 1024 / pieceLength) | 0, 2); +module.exports = Writable; - // debug('FileStream _startPiece:'+this._startPiece); - // debug('FileStream _endPiece:'+this._endPiece); - // debug('FileStream _offset:'+this._offset); - // debug('FileStream _missing:'+this._missing); +/* */ +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; } -FileStream.prototype._read = function () { - if (this._reading) return; - this._reading = true; - this._notify(); -}; - -FileStream.prototype._notify = function () { - var self = this; - - if (!self._reading || self._missing === 0) return; - if (!self._dispatcher.bitfield.get(self._piece)) { - // return self._dispatcher.critical(self._piece, self._piece + self._criticalLength) - return noop(); - } - - if (self._notifying) return; - - // self._ifCanPlay(); - // if (!this._dispatcher.enoughInitBuffer) return; - self._notifying = true; - - var p = self._piece; - // debug('FileStream get piece:' + p); - self._dispatcher.store.get(p, function (err, buffer) { - self._notifying = false; - if (self.destroyed) return; - if (err) return self._destroy(err); - // debug('read %s (length %s) (err %s)', p, buffer.length, err && err.message) - // debug('read '+p+' length:'+buffer.length); - if (self._offset) { - buffer = buffer.slice(self._offset); - self._offset = 0; - } +// It seems a linked list but it is not +// there will be only 2 of these for each stream +function CorkedRequest(state) { + var _this = this; - if (self._missing < buffer.length) { - buffer = buffer.slice(0, self._missing); - } - self._missing -= buffer.length; - - // debug('pushing buffer of length:'+buffer.length); - self._reading = false; - self.push(buffer); - // if (p === self._dispatcher._windowLength/2) { - // self.emit('canplay'); - // } - if (self._missing === 0) self.push(null); - }); - self._piece += 1; -}; + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ -FileStream.prototype.destroy = function (onclose) { - this._destroy(null, onclose) -}; +/**/ +var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : processNextTick; +/**/ -FileStream.prototype._destroy = function (err, onclose) { - if (this.destroyed) return; - this.destroyed = true; +/**/ +var Duplex; +/**/ - if (!this._dispatcher.destroyed) { - this._dispatcher.deselect(this._startPiece, this._endPiece, true); - } - debug('FileStream destroy'); - if (err) this.emit('error', err); - this.emit('close'); - if (onclose) onclose(); -}; +Writable.WritableState = WritableState; -// FileStream.prototype._ifCanPlay = function () { //缓存足够的buffer后才播放 -// if (this._dispatcher.enoughInitBuffer) return; -// var bitfield = this._dispatcher.bitfield; -// debug('this._dispatcher.normalWindowLength:'+this._dispatcher.normalWindowLength); -// for (var i=this._startPiece;i*/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ -function noop () {} -},{"debug":27,"inherits":58,"readable-stream":92}],137:[function(require,module,exports){ -(function (process){ +/**/ +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ -module.exports = File; +/**/ +var Stream = require('./internal/streams/stream'); +/**/ -var debug = require('debug')('pear:file'); -var eos = require('end-of-stream'); -var EventEmitter = require('events').EventEmitter; -var path = require('path'); -var inherits = require('inherits'); -var mime = require('render-media/lib/mime.json') -var stream = require('readable-stream'); -var FileStream = require('./file-stream'); -var streamToBlobURL = require('stream-to-blob-url'); -var streamToBuffer = require('stream-with-known-length-to-buffer'); -// var WebTorrent = require('./pear-torrent'); +/**/ +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} +/**/ -inherits(File, EventEmitter); +var destroyImpl = require('./internal/streams/destroy'); -function File (dispatcher, file){ - EventEmitter.call(this); +util.inherits(Writable, Stream); - this._dispatcher = dispatcher; - // this.seeder = null; - this._destroyed = false; +function nop() {} - this.name = file.name; - this.path = '/tmp/player'+this.name; +function WritableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); - this.length = file.length; - this.offset = file.offset; + options = options || {}; - this.done = false; + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; - var start = file.offset; - var end = start + file.length - 1; + if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.writableObjectMode; - this._startPiece = start / this._dispatcher.pieceLength | 0; - this._endPiece = end / this._dispatcher.pieceLength | 0; - // debug('file _startPiece'+this._startPiece+' _endPiece:'+this._endPiece); + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; - if (this.length === 0) { - this.done = true; - this.emit('done'); - } + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); - this._dispatcher.path = this.path; - this._dispatcher.elem = file.elem; - // this._dispatcher._init(); + // if _final has been called + this.finalCalled = false; -}; + // drain event flag. + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; -File.prototype.createReadStream = function (opts) { - var self = this; - // opts = opts || {}; - // debug('createReadStream'); - // debug(opts.start?opts.start:0); + // has it been destroyed + this.destroyed = false; - // if (!opts) return; + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; - if (this.length === 0) { - var empty = new stream.PassThrough(); - process.nextTick(function () { - empty.end() - }); - return empty; - } + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; - var fileStream = new FileStream(self, opts); - self._dispatcher.select(fileStream._startPiece, fileStream._endPiece, true, function () { - fileStream._notify() - }); - // self._dispatcher.startFrom(fileStream._startPiece, true, function () { - // fileStream._notify(); - // }); - // self._dispatcher._updateAfterSeek(); - eos(fileStream, function () { - if (self._destroyed) return; - if (!self._dispatcher.destroyed) { - self._dispatcher.deselect(fileStream._startPiece, fileStream._endPiece, true); - // self._dispatcher.deStartFrom(fileStream._startPiece, true); - } - }); - return fileStream; -}; + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; -// File.prototype.renderTo = function (elem, opts, cb) { -// -// render.render(this, elem, opts, cb); -// -// }; + // a flag to see when we're in the middle of a write. + this.writing = false; -File.prototype.getBuffer = function (cb) { - streamToBuffer(this.createReadStream(), this.length, cb) -}; + // when true all writes will be buffered until .uncork() call + this.corked = 0; -File.prototype.getBlobURL = function (cb) { - // if (typeof window === 'undefined') throw new Error('browser-only method') - streamToBlobURL(this.createReadStream(), this._getMimeType(), cb) -}; + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; -File.prototype.getProgressiveBlobURL = function (cb) { - // if (typeof window === 'undefined') throw new Error('browser-only method') - streamToBlobURL(this.createReadStream({start: 0, end: this._dispatcher.downloaded}), this._getMimeType(), cb) -}; + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; -File.prototype._getMimeType = function () { - return mime[path.extname(this.name).toLowerCase()] -}; + // the callback that's passed to _write(chunk,cb) + this.onwrite = function (er) { + onwrite(stream, er); + }; -File.prototype._destroy = function () { - this._destroyed = true; - this._dispatcher = null; -}; + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + // the amount that is being written when _write is called. + this.writelen = 0; + this.bufferedRequest = null; + this.lastBufferedRequest = null; -}).call(this,require('_process')) -},{"./file-stream":136,"_process":170,"debug":27,"end-of-stream":52,"events":162,"inherits":58,"path":168,"readable-stream":92,"render-media/lib/mime.json":94,"stream-to-blob-url":105,"stream-with-known-length-to-buffer":107}],138:[function(require,module,exports){ + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; -module.exports = HttpDownloader; + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; -var debug = require('debug')('pear:http-downloader'); -var Buffer = require('buffer/').Buffer; -var EventEmitter = require('events').EventEmitter; -var inherits = require('inherits'); + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; -/* - type: 'server' or 'node' - */ + // count buffered requests + this.bufferedRequestCount = 0; -inherits(HttpDownloader, EventEmitter); - -function HttpDownloader(uri, type, opts) { - EventEmitter.call(this); - opts = opts || {}; - this.uri = uri; - this.type = (type === 'server' ? 0 : 1); //server node - this.downloading = false; //是否正在下载 - this.queue = []; //下载队列 - this.startTime = 0; - this.endTime = 0; - this.speed = 0; //当前速度 - this.meanSpeed = -1; //平均速度 - this.counter = 0; //记录下载的次数 - this.weight = type === 'server' ? 0.7 : 1.0; //下载排序时的权重系数 - this.isAsync = opts.isAsync || false; //默认并行下载 - //节点流量统计 - this.downloaded = 0; - this.mac = this.uri.split('.')[0].split('//')[1]; - debug('HttpDownloader mac:'+this.mac); -}; + // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + this.corkedRequestsFree = new CorkedRequest(this); +} -HttpDownloader.prototype.select = function (start, end) { - - // if (end < start) throw new Error('end must larger than start'); - // this.emit('start',start,end); - var index = Math.floor(start/(1024*1024)); - debug('HttpDownloader ' + this.uri + ' select:' + start + '-' + end + ' at ' + index + ' weight:' + this.weight); - if (this.isAsync) { //并行 - this._getChunk(start, end); - } else {                         //串行 - if (this.downloading){ - this.queue.push([start,end]); - } else { - this._getChunk(start, end); - } - } - // if (this.queue.length >= 3) { - // this.clearQueue(); - // this.weight -= 0.1; - // if (this.weight < 0.1) { - // this.emit('error'); - // } - // } +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + while (current) { + out.push(current); + current = current.next; + } + return out; }; -HttpDownloader.prototype.abort = function () { - var self = this; - // debug('[HttpDownloader] readyState:'+self._xhr.readyState); - if (self._xhr && (self._xhr.readyState == 2 || self._xhr.readyState == 3)) { //如果正在下载,则停止 - self._xhr.abort(); - debug('HttpDownloader ' + self.uri +' aborted!'); - } - self.downloading = false; -}; +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function () { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} +})(); -HttpDownloader.prototype.clearQueue = function () { //清空下载队列 +// Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. +var realHasInstance; +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function (object) { + if (realHasInstance.call(this, object)) return true; - // this.downloading = false; - if (this.queue.length > 0) { - // debug('[HttpDownloader] clear queue!'); - this.queue = []; + return object && object._writableState instanceof WritableState; } -}; - -HttpDownloader.prototype._getChunk = function (begin,end) { - var self = this; - debug('HttpDownloader _getChunk'); - self.downloading = true; - var xhr = new XMLHttpRequest(); - self._xhr = xhr; - xhr.open("GET", self.uri); - xhr.responseType = "arraybuffer"; - xhr.timeout = 2000; - self.startTime=(new Date()).getTime(); - // debug('get_file_index: start:'+begin+' end:'+end); - var range = "bytes="+begin+"-"+end; - // debug('request range: ' + range); - xhr.setRequestHeader("Range", range); - xhr.onload = function (event) { - if (this.status >= 200 || this.status < 300) { - self.downloading = false; - - self.endTime = (new Date()).getTime(); - // self.speed = Math.floor((event.total * 1000) / ((self.endTime - self.startTime) * 1024)); //单位: KB/s - self.speed = Math.floor(event.total / (self.endTime - self.startTime)); //单位: KB/s - debug('http speed:' + self.speed + 'KB/s'); - // self.meanSpeed = (self.meanSpeed*self.counter + self.speed)/(++self.counter); - if (self.meanSpeed == -1) self.meanSpeed = self.speed; - self.meanSpeed = 0.95*self.meanSpeed + 0.05*self.speed; - debug('http '+self.uri+' meanSpeed:' + self.meanSpeed + 'KB/s'); - if (!self.isAsync) { - if (self.queue.length > 0){ //如果下载队列不为空 - var pair = self.queue.shift(); - self._getChunk(pair[0], pair[1]); - } - } - var range = this.getResponseHeader("Content-Range").split(" ",2)[1].split('/',1)[0]; - // debug('xhr.onload range:'+range); - // self.emit('done'); - self._handleChunk(range,this.response); - } else { - self.emit('error'); - } - }; - xhr.onerror = function(_) { - - self.emit('error'); - }; - xhr.ontimeout = function (_) { - debug('HttpDownloader ' + self.uri + ' timeout'); - self.emit('error'); - }; - xhr.send(); -}; - -HttpDownloader.prototype._handleChunk = function (range,data) { - - var start = range.split('-')[0]; - var end = range.split('-')[1]; - var buffer = Buffer.from(data); - this.emit('data', buffer, start, end, this.speed); + }); +} else { + realHasInstance = function (object) { + return object instanceof this; + }; +} -}; +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); + // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { + return new Writable(options); + } + this._writableState = new WritableState(options, this); + // legacy. + this.writable = true; -},{"buffer/":46,"debug":27,"events":162,"inherits":58}],139:[function(require,module,exports){ -/** - * Created by XieTing on 17-6-6. - */ + if (options) { + if (typeof options.write === 'function') this._write = options.write; -module.exports = PearDownloader; + if (typeof options.writev === 'function') this._writev = options.writev; -var debug = require('debug'); -var inherits = require('inherits'); -var Worker = require('./worker'); -var version = require('../package.json').version; + if (typeof options.destroy === 'function') this._destroy = options.destroy; -inherits(PearDownloader, Worker); + if (typeof options.final === 'function') this._final = options.final; + } -function PearDownloader(urlStr, token, opts) { - var self = this; - if (!(self instanceof PearDownloader)) return new PearDownloader(urlStr, token, opts); - // if (!(self instanceof PearPlayer)) return new PearPlayer(selector, opts); - if (typeof token === 'object') return PearDownloader(urlStr, '', token); + Stream.call(this); +} - //validate - if (opts.algorithm && ['push', 'pull'].indexOf(opts.algorithm) == -1) throw new Error('Algorithm ' + opts.algorithm + ' is not supported'); +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function () { + this.emit('error', new Error('Cannot pipe, not readable')); +}; +function writeAfterEnd(stream, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + processNextTick(cb, er); +} - if (!opts) opts = {}; - if (opts.debug) { - debug.enable('pear:*'); - } else { - debug.disable(); - } - self.version = version; - console.info('pear version:'+version); +// Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. +function validChunk(stream, state, chunk, cb) { + var valid = true; + var er = false; - Worker.call(self, urlStr, token, opts); + if (chunk === null) { + er = new TypeError('May not write null values to stream'); + } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + if (er) { + stream.emit('error', er); + processNextTick(cb, er); + valid = false; + } + return valid; } -PearDownloader.isWebRTCSupported = function () { +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + var isBuf = _isUint8Array(chunk) && !state.objectMode; - return Worker.isRTCSupported(); -}; + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; -},{"../package.json":134,"./worker":155,"debug":27,"inherits":58}],140:[function(require,module,exports){ -arguments[4][123][0].apply(exports,arguments) -},{"debug":27,"dup":123,"inherits":58,"readable-stream":92}],141:[function(require,module,exports){ -(function (process){ -module.exports = File + if (typeof cb !== 'function') cb = nop; -var eos = require('end-of-stream') -var EventEmitter = require('events').EventEmitter -var FileStream = require('./file-stream') -var inherits = require('inherits') -var path = require('path') -// var render = require('render-media') -var stream = require('readable-stream') -// var streamToBlob = require('stream-to-blob') -// var streamToBlobURL = require('stream-to-blob-url') -var streamToBuffer = require('stream-with-known-length-to-buffer') + if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } -inherits(File, EventEmitter) + return ret; +}; -function File (torrent, file) { - EventEmitter.call(this) +Writable.prototype.cork = function () { + var state = this._writableState; - this._torrent = torrent - this._destroyed = false + state.corked++; +}; - this.name = file.name - this.path = file.path - this.length = file.length - this.offset = file.offset +Writable.prototype.uncork = function () { + var state = this._writableState; - this.done = false + if (state.corked) { + state.corked--; - var start = file.offset - var end = start + file.length - 1 + if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; - this._startPiece = start / this._torrent.pieceLength | 0 - this._endPiece = end / this._torrent.pieceLength | 0 +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; - if (this.length === 0) { - this.done = true - this.emit('done') +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); } + return chunk; } -Object.defineProperty(File.prototype, 'downloaded', { - get: function () { - if (!this._torrent.bitfield) return 0 - var downloaded = 0 - for (var index = this._startPiece; index <= this._endPiece; ++index) { - if (this._torrent.bitfield.get(index)) { - // verified data - downloaded += this._torrent.pieceLength - } else { - // "in progress" data - var piece = this._torrent.pieces[index] - downloaded += (piece.length - piece.missing) - } +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; } - return downloaded } -}) + var len = state.objectMode ? 1 : chunk.length; -Object.defineProperty(File.prototype, 'progress', { - get: function () { return this.length ? this.downloaded / this.length : 0 } -}) + state.length += len; -File.prototype.select = function (priority) { - if (this.length === 0) return - this._torrent.select(this._startPiece, this._endPiece, priority) -} + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) state.needDrain = true; -File.prototype.deselect = function () { - if (this.length === 0) return - this._torrent.deselect(this._startPiece, this._endPiece, false) + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; } -File.prototype.createReadStream = function (opts) { - var self = this - if (this.length === 0) { - var empty = new stream.PassThrough() - process.nextTick(function () { - empty.end() - }) - return empty - } - - var fileStream = new FileStream(self, opts) - self._torrent.select(fileStream._startPiece, fileStream._endPiece, true, function () { - fileStream._notify() - }) - eos(fileStream, function () { - if (self._destroyed) return - if (!self._torrent.destroyed) { - self._torrent.deselect(fileStream._startPiece, fileStream._endPiece, true) - } - }) - return fileStream +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; } -File.prototype.getBuffer = function (cb) { - streamToBuffer(this.createReadStream(), this.length, cb) +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + processNextTick(cb, er); + // this can emit finish, and it will always happen + // after error + processNextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + // this can emit finish, but finish must + // always follow error + finishMaybe(stream, state); + } } -// File.prototype.getBlob = function (cb) { -// if (typeof window === 'undefined') throw new Error('browser-only method') -// streamToBlob(this.createReadStream(), this._getMimeType(), cb) -// } +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} -// File.prototype.getBlobURL = function (cb) { -// if (typeof window === 'undefined') throw new Error('browser-only method') -// streamToBlobURL(this.createReadStream(), this._getMimeType(), cb) -// } +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; -// File.prototype.appendTo = function (elem, opts, cb) { -// if (typeof window === 'undefined') throw new Error('browser-only method') -// render.append(this, elem, opts, cb) -// } + onwriteStateUpdate(state); -// File.prototype.renderTo = function (elem, opts, cb) { -// if (typeof window === 'undefined') throw new Error('browser-only method') -// render.render(this, elem, opts, cb) -// } + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state); -// File.prototype._getMimeType = function () { -// return render.mime[path.extname(this.name).toLowerCase()] -// } + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } -File.prototype._destroy = function () { - this._destroyed = true - this._torrent = null + if (sync) { + /**/ + asyncWrite(afterWrite, stream, state, finished, cb); + /**/ + } else { + afterWrite(stream, state, finished, cb); + } + } } -}).call(this,require('_process')) -},{"./file-stream":140,"_process":170,"end-of-stream":52,"events":162,"inherits":58,"path":168,"readable-stream":92,"stream-with-known-length-to-buffer":107}],142:[function(require,module,exports){ -arguments[4][125][0].apply(exports,arguments) -},{"./webconn":145,"bittorrent-protocol":38,"debug":27,"dup":125,"unordered-array-remove":117}],143:[function(require,module,exports){ -arguments[4][126][0].apply(exports,arguments) -},{"dup":126}],144:[function(require,module,exports){ -(function (process,global){ -/* global URL, Blob */ - -module.exports = Torrent +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} -var addrToIPPort = require('addr-to-ip-port') -var BitField = require('bitfield') -var ChunkStoreWriteStream = require('chunk-store-stream/write') -var debug = require('debug')('webtorrent:torrent') -var Discovery = require('torrent-discovery') -var EventEmitter = require('events').EventEmitter -var extend = require('xtend') -var extendMutable = require('xtend/mutable') -var fs = require('fs') -var FSChunkStore = require('fs-chunk-store') // browser: `memory-chunk-store` -var get = require('simple-get') -var ImmediateChunkStore = require('immediate-chunk-store') -var inherits = require('inherits') -var MultiStream = require('multistream') -var net = require('net') // browser exclude -var os = require('os') // browser exclude -var parallel = require('run-parallel') -var parallelLimit = require('run-parallel-limit') -var parseTorrent = require('parse-torrent') -var path = require('path') -var Piece = require('torrent-piece') -var pump = require('pump') -var randomIterate = require('random-iterate') -var sha1 = require('simple-sha1') -var speedometer = require('speedometer') -var uniq = require('uniq') -var utMetadata = require('ut_metadata') -var utPex = require('ut_pex') // browser exclude +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} -var File = require('./file') -var Peer = require('./peer') -var RarityMap = require('./rarity-map') -var Server = require('./server') // browser exclude +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; -var MAX_BLOCK_LENGTH = 128 * 1024 -var PIECE_TIMEOUT = 30000 -var CHOKE_TIMEOUT = 5000 -var SPEED_THRESHOLD = 3 * Piece.BLOCK_LENGTH + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; -var PIPELINE_MIN_DURATION = 0.5 -var PIPELINE_MAX_DURATION = 1 + var count = 0; + var allBuffers = true; + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + buffer.allBuffers = allBuffers; -var RECHOKE_INTERVAL = 10000 // 10 seconds -var RECHOKE_OPTIMISTIC_DURATION = 2 // 30 seconds + doWrite(stream, state, true, state.length, buffer, '', holder.finish); -var FILESYSTEM_CONCURRENCY = 2 + // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + state.pendingcb++; + state.lastBufferedRequest = null; + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; -var RECONNECT_WAIT = [ 1000, 5000, 15000 ] + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + break; + } + } -var VERSION = require('../package.json').version -var USER_AGENT = 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' + if (entry === null) state.lastBufferedRequest = null; + } -var TMP -try { - TMP = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent') -} catch (err) { - TMP = path.join(typeof os.tmpdir === 'function' ? os.tmpdir() : '/', 'webtorrent') + state.bufferedRequestCount = 0; + state.bufferedRequest = entry; + state.bufferProcessing = false; } -inherits(Torrent, EventEmitter) - -function Torrent (torrentId, client, opts) { - EventEmitter.call(this) - - this._debugId = 'unknown infohash' - this.client = client - - this.announce = opts.announce - this.urlList = opts.urlList - - this.path = opts.path - this._store = FSChunkStore //pear modified - this._getAnnounceOpts = opts.getAnnounceOpts - - this.strategy = opts.strategy || 'sequential' +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new Error('_write() is not implemented')); +}; - this.maxWebConns = opts.maxWebConns || 4 +Writable.prototype._writev = null; - this._rechokeNumSlots = (opts.uploads === false || opts.uploads === 0) - ? 0 - : (+opts.uploads || 10) - this._rechokeOptimisticWire = null - this._rechokeOptimisticTime = 0 - this._rechokeIntervalId = null +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; - this.ready = false - this.destroyed = false - this.paused = false - this.done = false + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } - this.metadata = null - this.store = opts.store //pear modified - this.bitfield = opts.bitfield //pear modified - this.files = [] - this.pieces = [] + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); - this._amInterested = false - this._selections = [] - this._critical = [] + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } - this.wires = [] // open wires (added *after* handshake) + // ignore unnecessary end() calls. + if (!state.ending && !state.finished) endWritable(this, state, cb); +}; - this._queue = [] // queue of outgoing tcp peers to connect to - this._peers = {} // connected peers (addr/peerId -> Peer) - this._peersLength = 0 // number of elements in `this._peers` (cache, for perf) - - // stats - this.received = 0 - this.uploaded = 0 - this._downloadSpeed = speedometer() - this._uploadSpeed = speedometer() - - // for cleanup - this._servers = [] - this._xsRequests = [] - - // TODO: remove this and expose a hook instead - // optimization: don't recheck every file if it hasn't changed - this._fileModtimes = opts.fileModtimes - - if (torrentId !== null) this._onTorrentId(torrentId) - - this._debug('new torrent') +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; } - -Object.defineProperty(Torrent.prototype, 'timeRemaining', { - get: function () { - if (this.done) return 0 - if (this.downloadSpeed === 0) return Infinity - return ((this.length - this.downloaded) / this.downloadSpeed) * 1000 - } -}) - -Object.defineProperty(Torrent.prototype, 'downloaded', { - get: function () { - if (!this.bitfield) return 0 - var downloaded = 0 - for (var index = 0, len = this.pieces.length; index < len; ++index) { - if (this.bitfield.get(index)) { // verified data - downloaded += (index === len - 1) ? this.lastPieceLength : this.pieceLength - } else { // "in progress" data - var piece = this.pieces[index] - downloaded += (piece.length - piece.missing) - } +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + if (err) { + stream.emit('error', err); + } + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function') { + state.pendingcb++; + state.finalCalled = true; + processNextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); } - return downloaded - } -}) - -// TODO: re-enable this. The number of missing pieces. Used to implement 'end game' mode. -// Object.defineProperty(Storage.prototype, 'numMissing', { -// get: function () { -// var self = this -// var numMissing = self.pieces.length -// for (var index = 0, len = self.pieces.length; index < len; index++) { -// numMissing -= self.bitfield.get(index) -// } -// return numMissing -// } -// }) - -Object.defineProperty(Torrent.prototype, 'downloadSpeed', { - get: function () { return this._downloadSpeed() } -}) - -Object.defineProperty(Torrent.prototype, 'uploadSpeed', { - get: function () { return this._uploadSpeed() } -}) - -Object.defineProperty(Torrent.prototype, 'progress', { - get: function () { return this.length ? this.downloaded / this.length : 0 } -}) - -Object.defineProperty(Torrent.prototype, 'ratio', { - get: function () { return this.uploaded / (this.received || 1) } -}) - -Object.defineProperty(Torrent.prototype, 'numPeers', { - get: function () { return this.wires.length } -}) - -Object.defineProperty(Torrent.prototype, 'torrentFileBlobURL', { - get: function () { - if (typeof window === 'undefined') throw new Error('browser-only property') - if (!this.torrentFile) return null - return URL.createObjectURL( - new Blob([ this.torrentFile ], { type: 'application/x-bittorrent' }) - ) - } -}) - -Object.defineProperty(Torrent.prototype, '_numQueued', { - get: function () { - return this._queue.length + (this._peersLength - this._numConns) } -}) +} -Object.defineProperty(Torrent.prototype, '_numConns', { - get: function () { - var self = this - var numConns = 0 - for (var id in self._peers) { - if (self._peers[id].connected) numConns += 1 +function finishMaybe(stream, state) { + var need = needFinish(state); + if (need) { + prefinish(stream, state); + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); } - return numConns } -}) + return need; +} -// TODO: remove in v1 -Object.defineProperty(Torrent.prototype, 'swarm', { - get: function () { - console.warn('WebTorrent: `torrent.swarm` is deprecated. Use `torrent` directly instead.') - return this +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) processNextTick(cb);else stream.once('finish', cb); } -}) - -Torrent.prototype._onTorrentId = function (torrentId) { - var self = this - if (self.destroyed) return + state.ended = true; + stream.writable = false; +} - var parsedTorrent - try { parsedTorrent = parseTorrent(torrentId) } catch (err) {} - if (parsedTorrent) { - // Attempt to set infoHash property synchronously - self.infoHash = parsedTorrent.infoHash - self._debugId = parsedTorrent.infoHash.toString('hex').substring(0, 7) - process.nextTick(function () { - if (self.destroyed) return - self._onParsedTorrent(parsedTorrent) - }) +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + if (state.corkedRequestsFree) { + state.corkedRequestsFree.next = corkReq; } else { - // If torrentId failed to parse, it could be in a form that requires an async - // operation, i.e. http/https link, filesystem path, or Blob. - parseTorrent.remote(torrentId, function (err, parsedTorrent) { - if (self.destroyed) return - if (err) return self._destroy(err) - self._onParsedTorrent(parsedTorrent) - }) + state.corkedRequestsFree = corkReq; } } -Torrent.prototype._onParsedTorrent = function (parsedTorrent) { - var self = this - if (self.destroyed) return - - self._processParsedTorrent(parsedTorrent) +Object.defineProperty(Writable.prototype, 'destroyed', { + get: function () { + if (this._writableState === undefined) { + return false; + } + return this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } - if (!self.infoHash) { - return self._destroy(new Error('Malformed torrent data: No info hash')) + // backward compatibility, the user is explicitly + // managing destroyed + this._writableState.destroyed = value; } +}); - if (!self.path) self.path = path.join(TMP, self.infoHash) +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; +Writable.prototype._destroy = function (err, cb) { + this.end(); + cb(err); +}; +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate) +},{"./_stream_duplex":124,"./internal/streams/destroy":130,"./internal/streams/stream":131,"_process":15,"core-util-is":85,"inherits":96,"process-nextick-args":119,"safe-buffer":138,"timers":35,"util-deprecate":159}],129:[function(require,module,exports){ +'use strict'; - self._rechokeIntervalId = setInterval(function () { - self._rechoke() - }, RECHOKE_INTERVAL) - if (self._rechokeIntervalId.unref) self._rechokeIntervalId.unref() +/**/ - // Private 'infoHash' event allows client.add to check for duplicate torrents and - // destroy them before the normal 'infoHash' event is emitted. Prevents user - // applications from needing to deal with duplicate 'infoHash' events. - self.emit('_infoHash', self.infoHash) - if (self.destroyed) return +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - self.emit('infoHash', self.infoHash) - if (self.destroyed) return // user might destroy torrent in event handler +var Buffer = require('safe-buffer').Buffer; +/**/ - if (self.client.listening) { - self._onListening() - } else { - self.client.once('listening', function () { - self._onListening() - }) - } +function copyBuffer(src, target, offset) { + src.copy(target, offset); } -Torrent.prototype._processParsedTorrent = function (parsedTorrent) { - this._debugId = parsedTorrent.infoHash.toString('hex').substring(0, 7) +module.exports = function () { + function BufferList() { + _classCallCheck(this, BufferList); - if (this.announce) { - // Allow specifying trackers via `opts` parameter - parsedTorrent.announce = parsedTorrent.announce.concat(this.announce) + this.head = null; + this.tail = null; + this.length = 0; } - if (this.client.tracker && global.WEBTORRENT_ANNOUNCE && !this.private) { - // So `webtorrent-hybrid` can force specific trackers to be used - parsedTorrent.announce = parsedTorrent.announce.concat(global.WEBTORRENT_ANNOUNCE) - } + BufferList.prototype.push = function push(v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + }; - if (this.urlList) { - // Allow specifying web seeds via `opts` parameter - parsedTorrent.urlList = parsedTorrent.urlList.concat(this.urlList) - } + BufferList.prototype.unshift = function unshift(v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + }; - uniq(parsedTorrent.announce) - uniq(parsedTorrent.urlList) + BufferList.prototype.shift = function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + }; - extendMutable(this, parsedTorrent) + BufferList.prototype.clear = function clear() { + this.head = this.tail = null; + this.length = 0; + }; - this.magnetURI = parseTorrent.toMagnetURI(parsedTorrent) - this.torrentFile = parseTorrent.toTorrentFile(parsedTorrent) -} + BufferList.prototype.join = function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; + }; -Torrent.prototype._onListening = function () { - var self = this - if (self.discovery || self.destroyed) return + BufferList.prototype.concat = function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + if (this.length === 1) return this.head.data; + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + return ret; + }; - var trackerOpts = self.client.tracker - if (trackerOpts) { - trackerOpts = extend(self.client.tracker, { - getAnnounceOpts: function () { - var opts = { - uploaded: self.uploaded, - downloaded: self.downloaded, - left: Math.max(self.length - self.downloaded, 0) - } - if (self.client.tracker.getAnnounceOpts) { - extendMutable(opts, self.client.tracker.getAnnounceOpts()) - } - if (self._getAnnounceOpts) { - // TODO: consider deprecating this, as it's redundant with the former case - extendMutable(opts, self._getAnnounceOpts()) - } - return opts - } - }) - } + return BufferList; +}(); +},{"safe-buffer":138}],130:[function(require,module,exports){ +'use strict'; - // begin discovering peers via DHT and trackers - self.discovery = new Discovery({ - infoHash: self.infoHash, - announce: self.announce, - peerId: self.client.peerId, - dht: !self.private && self.client.dht, - tracker: trackerOpts, - port: self.client.torrentPort, - userAgent: USER_AGENT - }) +/**/ - self.discovery.on('error', onError) - self.discovery.on('peer', onPeer) - self.discovery.on('trackerAnnounce', onTrackerAnnounce) - self.discovery.on('dhtAnnounce', onDHTAnnounce) - self.discovery.on('warning', onWarning) +var processNextTick = require('process-nextick-args'); +/**/ - function onError (err) { - self._destroy(err) - } +// undocumented cb() API, needed for core, not for public API +function destroy(err, cb) { + var _this = this; - function onPeer (peer) { - // Don't create new outgoing TCP connections when torrent is done - if (typeof peer === 'string' && self.done) return - self.addPeer(peer) + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err && (!this._writableState || !this._writableState.errorEmitted)) { + processNextTick(emitErrorNT, this, err); + } + return; } - function onTrackerAnnounce () { - self.emit('trackerAnnounce') - if (self.numPeers === 0) self.emit('noPeers', 'tracker') + // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + if (this._readableState) { + this._readableState.destroyed = true; } - function onDHTAnnounce () { - self.emit('dhtAnnounce') - if (self.numPeers === 0) self.emit('noPeers', 'dht') + // if this is a duplex stream mark the writable part as destroyed as well + if (this._writableState) { + this._writableState.destroyed = true; } - function onWarning (err) { - self.emit('warning', err) + this._destroy(err || null, function (err) { + if (!cb && err) { + processNextTick(emitErrorNT, _this, err); + if (_this._writableState) { + _this._writableState.errorEmitted = true; + } + } else if (cb) { + cb(err); + } + }); +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; } - if (self.info) { - // if full metadata was included in initial torrent id, use it immediately. Otherwise, - // wait for torrent-discovery to find peers and ut_metadata to get the metadata. - self._onMetadata(self) - } else if (self.xs) { - self._getMetadataFromServer() + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; } } -Torrent.prototype._getMetadataFromServer = function () { - var self = this - var urls = Array.isArray(self.xs) ? self.xs : [ self.xs ] +function emitErrorNT(self, err) { + self.emit('error', err); +} - var tasks = urls.map(function (url) { - return function (cb) { - getMetadataFromURL(url, cb) - } - }) - parallel(tasks) +module.exports = { + destroy: destroy, + undestroy: undestroy +}; +},{"process-nextick-args":119}],131:[function(require,module,exports){ +arguments[4][27][0].apply(exports,arguments) +},{"dup":27,"events":7}],132:[function(require,module,exports){ +arguments[4][28][0].apply(exports,arguments) +},{"./lib/_stream_duplex.js":124,"./lib/_stream_passthrough.js":125,"./lib/_stream_readable.js":126,"./lib/_stream_transform.js":127,"./lib/_stream_writable.js":128,"dup":28}],133:[function(require,module,exports){ +exports.render = render +exports.append = append +exports.mime = require('./lib/mime.json') - function getMetadataFromURL (url, cb) { - if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { - self.emit('warning', new Error('skipping non-http xs param: ' + url)) - return cb(null) - } +var debug = require('debug')('render-media') +var isAscii = require('is-ascii') +var MediaElementWrapper = require('mediasource') +var path = require('path') +var streamToBlobURL = require('stream-to-blob-url') +var videostream = require('videostream') - var opts = { - url: url, - method: 'GET', - headers: { - 'user-agent': USER_AGENT - } - } - var req - try { - req = get.concat(opts, onResponse) - } catch (err) { - self.emit('warning', new Error('skipping invalid url xs param: ' + url)) - return cb(null) - } +var VIDEOSTREAM_EXTS = [ + '.m4a', + '.m4v', + '.mp4' +] - self._xsRequests.push(req) +var MEDIASOURCE_VIDEO_EXTS = [ + '.m4v', + '.mkv', + '.mp4', + '.webm' +] - function onResponse (err, res, torrent) { - if (self.destroyed) return cb(null) - if (self.metadata) return cb(null) +var MEDIASOURCE_AUDIO_EXTS = [ + '.m4a', + '.mp3' +] - if (err) { - self.emit('warning', new Error('http error from xs param: ' + url)) - return cb(null) - } - if (res.statusCode !== 200) { - self.emit('warning', new Error('non-200 status code ' + res.statusCode + ' from xs param: ' + url)) - return cb(null) - } +var MEDIASOURCE_EXTS = [].concat( + MEDIASOURCE_VIDEO_EXTS, + MEDIASOURCE_AUDIO_EXTS +) - var parsedTorrent - try { - parsedTorrent = parseTorrent(torrent) - } catch (err) {} +var AUDIO_EXTS = [ + '.aac', + '.oga', + '.ogg', + '.wav' +] - if (!parsedTorrent) { - self.emit('warning', new Error('got invalid torrent file from xs param: ' + url)) - return cb(null) - } +var IMAGE_EXTS = [ + '.bmp', + '.gif', + '.jpeg', + '.jpg', + '.png', + '.svg' +] - if (parsedTorrent.infoHash !== self.infoHash) { - self.emit('warning', new Error('got torrent file with incorrect info hash from xs param: ' + url)) - return cb(null) - } +var IFRAME_EXTS = [ + '.css', + '.html', + '.js', + '.md', + '.pdf', + '.txt' +] - self._onMetadata(parsedTorrent) - cb(null) - } +// Maximum file length for which the Blob URL strategy will be attempted +// See: https://github.com/feross/render-media/issues/18 +var MAX_BLOB_LENGTH = 200 * 1000 * 1000 // 200 MB + +var MediaSource = typeof window !== 'undefined' && window.MediaSource + +function render (file, elem, opts, cb) { + if (typeof opts === 'function') { + cb = opts + opts = {} } -} + if (!opts) opts = {} + if (!cb) cb = function () {} -/** - * Called when the full torrent metadata is received. - */ -Torrent.prototype._onMetadata = function (metadata) { - var self = this - if (self.metadata || self.destroyed) return - self._debug('got metadata') + validateFile(file) + parseOpts(opts) - self._xsRequests.forEach(function (req) { - req.abort() - }) - self._xsRequests = [] + if (typeof elem === 'string') elem = document.querySelector(elem) - var parsedTorrent - if (metadata && metadata.infoHash) { - // `metadata` is a parsed torrent (from parse-torrent module) - parsedTorrent = metadata - } else { - try { - parsedTorrent = parseTorrent(metadata) - } catch (err) { - return self._destroy(err) + renderMedia(file, function (tagName) { + if (elem.nodeName !== tagName.toUpperCase()) { + var extname = path.extname(file.name).toLowerCase() + + throw new Error( + 'Cannot render "' + extname + '" inside a "' + + elem.nodeName.toLowerCase() + '" element, expected "' + tagName + '"' + ) } - } - self._processParsedTorrent(parsedTorrent) - self.metadata = self.torrentFile + return elem + }, opts, cb) +} - // add web seed urls (BEP19) - if (self.client.enableWebSeeds) { - self.urlList.forEach(function (url) { - self.addWebSeed(url) - }) +function append (file, rootElem, opts, cb) { + if (typeof opts === 'function') { + cb = opts + opts = {} } + if (!opts) opts = {} + if (!cb) cb = function () {} - // start off selecting the entire torrent with low priority - // if (self.pieces.length !== 0) { //pear modified - // self.select(0, self.pieces.length - 1, false) - // } - - self._rarityMap = new RarityMap(self) + validateFile(file) + parseOpts(opts) - // self.store = new ImmediateChunkStore( //pear modified - // new self._store(self.pieceLength, { - // torrent: { - // infoHash: self.infoHash - // }, - // files: self.files.map(function (file) { - // return { - // path: path.join(self.path, file.path), - // length: file.length, - // offset: file.offset - // } - // }), - // length: self.length - // }) - // ) + if (typeof rootElem === 'string') rootElem = document.querySelector(rootElem) - self.files = self.files.map(function (file) { - return new File(self, file) - }) + if (rootElem && (rootElem.nodeName === 'VIDEO' || rootElem.nodeName === 'AUDIO')) { + throw new Error( + 'Invalid video/audio node argument. Argument must be root element that ' + + 'video/audio tag will be appended to.' + ) + } - self._hashes = self.pieces + renderMedia(file, getElem, opts, done) - self.pieces = self.pieces.map(function (hash, i) { - var pieceLength = (i === self.pieces.length - 1) - ? self.lastPieceLength - : self.pieceLength - return new Piece(pieceLength) - }) + function getElem (tagName) { + if (tagName === 'video' || tagName === 'audio') return createMedia(tagName) + else return createElem(tagName) + } - self._reservations = self.pieces.map(function () { - return [] - }) + function createMedia (tagName) { + var elem = createElem(tagName) + if (opts.controls) elem.controls = true + if (opts.autoplay) elem.autoplay = true + rootElem.appendChild(elem) + return elem + } - // self.bitfield = new BitField(self.pieces.length) //pear modified + function createElem (tagName) { + var elem = document.createElement(tagName) + rootElem.appendChild(elem) + return elem + } - self.wires.forEach(function (wire) { - // If we didn't have the metadata at the time ut_metadata was initialized for this - // wire, we still want to make it available to the peer in case they request it. - if (wire.ut_metadata) wire.ut_metadata.setMetadata(self.metadata) + function done (err, elem) { + if (err && elem) elem.remove() + cb(err, elem) + } +} - self._onWireWithMetadata(wire) - }) +function renderMedia (file, getElem, opts, cb) { + var extname = path.extname(file.name).toLowerCase() + var currentTime = 0 + var elem - self._debug('verifying existing torrent data') - if (self._fileModtimes && self._store === FSChunkStore) { - // don't verify if the files haven't been modified since we last checked - self.getFileModtimes(function (err, fileModtimes) { - if (err) return self._destroy(err) + if (MEDIASOURCE_EXTS.indexOf(extname) >= 0) { + renderMediaSource() + } else if (AUDIO_EXTS.indexOf(extname) >= 0) { + renderAudio() + } else if (IMAGE_EXTS.indexOf(extname) >= 0) { + renderImage() + } else if (IFRAME_EXTS.indexOf(extname) >= 0) { + renderIframe() + } else { + tryRenderIframe() + } - var unchanged = self.files.map(function (_, index) { - return fileModtimes[index] === self._fileModtimes[index] - }).every(function (x) { - return x - }) + function renderMediaSource () { + var tagName = MEDIASOURCE_VIDEO_EXTS.indexOf(extname) >= 0 ? 'video' : 'audio' - if (unchanged) { - for (var index = 0; index < self.pieces.length; index++) { - self._markVerified(index) - } - self._onStore() + if (MediaSource) { + if (VIDEOSTREAM_EXTS.indexOf(extname) >= 0) { + useVideostream() } else { - self._verifyPieces() + useMediaSource() } - }) - } else { - self._verifyPieces() - } - - self.emit('metadata') -} + } else { + useBlobURL() + } -/* - * TODO: remove this - * Gets the last modified time of every file on disk for this torrent. - * Only valid in Node, not in the browser. - */ -Torrent.prototype.getFileModtimes = function (cb) { - var self = this - var ret = [] - parallelLimit(self.files.map(function (file, index) { - return function (cb) { - fs.stat(path.join(self.path, file.path), function (err, stat) { - if (err && err.code !== 'ENOENT') return cb(err) - ret[index] = stat && stat.mtime.getTime() - cb(null) - }) + function useVideostream () { + debug('Use `videostream` package for ' + file.name) + prepareElem() + elem.addEventListener('error', fallbackToMediaSource) + elem.addEventListener('loadstart', onLoadStart) + elem.addEventListener('canplay', onCanPlay) + videostream(file, elem) } - }), FILESYSTEM_CONCURRENCY, function (err) { - self._debug('done getting file modtimes') - cb(err, ret) - }) -} -Torrent.prototype._verifyPieces = function () { - var self = this - parallelLimit(self.pieces.map(function (_, index) { - return function (cb) { - if (self.destroyed) return cb(new Error('torrent is destroyed')) + function useMediaSource () { + debug('Use MediaSource API for ' + file.name) + prepareElem() + elem.addEventListener('error', fallbackToBlobURL) + elem.addEventListener('loadstart', onLoadStart) + elem.addEventListener('canplay', onCanPlay) - self.store.get(index, function (err, buf) { - if (self.destroyed) return cb(new Error('torrent is destroyed')) + var wrapper = new MediaElementWrapper(elem) + var writable = wrapper.createWriteStream(getCodec(file.name)) + file.createReadStream().pipe(writable) - if (err) return process.nextTick(cb, null) // ignore error - sha1(buf, function (hash) { - if (self.destroyed) return cb(new Error('torrent is destroyed')) + if (currentTime) elem.currentTime = currentTime + } - if (hash === self._hashes[index]) { - if (!self.pieces[index]) return - self._debug('piece verified %s', index) - self._markVerified(index) - } else { - self._debug('piece invalid %s', index) - } - cb(null) - }) + function useBlobURL () { + debug('Use Blob URL for ' + file.name) + prepareElem() + elem.addEventListener('error', fatalError) + elem.addEventListener('loadstart', onLoadStart) + elem.addEventListener('canplay', onCanPlay) + getBlobURL(file, function (err, url) { + if (err) return fatalError(err) + elem.src = url + if (currentTime) elem.currentTime = currentTime }) } - }), FILESYSTEM_CONCURRENCY, function (err) { - if (err) return self._destroy(err) - self._debug('done verifying') - self._onStore() - }) -} -Torrent.prototype._markVerified = function (index) { - this.pieces[index] = null - this._reservations[index] = null; - if (!this.bitfield.get(index)) { //pear modified - this.emit('piecefromtorrent', index); - } - this.bitfield.set(index, true) -} + function fallbackToMediaSource (err) { + debug('videostream error: fallback to MediaSource API: %o', err.message || err) + elem.removeEventListener('error', fallbackToMediaSource) + elem.removeEventListener('canplay', onCanPlay) -/** - * Called when the metadata, listening server, and underlying chunk store is initialized. - */ -Torrent.prototype._onStore = function () { - var self = this - if (self.destroyed) return - self._debug('on store') - - self.ready = true - self.emit('ready') - - // Files may start out done if the file was already in the store - self._checkDone() - - // In case any selections were made before torrent was ready - self._updateSelections() -} + useMediaSource() + } -Torrent.prototype.destroy = function (cb) { - var self = this - self._destroy(null, cb) -} + function fallbackToBlobURL (err) { + debug('MediaSource API error: fallback to Blob URL: %o', err.message || err) -Torrent.prototype._destroy = function (err, cb) { - var self = this - if (self.destroyed) return - self.destroyed = true - self._debug('destroy') + if (typeof file.length === 'number' && file.length > opts.maxBlobLength) { + debug( + 'File length too large for Blob URL approach: %d (max: %d)', + file.length, opts.maxBlobLength + ) + return fatalError(new Error( + 'File length too large for Blob URL approach: ' + file.length + + ' (max: ' + opts.maxBlobLength + ')' + )) + } - self.client._remove(self) + elem.removeEventListener('error', fallbackToBlobURL) + elem.removeEventListener('canplay', onCanPlay) - clearInterval(self._rechokeIntervalId) + useBlobURL() + } - self._xsRequests.forEach(function (req) { - req.abort() - }) + function prepareElem () { + if (!elem) { + elem = getElem(tagName) - if (self._rarityMap) { - self._rarityMap.destroy() + elem.addEventListener('progress', function () { + currentTime = elem.currentTime + }) + } + } } - for (var id in self._peers) { - self.removePeer(id) + function renderAudio () { + elem = getElem('audio') + getBlobURL(file, function (err, url) { + if (err) return fatalError(err) + elem.addEventListener('error', fatalError) + elem.addEventListener('loadstart', onLoadStart) + elem.addEventListener('canplay', onCanPlay) + elem.src = url + }) } - self.files.forEach(function (file) { - if (file instanceof File) file._destroy() - }) - - var tasks = self._servers.map(function (server) { - return function (cb) { - server.destroy(cb) - } - }) + function onLoadStart () { + elem.removeEventListener('loadstart', onLoadStart) + if (opts.autoplay) elem.play() + } - if (self.discovery) { - tasks.push(function (cb) { - self.discovery.destroy(cb) - }) + function onCanPlay () { + elem.removeEventListener('canplay', onCanPlay) + cb(null, elem) } - if (self.store) { - tasks.push(function (cb) { - self.store.close(cb) + + function renderImage () { + elem = getElem('img') + getBlobURL(file, function (err, url) { + if (err) return fatalError(err) + elem.src = url + elem.alt = file.name + cb(null, elem) }) } - parallel(tasks, cb) + function renderIframe () { + elem = getElem('iframe') - if (err) { - // Torrent errors are emitted at `torrent.on('error')`. If there are no 'error' - // event handlers on the torrent instance, then the error will be emitted at - // `client.on('error')`. This prevents throwing an uncaught exception - // (unhandled 'error' event), but it makes it impossible to distinguish client - // errors versus torrent errors. Torrent errors are not fatal, and the client - // is still usable afterwards. Therefore, always listen for errors in both - // places (`client.on('error')` and `torrent.on('error')`). - if (self.listenerCount('error') === 0) { - self.client.emit('error', err) - } else { - self.emit('error', err) - } + getBlobURL(file, function (err, url) { + if (err) return fatalError(err) + elem.src = url + if (extname !== '.pdf') elem.sandbox = 'allow-forms allow-scripts' + cb(null, elem) + }) } - self.emit('close') - - self.client = null - self.files = [] - self.discovery = null - self.store = null - self._rarityMap = null - self._peers = null - self._servers = null - self._xsRequests = null -} + function tryRenderIframe () { + debug('Unknown file extension "%s" - will attempt to render into iframe', extname) -Torrent.prototype.addPeer = function (peer) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') - if (!self.infoHash) throw new Error('addPeer() must not be called before the `infoHash` event') + var str = '' + file.createReadStream({ start: 0, end: 1000 }) + .setEncoding('utf8') + .on('data', function (chunk) { + str += chunk + }) + .on('end', done) + .on('error', cb) - if (self.client.blocked) { - var host - if (typeof peer === 'string') { - var parts - try { - parts = addrToIPPort(peer) - } catch (e) { - self._debug('ignoring peer: invalid %s', peer) - self.emit('invalidPeer', peer) - return false + function done () { + if (isAscii(str)) { + debug('File extension "%s" appears ascii, so will render.', extname) + renderIframe() + } else { + debug('File extension "%s" appears non-ascii, will not render.', extname) + cb(new Error('Unsupported file type "' + extname + '": Cannot append to DOM')) } - host = parts[0] - } else if (typeof peer.remoteAddress === 'string') { - host = peer.remoteAddress - } - - if (host && self.client.blocked.contains(host)) { - self._debug('ignoring peer: blocked %s', peer) - if (typeof peer !== 'string') peer.destroy() - self.emit('blockedPeer', peer) - return false } } - var wasAdded = !!self._addPeer(peer) - if (wasAdded) { - self.emit('peer', peer) - } else { - self.emit('invalidPeer', peer) + function fatalError (err) { + err.message = 'Error rendering file "' + file.name + '": ' + err.message + debug(err.message) + cb(err) } - return wasAdded } -Torrent.prototype._addPeer = function (peer) { - var self = this - if (self.destroyed) { - if (typeof peer !== 'string') peer.destroy() - return null - } - if (typeof peer === 'string' && !self._validAddr(peer)) { - self._debug('ignoring peer: invalid %s', peer) - return null - } - - var id = (peer && peer.id) || peer - if (self._peers[id]) { - self._debug('ignoring peer: duplicate (%s)', id) - if (typeof peer !== 'string') peer.destroy() - return null - } +function getBlobURL (file, cb) { + var extname = path.extname(file.name).toLowerCase() + streamToBlobURL(file.createReadStream(), exports.mime[extname], cb) +} - if (self.paused) { - self._debug('ignoring peer: torrent is paused') - if (typeof peer !== 'string') peer.destroy() - return null +function validateFile (file) { + if (file == null) { + throw new Error('file cannot be null or undefined') } - - self._debug('add peer %s', id) - - var newPeer - if (typeof peer === 'string') { - // `peer` is an addr ("ip:port" string) - newPeer = Peer.createTCPOutgoingPeer(peer, self) - } else { - // `peer` is a WebRTC connection (simple-peer) - newPeer = Peer.createWebRTCPeer(peer, self) + if (typeof file.name !== 'string') { + throw new Error('missing or invalid file.name property') } - - self._peers[newPeer.id] = newPeer - self._peersLength += 1 - - if (typeof peer === 'string') { - // `peer` is an addr ("ip:port" string) - self._queue.push(newPeer) - self._drain() + if (typeof file.createReadStream !== 'function') { + throw new Error('missing or invalid file.createReadStream property') } +} - return newPeer +function getCodec (name) { + var extname = path.extname(name).toLowerCase() + return { + '.m4a': 'audio/mp4; codecs="mp4a.40.5"', + '.m4v': 'video/mp4; codecs="avc1.640029, mp4a.40.5"', + '.mkv': 'video/webm; codecs="avc1.640029, mp4a.40.5"', + '.mp3': 'audio/mpeg', + '.mp4': 'video/mp4; codecs="avc1.640029, mp4a.40.5"', + '.webm': 'video/webm; codecs="vorbis, vp8"' + }[extname] } -Torrent.prototype.addWebSeed = function (url) { - if (this.destroyed) throw new Error('torrent is destroyed') +function parseOpts (opts) { + if (opts.autoplay == null) opts.autoplay = true + if (opts.controls == null) opts.controls = true + if (opts.maxBlobLength == null) opts.maxBlobLength = MAX_BLOB_LENGTH +} - if (!/^https?:\/\/.+/.test(url)) { - this.emit('warning', new Error('ignoring invalid web seed: ' + url)) - this.emit('invalidPeer', url) - return - } - - if (this._peers[url]) { - this.emit('warning', new Error('ignoring duplicate web seed: ' + url)) - this.emit('invalidPeer', url) - return - } - - this._debug('add web seed %s', url) - - var newPeer = Peer.createWebSeedPeer(url, this) - this._peers[newPeer.id] = newPeer - this._peersLength += 1 - - this.emit('peer', url) -} - -/** - * Called whenever a new incoming TCP peer connects to this torrent swarm. Called with a - * peer that has already sent a handshake. - */ -Torrent.prototype._addIncomingPeer = function (peer) { - var self = this - if (self.destroyed) return peer.destroy(new Error('torrent is destroyed')) - if (self.paused) return peer.destroy(new Error('torrent is paused')) - - this._debug('add incoming peer %s', peer.id) - - self._peers[peer.id] = peer - self._peersLength += 1 +},{"./lib/mime.json":134,"debug":87,"is-ascii":97,"mediasource":104,"path":13,"stream-to-blob-url":145,"videostream":161}],134:[function(require,module,exports){ +module.exports={ + ".3gp": "video/3gpp", + ".aac": "audio/aac", + ".aif": "audio/x-aiff", + ".aiff": "audio/x-aiff", + ".atom": "application/atom+xml", + ".avi": "video/x-msvideo", + ".bmp": "image/bmp", + ".bz2": "application/x-bzip2", + ".conf": "text/plain", + ".css": "text/css", + ".csv": "text/plain", + ".diff": "text/x-diff", + ".doc": "application/msword", + ".flv": "video/x-flv", + ".gif": "image/gif", + ".gz": "application/x-gzip", + ".htm": "text/html", + ".html": "text/html", + ".ico": "image/vnd.microsoft.icon", + ".ics": "text/calendar", + ".iso": "application/octet-stream", + ".jar": "application/java-archive", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "application/javascript", + ".json": "application/json", + ".less": "text/css", + ".log": "text/plain", + ".m3u": "audio/x-mpegurl", + ".m4a": "audio/mp4", + ".m4v": "video/mp4", + ".manifest": "text/cache-manifest", + ".markdown": "text/x-markdown", + ".mathml": "application/mathml+xml", + ".md": "text/x-markdown", + ".mid": "audio/midi", + ".midi": "audio/midi", + ".mov": "video/quicktime", + ".mp3": "audio/mpeg", + ".mp4": "video/mp4", + ".mp4v": "video/mp4", + ".mpeg": "video/mpeg", + ".mpg": "video/mpeg", + ".odp": "application/vnd.oasis.opendocument.presentation", + ".ods": "application/vnd.oasis.opendocument.spreadsheet", + ".odt": "application/vnd.oasis.opendocument.text", + ".oga": "audio/ogg", + ".ogg": "application/ogg", + ".pdf": "application/pdf", + ".png": "image/png", + ".pps": "application/vnd.ms-powerpoint", + ".ppt": "application/vnd.ms-powerpoint", + ".ps": "application/postscript", + ".psd": "image/vnd.adobe.photoshop", + ".qt": "video/quicktime", + ".rar": "application/x-rar-compressed", + ".rdf": "application/rdf+xml", + ".rss": "application/rss+xml", + ".rtf": "application/rtf", + ".svg": "image/svg+xml", + ".svgz": "image/svg+xml", + ".swf": "application/x-shockwave-flash", + ".tar": "application/x-tar", + ".tbz": "application/x-bzip-compressed-tar", + ".text": "text/plain", + ".tif": "image/tiff", + ".tiff": "image/tiff", + ".torrent": "application/x-bittorrent", + ".ttf": "application/x-font-ttf", + ".txt": "text/plain", + ".wav": "audio/wav", + ".webm": "video/webm", + ".wma": "audio/x-ms-wma", + ".wmv": "video/x-ms-wmv", + ".xls": "application/vnd.ms-excel", + ".xml": "application/xml", + ".yaml": "text/yaml", + ".yml": "text/yaml", + ".zip": "application/zip" } -Torrent.prototype.removePeer = function (peer) { - var self = this - var id = (peer && peer.id) || peer - peer = self._peers[id] +},{}],135:[function(require,module,exports){ +(function (process){ +module.exports = function (tasks, limit, cb) { + if (typeof limit !== 'number') throw new Error('second argument must be a Number') + var results, len, pending, keys, isErrored + var isSync = true - if (!peer) return + if (Array.isArray(tasks)) { + results = [] + pending = len = tasks.length + } else { + keys = Object.keys(tasks) + results = {} + pending = len = keys.length + } - this._debug('removePeer %s', id) + function done (err) { + function end () { + if (cb) cb(err, results) + cb = null + } + if (isSync) process.nextTick(end) + else end() + } - delete self._peers[id] - self._peersLength -= 1 + function each (i, err, result) { + results[i] = result + if (err) isErrored = true + if (--pending === 0 || err) { + done(err) + } else if (!isErrored && next < len) { + var key + if (keys) { + key = keys[next] + next += 1 + tasks[key](function (err, result) { each(key, err, result) }) + } else { + key = next + next += 1 + tasks[key](function (err, result) { each(key, err, result) }) + } + } + } - peer.destroy() + var next = limit + if (!pending) { + // empty + done(null) + } else if (keys) { + // object + keys.some(function (key, i) { + tasks[key](function (err, result) { each(key, err, result) }) + if (i === limit - 1) return true // early return + }) + } else { + // array + tasks.some(function (task, i) { + task(function (err, result) { each(i, err, result) }) + if (i === limit - 1) return true // early return + }) + } - // If torrent swarm was at capacity before, try to open a new connection now - self._drain() + isSync = false } -Torrent.prototype.select = function (start, end, priority, notify) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') +}).call(this,require('_process')) +},{"_process":15}],136:[function(require,module,exports){ +(function (process){ +module.exports = function (tasks, cb) { + var results, pending, keys + var isSync = true - if (start < 0 || end < start || self.pieces.length <= end) { - console.log('start:'+start + 'end:'+end + 'length:'+self.pieces.length); - throw new Error('invalid selection ', start, ':', end) + if (Array.isArray(tasks)) { + results = [] + pending = tasks.length + } else { + keys = Object.keys(tasks) + results = {} + pending = keys.length } - priority = Number(priority) || 0 - - self._debug('select %s-%s (priority %s)', start, end, priority) - - self._selections.push({ - from: start, - to: end, - offset: 0, - priority: priority, - notify: notify || noop - }) - - self._selections.sort(function (a, b) { - return b.priority - a.priority - }) - - self._updateSelections() -} - -Torrent.prototype.deselect = function (start, end, priority) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') - priority = Number(priority) || 0 - self._debug('deselect %s-%s (priority %s)', start, end, priority) + function done (err) { + function end () { + if (cb) cb(err, results) + cb = null + } + if (isSync) process.nextTick(end) + else end() + } - for (var i = 0; i < self._selections.length; ++i) { - var s = self._selections[i] - if (s.from === start && s.to === end && s.priority === priority) { - self._selections.splice(i, 1) - break + function each (i, err, result) { + results[i] = result + if (--pending === 0 || err) { + done(err) } } - self._updateSelections() + if (!pending) { + // empty + done(null) + } else if (keys) { + // object + keys.forEach(function (key) { + tasks[key](function (err, result) { each(key, err, result) }) + }) + } else { + // array + tasks.forEach(function (task, i) { + task(function (err, result) { each(i, err, result) }) + }) + } + + isSync = false } -Torrent.prototype.critical = function (start, end) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') - - self._debug('critical %s-%s', start, end) - - for (var i = start; i <= end; ++i) { - self._critical[i] = true - } - - self._updateSelections() -} - -Torrent.prototype._onWire = function (wire, addr) { - var self = this - self._debug('got wire %s (%s)', wire._debugId, addr || 'Unknown') - - wire.on('download', function (downloaded) { - if (self.destroyed) return - self.received += downloaded - self._downloadSpeed(downloaded) - self.client._downloadSpeed(downloaded) - self.emit('download', downloaded) - self.client.emit('download', downloaded) - }) - - wire.on('upload', function (uploaded) { - if (self.destroyed) return - self.uploaded += uploaded - self._uploadSpeed(uploaded) - self.client._uploadSpeed(uploaded) - self.emit('upload', uploaded) - self.client.emit('upload', uploaded) - }) - - self.wires.push(wire) - - if (addr) { - // Sometimes RTCPeerConnection.getStats() doesn't return an ip:port for peers - var parts = addrToIPPort(addr) - wire.remoteAddress = parts[0] - wire.remotePort = parts[1] - } - - // When peer sends PORT message, add that DHT node to routing table - if (self.client.dht && self.client.dht.listening) { - wire.on('port', function (port) { - if (self.destroyed || self.client.dht.destroyed) { - return - } - if (!wire.remoteAddress) { - return self._debug('ignoring PORT from peer with no address') - } - if (port === 0 || port > 65536) { - return self._debug('ignoring invalid PORT from peer') - } - - self._debug('port: %s (from %s)', port, addr) - self.client.dht.addNode({ host: wire.remoteAddress, port: port }) - }) - } - - wire.on('timeout', function () { - self._debug('wire timeout (%s)', addr) - // TODO: this might be destroying wires too eagerly - wire.destroy() - }) - - // Timeout for piece requests to this peer - wire.setTimeout(PIECE_TIMEOUT, true) - - // Send KEEP-ALIVE (every 60s) so peers will not disconnect the wire - wire.setKeepAlive(true) - - // use ut_metadata extension - wire.use(utMetadata(self.metadata)) - - wire.ut_metadata.on('warning', function (err) { - self._debug('ut_metadata warning: %s', err.message) - }) - - if (!self.metadata) { - wire.ut_metadata.on('metadata', function (metadata) { - self._debug('got metadata via ut_metadata') - self._onMetadata(metadata) - }) - wire.ut_metadata.fetch() - } - - // use ut_pex extension if the torrent is not flagged as private - if (typeof utPex === 'function' && !self.private) { - wire.use(utPex()) - - wire.ut_pex.on('peer', function (peer) { - // Only add potential new peers when we're not seeding - if (self.done) return - self._debug('ut_pex: got peer: %s (from %s)', peer, addr) - self.addPeer(peer) - }) - - wire.ut_pex.on('dropped', function (peer) { - // the remote peer believes a given peer has been dropped from the torrent swarm. - // if we're not currently connected to it, then remove it from the queue. - var peerObj = self._peers[peer] - if (peerObj && !peerObj.connected) { - self._debug('ut_pex: dropped peer: %s (from %s)', peer, addr) - self.removePeer(peer) - } - }) - - wire.once('close', function () { - // Stop sending updates to remote peer - wire.ut_pex.reset() - }) - } - - // Hook to allow user-defined `bittorrent-protocol` extensions - // More info: https://github.com/webtorrent/bittorrent-protocol#extension-api - self.emit('wire', wire, addr) - - if (self.metadata) { - process.nextTick(function () { - // This allows wire.handshake() to be called (by Peer.onHandshake) before any - // messages get sent on the wire - self._onWireWithMetadata(wire) - }) - } -} - -Torrent.prototype._onWireWithMetadata = function (wire) { - var self = this - var timeoutId = null - - function onChokeTimeout () { - if (self.destroyed || wire.destroyed) return - - if (self._numQueued > 2 * (self._numConns - self.numPeers) && - wire.amInterested) { - wire.destroy() - } else { - timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) - if (timeoutId.unref) timeoutId.unref() - } - } - - var i - function updateSeedStatus () { - if (wire.peerPieces.buffer.length !== self.bitfield.buffer.length) return - for (i = 0; i < self.pieces.length; ++i) { - if (!wire.peerPieces.get(i)) return - } - wire.isSeeder = true - wire.choke() // always choke seeders - } - - wire.on('bitfield', function () { - updateSeedStatus() - self._update() - }) - - wire.on('have', function () { - updateSeedStatus() - self._update() - }) - - wire.once('interested', function () { - wire.unchoke() - }) - - wire.once('close', function () { - clearTimeout(timeoutId) - }) - - wire.on('choke', function () { - clearTimeout(timeoutId) - timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) - if (timeoutId.unref) timeoutId.unref() - }) - - wire.on('unchoke', function () { - clearTimeout(timeoutId) - self._update() - }) - - wire.on('request', function (index, offset, length, cb) { - if (length > MAX_BLOCK_LENGTH) { - // Per spec, disconnect from peers that request >128KB - return wire.destroy() - } - if (self.pieces[index]) return - self.store.get(index, { offset: offset, length: length }, cb) - }) - - wire.bitfield(self.bitfield) // always send bitfield (required) - wire.interested() // always start out interested - - // Send PORT message to peers that support DHT - if (wire.peerExtensions.dht && self.client.dht && self.client.dht.listening) { - wire.port(self.client.dht.address().port) - } - - if (wire.type !== 'webSeed') { // do not choke on webseeds - timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) - if (timeoutId.unref) timeoutId.unref() - } - - wire.isSeeder = false - updateSeedStatus() -} - -/** - * Called on selection changes. - */ -Torrent.prototype._updateSelections = function () { - var self = this - if (!self.ready || self.destroyed) return - - process.nextTick(function () { - self._gcSelections() - }) - self._updateInterest() - self._update() -} - -/** - * Garbage collect selections with respect to the store's current state. +}).call(this,require('_process')) +},{"_process":15}],137:[function(require,module,exports){ +(function (global){ +(function () { + var /* + * Rusha, a JavaScript implementation of the Secure Hash Algorithm, SHA-1, + * as defined in FIPS PUB 180-1, tuned for high performance with large inputs. + * (http://github.com/srijs/rusha) + * + * Inspired by Paul Johnstons implementation (http://pajhome.org.uk/crypt/md5). + * + * Copyright (c) 2013 Sam Rijs (http://awesam.de). + * Released under the terms of the MIT license as follows: + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. */ -Torrent.prototype._gcSelections = function () { - var self = this - - for (var i = 0; i < self._selections.length; ++i) { - var s = self._selections[i] - var oldOffset = s.offset - - // check for newly downloaded pieces in selection - while (self.bitfield.get(s.from + s.offset) && s.from + s.offset < s.to) { - s.offset += 1 - } - - if (oldOffset !== s.offset) s.notify() - if (s.to !== s.from + s.offset) continue - if (!self.bitfield.get(s.from + s.offset)) continue - - self._selections.splice(i, 1) // remove fully downloaded selection - i -= 1 // decrement i to offset splice - - s.notify() - self._updateInterest() - } - - if (!self._selections.length) self.emit('idle') -} - -/** - * Update interested status for all peers. - */ -Torrent.prototype._updateInterest = function () { - var self = this - - var prev = self._amInterested - self._amInterested = !!self._selections.length - - self.wires.forEach(function (wire) { - // TODO: only call wire.interested if the wire has at least one piece we need - if (self._amInterested) wire.interested() - else wire.uninterested() - }) - - if (prev === self._amInterested) return - if (self._amInterested) self.emit('interested') - else self.emit('uninterested') -} - -/** - * Heartbeat to update all peers and their requests. - */ -Torrent.prototype._update = function () { - var self = this - if (self.destroyed) return - - // update wires in random order for better request distribution - var ite = randomIterate(self.wires) - var wire - while ((wire = ite())) { - self._updateWire(wire) - } -} - -/** - * Attempts to update a peer's requests - */ -Torrent.prototype._updateWire = function (wire) { - var self = this - - if (wire.peerChoking) return - if (!wire.downloaded) return validateWire() - - var minOutstandingRequests = getBlockPipelineLength(wire, PIPELINE_MIN_DURATION) - if (wire.requests.length >= minOutstandingRequests) return - var maxOutstandingRequests = getBlockPipelineLength(wire, PIPELINE_MAX_DURATION) - - trySelectWire(false) || trySelectWire(true) - - function genPieceFilterFunc (start, end, tried, rank) { - return function (i) { - return i >= start && i <= end && !(i in tried) && wire.peerPieces.get(i) && (!rank || rank(i)) - } - } - - // TODO: Do we need both validateWire and trySelectWire? - function validateWire () { - if (wire.requests.length) return - - var i = self._selections.length - while (i--) { - var next = self._selections[i] - var piece - if (self.strategy === 'rarest') { - var start = next.from + next.offset - var end = next.to - var len = end - start + 1 - var tried = {} - var tries = 0 - var filter = genPieceFilterFunc(start, end, tried) - - while (tries < len) { - piece = self._rarityMap.getRarestPiece(filter) - if (piece < 0) break - if (self._request(wire, piece, false)) return - tried[piece] = true - tries += 1 - } - } else { - for (piece = next.to; piece >= next.from + next.offset; --piece) { - if (!wire.peerPieces.get(piece)) continue - if (self._request(wire, piece, false)) return - } - } - } - - // TODO: wire failed to validate as useful; should we close it? - // probably not, since 'have' and 'bitfield' messages might be coming - } - - function speedRanker () { - var speed = wire.downloadSpeed() || 1 - if (speed > SPEED_THRESHOLD) return function () { return true } - - var secs = Math.max(1, wire.requests.length) * Piece.BLOCK_LENGTH / speed - var tries = 10 - var ptr = 0 - - return function (index) { - if (!tries || self.bitfield.get(index)) return true - - var missing = self.pieces[index].missing - - for (; ptr < self.wires.length; ptr++) { - var otherWire = self.wires[ptr] - var otherSpeed = otherWire.downloadSpeed() - - if (otherSpeed < SPEED_THRESHOLD) continue - if (otherSpeed <= speed) continue - if (!otherWire.peerPieces.get(index)) continue - if ((missing -= otherSpeed * secs) > 0) continue - - tries-- - return false - } - - return true - } - } - - function shufflePriority (i) { - var last = i - for (var j = i; j < self._selections.length && self._selections[j].priority; j++) { - last = j - } - var tmp = self._selections[i] - self._selections[i] = self._selections[last] - self._selections[last] = tmp - } - - function trySelectWire (hotswap) { - if (wire.requests.length >= maxOutstandingRequests) return true - var rank = speedRanker() - - for (var i = 0; i < self._selections.length; i++) { - var next = self._selections[i] - - var piece - if (self.strategy === 'rarest') { - var start = next.from + next.offset - var end = next.to - var len = end - start + 1 - var tried = {} - var tries = 0 - var filter = genPieceFilterFunc(start, end, tried, rank) - - while (tries < len) { - piece = self._rarityMap.getRarestPiece(filter) - if (piece < 0) break - - // request all non-reserved blocks in this piece - while (self._request(wire, piece, self._critical[piece] || hotswap)) {} - - if (wire.requests.length < maxOutstandingRequests) { - tried[piece] = true - tries++ - continue - } - - if (next.priority) shufflePriority(i) - return true - } - } else { - for (piece = next.from + next.offset; piece <= next.to; piece++) { - if (!wire.peerPieces.get(piece) || !rank(piece)) continue - - // request all non-reserved blocks in piece - while (self._request(wire, piece, self._critical[piece] || hotswap)) {} - - if (wire.requests.length < maxOutstandingRequests) continue - - if (next.priority) shufflePriority(i) - return true + util = { + getDataType: function (data) { + if (typeof data === 'string') { + return 'string'; + } + if (data instanceof Array) { + return 'array'; + } + if (typeof global !== 'undefined' && global.Buffer && global.Buffer.isBuffer(data)) { + return 'buffer'; + } + if (data instanceof ArrayBuffer) { + return 'arraybuffer'; + } + if (data.buffer instanceof ArrayBuffer) { + return 'view'; + } + if (data instanceof Blob) { + return 'blob'; + } + throw new Error('Unsupported data type.'); } - } - } - - return false - } -} - -/** - * Called periodically to update the choked status of all peers, handling optimistic - * unchoking as described in BEP3. - */ -Torrent.prototype._rechoke = function () { - var self = this - if (!self.ready) return - - if (self._rechokeOptimisticTime > 0) self._rechokeOptimisticTime -= 1 - else self._rechokeOptimisticWire = null - - var peers = [] - - self.wires.forEach(function (wire) { - if (!wire.isSeeder && wire !== self._rechokeOptimisticWire) { - peers.push({ - wire: wire, - downloadSpeed: wire.downloadSpeed(), - uploadSpeed: wire.uploadSpeed(), - salt: Math.random(), - isChoked: true - }) - } - }) - - peers.sort(rechokeSort) - - var unchokeInterested = 0 - var i = 0 - for (; i < peers.length && unchokeInterested < self._rechokeNumSlots; ++i) { - peers[i].isChoked = false - if (peers[i].wire.peerInterested) unchokeInterested += 1 + }; + function Rusha(chunkSize) { + 'use strict'; + var // Private object structure. + self$2 = { fill: 0 }; + var // Calculate the length of buffer that the sha1 routine uses + // including the padding. + padlen = function (len) { + for (len += 9; len % 64 > 0; len += 1); + return len; + }; + var padZeroes = function (bin, len) { + var h8 = new Uint8Array(bin.buffer); + var om = len % 4, align = len - om; + switch (om) { + case 0: + h8[align + 3] = 0; + case 1: + h8[align + 2] = 0; + case 2: + h8[align + 1] = 0; + case 3: + h8[align + 0] = 0; + } + for (var i$2 = (len >> 2) + 1; i$2 < bin.length; i$2++) + bin[i$2] = 0; + }; + var padData = function (bin, chunkLen, msgLen) { + bin[chunkLen >> 2] |= 128 << 24 - (chunkLen % 4 << 3); + // To support msgLen >= 2 GiB, use a float division when computing the + // high 32-bits of the big-endian message length in bits. + bin[((chunkLen >> 2) + 2 & ~15) + 14] = msgLen / (1 << 29) | 0; + bin[((chunkLen >> 2) + 2 & ~15) + 15] = msgLen << 3; + }; + var // Convert a binary string and write it to the heap. + // A binary string is expected to only contain char codes < 256. + convStr = function (H8, H32, start, len, off) { + var str = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm; + switch (om) { + case 0: + H8[off] = str.charCodeAt(start + 3); + case 1: + H8[off + 1 - (om << 1) | 0] = str.charCodeAt(start + 2); + case 2: + H8[off + 2 - (om << 1) | 0] = str.charCodeAt(start + 1); + case 3: + H8[off + 3 - (om << 1) | 0] = str.charCodeAt(start); + } + if (len < lm + om) { + return; + } + for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) { + H32[off + i$2 >> 2] = str.charCodeAt(start + i$2) << 24 | str.charCodeAt(start + i$2 + 1) << 16 | str.charCodeAt(start + i$2 + 2) << 8 | str.charCodeAt(start + i$2 + 3); + } + switch (lm) { + case 3: + H8[off + j + 1 | 0] = str.charCodeAt(start + j + 2); + case 2: + H8[off + j + 2 | 0] = str.charCodeAt(start + j + 1); + case 1: + H8[off + j + 3 | 0] = str.charCodeAt(start + j); + } + }; + var // Convert a buffer or array and write it to the heap. + // The buffer or array is expected to only contain elements < 256. + convBuf = function (H8, H32, start, len, off) { + var buf = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm; + switch (om) { + case 0: + H8[off] = buf[start + 3]; + case 1: + H8[off + 1 - (om << 1) | 0] = buf[start + 2]; + case 2: + H8[off + 2 - (om << 1) | 0] = buf[start + 1]; + case 3: + H8[off + 3 - (om << 1) | 0] = buf[start]; + } + if (len < lm + om) { + return; + } + for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) { + H32[off + i$2 >> 2 | 0] = buf[start + i$2] << 24 | buf[start + i$2 + 1] << 16 | buf[start + i$2 + 2] << 8 | buf[start + i$2 + 3]; + } + switch (lm) { + case 3: + H8[off + j + 1 | 0] = buf[start + j + 2]; + case 2: + H8[off + j + 2 | 0] = buf[start + j + 1]; + case 1: + H8[off + j + 3 | 0] = buf[start + j]; + } + }; + var convBlob = function (H8, H32, start, len, off) { + var blob = this, i$2, om = off % 4, lm = (len + om) % 4, j = len - lm; + var buf = new Uint8Array(reader.readAsArrayBuffer(blob.slice(start, start + len))); + switch (om) { + case 0: + H8[off] = buf[3]; + case 1: + H8[off + 1 - (om << 1) | 0] = buf[2]; + case 2: + H8[off + 2 - (om << 1) | 0] = buf[1]; + case 3: + H8[off + 3 - (om << 1) | 0] = buf[0]; + } + if (len < lm + om) { + return; + } + for (i$2 = 4 - om; i$2 < j; i$2 = i$2 + 4 | 0) { + H32[off + i$2 >> 2 | 0] = buf[i$2] << 24 | buf[i$2 + 1] << 16 | buf[i$2 + 2] << 8 | buf[i$2 + 3]; + } + switch (lm) { + case 3: + H8[off + j + 1 | 0] = buf[j + 2]; + case 2: + H8[off + j + 2 | 0] = buf[j + 1]; + case 1: + H8[off + j + 3 | 0] = buf[j]; + } + }; + var convFn = function (data) { + switch (util.getDataType(data)) { + case 'string': + return convStr.bind(data); + case 'array': + return convBuf.bind(data); + case 'buffer': + return convBuf.bind(data); + case 'arraybuffer': + return convBuf.bind(new Uint8Array(data)); + case 'view': + return convBuf.bind(new Uint8Array(data.buffer, data.byteOffset, data.byteLength)); + case 'blob': + return convBlob.bind(data); + } + }; + var slice = function (data, offset) { + switch (util.getDataType(data)) { + case 'string': + return data.slice(offset); + case 'array': + return data.slice(offset); + case 'buffer': + return data.slice(offset); + case 'arraybuffer': + return data.slice(offset); + case 'view': + return data.buffer.slice(offset); + } + }; + var // Precompute 00 - ff strings + precomputedHex = new Array(256); + for (var i = 0; i < 256; i++) { + precomputedHex[i] = (i < 16 ? '0' : '') + i.toString(16); + } + var // Convert an ArrayBuffer into its hexadecimal string representation. + hex = function (arrayBuffer) { + var binarray = new Uint8Array(arrayBuffer); + var res = new Array(arrayBuffer.byteLength); + for (var i$2 = 0; i$2 < res.length; i$2++) { + res[i$2] = precomputedHex[binarray[i$2]]; + } + return res.join(''); + }; + var ceilHeapSize = function (v) { + // The asm.js spec says: + // The heap object's byteLength must be either + // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1. + // Also, byteLengths smaller than 2^16 are deprecated. + var p; + if (// If v is smaller than 2^16, the smallest possible solution + // is 2^16. + v <= 65536) + return 65536; + if (// If v < 2^24, we round up to 2^n, + // otherwise we round up to 2^24 * n. + v < 16777216) { + for (p = 1; p < v; p = p << 1); + } else { + for (p = 16777216; p < v; p += 16777216); + } + return p; + }; + var // Initialize the internal data structures to a new capacity. + init = function (size) { + if (size % 64 > 0) { + throw new Error('Chunk size must be a multiple of 128 bit'); + } + self$2.offset = 0; + self$2.maxChunkLen = size; + self$2.padMaxChunkLen = padlen(size); + // The size of the heap is the sum of: + // 1. The padded input message size + // 2. The extended space the algorithm needs (320 byte) + // 3. The 160 bit state the algoritm uses + self$2.heap = new ArrayBuffer(ceilHeapSize(self$2.padMaxChunkLen + 320 + 20)); + self$2.h32 = new Int32Array(self$2.heap); + self$2.h8 = new Int8Array(self$2.heap); + self$2.core = new Rusha._core({ + Int32Array: Int32Array, + DataView: DataView + }, {}, self$2.heap); + self$2.buffer = null; + }; + // Iinitializethe datastructures according + // to a chunk siyze. + init(chunkSize || 64 * 1024); + var initState = function (heap, padMsgLen) { + self$2.offset = 0; + var io = new Int32Array(heap, padMsgLen + 320, 5); + io[0] = 1732584193; + io[1] = -271733879; + io[2] = -1732584194; + io[3] = 271733878; + io[4] = -1009589776; + }; + var padChunk = function (chunkLen, msgLen) { + var padChunkLen = padlen(chunkLen); + var view = new Int32Array(self$2.heap, 0, padChunkLen >> 2); + padZeroes(view, chunkLen); + padData(view, chunkLen, msgLen); + return padChunkLen; + }; + var // Write data to the heap. + write = function (data, chunkOffset, chunkLen, off) { + convFn(data)(self$2.h8, self$2.h32, chunkOffset, chunkLen, off || 0); + }; + var // Initialize and call the RushaCore, + // assuming an input buffer of length len * 4. + coreCall = function (data, chunkOffset, chunkLen, msgLen, finalize) { + var padChunkLen = chunkLen; + write(data, chunkOffset, chunkLen); + if (finalize) { + padChunkLen = padChunk(chunkLen, msgLen); + } + self$2.core.hash(padChunkLen, self$2.padMaxChunkLen); + }; + var getRawDigest = function (heap, padMaxChunkLen) { + var io = new Int32Array(heap, padMaxChunkLen + 320, 5); + var out = new Int32Array(5); + var arr = new DataView(out.buffer); + arr.setInt32(0, io[0], false); + arr.setInt32(4, io[1], false); + arr.setInt32(8, io[2], false); + arr.setInt32(12, io[3], false); + arr.setInt32(16, io[4], false); + return out; + }; + var // Calculate the hash digest as an array of 5 32bit integers. + rawDigest = this.rawDigest = function (str) { + var msgLen = str.byteLength || str.length || str.size || 0; + initState(self$2.heap, self$2.padMaxChunkLen); + var chunkOffset = 0, chunkLen = self$2.maxChunkLen; + for (chunkOffset = 0; msgLen > chunkOffset + chunkLen; chunkOffset += chunkLen) { + coreCall(str, chunkOffset, chunkLen, msgLen, false); + } + coreCall(str, chunkOffset, msgLen - chunkOffset, msgLen, true); + return getRawDigest(self$2.heap, self$2.padMaxChunkLen); + }; + // The digest and digestFrom* interface returns the hash digest + // as a hex string. + this.digest = this.digestFromString = this.digestFromBuffer = this.digestFromArrayBuffer = function (str) { + return hex(rawDigest(str).buffer); + }; + this.resetState = function () { + initState(self$2.heap, self$2.padMaxChunkLen); + return this; + }; + this.append = function (chunk) { + var chunkOffset = 0; + var chunkLen = chunk.byteLength || chunk.length || chunk.size || 0; + var turnOffset = self$2.offset % self$2.maxChunkLen; + var inputLen; + self$2.offset += chunkLen; + while (chunkOffset < chunkLen) { + inputLen = Math.min(chunkLen - chunkOffset, self$2.maxChunkLen - turnOffset); + write(chunk, chunkOffset, inputLen, turnOffset); + turnOffset += inputLen; + chunkOffset += inputLen; + if (turnOffset === self$2.maxChunkLen) { + self$2.core.hash(self$2.maxChunkLen, self$2.padMaxChunkLen); + turnOffset = 0; + } + } + return this; + }; + this.getState = function () { + var turnOffset = self$2.offset % self$2.maxChunkLen; + var heap; + if (!turnOffset) { + var io = new Int32Array(self$2.heap, self$2.padMaxChunkLen + 320, 5); + heap = io.buffer.slice(io.byteOffset, io.byteOffset + io.byteLength); + } else { + heap = self$2.heap.slice(0); + } + return { + offset: self$2.offset, + heap: heap + }; + }; + this.setState = function (state) { + self$2.offset = state.offset; + if (state.heap.byteLength === 20) { + var io = new Int32Array(self$2.heap, self$2.padMaxChunkLen + 320, 5); + io.set(new Int32Array(state.heap)); + } else { + self$2.h32.set(new Int32Array(state.heap)); + } + return this; + }; + var rawEnd = this.rawEnd = function () { + var msgLen = self$2.offset; + var chunkLen = msgLen % self$2.maxChunkLen; + var padChunkLen = padChunk(chunkLen, msgLen); + self$2.core.hash(padChunkLen, self$2.padMaxChunkLen); + var result = getRawDigest(self$2.heap, self$2.padMaxChunkLen); + initState(self$2.heap, self$2.padMaxChunkLen); + return result; + }; + this.end = function () { + return hex(rawEnd().buffer); + }; + } + ; + // The low-level RushCore module provides the heart of Rusha, + // a high-speed sha1 implementation working on an Int32Array heap. + // At first glance, the implementation seems complicated, however + // with the SHA1 spec at hand, it is obvious this almost a textbook + // implementation that has a few functions hand-inlined and a few loops + // hand-unrolled. + Rusha._core = function RushaCore(stdlib, foreign, heap) { + 'use asm'; + var H = new stdlib.Int32Array(heap); + function hash(k, x) { + // k in bytes + k = k | 0; + x = x | 0; + var i = 0, j = 0, y0 = 0, z0 = 0, y1 = 0, z1 = 0, y2 = 0, z2 = 0, y3 = 0, z3 = 0, y4 = 0, z4 = 0, t0 = 0, t1 = 0; + y0 = H[x + 320 >> 2] | 0; + y1 = H[x + 324 >> 2] | 0; + y2 = H[x + 328 >> 2] | 0; + y3 = H[x + 332 >> 2] | 0; + y4 = H[x + 336 >> 2] | 0; + for (i = 0; (i | 0) < (k | 0); i = i + 64 | 0) { + z0 = y0; + z1 = y1; + z2 = y2; + z3 = y3; + z4 = y4; + for (j = 0; (j | 0) < 64; j = j + 4 | 0) { + t1 = H[i + j >> 2] | 0; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + H[k + j >> 2] = t1; + } + for (j = k + 64 | 0; (j | 0) < (k + 80 | 0); j = j + 4 | 0) { + t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | ~y1 & y3) | 0) + ((t1 + y4 | 0) + 1518500249 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + H[j >> 2] = t1; + } + for (j = k + 80 | 0; (j | 0) < (k + 160 | 0); j = j + 4 | 0) { + t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) + 1859775393 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + H[j >> 2] = t1; + } + for (j = k + 160 | 0; (j | 0) < (k + 240 | 0); j = j + 4 | 0) { + t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 & y2 | y1 & y3 | y2 & y3) | 0) + ((t1 + y4 | 0) - 1894007588 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + H[j >> 2] = t1; + } + for (j = k + 240 | 0; (j | 0) < (k + 320 | 0); j = j + 4 | 0) { + t1 = (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) << 1 | (H[j - 12 >> 2] ^ H[j - 32 >> 2] ^ H[j - 56 >> 2] ^ H[j - 64 >> 2]) >>> 31; + t0 = ((y0 << 5 | y0 >>> 27) + (y1 ^ y2 ^ y3) | 0) + ((t1 + y4 | 0) - 899497514 | 0) | 0; + y4 = y3; + y3 = y2; + y2 = y1 << 30 | y1 >>> 2; + y1 = y0; + y0 = t0; + H[j >> 2] = t1; + } + y0 = y0 + z0 | 0; + y1 = y1 + z1 | 0; + y2 = y2 + z2 | 0; + y3 = y3 + z3 | 0; + y4 = y4 + z4 | 0; + } + H[x + 320 >> 2] = y0; + H[x + 324 >> 2] = y1; + H[x + 328 >> 2] = y2; + H[x + 332 >> 2] = y3; + H[x + 336 >> 2] = y4; + } + return { hash: hash }; + }; + if (// If we'e running in Node.JS, export a module. + typeof module !== 'undefined') { + module.exports = Rusha; + } else if (// If we're running in a DOM context, export + // the Rusha object to toplevel. + typeof window !== 'undefined') { + window.Rusha = Rusha; + } + if (// If we're running in a webworker, accept + // messages containing a jobid and a buffer + // or blob object, and return the hash result. + typeof FileReaderSync !== 'undefined') { + var reader = new FileReaderSync(); + var hashData = function hash(hasher, data, cb) { + try { + return cb(null, hasher.digest(data)); + } catch (e) { + return cb(e); + } + }; + var hashFile = function hashArrayBuffer(hasher, readTotal, blockSize, file, cb) { + var reader$2 = new self.FileReader(); + reader$2.onloadend = function onloadend() { + var buffer = reader$2.result; + readTotal += reader$2.result.byteLength; + try { + hasher.append(buffer); + } catch (e) { + cb(e); + return; + } + if (readTotal < file.size) { + hashFile(hasher, readTotal, blockSize, file, cb); + } else { + cb(null, hasher.end()); + } + }; + reader$2.readAsArrayBuffer(file.slice(readTotal, readTotal + blockSize)); + }; + self.onmessage = function onMessage(event) { + var data = event.data.data, file = event.data.file, id = event.data.id; + if (typeof id === 'undefined') + return; + if (!file && !data) + return; + var blockSize = event.data.blockSize || 4 * 1024 * 1024; + var hasher = new Rusha(blockSize); + hasher.resetState(); + var done = function done$2(err, hash) { + if (!err) { + self.postMessage({ + id: id, + hash: hash + }); + } else { + self.postMessage({ + id: id, + error: err.name + }); + } + }; + if (data) + hashData(hasher, data, done); + if (file) + hashFile(hasher, 0, blockSize, file, done); + }; + } +}()); +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],138:[function(require,module,exports){ +arguments[4][29][0].apply(exports,arguments) +},{"buffer":4,"dup":29}],139:[function(require,module,exports){ +(function (Buffer){ +module.exports = function (stream, cb) { + var chunks = [] + stream.on('data', function (chunk) { + chunks.push(chunk) + }) + stream.once('end', function () { + if (cb) cb(null, Buffer.concat(chunks)) + cb = null + }) + stream.once('error', function (err) { + if (cb) cb(err) + cb = null + }) +} + +}).call(this,require("buffer").Buffer) +},{"buffer":4}],140:[function(require,module,exports){ +(function (Buffer){ +module.exports = simpleGet + +var concat = require('simple-concat') +var http = require('http') +var https = require('https') +var once = require('once') +var querystring = require('querystring') +var unzipResponse = require('unzip-response') // excluded from browser build +var url = require('url') + +function simpleGet (opts, cb) { + opts = typeof opts === 'string' ? {url: opts} : Object.assign({}, opts) + cb = once(cb) + + if (opts.url) parseOptsUrl(opts) + if (opts.headers == null) opts.headers = {} + if (opts.maxRedirects == null) opts.maxRedirects = 10 + + var body + if (opts.form) body = typeof opts.form === 'string' ? opts.form : querystring.stringify(opts.form) + if (opts.body) body = opts.json ? JSON.stringify(opts.body) : opts.body + + if (opts.json) opts.headers.accept = 'application/json' + if (opts.json && body) opts.headers['content-type'] = 'application/json' + if (opts.form) opts.headers['content-type'] = 'application/x-www-form-urlencoded' + if (body && !isStream(body)) opts.headers['content-length'] = Buffer.byteLength(body) + delete opts.body + delete opts.form + + if (body && !opts.method) opts.method = 'POST' + if (opts.method) opts.method = opts.method.toUpperCase() + + // Request gzip/deflate + var customAcceptEncoding = Object.keys(opts.headers).some(function (h) { + return h.toLowerCase() === 'accept-encoding' + }) + if (!customAcceptEncoding) opts.headers['accept-encoding'] = 'gzip, deflate' + + // Support http/https urls + var protocol = opts.protocol === 'https:' ? https : http + var req = protocol.request(opts, function (res) { + // Follow 3xx redirects + if (res.statusCode >= 300 && res.statusCode < 400 && 'location' in res.headers) { + opts.url = res.headers.location + res.resume() // Discard response + + if (opts.maxRedirects > 0) { + opts.maxRedirects -= 1 + simpleGet(opts, cb) + } else { + cb(new Error('too many redirects')) + } + return + } + + var tryUnzip = typeof unzipResponse === 'function' && opts.method !== 'HEAD' + cb(null, tryUnzip ? unzipResponse(res) : res) + }) + req.on('timeout', function () { + req.abort() + cb(new Error('Request timed out')) + }) + req.on('error', cb) + + if (body && isStream(body)) body.on('error', cb).pipe(req) + else req.end(body) + + return req +} + +simpleGet.concat = function (opts, cb) { + return simpleGet(opts, function (err, res) { + if (err) return cb(err) + concat(res, function (err, data) { + if (err) return cb(err) + if (opts.json) { + try { + data = JSON.parse(data.toString()) + } catch (err) { + return cb(err, res, data) + } + } + cb(null, res, data) + }) + }) +} + +;['get', 'post', 'put', 'patch', 'head', 'delete'].forEach(function (method) { + simpleGet[method] = function (opts, cb) { + if (typeof opts === 'string') opts = {url: opts} + opts.method = method.toUpperCase() + return simpleGet(opts, cb) + } +}) + +function parseOptsUrl (opts) { + var loc = url.parse(opts.url) + if (loc.hostname) opts.hostname = loc.hostname + if (loc.port) opts.port = loc.port + if (loc.protocol) opts.protocol = loc.protocol + if (loc.auth) opts.auth = loc.auth + opts.path = loc.path + delete opts.url +} + +function isStream (obj) { return typeof obj.pipe === 'function' } + +}).call(this,require("buffer").Buffer) +},{"buffer":4,"http":30,"https":8,"once":115,"querystring":19,"simple-concat":139,"unzip-response":3,"url":37}],141:[function(require,module,exports){ +(function (Buffer){ +module.exports = Peer + +var debug = require('debug')('simple-peer') +var getBrowserRTC = require('get-browser-rtc') +var inherits = require('inherits') +var randombytes = require('randombytes') +var stream = require('readable-stream') + +var MAX_BUFFERED_AMOUNT = 64 * 1024 + +inherits(Peer, stream.Duplex) + +/** + * WebRTC peer connection. Same API as node core `net.Socket`, plus a few extra methods. + * Duplex stream. + * @param {Object} opts + */ +function Peer (opts) { + var self = this + if (!(self instanceof Peer)) return new Peer(opts) + + self._id = randombytes(4).toString('hex').slice(0, 7) + self._debug('new peer %o', opts) + + opts = Object.assign({ + allowHalfOpen: false + }, opts) + + stream.Duplex.call(self, opts) + + self.channelName = opts.initiator + ? opts.channelName || randombytes(20).toString('hex') + : null + + // Needed by _transformConstraints, so set this early + self._isChromium = typeof window !== 'undefined' && !!window.webkitRTCPeerConnection + + self.initiator = opts.initiator || false + self.channelConfig = opts.channelConfig || Peer.channelConfig + self.config = opts.config || Peer.config + self.constraints = self._transformConstraints(opts.constraints || Peer.constraints) + self.offerConstraints = self._transformConstraints(opts.offerConstraints || {}) + self.answerConstraints = self._transformConstraints(opts.answerConstraints || {}) + self.reconnectTimer = opts.reconnectTimer || false + self.sdpTransform = opts.sdpTransform || function (sdp) { return sdp } + self.stream = opts.stream || false + self.trickle = opts.trickle !== undefined ? opts.trickle : true + self._earlyMessage = null + + self.destroyed = false + self.connected = false + + self.remoteAddress = undefined + self.remoteFamily = undefined + self.remotePort = undefined + self.localAddress = undefined + self.localPort = undefined + + self._wrtc = (opts.wrtc && typeof opts.wrtc === 'object') + ? opts.wrtc + : getBrowserRTC() + + if (!self._wrtc) { + if (typeof window === 'undefined') { + throw new Error('No WebRTC support: Specify `opts.wrtc` option in this environment') + } else { + throw new Error('No WebRTC support: Not a supported browser') + } + } + + self._pcReady = false + self._channelReady = false + self._iceComplete = false // ice candidate trickle done (got null candidate) + self._channel = null + self._pendingCandidates = [] + self._previousStreams = [] + + self._chunk = null + self._cb = null + self._interval = null + self._reconnectTimeout = null + + self._pc = new (self._wrtc.RTCPeerConnection)(self.config, self.constraints) + + // We prefer feature detection whenever possible, but sometimes that's not + // possible for certain implementations. + self._isWrtc = Array.isArray(self._pc.RTCIceConnectionStates) + self._isReactNativeWebrtc = typeof self._pc._peerConnectionId === 'number' + + self._pc.oniceconnectionstatechange = function () { + self._onIceStateChange() + } + self._pc.onicegatheringstatechange = function () { + self._onIceStateChange() + } + self._pc.onsignalingstatechange = function () { + self._onSignalingStateChange() + } + self._pc.onicecandidate = function (event) { + self._onIceCandidate(event) + } + + // Other spec events, unused by this implementation: + // - onconnectionstatechange + // - onicecandidateerror + // - onfingerprintfailure + + if (self.initiator) { + var createdOffer = false + self._pc.onnegotiationneeded = function () { + if (!createdOffer) self._createOffer() + createdOffer = true + } + + self._setupData({ + channel: self._pc.createDataChannel(self.channelName, self.channelConfig) + }) + } else { + self._pc.ondatachannel = function (event) { + self._setupData(event) + } + } + + if ('addTrack' in self._pc) { + // WebRTC Spec, Firefox + if (self.stream) { + self.stream.getTracks().forEach(function (track) { + self._pc.addTrack(track, self.stream) + }) + } + self._pc.ontrack = function (event) { + self._onTrack(event) + } + } else { + // Chrome, etc. This can be removed once all browsers support `ontrack` + if (self.stream) self._pc.addStream(self.stream) + self._pc.onaddstream = function (event) { + self._onAddStream(event) + } + } + + // HACK: wrtc doesn't fire the 'negotionneeded' event + if (self.initiator && self._isWrtc) { + self._pc.onnegotiationneeded() + } + + self._onFinishBound = function () { + self._onFinish() + } + self.once('finish', self._onFinishBound) +} + +Peer.WEBRTC_SUPPORT = !!getBrowserRTC() + +/** + * Expose config, constraints, and data channel config for overriding all Peer + * instances. Otherwise, just set opts.config, opts.constraints, or opts.channelConfig + * when constructing a Peer. + */ +Peer.config = { + iceServers: [ + { + urls: 'stun:stun.l.google.com:19302' + }, + { + urls: 'stun:global.stun.twilio.com:3478?transport=udp' + } + ] +} +Peer.constraints = {} +Peer.channelConfig = {} + +Object.defineProperty(Peer.prototype, 'bufferSize', { + get: function () { + var self = this + return (self._channel && self._channel.bufferedAmount) || 0 + } +}) + +Peer.prototype.address = function () { + var self = this + return { port: self.localPort, family: 'IPv4', address: self.localAddress } +} + +Peer.prototype.signal = function (data) { + var self = this + if (self.destroyed) throw new Error('cannot signal after peer is destroyed') + if (typeof data === 'string') { + try { + data = JSON.parse(data) + } catch (err) { + data = {} + } + } + self._debug('signal()') + + if (data.candidate) { + if (self._pc.remoteDescription) self._addIceCandidate(data.candidate) + else self._pendingCandidates.push(data.candidate) + } + if (data.sdp) { + self._pc.setRemoteDescription(new (self._wrtc.RTCSessionDescription)(data), function () { + if (self.destroyed) return + + self._pendingCandidates.forEach(function (candidate) { + self._addIceCandidate(candidate) + }) + self._pendingCandidates = [] + + if (self._pc.remoteDescription.type === 'offer') self._createAnswer() + }, function (err) { self._destroy(err) }) + } + if (!data.sdp && !data.candidate) { + self._destroy(new Error('signal() called with invalid signal data')) + } +} + +Peer.prototype._addIceCandidate = function (candidate) { + var self = this + try { + self._pc.addIceCandidate( + new self._wrtc.RTCIceCandidate(candidate), + noop, + function (err) { self._destroy(err) } + ) + } catch (err) { + self._destroy(new Error('error adding candidate: ' + err.message)) + } +} + +/** + * Send text/binary data to the remote peer. + * @param {TypedArrayView|ArrayBuffer|Buffer|string|Blob|Object} chunk + */ +Peer.prototype.send = function (chunk) { + var self = this + + // HACK: `wrtc` module crashes on Node.js Buffer, so convert to Uint8Array + // See: https://github.com/feross/simple-peer/issues/60 + if (self._isWrtc && Buffer.isBuffer(chunk)) { + chunk = new Uint8Array(chunk) + } + + self._channel.send(chunk) +} + +Peer.prototype.destroy = function (onclose) { + var self = this + self._destroy(null, onclose) +} + +Peer.prototype._destroy = function (err, onclose) { + var self = this + if (self.destroyed) return + if (onclose) self.once('close', onclose) + + self._debug('destroy (error: %s)', err && (err.message || err)) + + self.readable = self.writable = false + + if (!self._readableState.ended) self.push(null) + if (!self._writableState.finished) self.end() + + self.destroyed = true + self.connected = false + self._pcReady = false + self._channelReady = false + self._previousStreams = null + self._earlyMessage = null + + clearInterval(self._interval) + clearTimeout(self._reconnectTimeout) + self._interval = null + self._reconnectTimeout = null + self._chunk = null + self._cb = null + + if (self._onFinishBound) self.removeListener('finish', self._onFinishBound) + self._onFinishBound = null + + if (self._pc) { + try { + self._pc.close() + } catch (err) {} + + self._pc.oniceconnectionstatechange = null + self._pc.onicegatheringstatechange = null + self._pc.onsignalingstatechange = null + self._pc.onicecandidate = null + if ('addTrack' in self._pc) { + self._pc.ontrack = null + } else { + self._pc.onaddstream = null + } + self._pc.onnegotiationneeded = null + self._pc.ondatachannel = null + } + + if (self._channel) { + try { + self._channel.close() + } catch (err) {} + + self._channel.onmessage = null + self._channel.onopen = null + self._channel.onclose = null + self._channel.onerror = null + } + self._pc = null + self._channel = null + + if (err) self.emit('error', err) + self.emit('close') +} + +Peer.prototype._setupData = function (event) { + var self = this + if (!event.channel) { + // In some situations `pc.createDataChannel()` returns `undefined` (in wrtc), + // which is invalid behavior. Handle it gracefully. + // See: https://github.com/feross/simple-peer/issues/163 + return self._destroy(new Error('Data channel event is missing `channel` property')) + } + + self._channel = event.channel + self._channel.binaryType = 'arraybuffer' + + if (typeof self._channel.bufferedAmountLowThreshold === 'number') { + self._channel.bufferedAmountLowThreshold = MAX_BUFFERED_AMOUNT + } + + self.channelName = self._channel.label + + self._channel.onmessage = function (event) { + if (!self._channelReady) { // HACK: Workaround for Chrome not firing "open" between tabs + self._earlyMessage = event + self._onChannelOpen() + } else { + self._onChannelMessage(event) + } + } + self._channel.onbufferedamountlow = function () { + self._onChannelBufferedAmountLow() + } + self._channel.onopen = function () { + if (!self._channelReady) self._onChannelOpen() + } + self._channel.onclose = function () { + self._onChannelClose() + } + self._channel.onerror = function (err) { + self._destroy(err) + } +} + +Peer.prototype._read = function () {} + +Peer.prototype._write = function (chunk, encoding, cb) { + var self = this + if (self.destroyed) return cb(new Error('cannot write after peer is destroyed')) + + if (self.connected) { + try { + self.send(chunk) + } catch (err) { + return self._destroy(err) + } + if (self._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) { + self._debug('start backpressure: bufferedAmount %d', self._channel.bufferedAmount) + self._cb = cb + } else { + cb(null) + } + } else { + self._debug('write before connect') + self._chunk = chunk + self._cb = cb + } +} + +// When stream finishes writing, close socket. Half open connections are not +// supported. +Peer.prototype._onFinish = function () { + var self = this + if (self.destroyed) return + + if (self.connected) { + destroySoon() + } else { + self.once('connect', destroySoon) + } + + // Wait a bit before destroying so the socket flushes. + // TODO: is there a more reliable way to accomplish this? + function destroySoon () { + setTimeout(function () { + self._destroy() + }, 1000) + } +} + +Peer.prototype._createOffer = function () { + var self = this + if (self.destroyed) return + + self._pc.createOffer(function (offer) { + if (self.destroyed) return + offer.sdp = self.sdpTransform(offer.sdp) + self._pc.setLocalDescription(offer, onSuccess, onError) + + function onSuccess () { + if (self.destroyed) return + if (self.trickle || self._iceComplete) sendOffer() + else self.once('_iceComplete', sendOffer) // wait for candidates + } + + function onError (err) { + self._destroy(err) + } + + function sendOffer () { + var signal = self._pc.localDescription || offer + self._debug('signal') + self.emit('signal', { + type: signal.type, + sdp: signal.sdp + }) + } + }, function (err) { self._destroy(err) }, self.offerConstraints) +} + +Peer.prototype._createAnswer = function () { + var self = this + if (self.destroyed) return + + self._pc.createAnswer(function (answer) { + if (self.destroyed) return + answer.sdp = self.sdpTransform(answer.sdp) + self._pc.setLocalDescription(answer, onSuccess, onError) + + function onSuccess () { + if (self.destroyed) return + if (self.trickle || self._iceComplete) sendAnswer() + else self.once('_iceComplete', sendAnswer) + } + + function onError (err) { + self._destroy(err) + } + + function sendAnswer () { + var signal = self._pc.localDescription || answer + self._debug('signal') + self.emit('signal', { + type: signal.type, + sdp: signal.sdp + }) + } + }, function (err) { self._destroy(err) }, self.answerConstraints) +} + +Peer.prototype._onIceStateChange = function () { + var self = this + if (self.destroyed) return + var iceConnectionState = self._pc.iceConnectionState + var iceGatheringState = self._pc.iceGatheringState + + self._debug( + 'iceStateChange (connection: %s) (gathering: %s)', + iceConnectionState, + iceGatheringState + ) + self.emit('iceStateChange', iceConnectionState, iceGatheringState) + + if (iceConnectionState === 'connected' || iceConnectionState === 'completed') { + clearTimeout(self._reconnectTimeout) + self._pcReady = true + self._maybeReady() + } + if (iceConnectionState === 'disconnected') { + if (self.reconnectTimer) { + // If user has set `opt.reconnectTimer`, allow time for ICE to attempt a reconnect + clearTimeout(self._reconnectTimeout) + self._reconnectTimeout = setTimeout(function () { + self._destroy() + }, self.reconnectTimer) + } else { + self._destroy() + } + } + if (iceConnectionState === 'failed') { + self._destroy(new Error('Ice connection failed.')) + } + if (iceConnectionState === 'closed') { + self._destroy() + } +} + +Peer.prototype.getStats = function (cb) { + var self = this + + // Promise-based getStats() (standard) + if (self._pc.getStats.length === 0) { + self._pc.getStats().then(function (res) { + var reports = [] + res.forEach(function (report) { + reports.push(report) + }) + cb(null, reports) + }, function (err) { cb(err) }) + + // Two-parameter callback-based getStats() (deprecated, former standard) + } else if (self._isReactNativeWebrtc) { + self._pc.getStats(null, function (res) { + var reports = [] + res.forEach(function (report) { + reports.push(report) + }) + cb(null, reports) + }, function (err) { cb(err) }) + + // Single-parameter callback-based getStats() (non-standard) + } else if (self._pc.getStats.length > 0) { + self._pc.getStats(function (res) { + var reports = [] + res.result().forEach(function (result) { + var report = {} + result.names().forEach(function (name) { + report[name] = result.stat(name) + }) + report.id = result.id + report.type = result.type + report.timestamp = result.timestamp + reports.push(report) + }) + cb(null, reports) + }, function (err) { cb(err) }) + + // Unknown browser, skip getStats() since it's anyone's guess which style of + // getStats() they implement. + } else { + cb(null, []) + } +} + +Peer.prototype._maybeReady = function () { + var self = this + self._debug('maybeReady pc %s channel %s', self._pcReady, self._channelReady) + if (self.connected || self._connecting || !self._pcReady || !self._channelReady) return + self._connecting = true + + self.getStats(function (err, items) { + if (self.destroyed) return + + // Treat getStats error as non-fatal. It's not essential. + if (err) items = [] + + self._connecting = false + self.connected = true + + var remoteCandidates = {} + var localCandidates = {} + var candidatePairs = {} + + items.forEach(function (item) { + // TODO: Once all browsers support the hyphenated stats report types, remove + // the non-hypenated ones + if (item.type === 'remotecandidate' || item.type === 'remote-candidate') { + remoteCandidates[item.id] = item + } + if (item.type === 'localcandidate' || item.type === 'local-candidate') { + localCandidates[item.id] = item + } + if (item.type === 'candidatepair' || item.type === 'candidate-pair') { + candidatePairs[item.id] = item + } + }) + + items.forEach(function (item) { + // Spec-compliant + if (item.type === 'transport') { + setSelectedCandidatePair(candidatePairs[item.selectedCandidatePairId]) + } + + // Old implementations + if ( + (item.type === 'googCandidatePair' && item.googActiveConnection === 'true') || + ((item.type === 'candidatepair' || item.type === 'candidate-pair') && item.selected) + ) { + setSelectedCandidatePair(item) + } + }) + + function setSelectedCandidatePair (selectedCandidatePair) { + var local = localCandidates[selectedCandidatePair.localCandidateId] + + if (local && local.ip) { + // Spec + self.localAddress = local.ip + self.localPort = Number(local.port) + } else if (local && local.ipAddress) { + // Firefox + self.localAddress = local.ipAddress + self.localPort = Number(local.portNumber) + } else if (typeof selectedCandidatePair.googLocalAddress === 'string') { + // TODO: remove this once Chrome 58 is released + local = selectedCandidatePair.googLocalAddress.split(':') + self.localAddress = local[0] + self.localPort = Number(local[1]) + } + + var remote = remoteCandidates[selectedCandidatePair.remoteCandidateId] + + if (remote && remote.ip) { + // Spec + self.remoteAddress = remote.ip + self.remotePort = Number(remote.port) + } else if (remote && remote.ipAddress) { + // Firefox + self.remoteAddress = remote.ipAddress + self.remotePort = Number(remote.portNumber) + } else if (typeof selectedCandidatePair.googRemoteAddress === 'string') { + // TODO: remove this once Chrome 58 is released + remote = selectedCandidatePair.googRemoteAddress.split(':') + self.remoteAddress = remote[0] + self.remotePort = Number(remote[1]) + } + self.remoteFamily = 'IPv4' + + self._debug( + 'connect local: %s:%s remote: %s:%s', + self.localAddress, self.localPort, self.remoteAddress, self.remotePort + ) + } + + if (self._chunk) { + try { + self.send(self._chunk) + } catch (err) { + return self._destroy(err) + } + self._chunk = null + self._debug('sent chunk from "write before connect"') + + var cb = self._cb + self._cb = null + cb(null) + } + + // If `bufferedAmountLowThreshold` and 'onbufferedamountlow' are unsupported, + // fallback to using setInterval to implement backpressure. + if (typeof self._channel.bufferedAmountLowThreshold !== 'number') { + self._interval = setInterval(function () { self._onInterval() }, 150) + if (self._interval.unref) self._interval.unref() + } + + self._debug('connect') + self.emit('connect') + if (self._earlyMessage) { // HACK: Workaround for Chrome not firing "open" between tabs + self._onChannelMessage(self._earlyMessage) + self._earlyMessage = null + } + }) +} + +Peer.prototype._onInterval = function () { + if (!this._cb || !this._channel || this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) { + return + } + this._onChannelBufferedAmountLow() +} + +Peer.prototype._onSignalingStateChange = function () { + var self = this + if (self.destroyed) return + self._debug('signalingStateChange %s', self._pc.signalingState) + self.emit('signalingStateChange', self._pc.signalingState) +} + +Peer.prototype._onIceCandidate = function (event) { + var self = this + if (self.destroyed) return + if (event.candidate && self.trickle) { + self.emit('signal', { + candidate: { + candidate: event.candidate.candidate, + sdpMLineIndex: event.candidate.sdpMLineIndex, + sdpMid: event.candidate.sdpMid + } + }) + } else if (!event.candidate) { + self._iceComplete = true + self.emit('_iceComplete') + } +} + +Peer.prototype._onChannelMessage = function (event) { + var self = this + if (self.destroyed) return + var data = event.data + if (data instanceof ArrayBuffer) data = Buffer.from(data) + self.push(data) +} + +Peer.prototype._onChannelBufferedAmountLow = function () { + var self = this + if (self.destroyed || !self._cb) return + self._debug('ending backpressure: bufferedAmount %d', self._channel.bufferedAmount) + var cb = self._cb + self._cb = null + cb(null) +} + +Peer.prototype._onChannelOpen = function () { + var self = this + if (self.connected || self.destroyed) return + self._debug('on channel open') + self._channelReady = true + self._maybeReady() +} + +Peer.prototype._onChannelClose = function () { + var self = this + if (self.destroyed) return + self._debug('on channel close') + self._destroy() +} + +Peer.prototype._onAddStream = function (event) { + var self = this + if (self.destroyed) return + self._debug('on add stream') + self.emit('stream', event.stream) +} + +Peer.prototype._onTrack = function (event) { + var self = this + if (self.destroyed) return + self._debug('on track') + var id = event.streams[0].id + if (self._previousStreams.indexOf(id) !== -1) return // Only fire one 'stream' event, even though there may be multiple tracks per stream + self._previousStreams.push(id) + self.emit('stream', event.streams[0]) +} + +Peer.prototype._debug = function () { + var self = this + var args = [].slice.call(arguments) + args[0] = '[' + self._id + '] ' + args[0] + debug.apply(null, args) +} + +// Transform constraints objects into the new format (unless Chromium) +// TODO: This can be removed when Chromium supports the new format +Peer.prototype._transformConstraints = function (constraints) { + var self = this + + if (Object.keys(constraints).length === 0) { + return constraints + } + + if ((constraints.mandatory || constraints.optional) && !self._isChromium) { + // convert to new format + + // Merge mandatory and optional objects, prioritizing mandatory + var newConstraints = Object.assign({}, constraints.optional, constraints.mandatory) + + // fix casing + if (newConstraints.OfferToReceiveVideo !== undefined) { + newConstraints.offerToReceiveVideo = newConstraints.OfferToReceiveVideo + delete newConstraints['OfferToReceiveVideo'] + } + + if (newConstraints.OfferToReceiveAudio !== undefined) { + newConstraints.offerToReceiveAudio = newConstraints.OfferToReceiveAudio + delete newConstraints['OfferToReceiveAudio'] + } + + return newConstraints + } else if (!constraints.mandatory && !constraints.optional && self._isChromium) { + // convert to old format + + // fix casing + if (constraints.offerToReceiveVideo !== undefined) { + constraints.OfferToReceiveVideo = constraints.offerToReceiveVideo + delete constraints['offerToReceiveVideo'] + } + + if (constraints.offerToReceiveAudio !== undefined) { + constraints.OfferToReceiveAudio = constraints.offerToReceiveAudio + delete constraints['offerToReceiveAudio'] + } + + return { + mandatory: constraints // NOTE: All constraints are upgraded to mandatory + } + } + + return constraints +} + +function noop () {} + +}).call(this,require("buffer").Buffer) +},{"buffer":4,"debug":87,"get-browser-rtc":93,"inherits":96,"randombytes":122,"readable-stream":132}],142:[function(require,module,exports){ +var Rusha = require('rusha') + +var rusha = new Rusha +var scope = typeof window !== 'undefined' ? window : self +var crypto = scope.crypto || scope.msCrypto || {} +var subtle = crypto.subtle || crypto.webkitSubtle + +function sha1sync (buf) { + return rusha.digest(buf) +} + +// Browsers throw if they lack support for an algorithm. +// Promise will be rejected on non-secure origins. (http://goo.gl/lq4gCo) +try { + subtle.digest({ name: 'sha-1' }, new Uint8Array).catch(function () { + subtle = false + }) +} catch (err) { subtle = false } + +function sha1 (buf, cb) { + if (!subtle) { + // Use Rusha + setTimeout(cb, 0, sha1sync(buf)) + return + } + + if (typeof buf === 'string') { + buf = uint8array(buf) + } + + subtle.digest({ name: 'sha-1' }, buf) + .then(function succeed (result) { + cb(hex(new Uint8Array(result))) + }, + function fail (error) { + cb(sha1sync(buf)) + }) +} + +function uint8array (s) { + var l = s.length + var array = new Uint8Array(l) + for (var i = 0; i < l; i++) { + array[i] = s.charCodeAt(i) } + return array +} - // Optimistically unchoke a peer - if (!self._rechokeOptimisticWire && i < peers.length && self._rechokeNumSlots) { - var candidates = peers.slice(i).filter(function (peer) { return peer.wire.peerInterested }) - var optimistic = candidates[randomInt(candidates.length)] +function hex (buf) { + var l = buf.length + var chars = [] + for (var i = 0; i < l; i++) { + var bite = buf[i] + chars.push((bite >>> 4).toString(16)) + chars.push((bite & 0x0f).toString(16)) + } + return chars.join('') +} - if (optimistic) { - optimistic.isChoked = false - self._rechokeOptimisticWire = optimistic.wire - self._rechokeOptimisticTime = RECHOKE_OPTIMISTIC_DURATION - } +module.exports = sha1 +module.exports.sync = sha1sync + +},{"rusha":137}],143:[function(require,module,exports){ +(function (process){ +/* global WebSocket */ + +module.exports = Socket + +var Buffer = require('safe-buffer').Buffer +var debug = require('debug')('simple-websocket') +var inherits = require('inherits') +var randombytes = require('randombytes') +var stream = require('readable-stream') +var ws = require('ws') // websockets in node - will be empty object in browser + +var _WebSocket = typeof ws !== 'function' ? WebSocket : ws + +var MAX_BUFFERED_AMOUNT = 64 * 1024 + +inherits(Socket, stream.Duplex) + +/** + * WebSocket. Same API as node core `net.Socket`. Duplex stream. + * @param {Object} opts + * @param {string=} opts.url websocket server url + * @param {string=} opts.socket raw websocket instance to wrap + */ +function Socket (opts) { + var self = this + if (!(self instanceof Socket)) return new Socket(opts) + if (!opts) opts = {} + + // Support simple usage: `new Socket(url)` + if (typeof opts === 'string') { + opts = { url: opts } } - // Unchoke best peers - peers.forEach(function (peer) { - if (peer.wire.amChoking !== peer.isChoked) { - if (peer.isChoked) peer.wire.choke() - else peer.wire.unchoke() - } - }) + if (opts.url == null && opts.socket == null) { + throw new Error('Missing required `url` or `socket` option') + } + if (opts.url != null && opts.socket != null) { + throw new Error('Must specify either `url` or `socket` option, not both') + } - function rechokeSort (peerA, peerB) { - // Prefer higher download speed - if (peerA.downloadSpeed !== peerB.downloadSpeed) { - return peerB.downloadSpeed - peerA.downloadSpeed - } + self._id = randombytes(4).toString('hex').slice(0, 7) + self._debug('new websocket: %o', opts) - // Prefer higher upload speed - if (peerA.uploadSpeed !== peerB.uploadSpeed) { - return peerB.uploadSpeed - peerA.uploadSpeed - } + opts = Object.assign({ + allowHalfOpen: false + }, opts) - // Prefer unchoked - if (peerA.wire.amChoking !== peerB.wire.amChoking) { - return peerA.wire.amChoking ? 1 : -1 + stream.Duplex.call(self, opts) + + self.connected = false + self.destroyed = false + + self._chunk = null + self._cb = null + self._interval = null + + if (opts.socket) { + self.url = opts.socket.url + self._ws = opts.socket + } else { + self.url = opts.url + try { + if (typeof ws === 'function') { + // `ws` package accepts options + self._ws = new _WebSocket(opts.url, opts) + } else { + self._ws = new _WebSocket(opts.url) + } + } catch (err) { + process.nextTick(function () { + self._destroy(err) + }) + return } + } - // Random order - return peerA.salt - peerB.salt + self._ws.binaryType = 'arraybuffer' + self._ws.onopen = function () { + self._onOpen() } + self._ws.onmessage = function (event) { + self._onMessage(event) + } + self._ws.onclose = function () { + self._onClose() + } + self._ws.onerror = function () { + self._destroy(new Error('connection error to ' + self.url)) + } + + self._onFinishBound = function () { + self._onFinish() + } + self.once('finish', self._onFinishBound) } +Socket.WEBSOCKET_SUPPORT = !!_WebSocket + /** - * Attempts to cancel a slow block request from another wire such that the - * given wire may effectively swap out the request for one of its own. + * Send text/binary data to the WebSocket server. + * @param {TypedArrayView|ArrayBuffer|Buffer|string|Blob|Object} chunk */ -Torrent.prototype._hotswap = function (wire, index) { +Socket.prototype.send = function (chunk) { + this._ws.send(chunk) +} + +Socket.prototype.destroy = function (onclose) { + this._destroy(null, onclose) +} + +Socket.prototype._destroy = function (err, onclose) { var self = this + if (self.destroyed) return + if (onclose) self.once('close', onclose) - var speed = wire.downloadSpeed() - if (speed < Piece.BLOCK_LENGTH) return false - if (!self._reservations[index]) return false + self._debug('destroy (error: %s)', err && (err.message || err)) - var r = self._reservations[index] - if (!r) { - return false + self.readable = self.writable = false + if (!self._readableState.ended) self.push(null) + if (!self._writableState.finished) self.end() + + self.connected = false + self.destroyed = true + + clearInterval(self._interval) + self._interval = null + self._chunk = null + self._cb = null + + if (self._onFinishBound) self.removeListener('finish', self._onFinishBound) + self._onFinishBound = null + + if (self._ws) { + var ws = self._ws + var onClose = function () { + ws.onclose = null + } + if (ws.readyState === _WebSocket.CLOSED) { + onClose() + } else { + try { + ws.onclose = onClose + ws.close() + } catch (err) { + onClose() + } + } + + ws.onopen = null + ws.onmessage = null + ws.onerror = null + } + self._ws = null + + if (err) self.emit('error', err) + self.emit('close') +} + +Socket.prototype._read = function () {} + +Socket.prototype._write = function (chunk, encoding, cb) { + if (this.destroyed) return cb(new Error('cannot write after socket is destroyed')) + + if (this.connected) { + try { + this.send(chunk) + } catch (err) { + return this._destroy(err) + } + if (typeof ws !== 'function' && this._ws.bufferedAmount > MAX_BUFFERED_AMOUNT) { + this._debug('start backpressure: bufferedAmount %d', this._ws.bufferedAmount) + this._cb = cb + } else { + cb(null) + } + } else { + this._debug('write before connect') + this._chunk = chunk + this._cb = cb + } +} + +// When stream finishes writing, close socket. Half open connections are not +// supported. +Socket.prototype._onFinish = function () { + var self = this + if (self.destroyed) return + + if (self.connected) { + destroySoon() + } else { + self.once('connect', destroySoon) + } + + // Wait a bit before destroying so the socket flushes. + // TODO: is there a more reliable way to accomplish this? + function destroySoon () { + setTimeout(function () { + self._destroy() + }, 1000) } +} + +Socket.prototype._onMessage = function (event) { + if (this.destroyed) return + var data = event.data + if (data instanceof ArrayBuffer) data = Buffer.from(data) + this.push(data) +} - var minSpeed = Infinity - var minWire +Socket.prototype._onOpen = function () { + var self = this + if (self.connected || self.destroyed) return + self.connected = true - var i - for (i = 0; i < r.length; i++) { - var otherWire = r[i] - if (!otherWire || otherWire === wire) continue + if (self._chunk) { + try { + self.send(self._chunk) + } catch (err) { + return self._destroy(err) + } + self._chunk = null + self._debug('sent chunk from "write before connect"') - var otherSpeed = otherWire.downloadSpeed() - if (otherSpeed >= SPEED_THRESHOLD) continue - if (2 * otherSpeed > speed || otherSpeed > minSpeed) continue + var cb = self._cb + self._cb = null + cb(null) + } - minWire = otherWire - minSpeed = otherSpeed + // Backpressure is not implemented in Node.js. The `ws` module has a buggy + // `bufferedAmount` property. See: https://github.com/websockets/ws/issues/492 + if (typeof ws !== 'function') { + self._interval = setInterval(function () { + self._onInterval() + }, 150) + if (self._interval.unref) self._interval.unref() } - if (!minWire) return false + self._debug('connect') + self.emit('connect') +} - for (i = 0; i < r.length; i++) { - if (r[i] === minWire) r[i] = null +Socket.prototype._onInterval = function () { + if (!this._cb || !this._ws || this._ws.bufferedAmount > MAX_BUFFERED_AMOUNT) { + return } + this._debug('ending backpressure: bufferedAmount %d', this._ws.bufferedAmount) + var cb = this._cb + this._cb = null + cb(null) +} - for (i = 0; i < minWire.requests.length; i++) { - var req = minWire.requests[i] - if (req.piece !== index) continue +Socket.prototype._onClose = function () { + if (this.destroyed) return + this._debug('on close') + this._destroy() +} - self.pieces[index].cancel((req.offset / Piece.BLOCK_LENGTH) | 0) - } +Socket.prototype._debug = function () { + var args = [].slice.call(arguments) + args[0] = '[' + this._id + '] ' + args[0] + debug.apply(null, args) +} - self.emit('hotswap', minWire, wire, index) - return true +}).call(this,require('_process')) +},{"_process":15,"debug":87,"inherits":96,"randombytes":122,"readable-stream":132,"safe-buffer":138,"ws":3}],144:[function(require,module,exports){ +var tick = 1 +var maxTick = 65535 +var resolution = 4 +var inc = function () { + tick = (tick + 1) & maxTick } -/** - * Attempts to request a block from the given wire. - */ -Torrent.prototype._request = function (wire, index, hotswap) { - var self = this - var numRequests = wire.requests.length - var isWebSeed = wire.type === 'webSeed' +var timer = setInterval(inc, (1000 / resolution) | 0) +if (timer.unref) timer.unref() - if (self.bitfield.get(index)) return false +module.exports = function (seconds) { + var size = resolution * (seconds || 5) + var buffer = [0] + var pointer = 1 + var last = (tick - 1) & maxTick - var maxOutstandingRequests = isWebSeed - ? Math.min( - getPiecePipelineLength(wire, PIPELINE_MAX_DURATION, self.pieceLength), - self.maxWebConns - ) - : getBlockPipelineLength(wire, PIPELINE_MAX_DURATION) + return function (delta) { + var dist = (tick - last) & maxTick + if (dist > size) dist = size + last = tick - if (numRequests >= maxOutstandingRequests) return false - // var endGame = (wire.requests.length === 0 && self.store.numMissing < 30) + while (dist--) { + if (pointer === size) pointer = 0 + buffer[pointer] = buffer[pointer === 0 ? size - 1 : pointer - 1] + pointer++ + } - var piece = self.pieces[index] - var reservation = isWebSeed ? piece.reserveRemaining() : piece.reserve() + if (delta) buffer[pointer - 1] += delta - if (reservation === -1 && hotswap && self._hotswap(wire, index)) { - reservation = isWebSeed ? piece.reserveRemaining() : piece.reserve() + var top = buffer[pointer - 1] + var btm = buffer.length < size ? 0 : buffer[pointer === size ? 0 : pointer] + + return buffer.length < resolution ? top : (top - btm) * resolution / buffer.length } - if (reservation === -1) return false +} - var r = self._reservations[index] - if (!r) r = self._reservations[index] = [] - var i = r.indexOf(null) - if (i === -1) i = r.length - r[i] = wire +},{}],145:[function(require,module,exports){ +/* global URL */ - var chunkOffset = piece.chunkOffset(reservation) - var chunkLength = isWebSeed ? piece.chunkLengthRemaining(reservation) : piece.chunkLength(reservation) +var getBlob = require('stream-to-blob') - wire.request(index, chunkOffset, chunkLength, function onChunk (err, chunk) { - if (self.destroyed) return +module.exports = function getBlobURL (stream, mimeType, cb) { + if (typeof mimeType === 'function') return getBlobURL(stream, null, mimeType) + getBlob(stream, mimeType, function (err, blob) { + if (err) return cb(err) + var url = URL.createObjectURL(blob) + cb(null, url) + }) +} - // TODO: what is this for? - if (!self.ready) return self.once('ready', function () { onChunk(err, chunk) }) +},{"stream-to-blob":146}],146:[function(require,module,exports){ +/* global Blob */ - if (r[i] === wire) r[i] = null +var once = require('once') - if (piece !== self.pieces[index]) return onUpdateTick() +module.exports = function getBlob (stream, mimeType, cb) { + if (typeof mimeType === 'function') return getBlob(stream, null, mimeType) + cb = once(cb) + var chunks = [] + stream + .on('data', function (chunk) { + chunks.push(chunk) + }) + .on('end', function () { + var blob = mimeType + ? new Blob(chunks, { type: mimeType }) + : new Blob(chunks) + cb(null, blob) + }) + .on('error', cb) +} - if (err) { - self._debug( - 'error getting piece %s (offset: %s length: %s) from %s: %s', - index, chunkOffset, chunkLength, wire.remoteAddress + ':' + wire.remotePort, - err.message - ) - isWebSeed ? piece.cancelRemaining(reservation) : piece.cancel(reservation) - onUpdateTick() - return - } +},{"once":115}],147:[function(require,module,exports){ +(function (Buffer){ +var once = require('once') - self._debug( - 'got piece %s (offset: %s length: %s) from %s', - index, chunkOffset, chunkLength, wire.remoteAddress + ':' + wire.remotePort - ) +module.exports = function getBuffer (stream, length, cb) { + cb = once(cb) + var buf = new Buffer(length) + var offset = 0 + stream + .on('data', function (chunk) { + chunk.copy(buf, offset) + offset += chunk.length + }) + .on('end', function () { cb(null, buf) }) + .on('error', cb) +} - if (!piece.set(reservation, chunk, wire)) return onUpdateTick() +}).call(this,require("buffer").Buffer) +},{"buffer":4,"once":115}],148:[function(require,module,exports){ +'use strict'; - var buf = piece.flush() +var Buffer = require('safe-buffer').Buffer; - // TODO: might need to set self.pieces[index] = null here since sha1 is async +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; + } +}; - sha1(buf, function (hash) { - if (self.destroyed) return +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } + } +}; + +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +exports.StringDecoder = StringDecoder; +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} - if (hash === self._hashes[index]) { - if (!self.pieces[index]) return - self._debug('piece verified %s', index) +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; - self.pieces[index] = null - self._reservations[index] = null - if (!self.bitfield.get(index)) { //pear modified - self.emit('piecefromtorrent', index); - } - self.bitfield.set(index, true) - self.store.put(index, buf) - // console.log('self.store.put:'+index); - self.wires.forEach(function (wire) { - wire.have(index) - }) +StringDecoder.prototype.end = utf8End; - // We also check `self.destroyed` since `torrent.destroy()` could have been - // called in the `torrent.on('done')` handler, triggered by `_checkDone()`. - if (self._checkDone() && !self.destroyed) self.discovery.complete() - } else { - self.pieces[index] = new Piece(piece.length) - self.emit('warning', new Error('Piece ' + index + ' failed verification')) - } - onUpdateTick() - }) - }) +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; - function onUpdateTick () { - process.nextTick(function () { self._update() }) +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +}; - return true +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return -1; } -Torrent.prototype._checkDone = function () { - var self = this - if (self.destroyed) return - - // are any new files done? - self.files.forEach(function (file) { - if (file.done) return - for (var i = file._startPiece; i <= file._endPiece; ++i) { - if (!self.bitfield.get(i)) return +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; } - file.done = true - file.emit('done') - self._debug('file done: ' + file.name) - }) + return nb; + } + return 0; +} - // is the torrent done? (if all current selections are satisfied, or there are - // no selections, then torrent is done) - var done = true - for (var i = 0; i < self._selections.length; i++) { - var selection = self._selections[i] - for (var piece = selection.from; piece <= selection.to; piece++) { - if (!self.bitfield.get(piece)) { - done = false - break +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// UTF-8 replacement characters ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'.repeat(p); + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'.repeat(p + 1); + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'.repeat(p + 2); } } - if (!done) break - } - if (!self.done && done) { - self.done = true - self._debug('torrent done: ' + self.infoHash) - self.emit('done') } - self._gcSelections() - - return done } -Torrent.prototype.load = function (streams, cb) { - var self = this - if (self.destroyed) throw new Error('torrent is destroyed') - if (!self.ready) return self.once('ready', function () { self.load(streams, cb) }) +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} - if (!Array.isArray(streams)) streams = [ streams ] - if (!cb) cb = noop +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} - var readable = new MultiStream(streams) - var writable = new ChunkStoreWriteStream(self.store, self.pieceLength) +// For UTF-8, a replacement character for each buffered byte of a (partial) +// character needs to be added to the output. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'.repeat(this.lastTotal - this.lastNeed); + return r; +} - pump(readable, writable, function (err) { - if (err) return cb(err) - self.pieces.forEach(function (piece, index) { - self.pieces[index] = null - self._reservations[index] = null; - if (!self.bitfield.get(index)) { //pear modified - self.emit('piecefromtorrent', index); +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); } - self.bitfield.set(index, true) - }) - self._checkDone() - cb(null) - }) + } + return r; + } + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); } -Torrent.prototype.createServer = function (requestListener) { - if (typeof Server !== 'function') throw new Error('node.js-only method') - if (this.destroyed) throw new Error('torrent is destroyed') - var server = new Server(this, requestListener) - this._servers.push(server) - return server +// For UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); + } + return r; } -Torrent.prototype.pause = function () { - if (this.destroyed) return - this._debug('pause') - this.paused = true +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString('base64', i, buf.length - n); } -Torrent.prototype.resume = function () { - if (this.destroyed) return - this._debug('resume') - this.paused = false - this._drain() +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; } -Torrent.prototype._debug = function () { - var args = [].slice.call(arguments) - args[0] = '[' + this.client._debugId + '] [' + this._debugId + '] ' + args[0] - debug.apply(null, args) +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); } -/** - * Pop a peer off the FIFO queue and connect to it. When _drain() gets called, - * the queue will usually have only one peer in it, except when there are too - * many peers (over `this.maxConns`) in which case they will just sit in the - * queue until another connection closes. - */ -Torrent.prototype._drain = function () { - var self = this - this._debug('_drain numConns %s maxConns %s', self._numConns, self.client.maxConns) - if (typeof net.connect !== 'function' || self.destroyed || self.paused || - self._numConns >= self.client.maxConns) { - return - } - this._debug('drain (%s queued, %s/%s peers)', self._numQueued, self.numPeers, self.client.maxConns) +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; +} +},{"safe-buffer":138}],149:[function(require,module,exports){ +/* +Copyright (c) 2011, Chris Umbel - var peer = self._queue.shift() - if (!peer) return // queue could be empty +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - this._debug('tcp connect attempt to %s', peer.addr) +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. - var parts = addrToIPPort(peer.addr) - var opts = { - host: parts[0], - port: parts[1] - } +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ - var conn = peer.conn = net.connect(opts) +var base32 = require('./thirty-two'); - conn.once('connect', function () { peer.onConnect() }) - conn.once('error', function (err) { peer.destroy(err) }) - peer.startConnectTimeout() +exports.encode = base32.encode; +exports.decode = base32.decode; - // When connection closes, attempt reconnect after timeout (with exponential backoff) - conn.on('close', function () { - if (self.destroyed) return +},{"./thirty-two":150}],150:[function(require,module,exports){ +(function (Buffer){ +/* +Copyright (c) 2011, Chris Umbel - // TODO: If torrent is done, do not try to reconnect after a timeout +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - if (peer.retries >= RECONNECT_WAIT.length) { - self._debug( - 'conn %s closed: will not re-add (max %s attempts)', - peer.addr, RECONNECT_WAIT.length - ) - return - } +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. - var ms = RECONNECT_WAIT[peer.retries] - self._debug( - 'conn %s closed: will re-add to queue in %sms (attempt %s)', - peer.addr, ms, peer.retries + 1 - ) +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +'use strict'; - var reconnectTimeout = setTimeout(function reconnectTimeout () { - var newPeer = self._addPeer(peer.addr) - if (newPeer) newPeer.retries = peer.retries + 1 - }, ms) - if (reconnectTimeout.unref) reconnectTimeout.unref() - }) -} +var charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +var byteTable = [ + 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff +]; -/** - * Returns `true` if string is valid IPv4/6 address. - * @param {string} addr - * @return {boolean} - */ -Torrent.prototype._validAddr = function (addr) { - var parts - try { - parts = addrToIPPort(addr) - } catch (e) { - return false - } - var host = parts[0] - var port = parts[1] - return port > 0 && port < 65535 && - !(host === '127.0.0.1' && port === this.client.torrentPort) +function quintetCount(buff) { + var quintets = Math.floor(buff.length / 5); + return buff.length % 5 === 0 ? quintets: quintets + 1; } -function getBlockPipelineLength (wire, duration) { - return 2 + Math.ceil(duration * wire.downloadSpeed() / Piece.BLOCK_LENGTH) -} +exports.encode = function(plain) { + if(!Buffer.isBuffer(plain)){ + plain = new Buffer(plain); + } + var i = 0; + var j = 0; + var shiftIndex = 0; + var digit = 0; + var encoded = new Buffer(quintetCount(plain) * 8); -function getPiecePipelineLength (wire, duration, pieceLength) { - return 1 + Math.ceil(duration * wire.downloadSpeed() / pieceLength) -} + /* byte by byte isn't as pretty as quintet by quintet but tests a bit + faster. will have to revisit. */ + while(i < plain.length) { + var current = plain[i]; -/** - * Returns a random integer in [0,high) - */ -function randomInt (high) { - return Math.random() * high | 0 -} + if(shiftIndex > 3) { + digit = current & (0xff >> shiftIndex); + shiftIndex = (shiftIndex + 5) % 8; + digit = (digit << shiftIndex) | ((i + 1 < plain.length) ? + plain[i + 1] : 0) >> (8 - shiftIndex); + i++; + } else { + digit = (current >> (8 - (shiftIndex + 5))) & 0x1f; + shiftIndex = (shiftIndex + 5) % 8; + if(shiftIndex === 0) i++; + } -function noop () {} + encoded[j] = charTable.charCodeAt(digit); + j++; + } -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"../package.json":148,"./file":141,"./peer":142,"./rarity-map":143,"./server":158,"_process":170,"addr-to-ip-port":31,"bitfield":37,"chunk-store-stream/write":47,"debug":27,"events":162,"fs":156,"fs-chunk-store":66,"immediate-chunk-store":57,"inherits":58,"multistream":73,"net":158,"os":158,"parse-torrent":77,"path":168,"pump":80,"random-iterate":81,"run-parallel":96,"run-parallel-limit":95,"simple-get":100,"simple-sha1":102,"speedometer":104,"torrent-discovery":112,"torrent-piece":113,"uniq":116,"ut_metadata":118,"ut_pex":158,"xtend":131,"xtend/mutable":132}],145:[function(require,module,exports){ -arguments[4][128][0].apply(exports,arguments) -},{"../package.json":148,"bitfield":37,"bittorrent-protocol":38,"debug":27,"dup":128,"inherits":58,"safe-buffer":98,"simple-get":100,"simple-sha1":102}],146:[function(require,module,exports){ -/** - * 过滤掉不能下载的节点 - */ + for(i = j; i < encoded.length; i++) { + encoded[i] = 0x3d; //'='.charCodeAt(0) + } -module.exports = NodeFilter; + return encoded; +}; -/* - nodesArray: {uri: string type: string capacity: number} - cb: function - range: {start: number end: number} - */ +exports.decode = function(encoded) { + var shiftIndex = 0; + var plainDigit = 0; + var plainChar; + var plainPos = 0; + if(!Buffer.isBuffer(encoded)){ + encoded = new Buffer(encoded); + } + var decoded = new Buffer(Math.ceil(encoded.length * 5 / 8)); + + /* byte by byte isn't as pretty as octet by octet but tests a bit + faster. will have to revisit. */ + for(var i = 0; i < encoded.length; i++) { + if(encoded[i] === 0x3d){ //'=' + break; + } -var debug = require('debug')('pear:node-filter'); + var encodedByte = encoded[i] - 0x30; -function NodeFilter(nodesArray, cb, range) { + if(encodedByte < byteTable.length) { + plainDigit = byteTable[encodedByte]; - // var ipArray = array.unique(); - var doneCount = 0; - var usefulNodes = []; - var fileLength = 0; - if (!range) { - range = { - start: 0, - end: nodesArray.length - } - } else if (range.end > nodesArray.length) { - range.end = nodesArray.length; - } + if(shiftIndex <= 3) { + shiftIndex = (shiftIndex + 5) % 8; - for (var i=range.start;i>> shiftIndex); + decoded[plainPos] = plainChar; + plainPos++; - try { - connectTest(nodesArray[i]); - } catch (e) { - // debug(nodesArray[i].uri + ':' + JSON.stringify(e)) + plainChar = 0xff & (plainDigit << (8 - shiftIndex)); + } + } else { + throw new Error('Invalid input - it is not base32 encoded string'); } } - function connectTest(node) { + return decoded.slice(0, plainPos); +}; + +}).call(this,require("buffer").Buffer) +},{"buffer":4}],151:[function(require,module,exports){ +arguments[4][36][0].apply(exports,arguments) +},{"buffer":4,"dup":36}],152:[function(require,module,exports){ +(function (process){ +module.exports = Discovery - var xhr = new XMLHttpRequest; - xhr.timeout = 1000; - xhr.open('head', node.uri); - xhr.onload = function () { - doneCount ++; - if (this.status >= 200 && this.status<300) { - usefulNodes.push(node); - fileLength = xhr.getResponseHeader('content-length'); - } - chenkDone(); - }; - xhr.ontimeout = function() { - doneCount ++; - debug(node.uri + ' timeout'); - chenkDone(); - }; - xhr.onerror = function() { - doneCount ++; - chenkDone(); - }; - xhr.send(); +var debug = require('debug')('torrent-discovery') +var DHT = require('bittorrent-dht/client') // empty object in browser +var EventEmitter = require('events').EventEmitter +var extend = require('xtend') +var inherits = require('inherits') +var parallel = require('run-parallel') +var Tracker = require('bittorrent-tracker/client') - }; +inherits(Discovery, EventEmitter) + +function Discovery (opts) { + var self = this + if (!(self instanceof Discovery)) return new Discovery(opts) + EventEmitter.call(self) + + if (!opts.peerId) throw new Error('Option `peerId` is required') + if (!opts.infoHash) throw new Error('Option `infoHash` is required') + if (!process.browser && !opts.port) throw new Error('Option `port` is required') + + self.peerId = typeof opts.peerId === 'string' + ? opts.peerId + : opts.peerId.toString('hex') + self.infoHash = typeof opts.infoHash === 'string' + ? opts.infoHash + : opts.infoHash.toString('hex') + self._port = opts.port // torrent port + self._userAgent = opts.userAgent // User-Agent header for http requests - function chenkDone() { + self.destroyed = false - // if (doneCount === nodesArray.length) { - // cb(usefulNodes, fileLength); - // } + self._announce = opts.announce || [] + self._intervalMs = opts.intervalMs || (15 * 60 * 1000) + self._trackerOpts = null + self._dhtAnnouncing = false + self._dhtTimeout = false + self._internalDHT = false // is the DHT created internally? - if (doneCount === (range.end-range.start)) { + self._onWarning = function (err) { + self.emit('warning', err) + } + self._onError = function (err) { + self.emit('error', err) + } + self._onDHTPeer = function (peer, infoHash) { + if (infoHash.toString('hex') !== self.infoHash) return + self.emit('peer', peer.host + ':' + peer.port, 'dht') + } + self._onTrackerPeer = function (peer) { + self.emit('peer', peer, 'tracker') + } + self._onTrackerAnnounce = function () { + self.emit('trackerAnnounce') + } - //根据capacity对节点进行排序 - usefulNodes.sort(function (a, b) { //按能力值从大到小排序 - return b.capacity - a.capacity; - }); + if (opts.tracker === false) { + self.tracker = null + } else if (opts.tracker && typeof opts.tracker === 'object') { + self._trackerOpts = extend(opts.tracker) + self.tracker = self._createTracker() + } else { + self.tracker = self._createTracker() + } - for(var i = 0; i < usefulNodes.length; i++) { - debug('node ' + i + ' capacity ' + usefulNodes[i].capacity); - } - debug('length: ' + usefulNodes.filter(function (node) { - return node.capacity >= 5; - }).length); + if (opts.dht === false || typeof DHT !== 'function') { + self.dht = null + } else if (opts.dht && typeof opts.dht.addNode === 'function') { + self.dht = opts.dht + } else if (opts.dht && typeof opts.dht === 'object') { + self.dht = createDHT(opts.dhtPort, opts.dht) + } else { + self.dht = createDHT(opts.dhtPort) + } - cb(usefulNodes, fileLength); - } - } + if (self.dht) { + self.dht.on('peer', self._onDHTPeer) + self._dhtAnnounce() + } -}; + function createDHT (port, opts) { + var dht = new DHT(opts) + dht.on('warning', self._onWarning) + dht.on('error', self._onError) + dht.listen(port) + self._internalDHT = true + return dht + } +} -},{"debug":27}],147:[function(require,module,exports){ -/** - * 节点调度算法的默认实现 - */ +Discovery.prototype.updatePort = function (port) { + var self = this + if (port === self._port) return + self._port = port -/* - nodesProvider: Array //未排序的节点数组 - info:{ // 窗口滑动过程中的信息 - windowLength: number, //滑动窗口长度 - windowOffset: number, //滑动窗口的起始索引 - interval2BufPos: number, //当前播放点距离缓冲前沿的时间,单位秒 - slideInterval: number //当前播放点距离缓冲前沿多少秒时滑动窗口 - } - */ -var debug = require('debug')('pear:node-scheduler'); + if (self.dht) self._dhtAnnounce() -module.exports = { + if (self.tracker) { + self.tracker.stop() + self.tracker.destroy(function () { + self.tracker = self._createTracker() + }) + } +} - IdleFirst: function (nodesProvider, info) { +Discovery.prototype.complete = function (opts) { + if (this.tracker) { + this.tracker.complete(opts) + } +} - var idles = nodesProvider.filter(function (item) { //空闲节点 - return item.downloading === false; - }); +Discovery.prototype.destroy = function (cb) { + var self = this + if (self.destroyed) return + self.destroyed = true - // for (var i=0;i info.windowLength) { - ret = ret.filter(function (item) { - return item.type !== 0 - }) - } + // cleanup + self.dht = null + self.tracker = null + self._announce = null +} +Discovery.prototype._createTracker = function () { + var opts = extend(this._trackerOpts, { + infoHash: this.infoHash, + announce: this._announce, + peerId: this.peerId, + port: this._port, + userAgent: this._userAgent + }) + var tracker = new Tracker(opts) + tracker.on('warning', this._onWarning) + tracker.on('error', this._onError) + tracker.on('peer', this._onTrackerPeer) + tracker.on('update', this._onTrackerAnnounce) + tracker.setInterval(this._intervalMs) + tracker.start() + return tracker +} - return ret; - }, +Discovery.prototype._dhtAnnounce = function () { + var self = this + if (self._dhtAnnouncing) return + debug('dht announce') - WebRTCFirst: function (nodesProvider, info) { - - var idles = nodesProvider.filter(function (item) { //datachannel优先级 > node > server - return item.downloading === false; - }).sort(function (a, b) { - return b.type - a.type; - }); - - var busys = nodesProvider.filter(function (item) { - return item.downloading === true && item.queue.length <= 1; - }).sort(function (a, b) { - return a.queue.length - b.queue.length; - }); - - var ret = idles.concat(busys); - // for (var i=0;i info.windowLength) { - ret = ret.filter(function (item) { - return item.type !== 0 - }) - } + self._dhtAnnouncing = true + clearTimeout(self._dhtTimeout) - return ret; - }, + self.dht.announce(self.infoHash, self._port, function (err) { + self._dhtAnnouncing = false + debug('dht announce complete') - CloudFirst: function (nodesProvider, info) { - - var idles = nodesProvider.filter(function (item) { //datachannel优先级 < node < server - return item.downloading === false; - }).sort(function (a, b) { - return a.type - b.type; - }); - - var busys = nodesProvider.filter(function (item) { - return item.downloading === true && item.queue.length <= 1; - }).sort(function (a, b) { - return a.queue.length - b.queue.length; - }); - - var ret = idles.concat(busys); - // for (var i=0;i info.windowLength) { - ret = ret.filter(function (item) { - return item.type !== 0 - }) - } + if (err) self.emit('warning', err) + self.emit('dhtAnnounce') - return ret; + if (!self.destroyed) { + self._dhtTimeout = setTimeout(function () { + self._dhtAnnounce() + }, getRandomTimeout()) + if (self._dhtTimeout.unref) self._dhtTimeout.unref() } + }) -}; - -},{"debug":27}],148:[function(require,module,exports){ -module.exports={ - "name": "webtorrent", - "description": "Streaming torrent client", - "version": "0.98.19", - "author": { - "name": "WebTorrent, LLC", - "email": "feross@webtorrent.io", - "url": "https://webtorrent.io" - }, - "browser": { - "./lib/server.js": false, - "./lib/tcp-pool.js": false, - "bittorrent-dht/client": false, - "fs-chunk-store": "memory-chunk-store", - "load-ip-set": false, - "net": false, - "os": false, - "ut_pex": false - }, - "browserify": { - "transform": [ - "package-json-versionify" - ] - }, - "bugs": { - "url": "https://github.com/webtorrent/webtorrent/issues" - }, - "dependencies": { - "addr-to-ip-port": "^1.4.2", - "bitfield": "^1.1.2", - "bittorrent-dht": "^7.2.2", - "bittorrent-protocol": "^2.1.5", - "chunk-store-stream": "^2.0.2", - "create-torrent": "^3.24.5", - "debug": "^2.2.0", - "end-of-stream": "^1.1.0", - "fs-chunk-store": "^1.6.2", - "immediate-chunk-store": "^1.0.8", - "inherits": "^2.0.1", - "load-ip-set": "^1.2.7", - "memory-chunk-store": "^1.2.0", - "mime": "^1.3.4", - "multistream": "^2.0.5", - "package-json-versionify": "^1.0.2", - "parse-torrent": "^5.8.0", - "pump": "^1.0.1", - "random-iterate": "^1.0.1", - "randombytes": "^2.0.3", - "range-parser": "^1.2.0", - "readable-stream": "^2.1.4", - "render-media": "^2.8.0", - "run-parallel": "^1.1.6", - "run-parallel-limit": "^1.0.3", - "safe-buffer": "^5.0.1", - "simple-concat": "^1.0.0", - "simple-get": "^2.2.1", - "simple-peer": "^8.0.0", - "simple-sha1": "^2.0.8", - "speedometer": "^1.0.0", - "stream-to-blob": "^1.0.0", - "stream-to-blob-url": "^2.1.0", - "stream-with-known-length-to-buffer": "^1.0.0", - "torrent-discovery": "^8.1.0", - "torrent-piece": "^1.1.0", - "uniq": "^1.0.1", - "unordered-array-remove": "^1.0.2", - "ut_metadata": "^3.0.8", - "ut_pex": "^1.1.1", - "xtend": "^4.0.1", - "zero-fill": "^2.2.3" - }, - "devDependencies": { - "babili": "^0.1.4", - "bittorrent-tracker": "^9.0.0", - "brfs": "^1.4.3", - "browserify": "^14.0.0", - "cross-spawn": "^5.0.1", - "electron-prebuilt": "^0.37.8", - "finalhandler": "^1.0.0", - "network-address": "^1.1.0", - "run-series": "^1.1.4", - "serve-static": "^1.11.1", - "standard": "*", - "tape": "^4.6.0", - "webtorrent-fixtures": "^1.5.0", - "zuul": "^3.10.1" - }, - "engines": { - "node": ">=4" - }, - "homepage": "https://webtorrent.io", - "keywords": [ - "bittorrent", - "bittorrent client", - "download", - "mad science", - "p2p", - "peer-to-peer", - "peers", - "streaming", - "swarm", - "torrent", - "web torrent", - "webrtc", - "webrtc data", - "webtorrent" - ], - "license": "MIT", - "main": "index.js", - "repository": { - "type": "git", - "url": "git://github.com/webtorrent/webtorrent.git" - }, - "scripts": { - "build": "browserify -s WebTorrent -e ./ | babili > webtorrent.min.js", - "build-debug": "browserify -s WebTorrent -e ./ > webtorrent.debug.js", - "size": "npm run build && cat webtorrent.min.js | gzip | wc -c", - "test": "standard && node ./bin/test.js", - "test-browser": "zuul -- test/*.js test/browser/*.js", - "test-browser-headless": "zuul --electron -- test/*.js test/browser/*.js", - "test-browser-local": "zuul --local -- test/*.js test/browser/*.js", - "test-node": "tape test/*.js test/node/*.js", - "update-authors": "./bin/update-authors.sh" + // Returns timeout interval, with some random jitter + function getRandomTimeout () { + return self._intervalMs + Math.floor(Math.random() * self._intervalMs / 5) } } -},{}],149:[function(require,module,exports){ -(function (process,global){ -/* global FileList */ - -module.exports = WebTorrent - -var Buffer = require('safe-buffer').Buffer -var concat = require('simple-concat') -var createTorrent = require('create-torrent') -var debug = require('debug')('webtorrent') -var DHT = require('bittorrent-dht/client') // browser exclude -var EventEmitter = require('events').EventEmitter -var extend = require('xtend') -var inherits = require('inherits') -var loadIPSet = require('load-ip-set') // browser exclude -var parallel = require('run-parallel') -var parseTorrent = require('parse-torrent') -var path = require('path') -var Peer = require('simple-peer') -var randombytes = require('randombytes') -var speedometer = require('speedometer') -var zeroFill = require('zero-fill') +}).call(this,require('_process')) +},{"_process":15,"bittorrent-dht/client":3,"bittorrent-tracker/client":75,"debug":87,"events":7,"inherits":96,"run-parallel":136,"xtend":171}],153:[function(require,module,exports){ +(function (Buffer){ +module.exports = Piece -var TCPPool = require('./lib/tcp-pool') // browser exclude -var Torrent = require('./lib/torrent') +var BLOCK_LENGTH = 1 << 14 -/** - * WebTorrent version. - */ -var VERSION = require('./package.json').version +function Piece (length) { + if (!(this instanceof Piece)) return new Piece(length) -/** - * Version number in Azureus-style. Generated from major and minor semver version. - * For example: - * '0.16.1' -> '0016' - * '1.2.5' -> '0102' - */ -var VERSION_STR = VERSION.match(/([0-9]+)/g) - .slice(0, 2) - .map(function (v) { return zeroFill(2, v) }) - .join('') + this.length = length + this.missing = length + this.sources = null -/** - * Version prefix string (used in peer ID). WebTorrent uses the Azureus-style - * encoding: '-', two characters for client id ('WW'), four ascii digits for version - * number, '-', followed by random numbers. - * For example: - * '-WW0102-'... - */ -var VERSION_PREFIX = '-WW' + VERSION_STR + '-' + this._chunks = Math.ceil(length / BLOCK_LENGTH) + this._remainder = (length % BLOCK_LENGTH) || BLOCK_LENGTH + this._buffered = 0 + this._buffer = null + this._cancellations = null + this._reservations = 0 + this._flushed = false +} -inherits(WebTorrent, EventEmitter) +Piece.BLOCK_LENGTH = BLOCK_LENGTH -/** - * WebTorrent Client - * @param {Object=} opts - */ -function WebTorrent (opts) { - var self = this - if (!(self instanceof WebTorrent)) return new WebTorrent(opts) - EventEmitter.call(self) +Piece.prototype.chunkLength = function (i) { + return i === this._chunks - 1 ? this._remainder : BLOCK_LENGTH +} - if (!opts) opts = {} +Piece.prototype.chunkLengthRemaining = function (i) { + return this.length - (i * BLOCK_LENGTH) +} - if (typeof opts.peerId === 'string') { - self.peerId = opts.peerId - } else if (Buffer.isBuffer(opts.peerId)) { - self.peerId = opts.peerId.toString('hex') - } else { - self.peerId = Buffer.from(VERSION_PREFIX + randombytes(9).toString('base64')).toString('hex') - } - self.peerIdBuffer = Buffer.from(self.peerId, 'hex') +Piece.prototype.chunkOffset = function (i) { + return i * BLOCK_LENGTH +} - if (typeof opts.nodeId === 'string') { - self.nodeId = opts.nodeId - } else if (Buffer.isBuffer(opts.nodeId)) { - self.nodeId = opts.nodeId.toString('hex') - } else { - self.nodeId = randombytes(20).toString('hex') +Piece.prototype.reserve = function () { + if (!this.init()) return -1 + if (this._cancellations.length) return this._cancellations.pop() + if (this._reservations < this._chunks) return this._reservations++ + return -1 +} + +Piece.prototype.reserveRemaining = function () { + if (!this.init()) return -1 + if (this._reservations < this._chunks) { + var min = this._reservations + this._reservations = this._chunks + return min } - self.nodeIdBuffer = Buffer.from(self.nodeId, 'hex') + return -1 +} - self._debugId = self.peerId.toString('hex').substring(0, 7) +Piece.prototype.cancel = function (i) { + if (!this.init()) return + this._cancellations.push(i) +} - self.destroyed = false - self.listening = false - self.torrentPort = opts.torrentPort || 0 - self.dhtPort = opts.dhtPort || 0 - self.tracker = opts.tracker !== undefined ? opts.tracker : {} - self.torrents = [] - self.maxConns = Number(opts.maxConns) || 55 +Piece.prototype.cancelRemaining = function (i) { + if (!this.init()) return + this._reservations = i +} - self._debug( - 'new webtorrent (peerId %s, nodeId %s, port %s)', - self.peerId, self.nodeId, self.torrentPort - ) +Piece.prototype.get = function (i) { + if (!this.init()) return null + return this._buffer[i] +} - if (self.tracker) { - if (typeof self.tracker !== 'object') self.tracker = {} - if (opts.rtcConfig) { - // TODO: remove in v1 - console.warn('WebTorrent: opts.rtcConfig is deprecated. Use opts.tracker.rtcConfig instead') - self.tracker.rtcConfig = opts.rtcConfig - } - if (opts.wrtc) { - // TODO: remove in v1 - console.warn('WebTorrent: opts.wrtc is deprecated. Use opts.tracker.wrtc instead') - self.tracker.wrtc = opts.wrtc - } - if (global.WRTC && !self.tracker.wrtc) { - self.tracker.wrtc = global.WRTC +Piece.prototype.set = function (i, data, source) { + if (!this.init()) return false + var len = data.length + var blocks = Math.ceil(len / BLOCK_LENGTH) + for (var j = 0; j < blocks; j++) { + if (!this._buffer[i + j]) { + var offset = j * BLOCK_LENGTH + var splitData = data.slice(offset, offset + BLOCK_LENGTH) + this._buffered++ + this._buffer[i + j] = splitData + this.missing -= splitData.length + if (this.sources.indexOf(source) === -1) { + this.sources.push(source) + } } } + return this._buffered === this._chunks +} - if (typeof TCPPool === 'function') { - self._tcpPool = new TCPPool(self) +Piece.prototype.flush = function () { + if (!this._buffer || this._chunks !== this._buffered) return null + var buffer = Buffer.concat(this._buffer, this.length) + this._buffer = null + this._cancellations = null + this.sources = null + this._flushed = true + return buffer +} + +Piece.prototype.init = function () { + if (this._flushed) return false + if (this._buffer) return true + this._buffer = new Array(this._chunks) + this._cancellations = [] + this.sources = [] + return true +} + +}).call(this,require("buffer").Buffer) +},{"buffer":4}],154:[function(require,module,exports){ +(function (Buffer){ +/** + * Convert a typed array to a Buffer without a copy + * + * Author: Feross Aboukhadijeh + * License: MIT + * + * `npm install typedarray-to-buffer` + */ + +var isTypedArray = require('is-typedarray').strict + +module.exports = function typedarrayToBuffer (arr) { + if (isTypedArray(arr)) { + // To avoid a copy, use the typed array's underlying ArrayBuffer to back new Buffer + var buf = new Buffer(arr.buffer) + if (arr.byteLength !== arr.buffer.byteLength) { + // Respect the "view", i.e. byteOffset and byteLength, without doing a copy + buf = buf.slice(arr.byteOffset, arr.byteOffset + arr.byteLength) + } + return buf } else { - process.nextTick(function () { - self._onListening() - }) + // Pass through all other types to the `Buffer` constructor + return new Buffer(arr) } +} - // stats - self._downloadSpeed = speedometer() - self._uploadSpeed = speedometer() +}).call(this,require("buffer").Buffer) +},{"buffer":4,"is-typedarray":100}],155:[function(require,module,exports){ +(function (Buffer){ +var UINT_32_MAX = 0xffffffff - if (opts.dht !== false && typeof DHT === 'function' /* browser exclude */) { - // use a single DHT instance for all torrents, so the routing table can be reused - self.dht = new DHT(extend({ nodeId: self.nodeId }, opts.dht)) +exports.encodingLength = function () { + return 8 +} - self.dht.once('error', function (err) { - self._destroy(err) - }) +exports.encode = function (num, buf, offset) { + if (!buf) buf = new Buffer(8) + if (!offset) offset = 0 - self.dht.once('listening', function () { - var address = self.dht.address() - if (address) self.dhtPort = address.port - }) + var top = Math.floor(num / UINT_32_MAX) + var rem = num - top * UINT_32_MAX - // Ignore warning when there are > 10 torrents in the client - self.dht.setMaxListeners(0) + buf.writeUInt32BE(top, offset) + buf.writeUInt32BE(rem, offset + 4) + return buf +} - self.dht.listen(self.dhtPort) - } else { - self.dht = false - } +exports.decode = function (buf, offset) { + if (!offset) offset = 0 - // Enable or disable BEP19 (Web Seeds). Enabled by default: - self.enableWebSeeds = opts.webSeeds !== false + if (!buf) buf = new Buffer(4) + if (!offset) offset = 0 - if (typeof loadIPSet === 'function' && opts.blocklist != null) { - loadIPSet(opts.blocklist, { - headers: { - 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' - } - }, function (err, ipSet) { - if (err) return self.error('Failed to load blocklist: ' + err.message) - self.blocked = ipSet - ready() - }) - } else { - process.nextTick(ready) - } + var top = buf.readUInt32BE(offset) + var rem = buf.readUInt32BE(offset + 4) - function ready () { - if (self.destroyed) return - self.ready = true - self.emit('ready') - } + return top * UINT_32_MAX + rem } -WebTorrent.WEBRTC_SUPPORT = Peer.WEBRTC_SUPPORT +exports.encode.bytes = 8 +exports.decode.bytes = 8 -Object.defineProperty(WebTorrent.prototype, 'downloadSpeed', { - get: function () { return this._downloadSpeed() } -}) +}).call(this,require("buffer").Buffer) +},{"buffer":4}],156:[function(require,module,exports){ +"use strict" -Object.defineProperty(WebTorrent.prototype, 'uploadSpeed', { - get: function () { return this._uploadSpeed() } -}) +function unique_pred(list, compare) { + var ptr = 1 + , len = list.length + , a=list[0], b=list[0] + for(var i=1; i= arr.length || i < 0) return + var last = arr.pop() + if (i < arr.length) { + var tmp = arr[i] + arr[i] = last + return tmp } - return null + return last } -// TODO: remove in v1 -WebTorrent.prototype.download = function (torrentId, opts, ontorrent) { - console.warn('WebTorrent: client.download() is deprecated. Use client.add() instead') - return this.add(torrentId, opts, ontorrent) -} +},{}],158:[function(require,module,exports){ +var bencode = require('bencode') +var BitField = require('bitfield') +var Buffer = require('safe-buffer').Buffer +var debug = require('debug')('ut_metadata') +var EventEmitter = require('events').EventEmitter +var inherits = require('inherits') +var sha1 = require('simple-sha1') -/** - * Start downloading a new torrent. Aliased as `client.download`. - * @param {string|Buffer|Object} torrentId - * @param {Object} opts torrent-specific options - * @param {function=} ontorrent called when the torrent is ready (has metadata) - */ -WebTorrent.prototype.add = function (torrentId, opts, ontorrent) { - var self = this - if (self.destroyed) throw new Error('client is destroyed') - if (typeof opts === 'function') return self.add(torrentId, null, opts) +var MAX_METADATA_SIZE = 10000000 // 10MB +var BITFIELD_GROW = 1000 +var PIECE_LENGTH = 16 * 1024 - self._debug('add') - opts = opts ? extend(opts) : {} +module.exports = function (metadata) { + inherits(utMetadata, EventEmitter) - var torrent = new Torrent(torrentId, self, opts) - self.torrents.push(torrent) + function utMetadata (wire) { + EventEmitter.call(this) - torrent.once('_infoHash', onInfoHash) - torrent.once('ready', onReady) - torrent.once('close', onClose) - // torrent.on('piecefromtorrent', function (index) { - // self.emit('piecefromtorrent', index); - // }) + this._wire = wire - function onInfoHash () { - if (self.destroyed) return - for (var i = 0, len = self.torrents.length; i < len; i++) { - var t = self.torrents[i] - if (t.infoHash === torrent.infoHash && t !== torrent) { - torrent._destroy(new Error('Cannot add duplicate torrent ' + torrent.infoHash)) - return - } + this._metadataComplete = false + this._metadataSize = null + this._remainingRejects = null // how many reject messages to tolerate before quitting + this._fetching = false + + // The largest .torrent file that I know of is ~1-2MB, which is ~100 pieces. + // Therefore, cap the bitfield to 10x that (1000 pieces) so a malicious peer can't + // make it grow to fill all memory. + this._bitfield = new BitField(0, { grow: BITFIELD_GROW }) + + if (Buffer.isBuffer(metadata)) { + this.setMetadata(metadata) } } - function onReady () { - if (self.destroyed) return - if (typeof ontorrent === 'function') ontorrent(torrent) - self.emit('torrent', torrent) + // Name of the bittorrent-protocol extension + utMetadata.prototype.name = 'ut_metadata' + + utMetadata.prototype.onHandshake = function (infoHash, peerId, extensions) { + this._infoHash = infoHash } - function onClose () { - torrent.removeListener('_infoHash', onInfoHash) - torrent.removeListener('ready', onReady) - torrent.removeListener('close', onClose) + utMetadata.prototype.onExtendedHandshake = function (handshake) { + if (!handshake.m || !handshake.m.ut_metadata) { + return this.emit('warning', new Error('Peer does not support ut_metadata')) + } + if (!handshake.metadata_size) { + return this.emit('warning', new Error('Peer does not have metadata')) + } + if (typeof handshake.metadata_size !== 'number' || + MAX_METADATA_SIZE < handshake.metadata_size || + handshake.metadata_size <= 0) { + return this.emit('warning', new Error('Peer gave invalid metadata size')) + } + + this._metadataSize = handshake.metadata_size + this._numPieces = Math.ceil(this._metadataSize / PIECE_LENGTH) + this._remainingRejects = this._numPieces * 2 + + if (this._fetching) { + this._requestPieces() + } } - return torrent -} + utMetadata.prototype.onMessage = function (buf) { + var dict, trailer + try { + var str = buf.toString() + var trailerIndex = str.indexOf('ee') + 2 + dict = bencode.decode(str.substring(0, trailerIndex)) + trailer = buf.slice(trailerIndex) + } catch (err) { + // drop invalid messages + return + } -/** - * Start seeding a new file/folder. - * @param {string|File|FileList|Buffer|Array.} input - * @param {Object=} opts - * @param {function=} onseed called when torrent is seeding - */ -WebTorrent.prototype.seed = function (input, opts, onseed) { - var self = this - if (self.destroyed) throw new Error('client is destroyed') - if (typeof opts === 'function') return self.seed(input, null, opts) + switch (dict.msg_type) { + case 0: + // ut_metadata request (from peer) + // example: { 'msg_type': 0, 'piece': 0 } + this._onRequest(dict.piece) + break + case 1: + // ut_metadata data (in response to our request) + // example: { 'msg_type': 1, 'piece': 0, 'total_size': 3425 } + this._onData(dict.piece, trailer, dict.total_size) + break + case 2: + // ut_metadata reject (peer doesn't have piece we requested) + // { 'msg_type': 2, 'piece': 0 } + this._onReject(dict.piece) + break + } + } - self._debug('seed') - opts = opts ? extend(opts) : {} + /** + * Ask the peer to send metadata. + * @public + */ + utMetadata.prototype.fetch = function () { + if (this._metadataComplete) { + return + } + this._fetching = true + if (this._metadataSize) { + this._requestPieces() + } + } - // When seeding from fs path, initialize store from that path to avoid a copy - if (typeof input === 'string') opts.path = path.dirname(input) - if (!opts.createdBy) opts.createdBy = 'WebTorrent/' + VERSION_STR + /** + * Stop asking the peer to send metadata. + * @public + */ + utMetadata.prototype.cancel = function () { + this._fetching = false + } - var torrent = self.add(null, opts, onTorrent) - var streams + utMetadata.prototype.setMetadata = function (metadata) { + if (this._metadataComplete) return true + debug('set metadata') - if (isFileList(input)) input = Array.prototype.slice.call(input) - if (!Array.isArray(input)) input = [ input ] + // if full torrent dictionary was passed in, pull out just `info` key + try { + var info = bencode.decode(metadata).info + if (info) { + metadata = bencode.encode(info) + } + } catch (err) {} - parallel(input.map(function (item) { - return function (cb) { - if (isReadable(item)) concat(item, cb) - else cb(null, item) + // check hash + if (this._infoHash && this._infoHash !== sha1.sync(metadata)) { + return false } - }), function (err, input) { - if (self.destroyed) return - if (err) return torrent._destroy(err) - createTorrent.parseInput(input, opts, function (err, files) { - if (self.destroyed) return - if (err) return torrent._destroy(err) + this.cancel() - streams = files.map(function (file) { - return file.getStream - }) + this.metadata = metadata + this._metadataComplete = true + this._metadataSize = this.metadata.length + this._wire.extendedHandshake.metadata_size = this._metadataSize - createTorrent(input, opts, function (err, torrentBuf) { - if (self.destroyed) return - if (err) return torrent._destroy(err) + this.emit('metadata', bencode.encode({ info: bencode.decode(this.metadata) })) - var existingTorrent = self.get(torrentBuf) - if (existingTorrent) { - torrent._destroy(new Error('Cannot add duplicate torrent ' + existingTorrent.infoHash)) - } else { - torrent._onTorrentId(torrentBuf) - } - }) - }) - }) + return true + } - function onTorrent (torrent) { - var tasks = [ - function (cb) { - torrent.load(streams, cb) - } - ] - if (self.dht) { - tasks.push(function (cb) { - torrent.once('dhtAnnounce', cb) - }) + utMetadata.prototype._send = function (dict, trailer) { + var buf = bencode.encode(dict) + if (Buffer.isBuffer(trailer)) { + buf = Buffer.concat([buf, trailer]) } - parallel(tasks, function (err) { - if (self.destroyed) return - if (err) return torrent._destroy(err) - _onseed(torrent) - }) + this._wire.extended('ut_metadata', buf) } - function _onseed (torrent) { - self._debug('on seed') - if (typeof onseed === 'function') onseed(torrent) - torrent.emit('seed') - self.emit('seed', torrent) + utMetadata.prototype._request = function (piece) { + this._send({ msg_type: 0, piece: piece }) } - return torrent -} + utMetadata.prototype._data = function (piece, buf, totalSize) { + var msg = { msg_type: 1, piece: piece } + if (typeof totalSize === 'number') { + msg.total_size = totalSize + } + this._send(msg, buf) + } -/** - * Remove a torrent from the client. - * @param {string|Buffer|Torrent} torrentId - * @param {function} cb - */ -WebTorrent.prototype.remove = function (torrentId, cb) { - this._debug('remove') - var torrent = this.get(torrentId) - if (!torrent) throw new Error('No torrent with id ' + torrentId) - this._remove(torrentId, cb) -} + utMetadata.prototype._reject = function (piece) { + this._send({ msg_type: 2, piece: piece }) + } -WebTorrent.prototype._remove = function (torrentId, cb) { - var torrent = this.get(torrentId) - if (!torrent) return - this.torrents.splice(this.torrents.indexOf(torrent), 1) - torrent.destroy(cb) -} + utMetadata.prototype._onRequest = function (piece) { + if (!this._metadataComplete) { + this._reject(piece) + return + } + var start = piece * PIECE_LENGTH + var end = start + PIECE_LENGTH + if (end > this._metadataSize) { + end = this._metadataSize + } + var buf = this.metadata.slice(start, end) + this._data(piece, buf, this._metadataSize) + } -WebTorrent.prototype.address = function () { - if (!this.listening) return null - return this._tcpPool - ? this._tcpPool.server.address() - : { address: '0.0.0.0', family: 'IPv4', port: 0 } -} + utMetadata.prototype._onData = function (piece, buf, totalSize) { + if (buf.length > PIECE_LENGTH) { + return + } + buf.copy(this.metadata, piece * PIECE_LENGTH) + this._bitfield.set(piece) + this._checkDone() + } -/** - * Destroy the client, including all torrents and connections to peers. - * @param {function} cb - */ -WebTorrent.prototype.destroy = function (cb) { - if (this.destroyed) throw new Error('client already destroyed') - this._destroy(null, cb) -} + utMetadata.prototype._onReject = function (piece) { + if (this._remainingRejects > 0 && this._fetching) { + // If we haven't been rejected too much, then try to request the piece again + this._request(piece) + this._remainingRejects -= 1 + } else { + this.emit('warning', new Error('Peer sent "reject" too much')) + } + } -WebTorrent.prototype._destroy = function (err, cb) { - var self = this - self._debug('client destroy') - self.destroyed = true + utMetadata.prototype._requestPieces = function () { + this.metadata = Buffer.alloc(this._metadataSize) + for (var piece = 0; piece < this._numPieces; piece++) { + this._request(piece) + } + } - var tasks = self.torrents.map(function (torrent) { - return function (cb) { - torrent.destroy(cb) + utMetadata.prototype._checkDone = function () { + var done = true + for (var piece = 0; piece < this._numPieces; piece++) { + if (!this._bitfield.get(piece)) { + done = false + break + } } - }) + if (!done) return - if (self._tcpPool) { - tasks.push(function (cb) { - self._tcpPool.destroy(cb) - }) + // attempt to set metadata -- may fail sha1 check + var success = this.setMetadata(this.metadata) + + if (!success) { + this._failedMetadata() + } } - if (self.dht) { - tasks.push(function (cb) { - self.dht.destroy(cb) - }) + utMetadata.prototype._failedMetadata = function () { + // reset bitfield & try again + this._bitfield = new BitField(0, { grow: BITFIELD_GROW }) + this._remainingRejects -= this._numPieces + if (this._remainingRejects > 0) { + this._requestPieces() + } else { + this.emit('warning', new Error('Peer sent invalid metadata')) + } } - parallel(tasks, cb) + return utMetadata +} - if (err) self.emit('error', err) +},{"bencode":71,"bitfield":73,"debug":87,"events":7,"inherits":96,"safe-buffer":138,"simple-sha1":142}],159:[function(require,module,exports){ +arguments[4][39][0].apply(exports,arguments) +},{"dup":39}],160:[function(require,module,exports){ +(function (Buffer){ +var bs = require('binary-search') +var EventEmitter = require('events').EventEmitter +var inherits = require('inherits') +var mp4 = require('mp4-stream') +var Box = require('mp4-box-encoding') +var RangeSliceStream = require('range-slice-stream') - self.torrents = [] - self._tcpPool = null - self.dht = null +module.exports = MP4Remuxer + +function MP4Remuxer (file) { + var self = this + EventEmitter.call(self) + self._tracks = [] + self._fragmentSequence = 1 + self._file = file + self._decoder = null + self._findMoov(0) } -WebTorrent.prototype._onListening = function () { - this._debug('listening') - this.listening = true +inherits(MP4Remuxer, EventEmitter) - if (this._tcpPool) { - // Sometimes server.address() returns `null` in Docker. - var address = this._tcpPool.server.address() - if (address) this.torrentPort = address.port - } +MP4Remuxer.prototype._findMoov = function (offset) { + var self = this - this.emit('listening') -} + if (self._decoder) { + self._decoder.destroy() + } -WebTorrent.prototype._debug = function () { - var args = [].slice.call(arguments) - args[0] = '[' + this._debugId + '] ' + args[0] - debug.apply(null, args) -} + self._decoder = mp4.decode() + var fileStream = self._file.createReadStream({ + start: offset + }) + fileStream.pipe(self._decoder) -/** - * Check if `obj` is a node Readable stream - * @param {*} obj - * @return {boolean} - */ -function isReadable (obj) { - return typeof obj === 'object' && obj != null && typeof obj.pipe === 'function' + self._decoder.once('box', function (headers) { + if (headers.type === 'moov') { + self._decoder.decode(function (moov) { + fileStream.destroy() + try { + self._processMoov(moov) + } catch (err) { + err.message = 'Cannot parse mp4 file: ' + err.message + self.emit('error', err) + } + }) + } else { + fileStream.destroy() + self._findMoov(offset + headers.length) + } + }) } -/** - * Check if `obj` is a W3C `FileList` object - * @param {*} obj - * @return {boolean} - */ -function isFileList (obj) { - return typeof FileList !== 'undefined' && obj instanceof FileList -} +function RunLengthIndex (entries, countName) { + var self = this + self._entries = entries + self._countName = countName || 'count' + self._index = 0 + self._offset = 0 -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./lib/tcp-pool":158,"./lib/torrent":144,"./package.json":148,"_process":170,"bittorrent-dht/client":158,"create-torrent":50,"debug":27,"events":162,"inherits":58,"load-ip-set":158,"parse-torrent":77,"path":168,"randombytes":82,"run-parallel":96,"safe-buffer":98,"simple-concat":99,"simple-peer":101,"speedometer":104,"xtend":131,"zero-fill":133}],150:[function(require,module,exports){ -module.exports = peerId; + self.value = self._entries[0] +} -function peerId() { +RunLengthIndex.prototype.inc = function () { + var self = this + self._offset++ + if (self._offset >= self._entries[self._index][self._countName]) { + self._index++ + self._offset = 0 + } - //20位 - // var num = Math.floor(Math.random()*10000000000); - // // } - // // console.log(str.toString('base64')); - // var buffer = new ArrayBuffer() - // console.log(num.toString('base64')); + self.value = self._entries[self._index] +} - var len = 9; - var timestamp = parseInt((new Date()).valueOf()/1000); +MP4Remuxer.prototype._processMoov = function (moov) { + var self = this + var traks = moov.traks + self._tracks = [] + self._hasVideo = false + self._hasAudio = false + for (var i = 0; i < traks.length; i++) { + var trak = traks[i] + var stbl = trak.mdia.minf.stbl + var stsdEntry = stbl.stsd.entries[0] + var handlerType = trak.mdia.hdlr.handlerType + var codec + var mime + if (handlerType === 'vide' && stsdEntry.type === 'avc1') { + if (self._hasVideo) { + continue + } + self._hasVideo = true + codec = 'avc1' + if (stsdEntry.avcC) { + codec += '.' + stsdEntry.avcC.mimeCodec + } + mime = 'video/mp4; codecs="' + codec + '"' + } else if (handlerType === 'soun' && stsdEntry.type === 'mp4a') { + if (self._hasAudio) { + continue + } + self._hasAudio = true + codec = 'mp4a' + if (stsdEntry.esds && stsdEntry.esds.mimeCodec) { + codec += '.' + stsdEntry.esds.mimeCodec + } + mime = 'audio/mp4; codecs="' + codec + '"' + } else { + continue + } - var x="0123456789qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"; - var tmp=""; - for(var i=0;i< len;i++) { - tmp += x.charAt(Math.ceil(Math.random()*100000000)%x.length); - } + var samples = [] + var sample = 0 - return timestamp+'-'+tmp; + // Chunk/position data + var sampleInChunk = 0 + var chunk = 0 + var offsetInChunk = 0 + var sampleToChunkIndex = 0 + // Time data + var dts = 0 + var decodingTimeEntry = new RunLengthIndex(stbl.stts.entries) + var presentationOffsetEntry = null + if (stbl.ctts) { + presentationOffsetEntry = new RunLengthIndex(stbl.ctts.entries) + } -} + // Sync table index + var syncSampleIndex = 0 -// console.log(peerId()); + while (true) { + var currChunkEntry = stbl.stsc.entries[sampleToChunkIndex] -},{}],151:[function(require,module,exports){ -/** - * Created by xieting on 2017/12/7. - */ + // Compute size + var size = stbl.stsz.entries[sample] -var debug = require('debug')('pear:reporter'); -var axios = require('axios'); + // Compute time data + var duration = decodingTimeEntry.value.duration + var presentationOffset = presentationOffsetEntry ? presentationOffsetEntry.value.compositionOffset : 0 -axios.defaults.baseURL = 'https://statdapi.webrtc.win:9801'; + // Compute sync + var sync = true + if (stbl.stss) { + sync = stbl.stss.entries[syncSampleIndex] === sample + 1 + } -var abilitiesURL = 'https://api.webrtc.win/v2/customer/stats/nodes/capacity'; + // Create new sample entry + samples.push({ + size: size, + duration: duration, + dts: dts, + presentationOffset: presentationOffset, + sync: sync, + offset: offsetInChunk + stbl.stco.entries[chunk] + }) + // Go to next sample + sample++ + if (sample >= stbl.stsz.entries.length) { + break + } -var totalReportTraffic = 0; + // Move position/chunk + sampleInChunk++ + offsetInChunk += size + if (sampleInChunk >= currChunkEntry.samplesPerChunk) { + // Move to new chunk + sampleInChunk = 0 + offsetInChunk = 0 + chunk++ + // Move sample to chunk box index + var nextChunkEntry = stbl.stsc.entries[sampleToChunkIndex + 1] + if (nextChunkEntry && chunk + 1 >= nextChunkEntry.firstChunk) { + sampleToChunkIndex++ + } + } -function reportTraffic(uuid, fileSize, traffics) { - var temp = 0; - for (var i=0; i= totalReportTraffic + 10485760) { //如果流量增加大于10 - var body = { - uuid: uuid, - size: Number(fileSize), - traffic: traffics - }; - axios({ - method: 'post', - url: '/traffic', - data: body - }) - .then(function(response) { - // debug('reportTraffic response:'+JSON.stringify(response)+' temp:'+temp+' totalReportTraffic:'+totalReportTraffic); - if (response.status == 200) { - totalReportTraffic = temp; - } - }); - } -} + // Move time forward + dts += duration + decodingTimeEntry.inc() + presentationOffsetEntry && presentationOffsetEntry.inc() -function finalyReportTraffic(uuid, fileSize, traffics) { - var body = { - uuid: uuid, - size: Number(fileSize), - traffic: traffics - }; - axios({ - method: 'post', - url: '/traffic', - data: body - }) - .then(function(response) { - if (response.status == 200) { - debug('finalyReportTraffic'); - } - }); -} + // Move sync table index + if (sync) { + syncSampleIndex++ + } + } -function reportAbilities(abilities) { - var benchmark = 0; - for (var mac in abilities) { - benchmark += abilities[mac]; - } - benchmark = benchmark/Object.getOwnPropertyNames(abilities).length; - var normalizeAbilities = {}; - for (var mac in abilities) { - normalizeAbilities[mac] = abilities[mac]/benchmark*5; - console.log('reportAbilities mac:'+mac+' ability:'+normalizeAbilities[mac]); - } - axios({ - method: 'post', - url: abilitiesURL, - data: normalizeAbilities - }) - .then(function(response) { - debug('reportAbilities response:'+JSON.stringify(response)); - }); -} + trak.mdia.mdhd.duration = 0 + trak.tkhd.duration = 0 -module.exports = { + var defaultSampleDescriptionIndex = currChunkEntry.sampleDescriptionId - reportTraffic : reportTraffic, - finalyReportTraffic: finalyReportTraffic, - reportAbilities: reportAbilities -}; + var trackMoov = { + type: 'moov', + mvhd: moov.mvhd, + traks: [{ + tkhd: trak.tkhd, + mdia: { + mdhd: trak.mdia.mdhd, + hdlr: trak.mdia.hdlr, + elng: trak.mdia.elng, + minf: { + vmhd: trak.mdia.minf.vmhd, + smhd: trak.mdia.minf.smhd, + dinf: trak.mdia.minf.dinf, + stbl: { + stsd: stbl.stsd, + stts: empty(), + ctts: empty(), + stsc: empty(), + stsz: empty(), + stco: empty(), + stss: empty() + } + } + } + }], + mvex: { + mehd: { + fragmentDuration: moov.mvhd.duration + }, + trexs: [{ + trackId: trak.tkhd.trackId, + defaultSampleDescriptionIndex: defaultSampleDescriptionIndex, + defaultSampleDuration: 0, + defaultSampleSize: 0, + defaultSampleFlags: 0 + }] + } + } + self._tracks.push({ + trackId: trak.tkhd.trackId, + timeScale: trak.mdia.mdhd.timeScale, + samples: samples, + currSample: null, + currTime: null, + moov: trackMoov, + mime: mime + }) + } + if (self._tracks.length === 0) { + self.emit('error', new Error('no playable tracks')) + return + } + // Must be set last since this is used above + moov.mvhd.duration = 0 -},{"axios":2,"debug":27}],152:[function(require,module,exports){ -/** - * Created by snow on 17-6-22. - */ + self._ftyp = { + type: 'ftyp', + brand: 'iso5', + brandVersion: 0, + compatibleBrands: [ + 'iso5' + ] + } -module.exports = Set; + var ftypBuf = Box.encode(self._ftyp) + var data = self._tracks.map(function (track) { + var moovBuf = Box.encode(track.moov) + return { + mime: track.mime, + init: Buffer.concat([ftypBuf, moovBuf]) + } + }) -function Set() { - this.items = {}; + self.emit('ready', data) } -Set.prototype = { - constructer: Set, - has: function(value) { - return value in this.items; - }, - add: function(value) { - if (!this.has(value)) { - this.items[value] = value; - return true; - } - return false; - }, - remove: function(value) { - if (this.has(value)) { - delete this.items[value]; - return true; - } - return false; - }, - clear: function() { - this.items = {}; - }, - size: function() { - return Object.keys(this.items).length; - }, - values: function() { - return Object.keys(this.items); //values是数组 - }, - union: function(otherSet) { - var unionSet = new Set(); - var values = this.values(); - for (var i = 0; i < values.length; i++) { - unionSet.add(values[i]); - } - values = otherSet.values(); - for (var i = 0; i < values.length; i++) { - unionSet.add(values[i]); - } - return unionSet; - }, - intersection: function(otherSet) { - var intersectionSet = new Set(); - var values = this.values(); - for (var i = 0; i < values.length; i++) { - if (otherSet.has(values[i])) { - intersectionSet.add(values[i]); - } - } - return intersectionSet; - }, - difference: function(otherSet) { - var differenceSet = new Set(); - var values = otherSet.values(); - for (var i = 0; i < values.length; i++) { - if (!this.has(values[i])) { - differenceSet.add(values[i]); - } - } - return differenceSet; - }, - subset: function(otherSet) { - if (this.size() > otherSet.size()) { - return false; - } else { - var values = this.values(); - for (var i = 0; i < values.length; i++) { - if (!otherSet.has(values[i])) { - return false; - } - } - } - return true; - } +function empty () { + return { + version: 0, + flags: 0, + entries: [] + } } -},{}],153:[function(require,module,exports){ -/** - * Created by snow on 17-6-19. - */ - -/* - config:{ - initiator : Boolean //true主动发送offer - stunServers: Array //stun服务器数组 - } - */ - -module.exports = SimpleRTC; +MP4Remuxer.prototype.seek = function (time) { + var self = this + if (!self._tracks) { + throw new Error('Not ready yet; wait for \'ready\' event') + } -var debug = require('debug')('pear:simple-RTC'); -var EventEmitter = require('events').EventEmitter; -var inherits = require('inherits'); + if (self._fileStream) { + self._fileStream.destroy() + self._fileStream = null + } -inherits(SimpleRTC, EventEmitter); + var startOffset = -1 + self._tracks.map(function (track, i) { + // find the keyframe before the time + // stream from there + if (track.outStream) { + track.outStream.destroy() + } + if (track.inStream) { + track.inStream.destroy() + track.inStream = null + } + var outStream = track.outStream = mp4.encode() + var fragment = self._generateFragment(i, time) + if (!fragment) { + return outStream.finalize() + } -function SimpleRTC(config) { - EventEmitter.call(this); + if (startOffset === -1 || fragment.ranges[0].start < startOffset) { + startOffset = fragment.ranges[0].start + } - debug('start simpleRTC'); - var self = this; - self.config = config || {}; + writeFragment(fragment) - var wrtc = getBrowserRTC(); + function writeFragment (frag) { + if (outStream.destroyed) return + outStream.box(frag.moof, function (err) { + if (err) return self.emit('error', err) + if (outStream.destroyed) return + var slicedStream = track.inStream.slice(frag.ranges) + slicedStream.pipe(outStream.mediaData(frag.length, function (err) { + if (err) return self.emit('error', err) + if (outStream.destroyed) return + var nextFrag = self._generateFragment(i) + if (!nextFrag) { + return outStream.finalize() + } + writeFragment(nextFrag) + })) + }) + } + }) - self.RTCPeerConnection = wrtc.RTCPeerConnection; - self.RTCSessionDescription = wrtc.RTCSessionDescription; - self.RTCIceCandidate = wrtc.RTCIceCandidate; - self.dataChannel = null; - self.peerConnection = null; - self.currentoffer = null; - self.sdp = ""; + if (startOffset >= 0) { + var fileStream = self._fileStream = self._file.createReadStream({ + start: startOffset + }) - self.isDataChannelCreating = false; - self.iceServers = [ {urls:'stun:stun.miwifi.com'},{urls:'stun:stun.ekiga.net'},{urls:'stun:stun.ideasip.com'}]; - self.pc_config = { - iceServers: self.iceServers - }; + self._tracks.forEach(function (track) { + track.inStream = new RangeSliceStream(startOffset, { + // Allow up to a 10MB offset between audio and video, + // which should be fine for any reasonable interleaving + // interval and bitrate + highWaterMark: 10000000 + }) + fileStream.pipe(track.inStream) + }) + } - self.createPeerConnect(); + return self._tracks.map(function (track) { + return track.outStream + }) } -SimpleRTC.prototype.signal = function (event) { - - debug('[pear_webrtc] event.type' + event.type); - debug('event JSON: ' + JSON.stringify(event)); - if (event.type === 'offer') { - this.receiveOffer(event); - } else if (event.type === 'answer') { - this.receiveAnswer(event); - } - // else if (event.type === 'candidate') { - // ReceiveIceCandidate(event); - // } - else { - this.receiveIceCandidate(event); - // debug('err event.type: ' + JSON.stringify(event)); - } -}; - -SimpleRTC.prototype.createPeerConnect = function () { - var self = this; - - try { - this.peerConnection = new self.RTCPeerConnection(this.pc_config); - // debug('[simpleRTC] PeerConnection created!'); - if (this.config.initiator && this.config.initiator == true){ - debug('[pear_webrtc] sendOffer'); - this.sendOffer(); - - }else { - // createDatachannel(); - } - } - catch (e) { - debug("pc established error:"+e.message); - this.emit('error', e.message); - } - - this.peerConnection.onopen = function() { - debug("PeerConnection established"); - - }; - - this.peerConnection.onicecandidate = function (event) { - // debug('[pear_webrtc] onicecandidate: ' + JSON.stringify(event)); - if (event.candidate == null) { - if (self.sdp == "") { - debug("sdp error"); - self.emit('error', "sdp error"); - return; - } - return; - } else { - // socketSend(event.candidate); - // debug('[pear_webrtc] sendCandidate'); - self.emit('signal',event.candidate); - if (!self.config.initiator || self.config.initiator == false){ - // createDatachannel(); - } - } - // debug("iceGatheringState: "+ self.peerConnection.iceGatheringState); - }; +MP4Remuxer.prototype._findSampleBefore = function (trackInd, time) { + var self = this - this.peerConnection.oniceconnectionstatechange = function (evt) { + var track = self._tracks[trackInd] + var scaledTime = Math.floor(track.timeScale * time) + var sample = bs(track.samples, scaledTime, function (sample, t) { + var pts = sample.dts + sample.presentationOffset// - track.editShift + return pts - t + }) + if (sample === -1) { + sample = 0 + } else if (sample < 0) { + sample = -sample - 2 + } + // sample is now the last sample with dts <= time + // Find the preceeding sync sample + while (!track.samples[sample].sync) { + sample-- + } + return sample +} - debug("connectionState: "+ self.peerConnection.connectionState); - debug("signalingstate:"+ self.peerConnection.signalingState); - if (self.peerConnection.signalingState=="stable" && !self.isDataChannelCreating) - { - debug('[pear_webrtc] oniceconnectionstatechange stable'); - self.createDatachannel(); - self.isDataChannelCreating = true; - } +var MIN_FRAGMENT_DURATION = 1 // second +MP4Remuxer.prototype._generateFragment = function (track, time) { + var self = this + /* + 1. Find correct sample + 2. Process backward until sync sample found + 3. Process forward until next sync sample after MIN_FRAGMENT_DURATION found + */ + var currTrack = self._tracks[track] + var firstSample + if (time !== undefined) { + firstSample = self._findSampleBefore(track, time) + } else { + firstSample = currTrack.currSample + } - }; + if (firstSample >= currTrack.samples.length) + return null - this.peerConnection.ondatachannel = function (evt) { - self.dataChannel = evt.channel; - debug(this.dataChannel.label+"dc state: "+ self.dataChannel.readyState); - self.dataChannelEvents(this.dataChannel); - }; + var startDts = currTrack.samples[firstSample].dts - this.peerConnection.onicegatheringstatechange = function() { - if (self.peerConnection.iceGatheringState === 'complete') { - self.emit('signal', { - "candidate":"completed" - }); - } - } -}; + var totalLen = 0 + var ranges = [] + for (var currSample = firstSample; currSample < currTrack.samples.length; currSample++) { + var sample = currTrack.samples[currSample] + if (sample.sync && sample.dts - startDts >= currTrack.timeScale * MIN_FRAGMENT_DURATION) { + break // This is a reasonable place to end the fragment + } -SimpleRTC.prototype.createDatachannel = function () { + totalLen += sample.size + var currRange = ranges.length - 1 + if (currRange < 0 || ranges[currRange].end !== sample.offset) { + // Push a new range + ranges.push({ + start: sample.offset, + end: sample.offset + sample.size + }) + } else { + ranges[currRange].end += sample.size + } + } - try { - this.dataChannel = this.peerConnection.createDataChannel('dataChannel', {reliable: true}); - debug("Channel [ " + this.dataChannel.label + " ] creating!"); - debug(this.dataChannel.label+" Datachannel state: "+ this.dataChannel.readyState); - } - catch (dce) { - debug("dc established error: "+dce.message); - this.emit('error', dce.message); - } + currTrack.currSample = currSample - this.dataChannelEvents(this.dataChannel); -}; + return { + moof: self._generateMoof(track, firstSample, currSample), + ranges: ranges, + length: totalLen + } +} -SimpleRTC.prototype.dataChannelEvents = function (channel) { - var self = this; +MP4Remuxer.prototype._generateMoof = function (track, firstSample, lastSample) { + var self = this - channel.onopen = function () { - debug("Datachannel opened, current stateis :\n" + self.dataChannel.readyState); - debug(channel); - self.emit('connect', self.dataChannel.readyState); - }; + var currTrack = self._tracks[track] - channel.onmessage = function (event) { + var entries = [] + for (var j = firstSample; j < lastSample; j++) { + var currSample = currTrack.samples[j] + entries.push({ + sampleDuration: currSample.duration, + sampleSize: currSample.size, + sampleFlags: currSample.sync ? 0x2000000 : 0x1010000, + sampleCompositionTimeOffset: currSample.presentationOffset + }) + } - var data = event.data; - if (data instanceof Blob) { //兼容firefox 将返回的Blob类型转为ArrayBuffer - var fileReader = new FileReader(); - fileReader.onload = function() { - // arrayBuffer = this.result; - self.emit('data', this.result); - }; - fileReader.readAsArrayBuffer(data); - } else { - self.emit('data', data); - } - }; + var moof = { + type: 'moof', + mfhd: { + sequenceNumber: self._fragmentSequence++ + }, + trafs: [{ + tfhd: { + flags: 0x20000, // default-base-is-moof + trackId: currTrack.trackId + }, + tfdt: { + baseMediaDecodeTime: currTrack.samples[firstSample].dts + }, + trun: { + flags: 0xf01, + dataOffset: 8, // The moof size has to be added to this later as well + entries: entries + } + }] + } - channel.onerror = function (err) { - self.emit('error', err); - }; + // Update the offset + moof.trafs[0].trun.dataOffset += Box.encodingLength(moof) - channel.onclose = function () { - debug("DataChannel is closed"); - clearInterval(self.timer); - self.timer = null; - } -}; + return moof +} -SimpleRTC.prototype.receiveOffer = function (evt) { - var self = this; - - this.peerConnection.setRemoteDescription(new RTCSessionDescription(evt)); - // debug("Received Offer, and set as Remote Desc:\n"+ evt.sdp); - this.peerConnection.createAnswer(function(desc) { - self.peerConnection.setLocalDescription(desc); - self.currentoffer = desc; - self.sdp = desc.sdp; - // debug("Create Answer, and set as Local Desc:\n"+JSON.stringify(desc)); - // socketSend(desc); - self.emit('signal',desc); - },function (err) { - debug(err); - }); -}; +}).call(this,require("buffer").Buffer) +},{"binary-search":72,"buffer":4,"events":7,"inherits":96,"mp4-box-encoding":108,"mp4-stream":111,"range-slice-stream":123}],161:[function(require,module,exports){ +var MediaElementWrapper = require('mediasource') +var pump = require('pump') -SimpleRTC.prototype.sendOffer = function () { - - this.peerConnection.createOffer(function (desc) { - this.currentoffer = desc; - debug("Create an offer : \n"+JSON.stringify(desc)); - this.peerConnection.setLocalDescription(desc); - debug("Offer Set as Local Desc"); - // socketSend(desc); - this.emit('signal', desc); - this.sdp = desc.sdp; - debug("Send offer:\n"+JSON.stringify(this.sdp)); - },function(error) { - debug(error); - }); -}; +var MP4Remuxer = require('./mp4-remuxer') -SimpleRTC.prototype.receiveAnswer = function (answer) { +module.exports = VideoStream - debug("Received remote Answer: \n"+JSON.stringify(answer)); - this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer)); - debug("already set remote desc, current ice gather state: "+ this.peerConnection.iceGatheringState); -}; +function VideoStream (file, mediaElem, opts) { + var self = this + if (!(this instanceof VideoStream)) return new VideoStream(file, mediaElem, opts) + opts = opts || {} -SimpleRTC.prototype.receiveIceCandidate = function (evt) { + self.detailedError = null - if (evt) { - debug("Received and add candidate:\n"+JSON.stringify(evt)); - this.peerConnection.addIceCandidate(new RTCIceCandidate(evt)); - } else{ - return; - } -}; + self._elem = mediaElem + self._elemWrapper = new MediaElementWrapper(mediaElem) + self._waitingFired = false + self._trackMeta = null + self._file = file + self._tracks = null + if (self._elem.preload !== 'none') { + self._createMuxer() + } -SimpleRTC.prototype.connect = function () { + self._onError = function (err) { + self.detailedError = self._elemWrapper.detailedError + self.destroy() // don't pass err though so the user doesn't need to listen for errors + } + self._onWaiting = function () { + self._waitingFired = true + if (!self._muxer) { + self._createMuxer() + } else if (self._tracks) { + self._pump() + } + } + self._elem.addEventListener('waiting', self._onWaiting) + self._elem.addEventListener('error', self._onError) +} - this.createDatachannel(); -}; +VideoStream.prototype._createMuxer = function () { + var self = this + self._muxer = new MP4Remuxer(self._file) + self._muxer.on('ready', function (data) { + self._tracks = data.map(function (trackData) { + var mediaSource = self._elemWrapper.createWriteStream(trackData.mime) + mediaSource.on('error', function (err) { + self._elemWrapper.error(err) + }) + var track = { + muxed: null, + mediaSource: mediaSource, + initFlushed: false, + onInitFlushed: null + } + mediaSource.write(trackData.init, function (err) { + track.initFlushed = true + if (track.onInitFlushed) { + track.onInitFlushed(err) + } + }) + return track + }) -SimpleRTC.prototype.send = function (data) { + if (self._waitingFired || self._elem.preload === 'auto') { + self._pump() + } + }) - try { - this.dataChannel.send(data); - debug("[pear_webrtc] send data:" + data); - } catch (e){ - debug("dataChannel send error:"+e.message); - } -}; + self._muxer.on('error', function (err) { + self._elemWrapper.error(err) + }) +} -SimpleRTC.prototype.close = function () { +VideoStream.prototype._pump = function () { + var self = this - if (this.peerConnection){ - this.peerConnection.close(); - clearInterval(this.timer); - } -}; + var muxed = self._muxer.seek(self._elem.currentTime, !self._tracks) -SimpleRTC.prototype.startHeartbeat = function () { - var self = this; - var heartbeat = { - action: 'ping' - }; + self._tracks.forEach(function (track, i) { + var pumpTrack = function () { + if (track.muxed) { + track.muxed.destroy() + track.mediaSource = self._elemWrapper.createWriteStream(track.mediaSource) + track.mediaSource.on('error', function (err) { + self._elemWrapper.error(err) + }) + } + track.muxed = muxed[i] + pump(track.muxed, track.mediaSource) + } + if (!track.initFlushed) { + track.onInitFlushed = function (err) { + if (err) { + self._elemWrapper.error(err) + return + } + pumpTrack() + } + } else { + pumpTrack() + } + }) +} - this.timer = setInterval(function () { - debug(JSON.stringify(heartbeat)); - self.send(JSON.stringify(heartbeat)); +VideoStream.prototype.destroy = function () { + var self = this + if (self.destroyed) { + return + } + self.destroyed = true - }, 90*1000); -}; + self._elem.removeEventListener('waiting', self._onWaiting) + self._elem.removeEventListener('error', self._onError) + if (self._tracks) { + self._tracks.forEach(function (track) { + track.muxed.destroy() + }) + } -function getBrowserRTC () { - if (typeof window === 'undefined') return null; - var wrtc = { - RTCPeerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection || - window.webkitRTCPeerConnection, - RTCSessionDescription: window.RTCSessionDescription || - window.mozRTCSessionDescription || window.webkitRTCSessionDescription, - RTCIceCandidate: window.RTCIceCandidate || window.mozRTCIceCandidate || - window.webkitRTCIceCandidate - }; - if (!wrtc.RTCPeerConnection) return null; - return wrtc + self._elem.src = '' } +},{"./mp4-remuxer":160,"mediasource":104,"pump":120}],162:[function(require,module,exports){ +(function (process,global){ +/* global FileList */ + +module.exports = WebTorrent +var Buffer = require('safe-buffer').Buffer +var concat = require('simple-concat') +var createTorrent = require('create-torrent') +var debug = require('debug')('webtorrent') +var DHT = require('bittorrent-dht/client') // browser exclude +var EventEmitter = require('events').EventEmitter +var extend = require('xtend') +var inherits = require('inherits') +var loadIPSet = require('load-ip-set') // browser exclude +var parallel = require('run-parallel') +var parseTorrent = require('parse-torrent') +var path = require('path') +var Peer = require('simple-peer') +var randombytes = require('randombytes') +var speedometer = require('speedometer') +var zeroFill = require('zero-fill') -},{"debug":27,"events":162,"inherits":58}],154:[function(require,module,exports){ +var TCPPool = require('./lib/tcp-pool') // browser exclude +var Torrent = require('./lib/torrent') -/* - config:{ - peer_id : - chunkSize : 每个块的大小,必须是32K的整数倍 (默认1M) - host : - uri : 文件路径, - fileSize : 文件大小 - useMonitor: 开启监控器 - } +/** + * WebTorrent version. */ +var VERSION = require('./package.json').version -module.exports = RTCDownloader; +/** + * Version number in Azureus-style. Generated from major and minor semver version. + * For example: + * '0.16.1' -> '0016' + * '1.2.5' -> '0102' + */ +var VERSION_STR = VERSION.match(/([0-9]+)/g) + .slice(0, 2) + .map(function (v) { return zeroFill(2, v) }) + .join('') -var debug = require('debug')('pear:webrtc-downloader-bin'); -var Buffer = require('buffer/').Buffer; -var SimpleRTC = require('./simple-RTC'); -var EventEmitter = require('events').EventEmitter; -var inherits = require('inherits'); +/** + * Version prefix string (used in peer ID). WebTorrent uses the Azureus-style + * encoding: '-', two characters for client id ('WW'), four ascii digits for version + * number, '-', followed by random numbers. + * For example: + * '-WW0102-'... + */ +var VERSION_PREFIX = '-WW' + VERSION_STR + '-' -inherits(RTCDownloader, EventEmitter); - -function RTCDownloader(config) { - EventEmitter.call(this); - - var self = this; - - self.type = 2; //datachannel - self.peer_id = config.peer_id; - self.chunkSize = config.chunkSize || 1*1024*1024; - self.uri = config.uri; - self.host = config.host; - self.fileSize = config.fileSize; - self.useMonitor = config.useMonitor || false; - self.chunkStore = []; - self.start = -1; - self.end = -1; - self.connectFlag = false; - self.downloading = false; //是否正在下载 - self.queue = []; //下载队列 - self.startTime = 0; - self.endTime = 0; - self.speed = 0; //当前速度 - self.meanSpeed = -1; //平均速度 - self.counter = 0; //记录下载的次数 - self.expectedLength = 1048576; //期望返回的buffer长度 - self.simpleRTC = new SimpleRTC(); - self._setupSimpleRTC(self.simpleRTC); - - self.dc_id = ''; //对等端的id - self.downloaded = 0; - self.mac = ''; +inherits(WebTorrent, EventEmitter) -}; +/** + * WebTorrent Client + * @param {Object=} opts + */ +function WebTorrent (opts) { + var self = this + if (!(self instanceof WebTorrent)) return new WebTorrent(opts) + EventEmitter.call(self) -RTCDownloader.prototype.offerFromWS = function (offer) { //由服务器传来的data channel的offer、peer_id、offer_id等信息 - var self = this; + if (!opts) opts = {} - self.message = offer; - debug('[webrtc] messageFromDC:' + JSON.stringify(offer)); - self.dc_id = offer.peer_id; - self.simpleRTC.signal(offer.sdp); -}; + if (typeof opts.peerId === 'string') { + self.peerId = opts.peerId + } else if (Buffer.isBuffer(opts.peerId)) { + self.peerId = opts.peerId.toString('hex') + } else { + self.peerId = Buffer.from(VERSION_PREFIX + randombytes(9).toString('base64')).toString('hex') + } + self.peerIdBuffer = Buffer.from(self.peerId, 'hex') -RTCDownloader.prototype.candidatesFromWS = function (candidates) { + if (typeof opts.nodeId === 'string') { + self.nodeId = opts.nodeId + } else if (Buffer.isBuffer(opts.nodeId)) { + self.nodeId = opts.nodeId.toString('hex') + } else { + self.nodeId = randombytes(20).toString('hex') + } + self.nodeIdBuffer = Buffer.from(self.nodeId, 'hex') - for (var i=0; i= 3) { - // self.clearQueue(); - // self.weight -= 0.1; - // if (self.weight < 0.1) { - // self.emit('error'); - // } - // } -}; + self.destroyed = false + self.listening = false + self.torrentPort = opts.torrentPort || 0 + self.dhtPort = opts.dhtPort || 0 + self.tracker = opts.tracker !== undefined ? opts.tracker : {} + self.torrents = [] + self.maxConns = Number(opts.maxConns) || 55 -RTCDownloader.prototype.startDownloading = function (start, end) { - var self = this; - - self.downloading = true; - var str = { - "host":self.host, - "uri":self.uri, - "action":"get", - "response_type":"binary", - "start":start, - "end":end - // "end":10*1024*1024 - }; - debug("pear_send_file : " + JSON.stringify(str)); - self.startTime=(new Date()).getTime(); - self.expectedLength = end - start + 1; - self.simpleRTC.send(JSON.stringify(str)); -}; + self._debug( + 'new webtorrent (peerId %s, nodeId %s, port %s)', + self.peerId, self.nodeId, self.torrentPort + ) -RTCDownloader.prototype._receive = function (chunk) { - var self = this; - // debug('[simpleRTC] chunk type:'+typeof chunk); - - var uint8 = new Uint8Array(chunk); - // debug('uint8.length:'+uint8.length); - // if (!uint8) { - // self.emit('error'); - // return; - // } - - var headerInfo = self._getHeaderInfo(uint8); - // debug('headerInfo:'+JSON.stringify(headerInfo)); - - if (headerInfo) { - - if (headerInfo.value){ - - // debug(self.mac+' headerInfo.start:'+headerInfo.start); - // if (headerInfo.start === self.lastChunkEnd + 1){ - // - // // self.chunkStore.push(uint8); - // self.lastChunkEnd = headerInfo.end; - // } else { - // console.error('RTCDownloader' +self.mac+ ' error start:' + headerInfo.start + ' lastChunkEnd:' + self.lastChunkEnd); - // // self.emit('error'); - // } - - self.chunkStore.push(uint8); - } else if (headerInfo.begin) { - // debug(self.mac+' headerInfo.begin:'+self.downloading); - self.emit('start'); - self.chunkStore = []; - } else if (headerInfo.done) { - // debug(self.mac+' headerInfo.done:'+self.downloading); - // debug('self.chunkStore done'); - var finalArray = [], length = self.chunkStore.length; - // self.downloading = false; - self.end = headerInfo.end; - - self.start = self._getHeaderInfo(self.chunkStore[0]).start; - - self.end = self._getHeaderInfo(self.chunkStore[self.chunkStore.length-1]).end; - - - self.endTime = (new Date()).getTime(); - // self.speed = Math.floor(((self.end - self.start) * 1000) / ((self.endTime - self.startTime) * 1024)); //单位: KB/s - self.speed = Math.floor((self.end - self.start + 1) / (self.endTime - self.startTime)); //单位: KB/s - debug('pear_webrtc speed:' + self.speed + 'KB/s'); - // self.meanSpeed = (self.meanSpeed*self.counter + self.speed)/(++self.counter); - if (self.meanSpeed == -1) self.meanSpeed = self.speed; - self.meanSpeed = 0.95*self.meanSpeed + 0.05*self.speed; - debug('datachannel '+self.dc_id+' meanSpeed:' + self.meanSpeed + 'KB/s'); - - for (var i = 0; i < length; i++) { - if (!!self.chunkStore[i]) { - var value = self.chunkStore[i].subarray(256); - // debug('value.length:'+value.length); - finalArray.push(Buffer.from(value)); - } - } - // debug('RTCDownloader' +self.mac+ ' emit data start:' + self.start + ' end:' + self.end); - var retBuf = Buffer.concat(finalArray); - if (retBuf.length === self.expectedLength) self.emit('data', retBuf, self.start, self.end, self.speed); - self.downloading = false; - if (self.queue.length>0) { //如果下载队列不为空 - var pair = self.queue.shift(); - self.startDownloading(pair[0], pair[1]); - } - } else if (headerInfo.action) { - //心跳信息 - } else { - debug('RTC error msg:'+JSON.stringify(headerInfo)); - self.emit('error'); - } - } else { - self.emit('error'); + if (self.tracker) { + if (typeof self.tracker !== 'object') self.tracker = {} + if (opts.rtcConfig) { + // TODO: remove in v1 + console.warn('WebTorrent: opts.rtcConfig is deprecated. Use opts.tracker.rtcConfig instead') + self.tracker.rtcConfig = opts.rtcConfig } + if (opts.wrtc) { + // TODO: remove in v1 + console.warn('WebTorrent: opts.wrtc is deprecated. Use opts.tracker.wrtc instead') + self.tracker.wrtc = opts.wrtc + } + if (global.WRTC && !self.tracker.wrtc) { + self.tracker.wrtc = global.WRTC + } + } + if (typeof TCPPool === 'function') { + self._tcpPool = new TCPPool(self) + } else { + process.nextTick(function () { + self._onListening() + }) + } -}; + // stats + self._downloadSpeed = speedometer() + self._uploadSpeed = speedometer() -RTCDownloader.prototype.abort = function () { + if (opts.dht !== false && typeof DHT === 'function' /* browser exclude */) { + // use a single DHT instance for all torrents, so the routing table can be reused + self.dht = new DHT(extend({ nodeId: self.nodeId }, opts.dht)) -}; + self.dht.once('error', function (err) { + self._destroy(err) + }) -RTCDownloader.prototype.close = function () { - var self = this; + self.dht.once('listening', function () { + var address = self.dht.address() + if (address) self.dhtPort = address.port + }) - if (self.simpleRTC){ - self.simpleRTC.close(); - } -}; + // Ignore warning when there are > 10 torrents in the client + self.dht.setMaxListeners(0) -RTCDownloader.prototype.clearQueue = function () { //清空下载队列 + self.dht.listen(self.dhtPort) + } else { + self.dht = false + } - // this.downloading = false; - if (this.queue.length > 0) { - this.queue = []; - } -}; + // Enable or disable BEP19 (Web Seeds). Enabled by default: + self.enableWebSeeds = opts.webSeeds !== false -RTCDownloader.prototype._getHeaderInfo = function (uint8arr) { - // debug('_getHeaderInfo mac:'+this.mac); - var sub = uint8arr.subarray(0, 256); - var headerString = String.fromCharCode.apply(String, sub); - // debug('headerString:'+headerString) - return JSON.parse(headerString.split('}')[0]+'}'); -}; + if (typeof loadIPSet === 'function' && opts.blocklist != null) { + loadIPSet(opts.blocklist, { + headers: { + 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' + } + }, function (err, ipSet) { + if (err) return self.error('Failed to load blocklist: ' + err.message) + self.blocked = ipSet + ready() + }) + } else { + process.nextTick(ready) + } -RTCDownloader.prototype._setupSimpleRTC = function (simpleRTC) { - var self = this; + function ready () { + if (self.destroyed) return + self.ready = true + self.emit('ready') + } +} - simpleRTC.on('data', function (data) { +WebTorrent.WEBRTC_SUPPORT = Peer.WEBRTC_SUPPORT - self._receive(data); - }); - simpleRTC.on('error', function (err) - { - debug('[simpleRTC] error', err); - self.emit('error'); - }); - simpleRTC.on('signal', function (data) { - // debug('[simpleRTC] SIGNAL', JSON.stringify(data)); +Object.defineProperty(WebTorrent.prototype, 'downloadSpeed', { + get: function () { return this._downloadSpeed() } +}) - var message = { - "peer_id":self.peer_id, - "to_peer_id":self.message.peer_id, - "offer_id":self.message.offer_id - }; - self.mac = self.message.peer_id.replace(/:/g, ''); - // debug('webrtc mac:'+self.mac); - if (data.type == 'answer'){ - message.action = 'answer'; - message.sdps = data; - } else if(data.candidate){ - message.action = 'candidate'; - debug('signal candidate:'+JSON.stringify(data)); - message.candidates = data; - } +Object.defineProperty(WebTorrent.prototype, 'uploadSpeed', { + get: function () { return this._uploadSpeed() } +}) - // websocket.send(JSON.stringify(message)); - self.emit('signal',message); - }); - simpleRTC.on('connect', function (state) { - debug('[datachannel] '+self.dc_id+' CONNECT'); - // simpleRTC.send('[simpleRTC] PEER CONNECTED!'); - simpleRTC.startHeartbeat(); //开始周期性发送心跳信息 - if (!self.connectFlag){ - self.emit('connect',state); - self.connectFlag = true; - } +Object.defineProperty(WebTorrent.prototype, 'progress', { + get: function () { + var torrents = this.torrents.filter(function (torrent) { + return torrent.progress !== 1 + }) + var downloaded = torrents.reduce(function (total, torrent) { + return total + torrent.downloaded + }, 0) + var length = torrents.reduce(function (total, torrent) { + return total + (torrent.length || 0) + }, 0) || 1 + return downloaded / length + } +}) - }); -}; +Object.defineProperty(WebTorrent.prototype, 'ratio', { + get: function () { + var uploaded = this.torrents.reduce(function (total, torrent) { + return total + torrent.uploaded + }, 0) + var received = this.torrents.reduce(function (total, torrent) { + return total + torrent.received + }, 0) || 1 + return uploaded / received + } +}) -},{"./simple-RTC":153,"buffer/":46,"debug":27,"events":162,"inherits":58}],155:[function(require,module,exports){ /** - * Created by xieting on 2017/11/9. + * Returns the torrent with the given `torrentId`. Convenience method. Easier than + * searching through the `client.torrents` array. Returns `null` if no matching torrent + * found. + * + * @param {string|Buffer|Object|Torrent} torrentId + * @return {Torrent|null} */ +WebTorrent.prototype.get = function (torrentId) { + var self = this + var i, torrent + var len = self.torrents.length -module.exports = Worker; - -var debug = require('debug')('pear:worker'); -var md5 = require('blueimp-md5'); -var Dispatcher = require('./dispatcher'); -var HttpDownloader = require('./http-downloader'); -var RTCDownloader = require('./webrtc-downloader-bin'); -var getPeerId = require('./peerid-generator'); -var url = require('url'); -var File = require('./file'); -var nodeFilter = require('./node-filter'); -var inherits = require('inherits'); -var EventEmitter = require('events').EventEmitter; -var Set = require('./set'); -var PearTorrent = require('./pear-torrent'); -var Scheduler = require('./node-scheduler'); -var Reporter = require('./reporter'); - -// var WEBSOCKET_ADDR = 'ws://signal.webrtc.win:9600/ws'; //test -var WEBSOCKET_ADDR = 'wss://signal.webrtc.win:7601/wss'; -var GETNODES_ADDR = 'https://api.webrtc.win:6601/v1/customer/nodes'; - -var BLOCK_LENGTH = 32 * 1024; - -inherits(Worker, EventEmitter); - -function Worker(urlStr, token, opts) { - var self = this; - - // if (!(self instanceof PearPlayer)) return new PearPlayer(selector, opts); - if (typeof token === 'object') return Worker(urlStr, '', token); - EventEmitter.call(self); - opts = opts || {}; - // token = ''; - // if (typeof token !== 'string') throw new Error('token must be a string!'); - // if (!(opts.type && opts.type === 'mp4')) throw new Error('only mp4 is supported!'); - // if (!((opts.src && typeof opts.src === 'string') || self.video.src)) throw new Error('video src is not valid!'); - // if (!(config.token && typeof config.token === 'string')) throw new Error('token is not valid!'); - - //player - self.render = opts.render; - self.selector = opts.selector; - self.autoplay = opts.autoplay === false ? false : true; - - self.src = urlStr; - self.urlObj = url.parse(self.src); - self.scheduler = opts.scheduler || 'IdleFirst'; - self.token = token; - self.useDataChannel = (opts.useDataChannel === false)? false : true; - self.useMonitor = (opts.useMonitor === false)? false : true; - self.useTorrent = (opts.useTorrent === false)? false : true; - self.magnetURI = opts.magnetURI || undefined; - self.trackers = opts.trackers && Array.isArray(opts.trackers) && opts.trackers.length > 0 ? opts.trackers : null; - self.sources = opts.sources && Array.isArray(opts.sources) && opts.sources.length > 0 ? opts.sources : null; - self.auto = (opts.auto === false) ? false : true; - self.dataChannels = opts.dataChannels || 20; - self.peerId = getPeerId(); - self.isPlaying = false; - self.fileLength = 0; - self.nodes = []; - self.websocket = null; - self.dispatcher = null; - self.JDMap = {}; //根据dc的peer_id来获取jd的map - self.nodeSet = new Set(); //保存node的set - self.tempDCQueue = []; //暂时保存data channel的队列 - self.fileName = self.urlObj.path.split('/').pop(); - self.file = null; - self.dispatcherConfig = { - - chunkSize: opts.chunkSize && (opts.chunkSize%BLOCK_LENGTH === 0 ? opts.chunkSize : Math.ceil(opts.chunkSize/BLOCK_LENGTH)*BLOCK_LENGTH), //每个chunk的大小,默认1M - interval: opts.interval ? opts.interval : (opts.sequencial ? 5000 : 2000), //滑动窗口的时间间隔,单位毫秒,默认10s, - auto: self.auto, - useMonitor: self.useMonitor, - scheduler: Scheduler[self.scheduler], - sequencial: opts.sequencial, - maxLoaders: opts.maxLoaders || 10, - algorithm: opts.algorithm || 'push' // push or pull - }; + if (torrentId instanceof Torrent) { + for (i = 0; i < len; i++) { + torrent = self.torrents[i] + if (torrent === torrentId) return torrent + } + } else { + var parsed + try { parsed = parseTorrent(torrentId) } catch (err) {} - if (self.useDataChannel) { - self._pearSignalHandshake(); - } - //candidate - self.candidateMap = {}; - - //info - self.connectedDC = 0; - self.usefulDC = 0; - - self._debugInfo = { - totalDCs: 0, - connectedDCs: 0, - usefulDCs: 0, - totalHTTP: 0, - totalHTTPS: 0, - usefulHTTPAndHTTPS: 0, - windowOffset: 0, - windowLength: 0, - signalServerConnected: false, - traffics: {}, - abilities: {} - }; + if (!parsed) return null + if (!parsed.infoHash) throw new Error('Invalid torrent identifier') - self._start(); + for (i = 0; i < len; i++) { + torrent = self.torrents[i] + if (torrent.infoHash === parsed.infoHash) return torrent + } + } + return null } -Worker.isRTCSupported = function () { - - return !!getBrowserRTC(); +// TODO: remove in v1 +WebTorrent.prototype.download = function (torrentId, opts, ontorrent) { + console.warn('WebTorrent: client.download() is deprecated. Use client.add() instead') + return this.add(torrentId, opts, ontorrent) } -Object.defineProperty(Worker.prototype, 'debugInfo', { - get: function () { return this._debugInfo } -}); - - - -Worker.prototype._start = function () { - var self = this; - if (!window.WebSocket) { - self.useDataChannel = false; - } - - if (self.sources) { //如果用户指定下载源 - - self.sources = self.sources.map(function (source) { - - return {uri: source, type: 'server'}; - }); - nodeFilter(self.sources, function (nodes, fileLength) { //筛选出可用的节点,以及回调文件大小 - - var length = nodes.length; - debug('nodes:'+JSON.stringify(nodes)); - - if (length) { - // self.fileLength = fileLength; - debug('nodeFilter fileLength:'+fileLength); - - self._startPlaying(nodes); - } else { - - self._fallBack(); - } - }, {start: 0, end: 30}); - } else { - - self._getNodes(self.token, function (nodes) { - debug('debug _getNodes: %j', nodes); - if (nodes) { - self._startPlaying(nodes); - // if (self.useDataChannel) { - // self._pearSignalHandshake(); - // } - } else { - self._fallBackToWRTC(); - } - }); - } -}; - -Worker.prototype._fallBack = function () { +/** + * Start downloading a new torrent. Aliased as `client.download`. + * @param {string|Buffer|Object} torrentId + * @param {Object} opts torrent-specific options + * @param {function=} ontorrent called when the torrent is ready (has metadata) + */ +WebTorrent.prototype.add = function (torrentId, opts, ontorrent) { + var self = this + if (self.destroyed) throw new Error('client is destroyed') + if (typeof opts === 'function') return self.add(torrentId, null, opts) - debug('PearDownloader _fallBack'); + self._debug('add') + opts = opts ? extend(opts) : {} - this.emit('fallback'); -}; + var torrent = new Torrent(torrentId, self, opts) + self.torrents.push(torrent) -Worker.prototype._fallBackToWRTC = function () { - var self = this; - debug('_fallBackToWRTC'); - if (self._debugInfo.signalServerConnected === true) { //如果websocket已经连接上 - nodeFilter([{uri: self.src, type: 'server'}], function (nodes, fileLength) { //筛选出可用的节点,以及回调文件大小 - - var length = nodes.length; - if (length) { - // self.fileLength = fileLength; - debug('nodeFilter fileLength:'+fileLength); - self.fileLength = fileLength; - self._startPlaying(nodes); - } else { + torrent.once('_infoHash', onInfoHash) + torrent.once('ready', onReady) + torrent.once('close', onClose) - self._fallBack(); - } - }); - } else { - self._fallBack(); + function onInfoHash () { + if (self.destroyed) return + for (var i = 0, len = self.torrents.length; i < len; i++) { + var t = self.torrents[i] + if (t.infoHash === torrent.infoHash && t !== torrent) { + torrent._destroy(new Error('Cannot add duplicate torrent ' + torrent.infoHash)) + return + } } + } -}; - -Worker.prototype._getNodes = function (token, cb) { - var self = this; - - var postData = { - client_ip:'116.77.208.118', - host: self.urlObj.host, - uri: self.urlObj.path - }; - postData = (function(obj){ - var str = "?"; + function onReady () { + if (self.destroyed) return + if (typeof ontorrent === 'function') ontorrent(torrent) + self.emit('torrent', torrent) + } - for(var prop in obj){ - str += prop + "=" + obj[prop] + "&" - } - return str; - })(postData); - - var xhr = new XMLHttpRequest(); - xhr.open("GET", GETNODES_ADDR+postData); - xhr.timeout = 2000; - xhr.setRequestHeader('X-Pear-Token', self.token); - xhr.ontimeout = function() { - // self._fallBack(); - cb(null); - }; - xhr.onerror = function () { - self._fallBackToWRTC(); - }; - xhr.onload = function () { - if (this.status >= 200 && this.status < 300 || this.status == 304) { + function onClose () { + torrent.removeListener('_infoHash', onInfoHash) + torrent.removeListener('ready', onReady) + torrent.removeListener('close', onClose) + } - debug(this.response); + return torrent +} - var res = JSON.parse(this.response); - // debug(res.nodes); - if (res.size) { //如果filesize大于0 - self.fileLength = res.size; +/** + * Start seeding a new file/folder. + * @param {string|File|FileList|Buffer|Array.} input + * @param {Object=} opts + * @param {function=} onseed called when torrent is seeding + */ +WebTorrent.prototype.seed = function (input, opts, onseed) { + var self = this + if (self.destroyed) throw new Error('client is destroyed') + if (typeof opts === 'function') return self.seed(input, null, opts) - // if (self.useDataChannel) { - // self._pearSignalHandshake(); - // } + self._debug('seed') + opts = opts ? extend(opts) : {} - if (!res.nodes){ //如果没有可用节点则切换到纯webrtc模式 - // cb(null); - // cb([{uri: self.src, type: 'server'}]); - self._fallBackToWRTC(); - } else { + // When seeding from fs path, initialize store from that path to avoid a copy + if (typeof input === 'string') opts.path = path.dirname(input) + if (!opts.createdBy) opts.createdBy = 'WebTorrent/' + VERSION_STR - var nodes = res.nodes; - var allNodes = []; - var isLocationHTTP = location.protocol === 'http:' ? true : false; - var httpsCount = 0; - var httpCount = 0; - for (var i=0; i= 20){ - nodes = nodes.slice(0, 20); - cb(nodes); - } else { - cb(nodes); - } - } else { - // self._fallBack(); - cb([{uri: self.src, type: 'server'}]); - } - }, {start: 0, end: 30}); - } - } else { - cb(null); - } - } else { //返回码不正常 - // self._fallBack(); - cb(null); - } - }; - xhr.send(); -}; + var torrent = self.add(null, opts, onTorrent) + var streams -Worker.prototype._pearSignalHandshake = function () { - var self = this; - var dcCount = 0; //目前建立的data channel数量 - debug('_pearSignalHandshake'); - var websocket = new WebSocket(WEBSOCKET_ADDR); - self.websocket = websocket; - websocket.onopen = function() { - // debug('websocket connection opened!'); - self._debugInfo.signalServerConnected = true; - var hash = md5(self.urlObj.host + self.urlObj.path); - websocket.push(JSON.stringify({ - "action": "get", - "peer_id": self.peerId, - "host": self.urlObj.host, - "uri": self.urlObj.path, - "md5": hash - })); - // debug('peer_id:'+self.peerId); - }; - websocket.push = websocket.send; - websocket.send = function(data) { - if (websocket.readyState != 1) { - console.warn('websocket connection is not opened yet.'); - return setTimeout(function() { - websocket.send(data); - }, 1000); - } - // debug("send to signal is " + data); - websocket.push(data); - }; - websocket.onmessage = function(e) { - var message = JSON.parse(e.data); - // debug("[simpleRTC] websocket message is: " + JSON.stringify(message)); - // message = message.nodes[1]; - if (message.action === 'candidate' && message.type === 'end') { - - for (var peerId in self.candidateMap) { - if (message.peer_id === peerId) { - // debug('self.candidateMap[peerId]:'+self.candidateMap[peerId]); - self.JDMap[peerId].candidatesFromWS(self.candidateMap[peerId]); - } - } - } else if (message.nodes) { - var nodes = message.nodes; - - self._debugInfo.totalDCs = nodes.length; - - for (var i=0;i 1.0 ? 1.0 : downloaded; - self.emit('progress', progress); - }); - d.on('meanspeed', function (meanSpeed) { + var start = (opts && opts.start) || 0 + var end = (opts && opts.end && opts.end < file.length) + ? opts.end + : file.length - 1 + var pieceLength = file._torrent.pieceLength - self.emit('meanspeed', meanSpeed); - }); - d.on('fograte', function (fogRate) { + this._startPiece = (start + file.offset) / pieceLength | 0 + this._endPiece = (end + file.offset) / pieceLength | 0 - self.emit('fogratio', fogRate); - }); - d.on('fogspeed', function (speed) { + this._piece = this._startPiece + this._offset = (start + file.offset) - (this._startPiece * pieceLength) - self.emit('fogspeed', speed); - }); - d.on('cloudspeed', function (speed) { + this._missing = end - start + 1 + this._reading = false + this._notifying = false + this._criticalLength = Math.min((1024 * 1024 / pieceLength) | 0, 2) +} - self.emit('cloudspeed', speed); - }); - d.on('buffersources', function (bufferSources) { //s: server n: node d: data channel b: browser +FileStream.prototype._read = function () { + if (this._reading) return + this._reading = true + this._notify() +} - self.emit('buffersources', bufferSources); - }); - d.on('sourcemap', function (sourceType, index) { //s: server n: node d: data channel b: browser +FileStream.prototype._notify = function () { + var self = this - self.emit('sourcemap', sourceType, index); - }); - d.on('traffic', function (mac, size, type, meanSpeed) { + if (!self._reading || self._missing === 0) return + if (!self._torrent.bitfield.get(self._piece)) { + return self._torrent.critical(self._piece, self._piece + self._criticalLength) + } - //流量上报 - if (!self._debugInfo.traffics[mac]) { - self._debugInfo.traffics[mac] = {}; - self._debugInfo.traffics[mac].mac = mac; - self._debugInfo.traffics[mac].traffic = size; - } else { - self._debugInfo.traffics[mac].traffic += size; - } + if (self._notifying) return + self._notifying = true - //能力值上报 - if (meanSpeed) { - self._debugInfo.abilities[mac] = meanSpeed; - } + var p = self._piece + self._torrent.store.get(p, function (err, buffer) { + self._notifying = false + if (self.destroyed) return + if (err) return self._destroy(err) + debug('read %s (length %s) (err %s)', p, buffer.length, err && err.message) - self.emit('traffic', mac, size, type); - }); - d.on('datachannelerror', function () { + if (self._offset) { + buffer = buffer.slice(self._offset) + self._offset = 0 + } - self._debugInfo.usefulDCs --; - }); - d.on('fillwindow', function (windowOffset, windowLength) { + if (self._missing < buffer.length) { + buffer = buffer.slice(0, self._missing) + } + self._missing -= buffer.length - self._debugInfo.windowOffset = windowOffset; - self._debugInfo.windowLength = windowLength; - }); -}; + debug('pushing buffer of length %s', buffer.length) + self._reading = false + self.push(buffer) -function getBrowserRTC () { - if (typeof window === 'undefined') return null; - var wrtc = { - RTCPeerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection || - window.webkitRTCPeerConnection, - }; - if (!wrtc.RTCPeerConnection) return null; - return wrtc + if (self._missing === 0) self.push(null) + }) + self._piece += 1 } -function makeCandidateArr(sdp) { - var rawArr = sdp.split('\r\n'); +FileStream.prototype.destroy = function (onclose) { + this._destroy(null, onclose) +} - var ufrag_reg = /^(a=ice-ufrag)/; - var ice_ufrag; - for (var i=0; i 0 && this._events[type].length > m) { - this._events[type].warned = true; - console.error('(node) warning: possible EventEmitter memory ' + - 'leak detected. %d listeners added. ' + - 'Use emitter.setMaxListeners() to increase limit.', - this._events[type].length); - if (typeof console.trace === 'function') { - // not supported in IE 10 - console.trace(); - } - } + var addr = self.addr + if (!addr && self.conn.remoteAddress) { + addr = self.conn.remoteAddress + ':' + self.conn.remotePort } + self.swarm._onWire(self.wire, addr) - return this; -}; + // swarm could be destroyed in user's 'wire' event handler + if (!self.swarm || self.swarm.destroyed) return -EventEmitter.prototype.on = EventEmitter.prototype.addListener; + if (!self.sentHandshake) self.handshake() +} + +Peer.prototype.handshake = function () { + var self = this + var opts = { + dht: self.swarm.private ? false : !!self.swarm.client.dht + } + self.wire.handshake(self.swarm.infoHash, self.swarm.client.peerId, opts) + self.sentHandshake = true +} -EventEmitter.prototype.once = function(type, listener) { - if (!isFunction(listener)) - throw TypeError('listener must be a function'); +Peer.prototype.startConnectTimeout = function () { + var self = this + clearTimeout(self.connectTimeout) + self.connectTimeout = setTimeout(function () { + self.destroy(new Error('connect timeout')) + }, self.type === 'webrtc' ? CONNECT_TIMEOUT_WEBRTC : CONNECT_TIMEOUT_TCP) + if (self.connectTimeout.unref) self.connectTimeout.unref() +} - var fired = false; +Peer.prototype.startHandshakeTimeout = function () { + var self = this + clearTimeout(self.handshakeTimeout) + self.handshakeTimeout = setTimeout(function () { + self.destroy(new Error('handshake timeout')) + }, HANDSHAKE_TIMEOUT) + if (self.handshakeTimeout.unref) self.handshakeTimeout.unref() +} - function g() { - this.removeListener(type, g); +Peer.prototype.destroy = function (err) { + var self = this + if (self.destroyed) return + self.destroyed = true + self.connected = false - if (!fired) { - fired = true; - listener.apply(this, arguments); - } - } + debug('destroy %s (error: %s)', self.id, err && (err.message || err)) - g.listener = listener; - this.on(type, g); + clearTimeout(self.connectTimeout) + clearTimeout(self.handshakeTimeout) - return this; -}; + var swarm = self.swarm + var conn = self.conn + var wire = self.wire -// emits a 'removeListener' event iff the listener was removed -EventEmitter.prototype.removeListener = function(type, listener) { - var list, position, length, i; + self.swarm = null + self.conn = null + self.wire = null - if (!isFunction(listener)) - throw TypeError('listener must be a function'); + if (swarm && wire) { + arrayRemove(swarm.wires, swarm.wires.indexOf(wire)) + } + if (conn) { + conn.on('error', noop) + conn.destroy() + } + if (wire) wire.destroy() + if (swarm) swarm.removePeer(self.id) +} - if (!this._events || !this._events[type]) - return this; +function noop () {} - list = this._events[type]; - length = list.length; - position = -1; - - if (list === listener || - (isFunction(list.listener) && list.listener === listener)) { - delete this._events[type]; - if (this._events.removeListener) - this.emit('removeListener', type, listener); - - } else if (isObject(list)) { - for (i = length; i-- > 0;) { - if (list[i] === listener || - (list[i].listener && list[i].listener === listener)) { - position = i; - break; - } - } +},{"./webconn":168,"bittorrent-protocol":74,"debug":87,"unordered-array-remove":157}],166:[function(require,module,exports){ +module.exports = RarityMap - if (position < 0) - return this; +/** + * Mapping of torrent pieces to their respective availability in the torrent swarm. Used + * by the torrent manager for implementing the rarest piece first selection strategy. + */ +function RarityMap (torrent) { + var self = this - if (list.length === 1) { - list.length = 0; - delete this._events[type]; - } else { - list.splice(position, 1); - } + self._torrent = torrent + self._numPieces = torrent.pieces.length + self._pieces = [] - if (this._events.removeListener) - this.emit('removeListener', type, listener); + self._onWire = function (wire) { + self.recalculate() + self._initWire(wire) + } + self._onWireHave = function (index) { + self._pieces[index] += 1 + } + self._onWireBitfield = function () { + self.recalculate() } - return this; -}; + self._torrent.wires.forEach(function (wire) { + self._initWire(wire) + }) + self._torrent.on('wire', self._onWire) + self.recalculate() +} -EventEmitter.prototype.removeAllListeners = function(type) { - var key, listeners; +/** + * Get the index of the rarest piece. Optionally, pass a filter function to exclude + * certain pieces (for instance, those that we already have). + * + * @param {function} pieceFilterFunc + * @return {number} index of rarest piece, or -1 + */ +RarityMap.prototype.getRarestPiece = function (pieceFilterFunc) { + if (!pieceFilterFunc) pieceFilterFunc = trueFn - if (!this._events) - return this; + var candidates = [] + var min = Infinity - // not listening for removeListener, no need to emit - if (!this._events.removeListener) { - if (arguments.length === 0) - this._events = {}; - else if (this._events[type]) - delete this._events[type]; - return this; - } + for (var i = 0; i < this._numPieces; ++i) { + if (!pieceFilterFunc(i)) continue - // emit removeListener for all listeners on all events - if (arguments.length === 0) { - for (key in this._events) { - if (key === 'removeListener') continue; - this.removeAllListeners(key); + var availability = this._pieces[i] + if (availability === min) { + candidates.push(i) + } else if (availability < min) { + candidates = [ i ] + min = availability } - this.removeAllListeners('removeListener'); - this._events = {}; - return this; } - listeners = this._events[type]; - - if (isFunction(listeners)) { - this.removeListener(type, listeners); - } else if (listeners) { - // LIFO order - while (listeners.length) - this.removeListener(type, listeners[listeners.length - 1]); + if (candidates.length > 0) { + // if there are multiple pieces with the same availability, choose one randomly + return candidates[Math.random() * candidates.length | 0] + } else { + return -1 } - delete this._events[type]; +} - return this; -}; +RarityMap.prototype.destroy = function () { + var self = this + self._torrent.removeListener('wire', self._onWire) + self._torrent.wires.forEach(function (wire) { + self._cleanupWireEvents(wire) + }) + self._torrent = null + self._pieces = null -EventEmitter.prototype.listeners = function(type) { - var ret; - if (!this._events || !this._events[type]) - ret = []; - else if (isFunction(this._events[type])) - ret = [this._events[type]]; - else - ret = this._events[type].slice(); - return ret; -}; + self._onWire = null + self._onWireHave = null + self._onWireBitfield = null +} -EventEmitter.prototype.listenerCount = function(type) { - if (this._events) { - var evlistener = this._events[type]; +RarityMap.prototype._initWire = function (wire) { + var self = this - if (isFunction(evlistener)) - return 1; - else if (evlistener) - return evlistener.length; + wire._onClose = function () { + self._cleanupWireEvents(wire) + for (var i = 0; i < this._numPieces; ++i) { + self._pieces[i] -= wire.peerPieces.get(i) + } } - return 0; -}; -EventEmitter.listenerCount = function(emitter, type) { - return emitter.listenerCount(type); -}; + wire.on('have', self._onWireHave) + wire.on('bitfield', self._onWireBitfield) + wire.once('close', wire._onClose) +} + +/** + * Recalculates piece availability across all peers in the torrent. + */ +RarityMap.prototype.recalculate = function () { + var i + for (i = 0; i < this._numPieces; ++i) { + this._pieces[i] = 0 + } -function isFunction(arg) { - return typeof arg === 'function'; + var numWires = this._torrent.wires.length + for (i = 0; i < numWires; ++i) { + var wire = this._torrent.wires[i] + for (var j = 0; j < this._numPieces; ++j) { + this._pieces[j] += wire.peerPieces.get(j) + } + } } -function isNumber(arg) { - return typeof arg === 'number'; +RarityMap.prototype._cleanupWireEvents = function (wire) { + wire.removeListener('have', this._onWireHave) + wire.removeListener('bitfield', this._onWireBitfield) + if (wire._onClose) wire.removeListener('close', wire._onClose) + wire._onClose = null } -function isObject(arg) { - return typeof arg === 'object' && arg !== null; +function trueFn () { + return true } -function isUndefined(arg) { - return arg === void 0; -} +},{}],167:[function(require,module,exports){ +(function (process,global){ +/* global URL, Blob */ -},{}],163:[function(require,module,exports){ -var http = require('http') -var url = require('url') +module.exports = Torrent -var https = module.exports +var addrToIPPort = require('addr-to-ip-port') +var BitField = require('bitfield') +var ChunkStoreWriteStream = require('chunk-store-stream/write') +var debug = require('debug')('webtorrent:torrent') +var Discovery = require('torrent-discovery') +var EventEmitter = require('events').EventEmitter +var extend = require('xtend') +var extendMutable = require('xtend/mutable') +var fs = require('fs') +var FSChunkStore = require('fs-chunk-store') // browser: `memory-chunk-store` +var get = require('simple-get') +var ImmediateChunkStore = require('immediate-chunk-store') +var inherits = require('inherits') +var MultiStream = require('multistream') +var net = require('net') // browser exclude +var os = require('os') // browser exclude +var parallel = require('run-parallel') +var parallelLimit = require('run-parallel-limit') +var parseTorrent = require('parse-torrent') +var path = require('path') +var Piece = require('torrent-piece') +var pump = require('pump') +var randomIterate = require('random-iterate') +var sha1 = require('simple-sha1') +var speedometer = require('speedometer') +var uniq = require('uniq') +var utMetadata = require('ut_metadata') +var utPex = require('ut_pex') // browser exclude -for (var key in http) { - if (http.hasOwnProperty(key)) https[key] = http[key] -} +var File = require('./file') +var Peer = require('./peer') +var RarityMap = require('./rarity-map') +var Server = require('./server') // browser exclude -https.request = function (params, cb) { - params = validateParams(params) - return http.request.call(this, params, cb) -} +var MAX_BLOCK_LENGTH = 128 * 1024 +var PIECE_TIMEOUT = 30000 +var CHOKE_TIMEOUT = 5000 +var SPEED_THRESHOLD = 3 * Piece.BLOCK_LENGTH -https.get = function (params, cb) { - params = validateParams(params) - return http.get.call(this, params, cb) -} +var PIPELINE_MIN_DURATION = 0.5 +var PIPELINE_MAX_DURATION = 1 -function validateParams (params) { - if (typeof params === 'string') { - params = url.parse(params) - } - if (!params.protocol) { - params.protocol = 'https:' - } - if (params.protocol !== 'https:') { - throw new Error('Protocol "' + params.protocol + '" not supported. Expected "https:"') - } - return params -} +var RECHOKE_INTERVAL = 10000 // 10 seconds +var RECHOKE_OPTIMISTIC_DURATION = 2 // 30 seconds -},{"http":185,"url":191}],164:[function(require,module,exports){ -arguments[4][56][0].apply(exports,arguments) -},{"dup":56}],165:[function(require,module,exports){ -arguments[4][58][0].apply(exports,arguments) -},{"dup":58}],166:[function(require,module,exports){ -/*! - * Determine if an object is a Buffer - * - * @author Feross Aboukhadijeh - * @license MIT - */ +var FILESYSTEM_CONCURRENCY = 2 -// The _isBuffer check is for Safari 5-7 support, because it's missing -// Object.prototype.constructor. Remove this eventually -module.exports = function (obj) { - return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) -} +var RECONNECT_WAIT = [ 1000, 5000, 15000 ] -function isBuffer (obj) { - return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) -} +var VERSION = require('../package.json').version +var USER_AGENT = 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' -// For Node v0.10 support. Remove this eventually. -function isSlowBuffer (obj) { - return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) +var TMP +try { + TMP = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent') +} catch (err) { + TMP = path.join(typeof os.tmpdir === 'function' ? os.tmpdir() : '/', 'webtorrent') } -},{}],167:[function(require,module,exports){ -arguments[4][62][0].apply(exports,arguments) -},{"dup":62}],168:[function(require,module,exports){ -(function (process){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. +inherits(Torrent, EventEmitter) -// resolves . and .. elements in a path array with directory names there -// must be no slashes, empty elements, or device names (c:\) in the array -// (so also no leading and trailing slashes - it does not distinguish -// relative and absolute paths) -function normalizeArray(parts, allowAboveRoot) { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length - 1; i >= 0; i--) { - var last = parts[i]; - if (last === '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } +function Torrent (torrentId, client, opts) { + EventEmitter.call(this) - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up--; up) { - parts.unshift('..'); - } - } + this._debugId = 'unknown infohash' + this.client = client - return parts; -} + this.announce = opts.announce + this.urlList = opts.urlList -// Split a filename into [root, dir, basename, ext], unix version -// 'root' is just a slash, or nothing. -var splitPathRe = - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; -var splitPath = function(filename) { - return splitPathRe.exec(filename).slice(1); -}; + this.path = opts.path + this._store = opts.store || FSChunkStore + this._getAnnounceOpts = opts.getAnnounceOpts -// path.resolve([from ...], to) -// posix version -exports.resolve = function() { - var resolvedPath = '', - resolvedAbsolute = false; + this.strategy = opts.strategy || 'sequential' - for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) ? arguments[i] : process.cwd(); + this.maxWebConns = opts.maxWebConns || 4 - // Skip empty and invalid entries - if (typeof path !== 'string') { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!path) { - continue; - } + this._rechokeNumSlots = (opts.uploads === false || opts.uploads === 0) + ? 0 + : (+opts.uploads || 10) + this._rechokeOptimisticWire = null + this._rechokeOptimisticTime = 0 + this._rechokeIntervalId = null - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path.charAt(0) === '/'; - } + this.ready = false + this.destroyed = false + this.paused = false + this.done = false - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) + this.metadata = null + this.store = null + this.files = [] + this.pieces = [] - // Normalize the path - resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { - return !!p; - }), !resolvedAbsolute).join('/'); + this._amInterested = false + this._selections = [] + this._critical = [] - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; -}; + this.wires = [] // open wires (added *after* handshake) -// path.normalize(path) -// posix version -exports.normalize = function(path) { - var isAbsolute = exports.isAbsolute(path), - trailingSlash = substr(path, -1) === '/'; + this._queue = [] // queue of outgoing tcp peers to connect to + this._peers = {} // connected peers (addr/peerId -> Peer) + this._peersLength = 0 // number of elements in `this._peers` (cache, for perf) + + // stats + this.received = 0 + this.uploaded = 0 + this._downloadSpeed = speedometer() + this._uploadSpeed = speedometer() + + // for cleanup + this._servers = [] + this._xsRequests = [] + + // TODO: remove this and expose a hook instead + // optimization: don't recheck every file if it hasn't changed + this._fileModtimes = opts.fileModtimes + + if (torrentId !== null) this._onTorrentId(torrentId) - // Normalize the path - path = normalizeArray(filter(path.split('/'), function(p) { - return !!p; - }), !isAbsolute).join('/'); + this._debug('new torrent') +} - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; +Object.defineProperty(Torrent.prototype, 'timeRemaining', { + get: function () { + if (this.done) return 0 + if (this.downloadSpeed === 0) return Infinity + return ((this.length - this.downloaded) / this.downloadSpeed) * 1000 } +}) - return (isAbsolute ? '/' : '') + path; -}; +Object.defineProperty(Torrent.prototype, 'downloaded', { + get: function () { + if (!this.bitfield) return 0 + var downloaded = 0 + for (var index = 0, len = this.pieces.length; index < len; ++index) { + if (this.bitfield.get(index)) { // verified data + downloaded += (index === len - 1) ? this.lastPieceLength : this.pieceLength + } else { // "in progress" data + var piece = this.pieces[index] + downloaded += (piece.length - piece.missing) + } + } + return downloaded + } +}) -// posix version -exports.isAbsolute = function(path) { - return path.charAt(0) === '/'; -}; +// TODO: re-enable this. The number of missing pieces. Used to implement 'end game' mode. +// Object.defineProperty(Storage.prototype, 'numMissing', { +// get: function () { +// var self = this +// var numMissing = self.pieces.length +// for (var index = 0, len = self.pieces.length; index < len; index++) { +// numMissing -= self.bitfield.get(index) +// } +// return numMissing +// } +// }) -// posix version -exports.join = function() { - var paths = Array.prototype.slice.call(arguments, 0); - return exports.normalize(filter(paths, function(p, index) { - if (typeof p !== 'string') { - throw new TypeError('Arguments to path.join must be strings'); - } - return p; - }).join('/')); -}; +Object.defineProperty(Torrent.prototype, 'downloadSpeed', { + get: function () { return this._downloadSpeed() } +}) +Object.defineProperty(Torrent.prototype, 'uploadSpeed', { + get: function () { return this._uploadSpeed() } +}) -// path.relative(from, to) -// posix version -exports.relative = function(from, to) { - from = exports.resolve(from).substr(1); - to = exports.resolve(to).substr(1); +Object.defineProperty(Torrent.prototype, 'progress', { + get: function () { return this.length ? this.downloaded / this.length : 0 } +}) - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } +Object.defineProperty(Torrent.prototype, 'ratio', { + get: function () { return this.uploaded / (this.received || 1) } +}) - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } +Object.defineProperty(Torrent.prototype, 'numPeers', { + get: function () { return this.wires.length } +}) - if (start > end) return []; - return arr.slice(start, end - start + 1); +Object.defineProperty(Torrent.prototype, 'torrentFileBlobURL', { + get: function () { + if (typeof window === 'undefined') throw new Error('browser-only property') + if (!this.torrentFile) return null + return URL.createObjectURL( + new Blob([ this.torrentFile ], { type: 'application/x-bittorrent' }) + ) } +}) - var fromParts = trim(from.split('/')); - var toParts = trim(to.split('/')); +Object.defineProperty(Torrent.prototype, '_numQueued', { + get: function () { + return this._queue.length + (this._peersLength - this._numConns) + } +}) - var length = Math.min(fromParts.length, toParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (fromParts[i] !== toParts[i]) { - samePartsLength = i; - break; +Object.defineProperty(Torrent.prototype, '_numConns', { + get: function () { + var self = this + var numConns = 0 + for (var id in self._peers) { + if (self._peers[id].connected) numConns += 1 } + return numConns } +}) - var outputParts = []; - for (var i = samePartsLength; i < fromParts.length; i++) { - outputParts.push('..'); +// TODO: remove in v1 +Object.defineProperty(Torrent.prototype, 'swarm', { + get: function () { + console.warn('WebTorrent: `torrent.swarm` is deprecated. Use `torrent` directly instead.') + return this } +}) - outputParts = outputParts.concat(toParts.slice(samePartsLength)); +Torrent.prototype._onTorrentId = function (torrentId) { + var self = this + if (self.destroyed) return - return outputParts.join('/'); -}; + var parsedTorrent + try { parsedTorrent = parseTorrent(torrentId) } catch (err) {} + if (parsedTorrent) { + // Attempt to set infoHash property synchronously + self.infoHash = parsedTorrent.infoHash + self._debugId = parsedTorrent.infoHash.toString('hex').substring(0, 7) + process.nextTick(function () { + if (self.destroyed) return + self._onParsedTorrent(parsedTorrent) + }) + } else { + // If torrentId failed to parse, it could be in a form that requires an async + // operation, i.e. http/https link, filesystem path, or Blob. + parseTorrent.remote(torrentId, function (err, parsedTorrent) { + if (self.destroyed) return + if (err) return self._destroy(err) + self._onParsedTorrent(parsedTorrent) + }) + } +} -exports.sep = '/'; -exports.delimiter = ':'; +Torrent.prototype._onParsedTorrent = function (parsedTorrent) { + var self = this + if (self.destroyed) return -exports.dirname = function(path) { - var result = splitPath(path), - root = result[0], - dir = result[1]; + self._processParsedTorrent(parsedTorrent) - if (!root && !dir) { - // No dirname whatsoever - return '.'; + if (!self.infoHash) { + return self._destroy(new Error('Malformed torrent data: No info hash')) } - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); - } + if (!self.path) self.path = path.join(TMP, self.infoHash) - return root + dir; -}; + self._rechokeIntervalId = setInterval(function () { + self._rechoke() + }, RECHOKE_INTERVAL) + if (self._rechokeIntervalId.unref) self._rechokeIntervalId.unref() + + // Private 'infoHash' event allows client.add to check for duplicate torrents and + // destroy them before the normal 'infoHash' event is emitted. Prevents user + // applications from needing to deal with duplicate 'infoHash' events. + self.emit('_infoHash', self.infoHash) + if (self.destroyed) return + self.emit('infoHash', self.infoHash) + if (self.destroyed) return // user might destroy torrent in event handler -exports.basename = function(path, ext) { - var f = splitPath(path)[2]; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); + if (self.client.listening) { + self._onListening() + } else { + self.client.once('listening', function () { + self._onListening() + }) } - return f; -}; - +} -exports.extname = function(path) { - return splitPath(path)[3]; -}; +Torrent.prototype._processParsedTorrent = function (parsedTorrent) { + this._debugId = parsedTorrent.infoHash.toString('hex').substring(0, 7) -function filter (xs, f) { - if (xs.filter) return xs.filter(f); - var res = []; - for (var i = 0; i < xs.length; i++) { - if (f(xs[i], i, xs)) res.push(xs[i]); - } - return res; -} + if (this.announce) { + // Allow specifying trackers via `opts` parameter + parsedTorrent.announce = parsedTorrent.announce.concat(this.announce) + } -// String.prototype.substr - negative index don't work in IE8 -var substr = 'ab'.substr(-1) === 'b' - ? function (str, start, len) { return str.substr(start, len) } - : function (str, start, len) { - if (start < 0) start = str.length + start; - return str.substr(start, len); - } -; + if (this.client.tracker && global.WEBTORRENT_ANNOUNCE && !this.private) { + // So `webtorrent-hybrid` can force specific trackers to be used + parsedTorrent.announce = parsedTorrent.announce.concat(global.WEBTORRENT_ANNOUNCE) + } -}).call(this,require('_process')) -},{"_process":170}],169:[function(require,module,exports){ -arguments[4][79][0].apply(exports,arguments) -},{"_process":170,"dup":79}],170:[function(require,module,exports){ -// shim for using process in browser -var process = module.exports = {}; + if (this.urlList) { + // Allow specifying web seeds via `opts` parameter + parsedTorrent.urlList = parsedTorrent.urlList.concat(this.urlList) + } -// cached from whatever global is present so that test runners that stub it -// don't break things. But we need to wrap it in a try catch in case it is -// wrapped in strict mode code which doesn't define any globals. It's inside a -// function because try/catches deoptimize in certain engines. + uniq(parsedTorrent.announce) + uniq(parsedTorrent.urlList) -var cachedSetTimeout; -var cachedClearTimeout; + extendMutable(this, parsedTorrent) -function defaultSetTimout() { - throw new Error('setTimeout has not been defined'); -} -function defaultClearTimeout () { - throw new Error('clearTimeout has not been defined'); + this.magnetURI = parseTorrent.toMagnetURI(parsedTorrent) + this.torrentFile = parseTorrent.toTorrentFile(parsedTorrent) } -(function () { - try { - if (typeof setTimeout === 'function') { - cachedSetTimeout = setTimeout; - } else { - cachedSetTimeout = defaultSetTimout; + +Torrent.prototype._onListening = function () { + var self = this + if (self.discovery || self.destroyed) return + + var trackerOpts = self.client.tracker + if (trackerOpts) { + trackerOpts = extend(self.client.tracker, { + getAnnounceOpts: function () { + var opts = { + uploaded: self.uploaded, + downloaded: self.downloaded, + left: Math.max(self.length - self.downloaded, 0) } - } catch (e) { - cachedSetTimeout = defaultSetTimout; - } - try { - if (typeof clearTimeout === 'function') { - cachedClearTimeout = clearTimeout; - } else { - cachedClearTimeout = defaultClearTimeout; + if (self.client.tracker.getAnnounceOpts) { + extendMutable(opts, self.client.tracker.getAnnounceOpts()) } - } catch (e) { - cachedClearTimeout = defaultClearTimeout; - } -} ()) -function runTimeout(fun) { - if (cachedSetTimeout === setTimeout) { - //normal enviroments in sane situations - return setTimeout(fun, 0); - } - // if setTimeout wasn't available but was latter defined - if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { - cachedSetTimeout = setTimeout; - return setTimeout(fun, 0); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedSetTimeout(fun, 0); - } catch(e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedSetTimeout.call(null, fun, 0); - } catch(e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error - return cachedSetTimeout.call(this, fun, 0); + if (self._getAnnounceOpts) { + // TODO: consider deprecating this, as it's redundant with the former case + extendMutable(opts, self._getAnnounceOpts()) } - } + return opts + } + }) + } + // begin discovering peers via DHT and trackers + self.discovery = new Discovery({ + infoHash: self.infoHash, + announce: self.announce, + peerId: self.client.peerId, + dht: !self.private && self.client.dht, + tracker: trackerOpts, + port: self.client.torrentPort, + userAgent: USER_AGENT + }) -} -function runClearTimeout(marker) { - if (cachedClearTimeout === clearTimeout) { - //normal enviroments in sane situations - return clearTimeout(marker); - } - // if clearTimeout wasn't available but was latter defined - if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { - cachedClearTimeout = clearTimeout; - return clearTimeout(marker); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedClearTimeout(marker); - } catch (e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedClearTimeout.call(null, marker); - } catch (e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. - // Some versions of I.E. have different rules for clearTimeout vs setTimeout - return cachedClearTimeout.call(this, marker); - } - } + self.discovery.on('error', onError) + self.discovery.on('peer', onPeer) + self.discovery.on('trackerAnnounce', onTrackerAnnounce) + self.discovery.on('dhtAnnounce', onDHTAnnounce) + self.discovery.on('warning', onWarning) + function onError (err) { + self._destroy(err) + } + function onPeer (peer) { + // Don't create new outgoing TCP connections when torrent is done + if (typeof peer === 'string' && self.done) return + self.addPeer(peer) + } -} -var queue = []; -var draining = false; -var currentQueue; -var queueIndex = -1; + function onTrackerAnnounce () { + self.emit('trackerAnnounce') + if (self.numPeers === 0) self.emit('noPeers', 'tracker') + } -function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } + function onDHTAnnounce () { + self.emit('dhtAnnounce') + if (self.numPeers === 0) self.emit('noPeers', 'dht') + } + + function onWarning (err) { + self.emit('warning', err) + } + + if (self.info) { + // if full metadata was included in initial torrent id, use it immediately. Otherwise, + // wait for torrent-discovery to find peers and ut_metadata to get the metadata. + self._onMetadata(self) + } else if (self.xs) { + self._getMetadataFromServer() + } } -function drainQueue() { - if (draining) { - return; +Torrent.prototype._getMetadataFromServer = function () { + var self = this + var urls = Array.isArray(self.xs) ? self.xs : [ self.xs ] + + var tasks = urls.map(function (url) { + return function (cb) { + getMetadataFromURL(url, cb) } - var timeout = runTimeout(cleanUpNextTick); - draining = true; + }) + parallel(tasks) - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } - } - queueIndex = -1; - len = queue.length; + function getMetadataFromURL (url, cb) { + if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { + self.emit('warning', new Error('skipping non-http xs param: ' + url)) + return cb(null) } - currentQueue = null; - draining = false; - runClearTimeout(timeout); -} -process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } + var opts = { + url: url, + method: 'GET', + headers: { + 'user-agent': USER_AGENT + } } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - runTimeout(drainQueue); + var req + try { + req = get.concat(opts, onResponse) + } catch (err) { + self.emit('warning', new Error('skipping invalid url xs param: ' + url)) + return cb(null) } -}; -// v8 likes predictible objects -function Item(fun, array) { - this.fun = fun; - this.array = array; -} -Item.prototype.run = function () { - this.fun.apply(null, this.array); -}; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; + self._xsRequests.push(req) -function noop() {} + function onResponse (err, res, torrent) { + if (self.destroyed) return cb(null) + if (self.metadata) return cb(null) -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; -process.prependListener = noop; -process.prependOnceListener = noop; + if (err) { + self.emit('warning', new Error('http error from xs param: ' + url)) + return cb(null) + } + if (res.statusCode !== 200) { + self.emit('warning', new Error('non-200 status code ' + res.statusCode + ' from xs param: ' + url)) + return cb(null) + } -process.listeners = function (name) { return [] } + var parsedTorrent + try { + parsedTorrent = parseTorrent(torrent) + } catch (err) {} -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; + if (!parsedTorrent) { + self.emit('warning', new Error('got invalid torrent file from xs param: ' + url)) + return cb(null) + } -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; -process.umask = function() { return 0; }; + if (parsedTorrent.infoHash !== self.infoHash) { + self.emit('warning', new Error('got torrent file with incorrect info hash from xs param: ' + url)) + return cb(null) + } + + self._onMetadata(parsedTorrent) + cb(null) + } + } +} + +/** + * Called when the full torrent metadata is received. + */ +Torrent.prototype._onMetadata = function (metadata) { + var self = this + if (self.metadata || self.destroyed) return + self._debug('got metadata') -},{}],171:[function(require,module,exports){ -(function (global){ -/*! https://mths.be/punycode v1.4.1 by @mathias */ -;(function(root) { + self._xsRequests.forEach(function (req) { + req.abort() + }) + self._xsRequests = [] - /** Detect free variables */ - var freeExports = typeof exports == 'object' && exports && - !exports.nodeType && exports; - var freeModule = typeof module == 'object' && module && - !module.nodeType && module; - var freeGlobal = typeof global == 'object' && global; - if ( - freeGlobal.global === freeGlobal || - freeGlobal.window === freeGlobal || - freeGlobal.self === freeGlobal - ) { - root = freeGlobal; - } + var parsedTorrent + if (metadata && metadata.infoHash) { + // `metadata` is a parsed torrent (from parse-torrent module) + parsedTorrent = metadata + } else { + try { + parsedTorrent = parseTorrent(metadata) + } catch (err) { + return self._destroy(err) + } + } - /** - * The `punycode` object. - * @name punycode - * @type Object - */ - var punycode, + self._processParsedTorrent(parsedTorrent) + self.metadata = self.torrentFile - /** Highest positive signed 32-bit float value */ - maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + // add web seed urls (BEP19) + if (self.client.enableWebSeeds) { + self.urlList.forEach(function (url) { + self.addWebSeed(url) + }) + } - /** Bootstring parameters */ - base = 36, - tMin = 1, - tMax = 26, - skew = 38, - damp = 700, - initialBias = 72, - initialN = 128, // 0x80 - delimiter = '-', // '\x2D' + // start off selecting the entire torrent with low priority + if (self.pieces.length !== 0) { + self.select(0, self.pieces.length - 1, false) + } - /** Regular expressions */ - regexPunycode = /^xn--/, - regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars - regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + self._rarityMap = new RarityMap(self) - /** Error messages */ - errors = { - 'overflow': 'Overflow: input needs wider integers to process', - 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', - 'invalid-input': 'Invalid input' - }, + self.store = new ImmediateChunkStore( + new self._store(self.pieceLength, { + torrent: { + infoHash: self.infoHash + }, + files: self.files.map(function (file) { + return { + path: path.join(self.path, file.path), + length: file.length, + offset: file.offset + } + }), + length: self.length + }) + ) - /** Convenience shortcuts */ - baseMinusTMin = base - tMin, - floor = Math.floor, - stringFromCharCode = String.fromCharCode, + self.files = self.files.map(function (file) { + return new File(self, file) + }) - /** Temporary variable */ - key; + self._hashes = self.pieces - /*--------------------------------------------------------------------------*/ + self.pieces = self.pieces.map(function (hash, i) { + var pieceLength = (i === self.pieces.length - 1) + ? self.lastPieceLength + : self.pieceLength + return new Piece(pieceLength) + }) - /** - * A generic error utility function. - * @private - * @param {String} type The error type. - * @returns {Error} Throws a `RangeError` with the applicable error message. - */ - function error(type) { - throw new RangeError(errors[type]); - } + self._reservations = self.pieces.map(function () { + return [] + }) - /** - * A generic `Array#map` utility function. - * @private - * @param {Array} array The array to iterate over. - * @param {Function} callback The function that gets called for every array - * item. - * @returns {Array} A new array of values returned by the callback function. - */ - function map(array, fn) { - var length = array.length; - var result = []; - while (length--) { - result[length] = fn(array[length]); - } - return result; - } + self.bitfield = new BitField(self.pieces.length) - /** - * A simple `Array#map`-like wrapper to work with domain name strings or email - * addresses. - * @private - * @param {String} domain The domain name or email address. - * @param {Function} callback The function that gets called for every - * character. - * @returns {Array} A new string of characters returned by the callback - * function. - */ - function mapDomain(string, fn) { - var parts = string.split('@'); - var result = ''; - if (parts.length > 1) { - // In email addresses, only the domain name should be punycoded. Leave - // the local part (i.e. everything up to `@`) intact. - result = parts[0] + '@'; - string = parts[1]; - } - // Avoid `split(regex)` for IE8 compatibility. See #17. - string = string.replace(regexSeparators, '\x2E'); - var labels = string.split('.'); - var encoded = map(labels, fn).join('.'); - return result + encoded; - } + self.wires.forEach(function (wire) { + // If we didn't have the metadata at the time ut_metadata was initialized for this + // wire, we still want to make it available to the peer in case they request it. + if (wire.ut_metadata) wire.ut_metadata.setMetadata(self.metadata) - /** - * Creates an array containing the numeric code points of each Unicode - * character in the string. While JavaScript uses UCS-2 internally, - * this function will convert a pair of surrogate halves (each of which - * UCS-2 exposes as separate characters) into a single code point, - * matching UTF-16. - * @see `punycode.ucs2.encode` - * @see - * @memberOf punycode.ucs2 - * @name decode - * @param {String} string The Unicode input string (UCS-2). - * @returns {Array} The new array of code points. - */ - function ucs2decode(string) { - var output = [], - counter = 0, - length = string.length, - value, - extra; - while (counter < length) { - value = string.charCodeAt(counter++); - if (value >= 0xD800 && value <= 0xDBFF && counter < length) { - // high surrogate, and there is a next character - extra = string.charCodeAt(counter++); - if ((extra & 0xFC00) == 0xDC00) { // low surrogate - output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); - } else { - // unmatched surrogate; only append this code unit, in case the next - // code unit is the high surrogate of a surrogate pair - output.push(value); - counter--; - } - } else { - output.push(value); - } - } - return output; - } + self._onWireWithMetadata(wire) + }) - /** - * Creates a string based on an array of numeric code points. - * @see `punycode.ucs2.decode` - * @memberOf punycode.ucs2 - * @name encode - * @param {Array} codePoints The array of numeric code points. - * @returns {String} The new Unicode string (UCS-2). - */ - function ucs2encode(array) { - return map(array, function(value) { - var output = ''; - if (value > 0xFFFF) { - value -= 0x10000; - output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); - value = 0xDC00 | value & 0x3FF; - } - output += stringFromCharCode(value); - return output; - }).join(''); - } + self._debug('verifying existing torrent data') + if (self._fileModtimes && self._store === FSChunkStore) { + // don't verify if the files haven't been modified since we last checked + self.getFileModtimes(function (err, fileModtimes) { + if (err) return self._destroy(err) - /** - * Converts a basic code point into a digit/integer. - * @see `digitToBasic()` - * @private - * @param {Number} codePoint The basic numeric code point value. - * @returns {Number} The numeric value of a basic code point (for use in - * representing integers) in the range `0` to `base - 1`, or `base` if - * the code point does not represent a value. - */ - function basicToDigit(codePoint) { - if (codePoint - 48 < 10) { - return codePoint - 22; - } - if (codePoint - 65 < 26) { - return codePoint - 65; - } - if (codePoint - 97 < 26) { - return codePoint - 97; - } - return base; - } + var unchanged = self.files.map(function (_, index) { + return fileModtimes[index] === self._fileModtimes[index] + }).every(function (x) { + return x + }) + + if (unchanged) { + for (var index = 0; index < self.pieces.length; index++) { + self._markVerified(index) + } + self._onStore() + } else { + self._verifyPieces() + } + }) + } else { + self._verifyPieces() + } - /** - * Converts a digit/integer into a basic code point. - * @see `basicToDigit()` - * @private - * @param {Number} digit The numeric value of a basic code point. - * @returns {Number} The basic code point whose value (when used for - * representing integers) is `digit`, which needs to be in the range - * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is - * used; else, the lowercase form is used. The behavior is undefined - * if `flag` is non-zero and `digit` has no uppercase form. - */ - function digitToBasic(digit, flag) { - // 0..25 map to ASCII a..z or A..Z - // 26..35 map to ASCII 0..9 - return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); - } + self.emit('metadata') +} - /** - * Bias adaptation function as per section 3.4 of RFC 3492. - * https://tools.ietf.org/html/rfc3492#section-3.4 - * @private - */ - function adapt(delta, numPoints, firstTime) { - var k = 0; - delta = firstTime ? floor(delta / damp) : delta >> 1; - delta += floor(delta / numPoints); - for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { - delta = floor(delta / baseMinusTMin); - } - return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); - } +/* + * TODO: remove this + * Gets the last modified time of every file on disk for this torrent. + * Only valid in Node, not in the browser. + */ +Torrent.prototype.getFileModtimes = function (cb) { + var self = this + var ret = [] + parallelLimit(self.files.map(function (file, index) { + return function (cb) { + fs.stat(path.join(self.path, file.path), function (err, stat) { + if (err && err.code !== 'ENOENT') return cb(err) + ret[index] = stat && stat.mtime.getTime() + cb(null) + }) + } + }), FILESYSTEM_CONCURRENCY, function (err) { + self._debug('done getting file modtimes') + cb(err, ret) + }) +} - /** - * Converts a Punycode string of ASCII-only symbols to a string of Unicode - * symbols. - * @memberOf punycode - * @param {String} input The Punycode string of ASCII-only symbols. - * @returns {String} The resulting string of Unicode symbols. - */ - function decode(input) { - // Don't use UCS-2 - var output = [], - inputLength = input.length, - out, - i = 0, - n = initialN, - bias = initialBias, - basic, - j, - index, - oldi, - w, - k, - digit, - t, - /** Cached calculation results */ - baseMinusT; +Torrent.prototype._verifyPieces = function () { + var self = this + parallelLimit(self.pieces.map(function (_, index) { + return function (cb) { + if (self.destroyed) return cb(new Error('torrent is destroyed')) - // Handle the basic code points: let `basic` be the number of input code - // points before the last delimiter, or `0` if there is none, then copy - // the first basic code points to the output. + self.store.get(index, function (err, buf) { + if (self.destroyed) return cb(new Error('torrent is destroyed')) - basic = input.lastIndexOf(delimiter); - if (basic < 0) { - basic = 0; - } + if (err) return process.nextTick(cb, null) // ignore error + sha1(buf, function (hash) { + if (self.destroyed) return cb(new Error('torrent is destroyed')) - for (j = 0; j < basic; ++j) { - // if it's not a basic code point - if (input.charCodeAt(j) >= 0x80) { - error('not-basic'); - } - output.push(input.charCodeAt(j)); - } + if (hash === self._hashes[index]) { + if (!self.pieces[index]) return + self._debug('piece verified %s', index) + self._markVerified(index) + } else { + self._debug('piece invalid %s', index) + } + cb(null) + }) + }) + } + }), FILESYSTEM_CONCURRENCY, function (err) { + if (err) return self._destroy(err) + self._debug('done verifying') + self._onStore() + }) +} - // Main decoding loop: start just after the last delimiter if any basic code - // points were copied; start at the beginning otherwise. +Torrent.prototype._markVerified = function (index) { + this.pieces[index] = null + this._reservations[index] = null + this.bitfield.set(index, true) +} - for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { +/** + * Called when the metadata, listening server, and underlying chunk store is initialized. + */ +Torrent.prototype._onStore = function () { + var self = this + if (self.destroyed) return + self._debug('on store') - // `index` is the index of the next character to be consumed. - // Decode a generalized variable-length integer into `delta`, - // which gets added to `i`. The overflow checking is easier - // if we increase `i` as we go, then subtract off its starting - // value at the end to obtain `delta`. - for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + self.ready = true + self.emit('ready') - if (index >= inputLength) { - error('invalid-input'); - } + // Files may start out done if the file was already in the store + self._checkDone() - digit = basicToDigit(input.charCodeAt(index++)); + // In case any selections were made before torrent was ready + self._updateSelections() +} - if (digit >= base || digit > floor((maxInt - i) / w)) { - error('overflow'); - } +Torrent.prototype.destroy = function (cb) { + var self = this + self._destroy(null, cb) +} - i += digit * w; - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); +Torrent.prototype._destroy = function (err, cb) { + var self = this + if (self.destroyed) return + self.destroyed = true + self._debug('destroy') - if (digit < t) { - break; - } + self.client._remove(self) - baseMinusT = base - t; - if (w > floor(maxInt / baseMinusT)) { - error('overflow'); - } + clearInterval(self._rechokeIntervalId) - w *= baseMinusT; + self._xsRequests.forEach(function (req) { + req.abort() + }) - } + if (self._rarityMap) { + self._rarityMap.destroy() + } - out = output.length + 1; - bias = adapt(i - oldi, out, oldi == 0); + for (var id in self._peers) { + self.removePeer(id) + } - // `i` was supposed to wrap around from `out` to `0`, - // incrementing `n` each time, so we'll fix that now: - if (floor(i / out) > maxInt - n) { - error('overflow'); - } + self.files.forEach(function (file) { + if (file instanceof File) file._destroy() + }) - n += floor(i / out); - i %= out; + var tasks = self._servers.map(function (server) { + return function (cb) { + server.destroy(cb) + } + }) - // Insert `n` at position `i` of the output - output.splice(i++, 0, n); + if (self.discovery) { + tasks.push(function (cb) { + self.discovery.destroy(cb) + }) + } - } + if (self.store) { + tasks.push(function (cb) { + self.store.close(cb) + }) + } - return ucs2encode(output); - } + parallel(tasks, cb) - /** - * Converts a string of Unicode symbols (e.g. a domain name label) to a - * Punycode string of ASCII-only symbols. - * @memberOf punycode - * @param {String} input The string of Unicode symbols. - * @returns {String} The resulting Punycode string of ASCII-only symbols. - */ - function encode(input) { - var n, - delta, - handledCPCount, - basicLength, - bias, - j, - m, - q, - k, - t, - currentValue, - output = [], - /** `inputLength` will hold the number of code points in `input`. */ - inputLength, - /** Cached calculation results */ - handledCPCountPlusOne, - baseMinusT, - qMinusT; + if (err) { + // Torrent errors are emitted at `torrent.on('error')`. If there are no 'error' + // event handlers on the torrent instance, then the error will be emitted at + // `client.on('error')`. This prevents throwing an uncaught exception + // (unhandled 'error' event), but it makes it impossible to distinguish client + // errors versus torrent errors. Torrent errors are not fatal, and the client + // is still usable afterwards. Therefore, always listen for errors in both + // places (`client.on('error')` and `torrent.on('error')`). + if (self.listenerCount('error') === 0) { + self.client.emit('error', err) + } else { + self.emit('error', err) + } + } - // Convert the input in UCS-2 to Unicode - input = ucs2decode(input); + self.emit('close') - // Cache the length - inputLength = input.length; + self.client = null + self.files = [] + self.discovery = null + self.store = null + self._rarityMap = null + self._peers = null + self._servers = null + self._xsRequests = null +} - // Initialize the state - n = initialN; - delta = 0; - bias = initialBias; +Torrent.prototype.addPeer = function (peer) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') + if (!self.infoHash) throw new Error('addPeer() must not be called before the `infoHash` event') - // Handle the basic code points - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue < 0x80) { - output.push(stringFromCharCode(currentValue)); - } - } + if (self.client.blocked) { + var host + if (typeof peer === 'string') { + var parts + try { + parts = addrToIPPort(peer) + } catch (e) { + self._debug('ignoring peer: invalid %s', peer) + self.emit('invalidPeer', peer) + return false + } + host = parts[0] + } else if (typeof peer.remoteAddress === 'string') { + host = peer.remoteAddress + } - handledCPCount = basicLength = output.length; + if (host && self.client.blocked.contains(host)) { + self._debug('ignoring peer: blocked %s', peer) + if (typeof peer !== 'string') peer.destroy() + self.emit('blockedPeer', peer) + return false + } + } - // `handledCPCount` is the number of code points that have been handled; - // `basicLength` is the number of basic code points. + var wasAdded = !!self._addPeer(peer) + if (wasAdded) { + self.emit('peer', peer) + } else { + self.emit('invalidPeer', peer) + } + return wasAdded +} - // Finish the basic string - if it is not empty - with a delimiter - if (basicLength) { - output.push(delimiter); - } +Torrent.prototype._addPeer = function (peer) { + var self = this + if (self.destroyed) { + if (typeof peer !== 'string') peer.destroy() + return null + } + if (typeof peer === 'string' && !self._validAddr(peer)) { + self._debug('ignoring peer: invalid %s', peer) + return null + } - // Main encoding loop: - while (handledCPCount < inputLength) { + var id = (peer && peer.id) || peer + if (self._peers[id]) { + self._debug('ignoring peer: duplicate (%s)', id) + if (typeof peer !== 'string') peer.destroy() + return null + } - // All non-basic code points < n have been handled already. Find the next - // larger one: - for (m = maxInt, j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue >= n && currentValue < m) { - m = currentValue; - } - } + if (self.paused) { + self._debug('ignoring peer: torrent is paused') + if (typeof peer !== 'string') peer.destroy() + return null + } - // Increase `delta` enough to advance the decoder's state to , - // but guard against overflow - handledCPCountPlusOne = handledCPCount + 1; - if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { - error('overflow'); - } + self._debug('add peer %s', id) - delta += (m - n) * handledCPCountPlusOne; - n = m; + var newPeer + if (typeof peer === 'string') { + // `peer` is an addr ("ip:port" string) + newPeer = Peer.createTCPOutgoingPeer(peer, self) + } else { + // `peer` is a WebRTC connection (simple-peer) + newPeer = Peer.createWebRTCPeer(peer, self) + } - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; + self._peers[newPeer.id] = newPeer + self._peersLength += 1 - if (currentValue < n && ++delta > maxInt) { - error('overflow'); - } + if (typeof peer === 'string') { + // `peer` is an addr ("ip:port" string) + self._queue.push(newPeer) + self._drain() + } - if (currentValue == n) { - // Represent delta as a generalized variable-length integer - for (q = delta, k = base; /* no condition */; k += base) { - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - if (q < t) { - break; - } - qMinusT = q - t; - baseMinusT = base - t; - output.push( - stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) - ); - q = floor(qMinusT / baseMinusT); - } + return newPeer +} - output.push(stringFromCharCode(digitToBasic(q, 0))); - bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); - delta = 0; - ++handledCPCount; - } - } +Torrent.prototype.addWebSeed = function (url) { + if (this.destroyed) throw new Error('torrent is destroyed') - ++delta; - ++n; + if (!/^https?:\/\/.+/.test(url)) { + this.emit('warning', new Error('ignoring invalid web seed: ' + url)) + this.emit('invalidPeer', url) + return + } - } - return output.join(''); - } + if (this._peers[url]) { + this.emit('warning', new Error('ignoring duplicate web seed: ' + url)) + this.emit('invalidPeer', url) + return + } - /** - * Converts a Punycode string representing a domain name or an email address - * to Unicode. Only the Punycoded parts of the input will be converted, i.e. - * it doesn't matter if you call it on a string that has already been - * converted to Unicode. - * @memberOf punycode - * @param {String} input The Punycoded domain name or email address to - * convert to Unicode. - * @returns {String} The Unicode representation of the given Punycode - * string. - */ - function toUnicode(input) { - return mapDomain(input, function(string) { - return regexPunycode.test(string) - ? decode(string.slice(4).toLowerCase()) - : string; - }); - } + this._debug('add web seed %s', url) - /** - * Converts a Unicode string representing a domain name or an email address to - * Punycode. Only the non-ASCII parts of the domain name will be converted, - * i.e. it doesn't matter if you call it with a domain that's already in - * ASCII. - * @memberOf punycode - * @param {String} input The domain name or email address to convert, as a - * Unicode string. - * @returns {String} The Punycode representation of the given domain name or - * email address. - */ - function toASCII(input) { - return mapDomain(input, function(string) { - return regexNonASCII.test(string) - ? 'xn--' + encode(string) - : string; - }); - } + var newPeer = Peer.createWebSeedPeer(url, this) + this._peers[newPeer.id] = newPeer + this._peersLength += 1 - /*--------------------------------------------------------------------------*/ + this.emit('peer', url) +} - /** Define the public API */ - punycode = { - /** - * A string representing the current Punycode.js version number. - * @memberOf punycode - * @type String - */ - 'version': '1.4.1', - /** - * An object of methods to convert from JavaScript's internal character - * representation (UCS-2) to Unicode code points, and back. - * @see - * @memberOf punycode - * @type Object - */ - 'ucs2': { - 'decode': ucs2decode, - 'encode': ucs2encode - }, - 'decode': decode, - 'encode': encode, - 'toASCII': toASCII, - 'toUnicode': toUnicode - }; +/** + * Called whenever a new incoming TCP peer connects to this torrent swarm. Called with a + * peer that has already sent a handshake. + */ +Torrent.prototype._addIncomingPeer = function (peer) { + var self = this + if (self.destroyed) return peer.destroy(new Error('torrent is destroyed')) + if (self.paused) return peer.destroy(new Error('torrent is paused')) + + this._debug('add incoming peer %s', peer.id) + + self._peers[peer.id] = peer + self._peersLength += 1 +} - /** Expose `punycode` */ - // Some AMD build optimizers, like r.js, check for specific condition patterns - // like the following: - if ( - typeof define == 'function' && - typeof define.amd == 'object' && - define.amd - ) { - define('punycode', function() { - return punycode; - }); - } else if (freeExports && freeModule) { - if (module.exports == freeExports) { - // in Node.js, io.js, or RingoJS v0.8.0+ - freeModule.exports = punycode; - } else { - // in Narwhal or RingoJS v0.7.0- - for (key in punycode) { - punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); - } - } - } else { - // in Rhino or a web browser - root.punycode = punycode; - } +Torrent.prototype.removePeer = function (peer) { + var self = this + var id = (peer && peer.id) || peer + peer = self._peers[id] -}(this)); + if (!peer) return -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],172:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. + this._debug('removePeer %s', id) -'use strict'; + delete self._peers[id] + self._peersLength -= 1 -// If obj.hasOwnProperty has been overridden, then calling -// obj.hasOwnProperty(prop) will break. -// See: https://github.com/joyent/node/issues/1707 -function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); + peer.destroy() + + // If torrent swarm was at capacity before, try to open a new connection now + self._drain() } -module.exports = function(qs, sep, eq, options) { - sep = sep || '&'; - eq = eq || '='; - var obj = {}; +Torrent.prototype.select = function (start, end, priority, notify) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') - if (typeof qs !== 'string' || qs.length === 0) { - return obj; + if (start < 0 || end < start || self.pieces.length <= end) { + throw new Error('invalid selection ', start, ':', end) } + priority = Number(priority) || 0 - var regexp = /\+/g; - qs = qs.split(sep); + self._debug('select %s-%s (priority %s)', start, end, priority) - var maxKeys = 1000; - if (options && typeof options.maxKeys === 'number') { - maxKeys = options.maxKeys; - } + self._selections.push({ + from: start, + to: end, + offset: 0, + priority: priority, + notify: notify || noop + }) - var len = qs.length; - // maxKeys <= 0 means that we should not limit keys count - if (maxKeys > 0 && len > maxKeys) { - len = maxKeys; - } + self._selections.sort(function (a, b) { + return b.priority - a.priority + }) - for (var i = 0; i < len; ++i) { - var x = qs[i].replace(regexp, '%20'), - idx = x.indexOf(eq), - kstr, vstr, k, v; + self._updateSelections() +} - if (idx >= 0) { - kstr = x.substr(0, idx); - vstr = x.substr(idx + 1); - } else { - kstr = x; - vstr = ''; - } +Torrent.prototype.deselect = function (start, end, priority) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') - k = decodeURIComponent(kstr); - v = decodeURIComponent(vstr); + priority = Number(priority) || 0 + self._debug('deselect %s-%s (priority %s)', start, end, priority) - if (!hasOwnProperty(obj, k)) { - obj[k] = v; - } else if (isArray(obj[k])) { - obj[k].push(v); - } else { - obj[k] = [obj[k], v]; + for (var i = 0; i < self._selections.length; ++i) { + var s = self._selections[i] + if (s.from === start && s.to === end && s.priority === priority) { + self._selections.splice(i, 1) + break } } - return obj; -}; + self._updateSelections() +} -var isArray = Array.isArray || function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; -}; +Torrent.prototype.critical = function (start, end) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') -},{}],173:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. + self._debug('critical %s-%s', start, end) -'use strict'; + for (var i = start; i <= end; ++i) { + self._critical[i] = true + } -var stringifyPrimitive = function(v) { - switch (typeof v) { - case 'string': - return v; + self._updateSelections() +} - case 'boolean': - return v ? 'true' : 'false'; +Torrent.prototype._onWire = function (wire, addr) { + var self = this + self._debug('got wire %s (%s)', wire._debugId, addr || 'Unknown') - case 'number': - return isFinite(v) ? v : ''; + wire.on('download', function (downloaded) { + if (self.destroyed) return + self.received += downloaded + self._downloadSpeed(downloaded) + self.client._downloadSpeed(downloaded) + self.emit('download', downloaded) + self.client.emit('download', downloaded) + }) - default: - return ''; + wire.on('upload', function (uploaded) { + if (self.destroyed) return + self.uploaded += uploaded + self._uploadSpeed(uploaded) + self.client._uploadSpeed(uploaded) + self.emit('upload', uploaded) + self.client.emit('upload', uploaded) + }) + + self.wires.push(wire) + + if (addr) { + // Sometimes RTCPeerConnection.getStats() doesn't return an ip:port for peers + var parts = addrToIPPort(addr) + wire.remoteAddress = parts[0] + wire.remotePort = parts[1] } -}; -module.exports = function(obj, sep, eq, name) { - sep = sep || '&'; - eq = eq || '='; - if (obj === null) { - obj = undefined; + // When peer sends PORT message, add that DHT node to routing table + if (self.client.dht && self.client.dht.listening) { + wire.on('port', function (port) { + if (self.destroyed || self.client.dht.destroyed) { + return + } + if (!wire.remoteAddress) { + return self._debug('ignoring PORT from peer with no address') + } + if (port === 0 || port > 65536) { + return self._debug('ignoring invalid PORT from peer') + } + + self._debug('port: %s (from %s)', port, addr) + self.client.dht.addNode({ host: wire.remoteAddress, port: port }) + }) + } + + wire.on('timeout', function () { + self._debug('wire timeout (%s)', addr) + // TODO: this might be destroying wires too eagerly + wire.destroy() + }) + + // Timeout for piece requests to this peer + wire.setTimeout(PIECE_TIMEOUT, true) + + // Send KEEP-ALIVE (every 60s) so peers will not disconnect the wire + wire.setKeepAlive(true) + + // use ut_metadata extension + wire.use(utMetadata(self.metadata)) + + wire.ut_metadata.on('warning', function (err) { + self._debug('ut_metadata warning: %s', err.message) + }) + + if (!self.metadata) { + wire.ut_metadata.on('metadata', function (metadata) { + self._debug('got metadata via ut_metadata') + self._onMetadata(metadata) + }) + wire.ut_metadata.fetch() } - if (typeof obj === 'object') { - return map(objectKeys(obj), function(k) { - var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; - if (isArray(obj[k])) { - return map(obj[k], function(v) { - return ks + encodeURIComponent(stringifyPrimitive(v)); - }).join(sep); - } else { - return ks + encodeURIComponent(stringifyPrimitive(obj[k])); + // use ut_pex extension if the torrent is not flagged as private + if (typeof utPex === 'function' && !self.private) { + wire.use(utPex()) + + wire.ut_pex.on('peer', function (peer) { + // Only add potential new peers when we're not seeding + if (self.done) return + self._debug('ut_pex: got peer: %s (from %s)', peer, addr) + self.addPeer(peer) + }) + + wire.ut_pex.on('dropped', function (peer) { + // the remote peer believes a given peer has been dropped from the torrent swarm. + // if we're not currently connected to it, then remove it from the queue. + var peerObj = self._peers[peer] + if (peerObj && !peerObj.connected) { + self._debug('ut_pex: dropped peer: %s (from %s)', peer, addr) + self.removePeer(peer) } - }).join(sep); + }) + wire.once('close', function () { + // Stop sending updates to remote peer + wire.ut_pex.reset() + }) } - if (!name) return ''; - return encodeURIComponent(stringifyPrimitive(name)) + eq + - encodeURIComponent(stringifyPrimitive(obj)); -}; - -var isArray = Array.isArray || function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; -}; + // Hook to allow user-defined `bittorrent-protocol` extensions + // More info: https://github.com/webtorrent/bittorrent-protocol#extension-api + self.emit('wire', wire, addr) -function map (xs, f) { - if (xs.map) return xs.map(f); - var res = []; - for (var i = 0; i < xs.length; i++) { - res.push(f(xs[i], i)); + if (self.metadata) { + process.nextTick(function () { + // This allows wire.handshake() to be called (by Peer.onHandshake) before any + // messages get sent on the wire + self._onWireWithMetadata(wire) + }) } - return res; } -var objectKeys = Object.keys || function (obj) { - var res = []; - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key); +Torrent.prototype._onWireWithMetadata = function (wire) { + var self = this + var timeoutId = null + + function onChokeTimeout () { + if (self.destroyed || wire.destroyed) return + + if (self._numQueued > 2 * (self._numConns - self.numPeers) && + wire.amInterested) { + wire.destroy() + } else { + timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) + if (timeoutId.unref) timeoutId.unref() + } } - return res; -}; -},{}],174:[function(require,module,exports){ -'use strict'; + var i + function updateSeedStatus () { + if (wire.peerPieces.buffer.length !== self.bitfield.buffer.length) return + for (i = 0; i < self.pieces.length; ++i) { + if (!wire.peerPieces.get(i)) return + } + wire.isSeeder = true + wire.choke() // always choke seeders + } -exports.decode = exports.parse = require('./decode'); -exports.encode = exports.stringify = require('./encode'); + wire.on('bitfield', function () { + updateSeedStatus() + self._update() + }) -},{"./decode":172,"./encode":173}],175:[function(require,module,exports){ -arguments[4][84][0].apply(exports,arguments) -},{"./_stream_readable":177,"./_stream_writable":179,"core-util-is":161,"dup":84,"inherits":165,"process-nextick-args":169}],176:[function(require,module,exports){ -arguments[4][85][0].apply(exports,arguments) -},{"./_stream_transform":178,"core-util-is":161,"dup":85,"inherits":165}],177:[function(require,module,exports){ -arguments[4][86][0].apply(exports,arguments) -},{"./_stream_duplex":175,"./internal/streams/BufferList":180,"./internal/streams/destroy":181,"./internal/streams/stream":182,"_process":170,"core-util-is":161,"dup":86,"events":162,"inherits":165,"isarray":167,"process-nextick-args":169,"safe-buffer":184,"string_decoder/":189,"util":158}],178:[function(require,module,exports){ -arguments[4][87][0].apply(exports,arguments) -},{"./_stream_duplex":175,"core-util-is":161,"dup":87,"inherits":165}],179:[function(require,module,exports){ -arguments[4][88][0].apply(exports,arguments) -},{"./_stream_duplex":175,"./internal/streams/destroy":181,"./internal/streams/stream":182,"_process":170,"core-util-is":161,"dup":88,"inherits":165,"process-nextick-args":169,"safe-buffer":184,"util-deprecate":193}],180:[function(require,module,exports){ -arguments[4][89][0].apply(exports,arguments) -},{"dup":89,"safe-buffer":184}],181:[function(require,module,exports){ -arguments[4][90][0].apply(exports,arguments) -},{"dup":90,"process-nextick-args":169}],182:[function(require,module,exports){ -arguments[4][91][0].apply(exports,arguments) -},{"dup":91,"events":162}],183:[function(require,module,exports){ -arguments[4][92][0].apply(exports,arguments) -},{"./lib/_stream_duplex.js":175,"./lib/_stream_passthrough.js":176,"./lib/_stream_readable.js":177,"./lib/_stream_transform.js":178,"./lib/_stream_writable.js":179,"dup":92}],184:[function(require,module,exports){ -arguments[4][98][0].apply(exports,arguments) -},{"buffer":159,"dup":98}],185:[function(require,module,exports){ -(function (global){ -var ClientRequest = require('./lib/request') -var extend = require('xtend') -var statusCodes = require('builtin-status-codes') -var url = require('url') + wire.on('have', function () { + updateSeedStatus() + self._update() + }) -var http = exports + wire.once('interested', function () { + wire.unchoke() + }) -http.request = function (opts, cb) { - if (typeof opts === 'string') - opts = url.parse(opts) - else - opts = extend(opts) + wire.once('close', function () { + clearTimeout(timeoutId) + }) - // Normally, the page is loaded from http or https, so not specifying a protocol - // will result in a (valid) protocol-relative url. However, this won't work if - // the protocol is something else, like 'file:' - var defaultProtocol = global.location.protocol.search(/^https?:$/) === -1 ? 'http:' : '' + wire.on('choke', function () { + clearTimeout(timeoutId) + timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) + if (timeoutId.unref) timeoutId.unref() + }) - var protocol = opts.protocol || defaultProtocol - var host = opts.hostname || opts.host - var port = opts.port - var path = opts.path || '/' + wire.on('unchoke', function () { + clearTimeout(timeoutId) + self._update() + }) - // Necessary for IPv6 addresses - if (host && host.indexOf(':') !== -1) - host = '[' + host + ']' + wire.on('request', function (index, offset, length, cb) { + if (length > MAX_BLOCK_LENGTH) { + // Per spec, disconnect from peers that request >128KB + return wire.destroy() + } + if (self.pieces[index]) return + self.store.get(index, { offset: offset, length: length }, cb) + }) - // This may be a relative url. The browser should always be able to interpret it correctly. - opts.url = (host ? (protocol + '//' + host) : '') + (port ? ':' + port : '') + path - opts.method = (opts.method || 'GET').toUpperCase() - opts.headers = opts.headers || {} + wire.bitfield(self.bitfield) // always send bitfield (required) + wire.interested() // always start out interested - // Also valid opts.auth, opts.mode + // Send PORT message to peers that support DHT + if (wire.peerExtensions.dht && self.client.dht && self.client.dht.listening) { + wire.port(self.client.dht.address().port) + } - var req = new ClientRequest(opts) - if (cb) - req.on('response', cb) - return req -} + if (wire.type !== 'webSeed') { // do not choke on webseeds + timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) + if (timeoutId.unref) timeoutId.unref() + } -http.get = function get (opts, cb) { - var req = http.request(opts, cb) - req.end() - return req + wire.isSeeder = false + updateSeedStatus() } -http.Agent = function () {} -http.Agent.defaultMaxSockets = 4 +/** + * Called on selection changes. + */ +Torrent.prototype._updateSelections = function () { + var self = this + if (!self.ready || self.destroyed) return -http.STATUS_CODES = statusCodes + process.nextTick(function () { + self._gcSelections() + }) + self._updateInterest() + self._update() +} -http.METHODS = [ - 'CHECKOUT', - 'CONNECT', - 'COPY', - 'DELETE', - 'GET', - 'HEAD', - 'LOCK', - 'M-SEARCH', - 'MERGE', - 'MKACTIVITY', - 'MKCOL', - 'MOVE', - 'NOTIFY', - 'OPTIONS', - 'PATCH', - 'POST', - 'PROPFIND', - 'PROPPATCH', - 'PURGE', - 'PUT', - 'REPORT', - 'SEARCH', - 'SUBSCRIBE', - 'TRACE', - 'UNLOCK', - 'UNSUBSCRIBE' -] -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./lib/request":187,"builtin-status-codes":160,"url":191,"xtend":194}],186:[function(require,module,exports){ -(function (global){ -exports.fetch = isFunction(global.fetch) && isFunction(global.ReadableStream) +/** + * Garbage collect selections with respect to the store's current state. + */ +Torrent.prototype._gcSelections = function () { + var self = this -exports.blobConstructor = false -try { - new Blob([new ArrayBuffer(1)]) - exports.blobConstructor = true -} catch (e) {} + for (var i = 0; i < self._selections.length; ++i) { + var s = self._selections[i] + var oldOffset = s.offset -// The xhr request to example.com may violate some restrictive CSP configurations, -// so if we're running in a browser that supports `fetch`, avoid calling getXHR() -// and assume support for certain features below. -var xhr -function getXHR () { - // Cache the xhr value - if (xhr !== undefined) return xhr + // check for newly downloaded pieces in selection + while (self.bitfield.get(s.from + s.offset) && s.from + s.offset < s.to) { + s.offset += 1 + } - if (global.XMLHttpRequest) { - xhr = new global.XMLHttpRequest() - // If XDomainRequest is available (ie only, where xhr might not work - // cross domain), use the page location. Otherwise use example.com - // Note: this doesn't actually make an http request. - try { - xhr.open('GET', global.XDomainRequest ? '/' : 'https://example.com') - } catch(e) { - xhr = null - } - } else { - // Service workers don't have XHR - xhr = null - } - return xhr -} + if (oldOffset !== s.offset) s.notify() + if (s.to !== s.from + s.offset) continue + if (!self.bitfield.get(s.from + s.offset)) continue -function checkTypeSupport (type) { - var xhr = getXHR() - if (!xhr) return false - try { - xhr.responseType = type - return xhr.responseType === type - } catch (e) {} - return false + self._selections.splice(i, 1) // remove fully downloaded selection + i -= 1 // decrement i to offset splice + + s.notify() + self._updateInterest() + } + + if (!self._selections.length) self.emit('idle') } -// For some strange reason, Safari 7.0 reports typeof global.ArrayBuffer === 'object'. -// Safari 7.1 appears to have fixed this bug. -var haveArrayBuffer = typeof global.ArrayBuffer !== 'undefined' -var haveSlice = haveArrayBuffer && isFunction(global.ArrayBuffer.prototype.slice) +/** + * Update interested status for all peers. + */ +Torrent.prototype._updateInterest = function () { + var self = this -// If fetch is supported, then arraybuffer will be supported too. Skip calling -// checkTypeSupport(), since that calls getXHR(). -exports.arraybuffer = exports.fetch || (haveArrayBuffer && checkTypeSupport('arraybuffer')) + var prev = self._amInterested + self._amInterested = !!self._selections.length -// These next two tests unavoidably show warnings in Chrome. Since fetch will always -// be used if it's available, just return false for these to avoid the warnings. -exports.msstream = !exports.fetch && haveSlice && checkTypeSupport('ms-stream') -exports.mozchunkedarraybuffer = !exports.fetch && haveArrayBuffer && - checkTypeSupport('moz-chunked-arraybuffer') + self.wires.forEach(function (wire) { + // TODO: only call wire.interested if the wire has at least one piece we need + if (self._amInterested) wire.interested() + else wire.uninterested() + }) -// If fetch is supported, then overrideMimeType will be supported too. Skip calling -// getXHR(). -exports.overrideMimeType = exports.fetch || (getXHR() ? isFunction(getXHR().overrideMimeType) : false) + if (prev === self._amInterested) return + if (self._amInterested) self.emit('interested') + else self.emit('uninterested') +} -exports.vbArray = isFunction(global.VBArray) +/** + * Heartbeat to update all peers and their requests. + */ +Torrent.prototype._update = function () { + var self = this + if (self.destroyed) return -function isFunction (value) { - return typeof value === 'function' + // update wires in random order for better request distribution + var ite = randomIterate(self.wires) + var wire + while ((wire = ite())) { + self._updateWire(wire) + } } -xhr = null // Help gc +/** + * Attempts to update a peer's requests + */ +Torrent.prototype._updateWire = function (wire) { + var self = this -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],187:[function(require,module,exports){ -(function (process,global,Buffer){ -var capability = require('./capability') -var inherits = require('inherits') -var response = require('./response') -var stream = require('readable-stream') -var toArrayBuffer = require('to-arraybuffer') + if (wire.peerChoking) return + if (!wire.downloaded) return validateWire() -var IncomingMessage = response.IncomingMessage -var rStates = response.readyStates + var minOutstandingRequests = getBlockPipelineLength(wire, PIPELINE_MIN_DURATION) + if (wire.requests.length >= minOutstandingRequests) return + var maxOutstandingRequests = getBlockPipelineLength(wire, PIPELINE_MAX_DURATION) -function decideMode (preferBinary, useFetch) { - if (capability.fetch && useFetch) { - return 'fetch' - } else if (capability.mozchunkedarraybuffer) { - return 'moz-chunked-arraybuffer' - } else if (capability.msstream) { - return 'ms-stream' - } else if (capability.arraybuffer && preferBinary) { - return 'arraybuffer' - } else if (capability.vbArray && preferBinary) { - return 'text:vbarray' - } else { - return 'text' - } -} + trySelectWire(false) || trySelectWire(true) -var ClientRequest = module.exports = function (opts) { - var self = this - stream.Writable.call(self) + function genPieceFilterFunc (start, end, tried, rank) { + return function (i) { + return i >= start && i <= end && !(i in tried) && wire.peerPieces.get(i) && (!rank || rank(i)) + } + } - self._opts = opts - self._body = [] - self._headers = {} - if (opts.auth) - self.setHeader('Authorization', 'Basic ' + new Buffer(opts.auth).toString('base64')) - Object.keys(opts.headers).forEach(function (name) { - self.setHeader(name, opts.headers[name]) - }) + // TODO: Do we need both validateWire and trySelectWire? + function validateWire () { + if (wire.requests.length) return - var preferBinary - var useFetch = true - if (opts.mode === 'disable-fetch' || 'timeout' in opts) { - // If the use of XHR should be preferred and includes preserving the 'content-type' header. - // Force XHR to be used since the Fetch API does not yet support timeouts. - useFetch = false - preferBinary = true - } else if (opts.mode === 'prefer-streaming') { - // If streaming is a high priority but binary compatibility and - // the accuracy of the 'content-type' header aren't - preferBinary = false - } else if (opts.mode === 'allow-wrong-content-type') { - // If streaming is more important than preserving the 'content-type' header - preferBinary = !capability.overrideMimeType - } else if (!opts.mode || opts.mode === 'default' || opts.mode === 'prefer-fast') { - // Use binary if text streaming may corrupt data or the content-type header, or for speed - preferBinary = true - } else { - throw new Error('Invalid value for opts.mode') - } - self._mode = decideMode(preferBinary, useFetch) + var i = self._selections.length + while (i--) { + var next = self._selections[i] + var piece + if (self.strategy === 'rarest') { + var start = next.from + next.offset + var end = next.to + var len = end - start + 1 + var tried = {} + var tries = 0 + var filter = genPieceFilterFunc(start, end, tried) - self.on('finish', function () { - self._onFinish() - }) -} + while (tries < len) { + piece = self._rarityMap.getRarestPiece(filter) + if (piece < 0) break + if (self._request(wire, piece, false)) return + tried[piece] = true + tries += 1 + } + } else { + for (piece = next.to; piece >= next.from + next.offset; --piece) { + if (!wire.peerPieces.get(piece)) continue + if (self._request(wire, piece, false)) return + } + } + } -inherits(ClientRequest, stream.Writable) + // TODO: wire failed to validate as useful; should we close it? + // probably not, since 'have' and 'bitfield' messages might be coming + } -ClientRequest.prototype.setHeader = function (name, value) { - var self = this - var lowerName = name.toLowerCase() - // This check is not necessary, but it prevents warnings from browsers about setting unsafe - // headers. To be honest I'm not entirely sure hiding these warnings is a good thing, but - // http-browserify did it, so I will too. - if (unsafeHeaders.indexOf(lowerName) !== -1) - return + function speedRanker () { + var speed = wire.downloadSpeed() || 1 + if (speed > SPEED_THRESHOLD) return function () { return true } - self._headers[lowerName] = { - name: name, - value: value - } -} + var secs = Math.max(1, wire.requests.length) * Piece.BLOCK_LENGTH / speed + var tries = 10 + var ptr = 0 -ClientRequest.prototype.getHeader = function (name) { - var header = this._headers[name.toLowerCase()] - if (header) - return header.value - return null -} + return function (index) { + if (!tries || self.bitfield.get(index)) return true -ClientRequest.prototype.removeHeader = function (name) { - var self = this - delete self._headers[name.toLowerCase()] -} + var missing = self.pieces[index].missing -ClientRequest.prototype._onFinish = function () { - var self = this + for (; ptr < self.wires.length; ptr++) { + var otherWire = self.wires[ptr] + var otherSpeed = otherWire.downloadSpeed() - if (self._destroyed) - return - var opts = self._opts + if (otherSpeed < SPEED_THRESHOLD) continue + if (otherSpeed <= speed) continue + if (!otherWire.peerPieces.get(index)) continue + if ((missing -= otherSpeed * secs) > 0) continue - var headersObj = self._headers - var body = null - if (opts.method !== 'GET' && opts.method !== 'HEAD') { - if (capability.blobConstructor) { - body = new global.Blob(self._body.map(function (buffer) { - return toArrayBuffer(buffer) - }), { - type: (headersObj['content-type'] || {}).value || '' - }) - } else { - // get utf8 string - body = Buffer.concat(self._body).toString() - } - } + tries-- + return false + } - // create flattened list of headers - var headersList = [] - Object.keys(headersObj).forEach(function (keyName) { - var name = headersObj[keyName].name - var value = headersObj[keyName].value - if (Array.isArray(value)) { - value.forEach(function (v) { - headersList.push([name, v]) - }) - } else { - headersList.push([name, value]) - } - }) + return true + } + } + + function shufflePriority (i) { + var last = i + for (var j = i; j < self._selections.length && self._selections[j].priority; j++) { + last = j + } + var tmp = self._selections[i] + self._selections[i] = self._selections[last] + self._selections[last] = tmp + } - if (self._mode === 'fetch') { - global.fetch(self._opts.url, { - method: self._opts.method, - headers: headersList, - body: body || undefined, - mode: 'cors', - credentials: opts.withCredentials ? 'include' : 'same-origin' - }).then(function (response) { - self._fetchResponse = response - self._connect() - }, function (reason) { - self.emit('error', reason) - }) - } else { - var xhr = self._xhr = new global.XMLHttpRequest() - try { - xhr.open(self._opts.method, self._opts.url, true) - } catch (err) { - process.nextTick(function () { - self.emit('error', err) - }) - return - } + function trySelectWire (hotswap) { + if (wire.requests.length >= maxOutstandingRequests) return true + var rank = speedRanker() - // Can't set responseType on really old browsers - if ('responseType' in xhr) - xhr.responseType = self._mode.split(':')[0] + for (var i = 0; i < self._selections.length; i++) { + var next = self._selections[i] - if ('withCredentials' in xhr) - xhr.withCredentials = !!opts.withCredentials + var piece + if (self.strategy === 'rarest') { + var start = next.from + next.offset + var end = next.to + var len = end - start + 1 + var tried = {} + var tries = 0 + var filter = genPieceFilterFunc(start, end, tried, rank) - if (self._mode === 'text' && 'overrideMimeType' in xhr) - xhr.overrideMimeType('text/plain; charset=x-user-defined') + while (tries < len) { + piece = self._rarityMap.getRarestPiece(filter) + if (piece < 0) break - if ('timeout' in opts) { - xhr.timeout = opts.timeout - xhr.ontimeout = function () { - self.emit('timeout') - } - } + // request all non-reserved blocks in this piece + while (self._request(wire, piece, self._critical[piece] || hotswap)) {} - headersList.forEach(function (header) { - xhr.setRequestHeader(header[0], header[1]) - }) + if (wire.requests.length < maxOutstandingRequests) { + tried[piece] = true + tries++ + continue + } - self._response = null - xhr.onreadystatechange = function () { - switch (xhr.readyState) { - case rStates.LOADING: - case rStates.DONE: - self._onXHRProgress() - break - } - } - // Necessary for streaming in Firefox, since xhr.response is ONLY defined - // in onprogress, not in onreadystatechange with xhr.readyState = 3 - if (self._mode === 'moz-chunked-arraybuffer') { - xhr.onprogress = function () { - self._onXHRProgress() - } - } + if (next.priority) shufflePriority(i) + return true + } + } else { + for (piece = next.from + next.offset; piece <= next.to; piece++) { + if (!wire.peerPieces.get(piece) || !rank(piece)) continue - xhr.onerror = function () { - if (self._destroyed) - return - self.emit('error', new Error('XHR error')) - } + // request all non-reserved blocks in piece + while (self._request(wire, piece, self._critical[piece] || hotswap)) {} - try { - xhr.send(body) - } catch (err) { - process.nextTick(function () { - self.emit('error', err) - }) - return - } - } + if (wire.requests.length < maxOutstandingRequests) continue + + if (next.priority) shufflePriority(i) + return true + } + } + } + + return false + } } /** - * Checks if xhr.status is readable and non-zero, indicating no error. - * Even though the spec says it should be available in readyState 3, - * accessing it throws an exception in IE8 + * Called periodically to update the choked status of all peers, handling optimistic + * unchoking as described in BEP3. */ -function statusValid (xhr) { - try { - var status = xhr.status - return (status !== null && status !== 0) - } catch (e) { - return false - } -} +Torrent.prototype._rechoke = function () { + var self = this + if (!self.ready) return -ClientRequest.prototype._onXHRProgress = function () { - var self = this + if (self._rechokeOptimisticTime > 0) self._rechokeOptimisticTime -= 1 + else self._rechokeOptimisticWire = null - if (!statusValid(self._xhr) || self._destroyed) - return + var peers = [] - if (!self._response) - self._connect() + self.wires.forEach(function (wire) { + if (!wire.isSeeder && wire !== self._rechokeOptimisticWire) { + peers.push({ + wire: wire, + downloadSpeed: wire.downloadSpeed(), + uploadSpeed: wire.uploadSpeed(), + salt: Math.random(), + isChoked: true + }) + } + }) - self._response._onXHRProgress() -} + peers.sort(rechokeSort) -ClientRequest.prototype._connect = function () { - var self = this + var unchokeInterested = 0 + var i = 0 + for (; i < peers.length && unchokeInterested < self._rechokeNumSlots; ++i) { + peers[i].isChoked = false + if (peers[i].wire.peerInterested) unchokeInterested += 1 + } - if (self._destroyed) - return + // Optimistically unchoke a peer + if (!self._rechokeOptimisticWire && i < peers.length && self._rechokeNumSlots) { + var candidates = peers.slice(i).filter(function (peer) { return peer.wire.peerInterested }) + var optimistic = candidates[randomInt(candidates.length)] - self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode) - self._response.on('error', function(err) { - self.emit('error', err) - }) + if (optimistic) { + optimistic.isChoked = false + self._rechokeOptimisticWire = optimistic.wire + self._rechokeOptimisticTime = RECHOKE_OPTIMISTIC_DURATION + } + } - self.emit('response', self._response) -} + // Unchoke best peers + peers.forEach(function (peer) { + if (peer.wire.amChoking !== peer.isChoked) { + if (peer.isChoked) peer.wire.choke() + else peer.wire.unchoke() + } + }) -ClientRequest.prototype._write = function (chunk, encoding, cb) { - var self = this + function rechokeSort (peerA, peerB) { + // Prefer higher download speed + if (peerA.downloadSpeed !== peerB.downloadSpeed) { + return peerB.downloadSpeed - peerA.downloadSpeed + } - self._body.push(chunk) - cb() -} + // Prefer higher upload speed + if (peerA.uploadSpeed !== peerB.uploadSpeed) { + return peerB.uploadSpeed - peerA.uploadSpeed + } -ClientRequest.prototype.abort = ClientRequest.prototype.destroy = function () { - var self = this - self._destroyed = true - if (self._response) - self._response._destroyed = true - if (self._xhr) - self._xhr.abort() - // Currently, there isn't a way to truly abort a fetch. - // If you like bikeshedding, see https://github.com/whatwg/fetch/issues/27 + // Prefer unchoked + if (peerA.wire.amChoking !== peerB.wire.amChoking) { + return peerA.wire.amChoking ? 1 : -1 + } + + // Random order + return peerA.salt - peerB.salt + } } -ClientRequest.prototype.end = function (data, encoding, cb) { - var self = this - if (typeof data === 'function') { - cb = data - data = undefined - } +/** + * Attempts to cancel a slow block request from another wire such that the + * given wire may effectively swap out the request for one of its own. + */ +Torrent.prototype._hotswap = function (wire, index) { + var self = this - stream.Writable.prototype.end.call(self, data, encoding, cb) -} + var speed = wire.downloadSpeed() + if (speed < Piece.BLOCK_LENGTH) return false + if (!self._reservations[index]) return false -ClientRequest.prototype.flushHeaders = function () {} -ClientRequest.prototype.setTimeout = function () {} -ClientRequest.prototype.setNoDelay = function () {} -ClientRequest.prototype.setSocketKeepAlive = function () {} + var r = self._reservations[index] + if (!r) { + return false + } -// Taken from http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader%28%29-method -var unsafeHeaders = [ - 'accept-charset', - 'accept-encoding', - 'access-control-request-headers', - 'access-control-request-method', - 'connection', - 'content-length', - 'cookie', - 'cookie2', - 'date', - 'dnt', - 'expect', - 'host', - 'keep-alive', - 'origin', - 'referer', - 'te', - 'trailer', - 'transfer-encoding', - 'upgrade', - 'user-agent', - 'via' -] + var minSpeed = Infinity + var minWire + + var i + for (i = 0; i < r.length; i++) { + var otherWire = r[i] + if (!otherWire || otherWire === wire) continue -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) -},{"./capability":186,"./response":188,"_process":170,"buffer":159,"inherits":165,"readable-stream":183,"to-arraybuffer":190}],188:[function(require,module,exports){ -(function (process,global,Buffer){ -var capability = require('./capability') -var inherits = require('inherits') -var stream = require('readable-stream') + var otherSpeed = otherWire.downloadSpeed() + if (otherSpeed >= SPEED_THRESHOLD) continue + if (2 * otherSpeed > speed || otherSpeed > minSpeed) continue -var rStates = exports.readyStates = { - UNSENT: 0, - OPENED: 1, - HEADERS_RECEIVED: 2, - LOADING: 3, - DONE: 4 -} + minWire = otherWire + minSpeed = otherSpeed + } -var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode) { - var self = this - stream.Readable.call(self) + if (!minWire) return false - self._mode = mode - self.headers = {} - self.rawHeaders = [] - self.trailers = {} - self.rawTrailers = [] + for (i = 0; i < r.length; i++) { + if (r[i] === minWire) r[i] = null + } - // Fake the 'close' event, but only once 'end' fires - self.on('end', function () { - // The nextTick is necessary to prevent the 'request' module from causing an infinite loop - process.nextTick(function () { - self.emit('close') - }) - }) + for (i = 0; i < minWire.requests.length; i++) { + var req = minWire.requests[i] + if (req.piece !== index) continue - if (mode === 'fetch') { - self._fetchResponse = response + self.pieces[index].cancel((req.offset / Piece.BLOCK_LENGTH) | 0) + } - self.url = response.url - self.statusCode = response.status - self.statusMessage = response.statusText - - response.headers.forEach(function(header, key){ - self.headers[key.toLowerCase()] = header - self.rawHeaders.push(key, header) - }) + self.emit('hotswap', minWire, wire, index) + return true +} +/** + * Attempts to request a block from the given wire. + */ +Torrent.prototype._request = function (wire, index, hotswap) { + var self = this + var numRequests = wire.requests.length + var isWebSeed = wire.type === 'webSeed' - // TODO: this doesn't respect backpressure. Once WritableStream is available, this can be fixed - var reader = response.body.getReader() - function read () { - reader.read().then(function (result) { - if (self._destroyed) - return - if (result.done) { - self.push(null) - return - } - self.push(new Buffer(result.value)) - read() - }).catch(function(err) { - self.emit('error', err) - }) - } - read() + if (self.bitfield.get(index)) return false - } else { - self._xhr = xhr - self._pos = 0 + var maxOutstandingRequests = isWebSeed + ? Math.min( + getPiecePipelineLength(wire, PIPELINE_MAX_DURATION, self.pieceLength), + self.maxWebConns + ) + : getBlockPipelineLength(wire, PIPELINE_MAX_DURATION) - self.url = xhr.responseURL - self.statusCode = xhr.status - self.statusMessage = xhr.statusText - var headers = xhr.getAllResponseHeaders().split(/\r?\n/) - headers.forEach(function (header) { - var matches = header.match(/^([^:]+):\s*(.*)/) - if (matches) { - var key = matches[1].toLowerCase() - if (key === 'set-cookie') { - if (self.headers[key] === undefined) { - self.headers[key] = [] - } - self.headers[key].push(matches[2]) - } else if (self.headers[key] !== undefined) { - self.headers[key] += ', ' + matches[2] - } else { - self.headers[key] = matches[2] - } - self.rawHeaders.push(matches[1], matches[2]) - } - }) + if (numRequests >= maxOutstandingRequests) return false + // var endGame = (wire.requests.length === 0 && self.store.numMissing < 30) - self._charset = 'x-user-defined' - if (!capability.overrideMimeType) { - var mimeType = self.rawHeaders['mime-type'] - if (mimeType) { - var charsetMatch = mimeType.match(/;\s*charset=([^;])(;|$)/) - if (charsetMatch) { - self._charset = charsetMatch[1].toLowerCase() - } - } - if (!self._charset) - self._charset = 'utf-8' // best guess - } - } -} + var piece = self.pieces[index] + var reservation = isWebSeed ? piece.reserveRemaining() : piece.reserve() -inherits(IncomingMessage, stream.Readable) + if (reservation === -1 && hotswap && self._hotswap(wire, index)) { + reservation = isWebSeed ? piece.reserveRemaining() : piece.reserve() + } + if (reservation === -1) return false -IncomingMessage.prototype._read = function () {} + var r = self._reservations[index] + if (!r) r = self._reservations[index] = [] + var i = r.indexOf(null) + if (i === -1) i = r.length + r[i] = wire -IncomingMessage.prototype._onXHRProgress = function () { - var self = this + var chunkOffset = piece.chunkOffset(reservation) + var chunkLength = isWebSeed ? piece.chunkLengthRemaining(reservation) : piece.chunkLength(reservation) - var xhr = self._xhr + wire.request(index, chunkOffset, chunkLength, function onChunk (err, chunk) { + if (self.destroyed) return - var response = null - switch (self._mode) { - case 'text:vbarray': // For IE9 - if (xhr.readyState !== rStates.DONE) - break - try { - // This fails in IE8 - response = new global.VBArray(xhr.responseBody).toArray() - } catch (e) {} - if (response !== null) { - self.push(new Buffer(response)) - break - } - // Falls through in IE8 - case 'text': - try { // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4 - response = xhr.responseText - } catch (e) { - self._mode = 'text:vbarray' - break - } - if (response.length > self._pos) { - var newData = response.substr(self._pos) - if (self._charset === 'x-user-defined') { - var buffer = new Buffer(newData.length) - for (var i = 0; i < newData.length; i++) - buffer[i] = newData.charCodeAt(i) & 0xff + // TODO: what is this for? + if (!self.ready) return self.once('ready', function () { onChunk(err, chunk) }) - self.push(buffer) - } else { - self.push(newData, self._charset) - } - self._pos = response.length - } - break - case 'arraybuffer': - if (xhr.readyState !== rStates.DONE || !xhr.response) - break - response = xhr.response - self.push(new Buffer(new Uint8Array(response))) - break - case 'moz-chunked-arraybuffer': // take whole - response = xhr.response - if (xhr.readyState !== rStates.LOADING || !response) - break - self.push(new Buffer(new Uint8Array(response))) - break - case 'ms-stream': - response = xhr.response - if (xhr.readyState !== rStates.LOADING) - break - var reader = new global.MSStreamReader() - reader.onprogress = function () { - if (reader.result.byteLength > self._pos) { - self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos)))) - self._pos = reader.result.byteLength - } - } - reader.onload = function () { - self.push(null) - } - // reader.onerror = ??? // TODO: this - reader.readAsArrayBuffer(response) - break - } + if (r[i] === wire) r[i] = null - // The ms-stream case handles end separately in reader.onload() - if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') { - self.push(null) - } -} + if (piece !== self.pieces[index]) return onUpdateTick() -}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) -},{"./capability":186,"_process":170,"buffer":159,"inherits":165,"readable-stream":183}],189:[function(require,module,exports){ -arguments[4][108][0].apply(exports,arguments) -},{"dup":108,"safe-buffer":184}],190:[function(require,module,exports){ -arguments[4][111][0].apply(exports,arguments) -},{"buffer":159,"dup":111}],191:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. + if (err) { + self._debug( + 'error getting piece %s (offset: %s length: %s) from %s: %s', + index, chunkOffset, chunkLength, wire.remoteAddress + ':' + wire.remotePort, + err.message + ) + isWebSeed ? piece.cancelRemaining(reservation) : piece.cancel(reservation) + onUpdateTick() + return + } + + self._debug( + 'got piece %s (offset: %s length: %s) from %s', + index, chunkOffset, chunkLength, wire.remoteAddress + ':' + wire.remotePort + ) + + if (!piece.set(reservation, chunk, wire)) return onUpdateTick() -'use strict'; + var buf = piece.flush() -var punycode = require('punycode'); -var util = require('./util'); + // TODO: might need to set self.pieces[index] = null here since sha1 is async -exports.parse = urlParse; -exports.resolve = urlResolve; -exports.resolveObject = urlResolveObject; -exports.format = urlFormat; + sha1(buf, function (hash) { + if (self.destroyed) return -exports.Url = Url; + if (hash === self._hashes[index]) { + if (!self.pieces[index]) return + self._debug('piece verified %s', index) -function Url() { - this.protocol = null; - this.slashes = null; - this.auth = null; - this.host = null; - this.port = null; - this.hostname = null; - this.hash = null; - this.search = null; - this.query = null; - this.pathname = null; - this.path = null; - this.href = null; + self.pieces[index] = null + self._reservations[index] = null + self.bitfield.set(index, true) + + self.store.put(index, buf) + + self.wires.forEach(function (wire) { + wire.have(index) + }) + + // We also check `self.destroyed` since `torrent.destroy()` could have been + // called in the `torrent.on('done')` handler, triggered by `_checkDone()`. + if (self._checkDone() && !self.destroyed) self.discovery.complete() + } else { + self.pieces[index] = new Piece(piece.length) + self.emit('warning', new Error('Piece ' + index + ' failed verification')) + } + onUpdateTick() + }) + }) + + function onUpdateTick () { + process.nextTick(function () { self._update() }) + } + + return true } -// Reference: RFC 3986, RFC 1808, RFC 2396 +Torrent.prototype._checkDone = function () { + var self = this + if (self.destroyed) return -// define these here so at least they only have to be -// compiled once on the first module load. -var protocolPattern = /^([a-z0-9.+-]+:)/i, - portPattern = /:[0-9]*$/, + // are any new files done? + self.files.forEach(function (file) { + if (file.done) return + for (var i = file._startPiece; i <= file._endPiece; ++i) { + if (!self.bitfield.get(i)) return + } + file.done = true + file.emit('done') + self._debug('file done: ' + file.name) + }) - // Special case for a simple path URL - simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, + // is the torrent done? (if all current selections are satisfied, or there are + // no selections, then torrent is done) + var done = true + for (var i = 0; i < self._selections.length; i++) { + var selection = self._selections[i] + for (var piece = selection.from; piece <= selection.to; piece++) { + if (!self.bitfield.get(piece)) { + done = false + break + } + } + if (!done) break + } + if (!self.done && done) { + self.done = true + self._debug('torrent done: ' + self.infoHash) + self.emit('done') + } + self._gcSelections() - // RFC 2396: characters reserved for delimiting URLs. - // We actually just auto-escape these. - delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], + return done +} - // RFC 2396: characters not allowed for various reasons. - unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims), +Torrent.prototype.load = function (streams, cb) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') + if (!self.ready) return self.once('ready', function () { self.load(streams, cb) }) - // Allowed by RFCs, but cause of XSS attacks. Always escape these. - autoEscape = ['\''].concat(unwise), - // Characters that are never ever allowed in a hostname. - // Note that any invalid chars are also handled, but these - // are the ones that are *expected* to be seen, so we fast-path - // them. - nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape), - hostEndingChars = ['/', '?', '#'], - hostnameMaxLen = 255, - hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, - hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, - // protocols that can allow "unsafe" and "unwise" chars. - unsafeProtocol = { - 'javascript': true, - 'javascript:': true - }, - // protocols that never have a hostname. - hostlessProtocol = { - 'javascript': true, - 'javascript:': true - }, - // protocols that always contain a // bit. - slashedProtocol = { - 'http': true, - 'https': true, - 'ftp': true, - 'gopher': true, - 'file': true, - 'http:': true, - 'https:': true, - 'ftp:': true, - 'gopher:': true, - 'file:': true - }, - querystring = require('querystring'); + if (!Array.isArray(streams)) streams = [ streams ] + if (!cb) cb = noop -function urlParse(url, parseQueryString, slashesDenoteHost) { - if (url && util.isObject(url) && url instanceof Url) return url; + var readable = new MultiStream(streams) + var writable = new ChunkStoreWriteStream(self.store, self.pieceLength) - var u = new Url; - u.parse(url, parseQueryString, slashesDenoteHost); - return u; + pump(readable, writable, function (err) { + if (err) return cb(err) + self.pieces.forEach(function (piece, index) { + self.pieces[index] = null + self._reservations[index] = null + self.bitfield.set(index, true) + }) + self._checkDone() + cb(null) + }) } -Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { - if (!util.isString(url)) { - throw new TypeError("Parameter 'url' must be a string, not " + typeof url); - } +Torrent.prototype.createServer = function (requestListener) { + if (typeof Server !== 'function') throw new Error('node.js-only method') + if (this.destroyed) throw new Error('torrent is destroyed') + var server = new Server(this, requestListener) + this._servers.push(server) + return server +} - // Copy chrome, IE, opera backslash-handling behavior. - // Back slashes before the query string get converted to forward slashes - // See: https://code.google.com/p/chromium/issues/detail?id=25916 - var queryIndex = url.indexOf('?'), - splitter = - (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#', - uSplit = url.split(splitter), - slashRegex = /\\/g; - uSplit[0] = uSplit[0].replace(slashRegex, '/'); - url = uSplit.join(splitter); +Torrent.prototype.pause = function () { + if (this.destroyed) return + this._debug('pause') + this.paused = true +} - var rest = url; +Torrent.prototype.resume = function () { + if (this.destroyed) return + this._debug('resume') + this.paused = false + this._drain() +} - // trim before proceeding. - // This is to support parse stuff like " http://foo.com \n" - rest = rest.trim(); +Torrent.prototype._debug = function () { + var args = [].slice.call(arguments) + args[0] = '[' + this.client._debugId + '] [' + this._debugId + '] ' + args[0] + debug.apply(null, args) +} - if (!slashesDenoteHost && url.split('#').length === 1) { - // Try fast path regexp - var simplePath = simplePathPattern.exec(rest); - if (simplePath) { - this.path = rest; - this.href = rest; - this.pathname = simplePath[1]; - if (simplePath[2]) { - this.search = simplePath[2]; - if (parseQueryString) { - this.query = querystring.parse(this.search.substr(1)); - } else { - this.query = this.search.substr(1); - } - } else if (parseQueryString) { - this.search = ''; - this.query = {}; - } - return this; - } +/** + * Pop a peer off the FIFO queue and connect to it. When _drain() gets called, + * the queue will usually have only one peer in it, except when there are too + * many peers (over `this.maxConns`) in which case they will just sit in the + * queue until another connection closes. + */ +Torrent.prototype._drain = function () { + var self = this + this._debug('_drain numConns %s maxConns %s', self._numConns, self.client.maxConns) + if (typeof net.connect !== 'function' || self.destroyed || self.paused || + self._numConns >= self.client.maxConns) { + return } + this._debug('drain (%s queued, %s/%s peers)', self._numQueued, self.numPeers, self.client.maxConns) - var proto = protocolPattern.exec(rest); - if (proto) { - proto = proto[0]; - var lowerProto = proto.toLowerCase(); - this.protocol = lowerProto; - rest = rest.substr(proto.length); - } + var peer = self._queue.shift() + if (!peer) return // queue could be empty - // figure out if it's got a host - // user@server is *always* interpreted as a hostname, and url - // resolution will treat //foo/bar as host=foo,path=bar because that's - // how the browser resolves relative URLs. - if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { - var slashes = rest.substr(0, 2) === '//'; - if (slashes && !(proto && hostlessProtocol[proto])) { - rest = rest.substr(2); - this.slashes = true; - } + this._debug('tcp connect attempt to %s', peer.addr) + + var parts = addrToIPPort(peer.addr) + var opts = { + host: parts[0], + port: parts[1] } - if (!hostlessProtocol[proto] && - (slashes || (proto && !slashedProtocol[proto]))) { + var conn = peer.conn = net.connect(opts) - // there's a hostname. - // the first instance of /, ?, ;, or # ends the host. - // - // If there is an @ in the hostname, then non-host chars *are* allowed - // to the left of the last @ sign, unless some host-ending character - // comes *before* the @-sign. - // URLs are obnoxious. - // - // ex: - // http://a@b@c/ => user:a@b host:c - // http://a@b?@c => user:a host:c path:/?@c + conn.once('connect', function () { peer.onConnect() }) + conn.once('error', function (err) { peer.destroy(err) }) + peer.startConnectTimeout() - // v0.12 TODO(isaacs): This is not quite how Chrome does things. - // Review our test case against browsers more comprehensively. + // When connection closes, attempt reconnect after timeout (with exponential backoff) + conn.on('close', function () { + if (self.destroyed) return - // find the first instance of any hostEndingChars - var hostEnd = -1; - for (var i = 0; i < hostEndingChars.length; i++) { - var hec = rest.indexOf(hostEndingChars[i]); - if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) - hostEnd = hec; - } + // TODO: If torrent is done, do not try to reconnect after a timeout - // at this point, either we have an explicit point where the - // auth portion cannot go past, or the last @ char is the decider. - var auth, atSign; - if (hostEnd === -1) { - // atSign can be anywhere. - atSign = rest.lastIndexOf('@'); - } else { - // atSign must be in auth portion. - // http://a@b/c@d => host:b auth:a path:/c@d - atSign = rest.lastIndexOf('@', hostEnd); + if (peer.retries >= RECONNECT_WAIT.length) { + self._debug( + 'conn %s closed: will not re-add (max %s attempts)', + peer.addr, RECONNECT_WAIT.length + ) + return } - // Now we have a portion which is definitely the auth. - // Pull that off. - if (atSign !== -1) { - auth = rest.slice(0, atSign); - rest = rest.slice(atSign + 1); - this.auth = decodeURIComponent(auth); - } + var ms = RECONNECT_WAIT[peer.retries] + self._debug( + 'conn %s closed: will re-add to queue in %sms (attempt %s)', + peer.addr, ms, peer.retries + 1 + ) - // the host is the remaining to the left of the first non-host char - hostEnd = -1; - for (var i = 0; i < nonHostChars.length; i++) { - var hec = rest.indexOf(nonHostChars[i]); - if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) - hostEnd = hec; - } - // if we still have not hit it, then the entire thing is a host. - if (hostEnd === -1) - hostEnd = rest.length; + var reconnectTimeout = setTimeout(function reconnectTimeout () { + var newPeer = self._addPeer(peer.addr) + if (newPeer) newPeer.retries = peer.retries + 1 + }, ms) + if (reconnectTimeout.unref) reconnectTimeout.unref() + }) +} - this.host = rest.slice(0, hostEnd); - rest = rest.slice(hostEnd); +/** + * Returns `true` if string is valid IPv4/6 address. + * @param {string} addr + * @return {boolean} + */ +Torrent.prototype._validAddr = function (addr) { + var parts + try { + parts = addrToIPPort(addr) + } catch (e) { + return false + } + var host = parts[0] + var port = parts[1] + return port > 0 && port < 65535 && + !(host === '127.0.0.1' && port === this.client.torrentPort) +} - // pull out port. - this.parseHost(); +function getBlockPipelineLength (wire, duration) { + return 2 + Math.ceil(duration * wire.downloadSpeed() / Piece.BLOCK_LENGTH) +} - // we've indicated that there is a hostname, - // so even if it's empty, it has to be present. - this.hostname = this.hostname || ''; +function getPiecePipelineLength (wire, duration, pieceLength) { + return 1 + Math.ceil(duration * wire.downloadSpeed() / pieceLength) +} - // if hostname begins with [ and ends with ] - // assume that it's an IPv6 address. - var ipv6Hostname = this.hostname[0] === '[' && - this.hostname[this.hostname.length - 1] === ']'; +/** + * Returns a random integer in [0,high) + */ +function randomInt (high) { + return Math.random() * high | 0 +} - // validate a little. - if (!ipv6Hostname) { - var hostparts = this.hostname.split(/\./); - for (var i = 0, l = hostparts.length; i < l; i++) { - var part = hostparts[i]; - if (!part) continue; - if (!part.match(hostnamePartPattern)) { - var newpart = ''; - for (var j = 0, k = part.length; j < k; j++) { - if (part.charCodeAt(j) > 127) { - // we replace non-ASCII char with a temporary placeholder - // we need this to make sure size of hostname is not - // broken by replacing non-ASCII by nothing - newpart += 'x'; - } else { - newpart += part[j]; - } - } - // we test again with ASCII char only - if (!newpart.match(hostnamePartPattern)) { - var validParts = hostparts.slice(0, i); - var notHost = hostparts.slice(i + 1); - var bit = part.match(hostnamePartStart); - if (bit) { - validParts.push(bit[1]); - notHost.unshift(bit[2]); - } - if (notHost.length) { - rest = '/' + notHost.join('.') + rest; - } - this.hostname = validParts.join('.'); - break; - } - } - } - } +function noop () {} - if (this.hostname.length > hostnameMaxLen) { - this.hostname = ''; - } else { - // hostnames are always lower case. - this.hostname = this.hostname.toLowerCase(); - } +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../package.json":169,"./file":164,"./peer":165,"./rarity-map":166,"./server":3,"_process":15,"addr-to-ip-port":42,"bitfield":73,"chunk-store-stream/write":83,"debug":87,"events":7,"fs":1,"fs-chunk-store":105,"immediate-chunk-store":95,"inherits":96,"multistream":113,"net":3,"os":3,"parse-torrent":117,"path":13,"pump":120,"random-iterate":121,"run-parallel":136,"run-parallel-limit":135,"simple-get":140,"simple-sha1":142,"speedometer":144,"torrent-discovery":152,"torrent-piece":153,"uniq":156,"ut_metadata":158,"ut_pex":3,"xtend":171,"xtend/mutable":172}],168:[function(require,module,exports){ +module.exports = WebConn - if (!ipv6Hostname) { - // IDNA Support: Returns a punycoded representation of "domain". - // It only converts parts of the domain name that - // have non-ASCII characters, i.e. it doesn't matter if - // you call it with a domain that already is ASCII-only. - this.hostname = punycode.toASCII(this.hostname); - } +var BitField = require('bitfield') +var Buffer = require('safe-buffer').Buffer +var debug = require('debug')('webtorrent:webconn') +var get = require('simple-get') +var inherits = require('inherits') +var sha1 = require('simple-sha1') +var Wire = require('bittorrent-protocol') - var p = this.port ? ':' + this.port : ''; - var h = this.hostname || ''; - this.host = h + p; - this.href += this.host; +var VERSION = require('../package.json').version - // strip [ and ] from the hostname - // the host field still retains them, though - if (ipv6Hostname) { - this.hostname = this.hostname.substr(1, this.hostname.length - 2); - if (rest[0] !== '/') { - rest = '/' + rest; - } - } - } +inherits(WebConn, Wire) - // now rest is set to the post-host stuff. - // chop off any delim chars. - if (!unsafeProtocol[lowerProto]) { +/** + * Converts requests for torrent blocks into http range requests. + * @param {string} url web seed url + * @param {Object} torrent + */ +function WebConn (url, torrent) { + Wire.call(this) - // First, make 100% sure that any "autoEscape" chars get - // escaped, even if encodeURIComponent doesn't think they - // need to be. - for (var i = 0, l = autoEscape.length; i < l; i++) { - var ae = autoEscape[i]; - if (rest.indexOf(ae) === -1) - continue; - var esc = encodeURIComponent(ae); - if (esc === ae) { - esc = escape(ae); - } - rest = rest.split(ae).join(esc); - } - } + this.url = url + this.webPeerId = sha1.sync(url) + this._torrent = torrent + this._init() +} - // chop off from the tail first. - var hash = rest.indexOf('#'); - if (hash !== -1) { - // got a fragment string. - this.hash = rest.substr(hash); - rest = rest.slice(0, hash); - } - var qm = rest.indexOf('?'); - if (qm !== -1) { - this.search = rest.substr(qm); - this.query = rest.substr(qm + 1); - if (parseQueryString) { - this.query = querystring.parse(this.query); +WebConn.prototype._init = function () { + var self = this + self.setKeepAlive(true) + + self.once('handshake', function (infoHash, peerId) { + if (self.destroyed) return + self.handshake(infoHash, self.webPeerId) + var numPieces = self._torrent.pieces.length + var bitfield = new BitField(numPieces) + for (var i = 0; i <= numPieces; i++) { + bitfield.set(i, true) } - rest = rest.slice(0, qm); - } else if (parseQueryString) { - // no query string, but parseQueryString still requested - this.search = ''; - this.query = {}; - } - if (rest) this.pathname = rest; - if (slashedProtocol[lowerProto] && - this.hostname && !this.pathname) { - this.pathname = '/'; - } + self.bitfield(bitfield) + }) - //to support http.request - if (this.pathname || this.search) { - var p = this.pathname || ''; - var s = this.search || ''; - this.path = p + s; - } + self.once('interested', function () { + debug('interested') + self.unchoke() + }) - // finally, reconstruct the href based on what has been validated. - this.href = this.format(); - return this; -}; + self.on('uninterested', function () { debug('uninterested') }) + self.on('choke', function () { debug('choke') }) + self.on('unchoke', function () { debug('unchoke') }) + self.on('bitfield', function () { debug('bitfield') }) -// format a parsed object into a url string -function urlFormat(obj) { - // ensure it's an object, and not a string url. - // If it's an obj, this is a no-op. - // this way, you can call url_format() on strings - // to clean up potentially wonky urls. - if (util.isString(obj)) obj = urlParse(obj); - if (!(obj instanceof Url)) return Url.prototype.format.call(obj); - return obj.format(); + self.on('request', function (pieceIndex, offset, length, callback) { + debug('request pieceIndex=%d offset=%d length=%d', pieceIndex, offset, length) + self.httpRequest(pieceIndex, offset, length, callback) + }) } -Url.prototype.format = function() { - var auth = this.auth || ''; - if (auth) { - auth = encodeURIComponent(auth); - auth = auth.replace(/%3A/i, ':'); - auth += '@'; - } - - var protocol = this.protocol || '', - pathname = this.pathname || '', - hash = this.hash || '', - host = false, - query = ''; +WebConn.prototype.httpRequest = function (pieceIndex, offset, length, cb) { + var self = this + var pieceOffset = pieceIndex * self._torrent.pieceLength + var rangeStart = pieceOffset + offset /* offset within whole torrent */ + var rangeEnd = rangeStart + length - 1 - if (this.host) { - host = auth + this.host; - } else if (this.hostname) { - host = auth + (this.hostname.indexOf(':') === -1 ? - this.hostname : - '[' + this.hostname + ']'); - if (this.port) { - host += ':' + this.port; + // Web seed URL format: + // For single-file torrents, make HTTP range requests directly to the web seed URL + // For multi-file torrents, add the torrent folder and file name to the URL + var files = self._torrent.files + var requests + if (files.length <= 1) { + requests = [{ + url: self.url, + start: rangeStart, + end: rangeEnd + }] + } else { + var requestedFiles = files.filter(function (file) { + return file.offset <= rangeEnd && (file.offset + file.length) > rangeStart + }) + if (requestedFiles.length < 1) { + return cb(new Error('Could not find file corresponnding to web seed range request')) } - } - if (this.query && - util.isObject(this.query) && - Object.keys(this.query).length) { - query = querystring.stringify(this.query); + requests = requestedFiles.map(function (requestedFile) { + var fileEnd = requestedFile.offset + requestedFile.length - 1 + var url = self.url + + (self.url[self.url.length - 1] === '/' ? '' : '/') + + requestedFile.path + return { + url: url, + fileOffsetInRange: Math.max(requestedFile.offset - rangeStart, 0), + start: Math.max(rangeStart - requestedFile.offset, 0), + end: Math.min(fileEnd, rangeEnd - requestedFile.offset) + } + }) } - var search = this.search || (query && ('?' + query)) || ''; - - if (protocol && protocol.substr(-1) !== ':') protocol += ':'; + // Now make all the HTTP requests we need in order to load this piece + // Usually that's one requests, but sometimes it will be multiple + // Send requests in parallel and wait for them all to come back + var numRequestsSucceeded = 0 + var hasError = false - // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. - // unless they had them to begin with. - if (this.slashes || - (!protocol || slashedProtocol[protocol]) && host !== false) { - host = '//' + (host || ''); - if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; - } else if (!host) { - host = ''; + var ret + if (requests.length > 1) { + ret = Buffer.alloc(length) } - if (hash && hash.charAt(0) !== '#') hash = '#' + hash; - if (search && search.charAt(0) !== '?') search = '?' + search; + requests.forEach(function (request) { + var url = request.url + var start = request.start + var end = request.end + debug( + 'Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d', + url, pieceIndex, offset, length, start, end + ) + var opts = { + url: url, + method: 'GET', + headers: { + 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)', + range: 'bytes=' + start + '-' + end + } + } + function onResponse (res, data) { + if (res.statusCode < 200 || res.statusCode >= 300) { + hasError = true + return cb(new Error('Unexpected HTTP status code ' + res.statusCode)) + } + debug('Got data of length %d', data.length) - pathname = pathname.replace(/[?#]/g, function(match) { - return encodeURIComponent(match); - }); - search = search.replace('#', '%23'); + if (requests.length === 1) { + // Common case: fetch piece in a single HTTP request, return directly + cb(null, data) + } else { + // Rare case: reconstruct multiple HTTP requests across 2+ files into one + // piece buffer + data.copy(ret, request.fileOffsetInRange) + if (++numRequestsSucceeded === requests.length) { + cb(null, ret) + } + } + } + get.concat(opts, function (err, res, data) { + if (hasError) return + if (err) { + // Browsers allow HTTP redirects for simple cross-origin + // requests but not for requests that require preflight. + // Use a simple request to unravel any redirects and get the + // final URL. Retry the original request with the new URL if + // it's different. + // + // This test is imperfect but it's simple and good for common + // cases. It catches all cross-origin cases but matches a few + // same-origin cases too. + if (typeof window === 'undefined' || url.startsWith(window.location.origin + '/')) { + hasError = true + return cb(err) + } - return protocol + host + pathname + search + hash; -}; + return get.head(url, function (errHead, res) { + if (hasError) return + if (errHead) { + hasError = true + return cb(errHead) + } + if (res.statusCode < 200 || res.statusCode >= 300) { + hasError = true + return cb(new Error('Unexpected HTTP status code ' + res.statusCode)) + } + if (res.url === url) { + hasError = true + return cb(err) + } -function urlResolve(source, relative) { - return urlParse(source, false, true).resolve(relative); + opts.url = res.url + get.concat(opts, function (err, res, data) { + if (hasError) return + if (err) { + hasError = true + return cb(err) + } + onResponse(res, data) + }) + }) + } + onResponse(res, data) + }) + }) } -Url.prototype.resolve = function(relative) { - return this.resolveObject(urlParse(relative, false, true)).format(); -}; +WebConn.prototype.destroy = function () { + Wire.prototype.destroy.call(this) + this._torrent = null +} -function urlResolveObject(source, relative) { - if (!source) return relative; - return urlParse(source, false, true).resolveObject(relative); +},{"../package.json":169,"bitfield":73,"bittorrent-protocol":74,"debug":87,"inherits":96,"safe-buffer":138,"simple-get":140,"simple-sha1":142}],169:[function(require,module,exports){ +module.exports={ + "version": "0.98.19" } +},{}],170:[function(require,module,exports){ +// Returns a wrapper function that returns a wrapped callback +// The wrapper function should do some stuff, and return a +// presumably different callback function. +// This makes sure that own properties are retained, so that +// decorations and such are not lost along the way. +module.exports = wrappy +function wrappy (fn, cb) { + if (fn && cb) return wrappy(fn)(cb) -Url.prototype.resolveObject = function(relative) { - if (util.isString(relative)) { - var rel = new Url(); - rel.parse(relative, false, true); - relative = rel; - } + if (typeof fn !== 'function') + throw new TypeError('need wrapper function') - var result = new Url(); - var tkeys = Object.keys(this); - for (var tk = 0; tk < tkeys.length; tk++) { - var tkey = tkeys[tk]; - result[tkey] = this[tkey]; - } + Object.keys(fn).forEach(function (k) { + wrapper[k] = fn[k] + }) - // hash is always overridden, no matter what. - // even href="" will remove it. - result.hash = relative.hash; + return wrapper - // if the relative url is empty, then there's nothing left to do here. - if (relative.href === '') { - result.href = result.format(); - return result; + function wrapper() { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i] + } + var ret = fn.apply(this, args) + var cb = args[args.length-1] + if (typeof ret === 'function' && ret !== cb) { + Object.keys(cb).forEach(function (k) { + ret[k] = cb[k] + }) + } + return ret } +} - // hrefs like //foo/bar always cut to the protocol. - if (relative.slashes && !relative.protocol) { - // take everything except the protocol from relative - var rkeys = Object.keys(relative); - for (var rk = 0; rk < rkeys.length; rk++) { - var rkey = rkeys[rk]; - if (rkey !== 'protocol') - result[rkey] = relative[rkey]; - } +},{}],171:[function(require,module,exports){ +arguments[4][40][0].apply(exports,arguments) +},{"dup":40}],172:[function(require,module,exports){ +module.exports = extend - //urlParse appends trailing / to urls like http://www.example.com - if (slashedProtocol[result.protocol] && - result.hostname && !result.pathname) { - result.path = result.pathname = '/'; +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } } - result.href = result.format(); - return result; - } + return target +} - if (relative.protocol && relative.protocol !== result.protocol) { - // if it's a known url protocol, then changing - // the protocol does weird things - // first, if it's not file:, then we MUST have a host, - // and if there was a path - // to begin with, then we MUST have a path. - // if it is file:, then the host is dropped, - // because that's known to be hostless. - // anything else is assumed to be absolute. - if (!slashedProtocol[relative.protocol]) { - var keys = Object.keys(relative); - for (var v = 0; v < keys.length; v++) { - var k = keys[v]; - result[k] = relative[k]; - } - result.href = result.format(); - return result; +},{}],173:[function(require,module,exports){ +/** + * Given a number, return a zero-filled string. + * From http://stackoverflow.com/questions/1267283/ + * @param {number} width + * @param {number} number + * @return {string} + */ +module.exports = function zeroFill (width, number, pad) { + if (number === undefined) { + return function (number, pad) { + return zeroFill(width, number, pad) } + } + if (pad === undefined) pad = '0' + width -= number.toString().length + if (width > 0) return new Array(width + (/\./.test(number) ? 2 : 1)).join(pad) + number + return number + '' +} + +},{}],174:[function(require,module,exports){ +module.exports={ + "name": "pearplayer", + "version": "2.4.12", + "description": "", + "main": "./dist/pear-player.js", + "dependencies": { + "axios": "^0.17.1", + "babili": "^0.1.4", + "blueimp-md5": "^2.7.0", + "buffer": "^5.0.6", + "end-of-stream": "^1.4.0", + "events": "^1.1.1", + "fs-chunk-store": "^1.6.5", + "inherits": "^2.0.3", + "jquery": "^3.2.1", + "mp4box": "^0.3.15", + "readable-stream": "^2.2.9", + "render-media": "^2.10.0", + "webtorrent": "^0.98.16" + }, + "devDependencies": { + "copy": "^0.1.2", + "del": "^2.0.2" + }, + "scripts": { + "build-player": "browserify index.player.js -s PearPlayer > ./dist/pear-player.js", + "uglify-player": "browserify -s PearPlayer -e ./index.player.js | babili > ./dist/pear-player.min.js", + "pull-from-github": "git pull", + "push-to-github": "git add . && git commit -m 'update' && git push", + "npm-publish": "npm publish" + }, + "author": "Xie Ting Pear Limited", + "license": "MIT", + "homepage": "https://pear.hk", + "keywords": [ + "WebRTC", + "video", + "player", + "p2p", + "peer-to-peer", + "peers", + "streaming", + "multiple source", + "torrent", + "web torrent", + "webrtc data channel", + "webtorrent" + ] +} + +},{}],175:[function(require,module,exports){ +(function (process){ + +/* + 该模块用于调度HttpDownloader和RTCDownloader + + config:{ + + initialDownloaders: [], //初始的httpdownloader数组,必须 + chunkSize: number, //每个chunk的大小,默认1M + fileSize: number, //下载文件的总大小,必须 + interval: number, //滑动窗口的时间间隔,单位毫秒,默认5s + auto: boolean, //true为连续下载buffer + useMonitor: boolean, //开启监控器,默认关闭 + scheduler: function //节点调度算法 + sequencial: boolean //是否有序下载,默认false + maxLoaders: number //push算法中同时下载的节点数量 + algorithm: string //下载采用的算法 + } + */ +module.exports = Dispatcher; + +var debug = require('debug')('pear:dispatcher'); +var BitField = require('bitfield'); +var EventEmitter = require('events').EventEmitter; +var inherits = require('inherits'); +var FSChunkStore = require('fs-chunk-store'); +var ImmediateChunkStore = require('immediate-chunk-store'); + +inherits(Dispatcher, EventEmitter); + +function Dispatcher(config) { + EventEmitter.call(this); + + var self = this; + + if (!(config.initialDownloaders && config.fileSize && config.scheduler)) throw new Error('config is not completed'); + self.fileSize = config.fileSize; + self.initialDownloaders = config.initialDownloaders; + self.pieceLength = config.chunkSize || 1*1024*512; + self.interval = config.interval || 5000; + self.auto = config.auto || false; + // self.auto = true; + self.useMonitor = config.useMonitor || false; + self.downloaded = 0; + self.fogDownloaded = 0; //通过data channel下载的字节数 + self._windowOffset = 0; + // self.noDataChannel = false; //是否没有datachannel + self.ready = false; + self.done = false; //是否已完成下载 + self.destroyed = false; + + self.chunks = (config.fileSize % self.pieceLength)>0 ? Math.floor((config.fileSize / self.pieceLength)) +1: + (config.fileSize / self.pieceLength); + + // self._startPiece = 0; + // self._endPiece = (self.fileSize-1)/self.pieceLength; + + self._selections = []; //下载队列 + self._store = FSChunkStore; + self.path = ''; + + self.bufferSources = new Array(self.chunks); //记录每个buffer下载的方式 + self.slide = null; + self.noMoreNodes = false; //是否已没有新的节点可获取 + + //monitor + self.startTime = (new Date()).getTime(); //用于计算平均速度 + self.fogRatio = 0.0; + + //firstaid参数自适应 + self.sequencial = config.sequencial || false; + + var pushLength = self.initialDownloaders.filter(function (node) { //能力值超过平均值得节点个数 + return node.ability > 5; + }).length; + self.maxLoaders = (pushLength > 5 && pushLength <= self.maxLoaders) ? pushLength : config.maxLoaders; + self._windowLength = self.initialDownloaders.length >= 10 ? 10 : self.initialDownloaders.length; + // self._windowLength = 5; + // self._colddown = self._windowLength; //窗口滑动的冷却时间 + self._colddown = 5; //窗口滑动的冷却时间 + self.downloaders = []; + + //webtorrent + self.torrent = null; + + //scheduler + self.scheduler = config.scheduler; + self._windowEnd = 0; //当前窗口的end + + self.algorithm = config.algorithm; +}; + +Dispatcher.prototype._init = function () { + var self = this; + + self.downloaders = self.initialDownloaders.map(function (item){ + + self._setupHttp(item); + return item; + }); + + self.store = new ImmediateChunkStore( + new self._store(self.pieceLength, { + path: self.path, + length: self.fileSize + }) + ); + // debug('self.path:'+self.path); + self.bitfield = new BitField(self.chunks); //记录哪个块已经下好 + + if (self.algorithm === 'push') { + while (self._windowEnd !== self.chunks) { + self._createPushStream(); + } + self._windowEnd = 0; + } + // self._checkDone(); + + // self._slide(); + if (self.auto) { + self.select(0, self.chunks-1, true); + self.autoSlide(); + self.slide = noop; + } else { + // self.slide = this._throttle(this._slide, this); + } + + //初始化buffersources + for (var k=0;k 0) { + + // debug('_update self._selections:'+JSON.stringify(self._selections)); + // var s = self._selections[length-1]; + var s = self._selections[0]; + var start = s.from + s.offset; + var end = s.to; + // self._windowOffset = start; + debug('current _windowOffset:' + self._windowOffset); + self._slide(); + // self.slide(); + // self._throttle(self.slide,self); + } +}; + +Dispatcher.prototype._resetWindow = function () { + + if (!this.done) { + for (var piece = 0; piece < this.chunks; piece++) { + if (!this.bitfield.get(piece)) { + this._windowEnd = piece; + break; + } + } + } +}; + +Dispatcher.prototype._checkDone = function () { + var self = this; + if (self.destroyed) return; + // is the torrent done? (if all current selections are satisfied, or there are + // no selections, then torrent is done) + var done = true; + // debug('_selections.length:'+self._selections.length); + // for (var i = 0; i < self._selections.length; i++) { + // var selection = self._selections[i]; + // for (var piece = selection.from; piece <= selection.to; piece++) { + // if (!self.bitfield.get(piece)) { + // done = false; + // break + // } + // } + // if (!done) break + // } + for (var i = 0; i < self.chunks; i++) { + if (!self.bitfield.get(i)) { + // self._windowOffset = i; + done = false; + break + } + } + // debug('_checkDone self.done:'+self.done+' done:'+done); + if (!self.done && done) { + self.done = true; + // debug('dispatcher done'); + self.emit('done'); + if (self.useMonitor) { + self.emit('downloaded', 1.0); + } + for (var k=0;k= self.chunks){ + break; + } + // debug('index:'+index); + if (count >= sortedNodes.length) break; + + if (!self.bitfield.get(index)) { + + var pair = self._calRange(index); + // var node = self._getNodes(count); + // node.select(pair[0],pair[1]); + var node = sortedNodes[count % sortedNodes.length]; + // var node = sortedNodes[count]; + node.select(pair[0],pair[1]); + count ++; + } else { + + } + index ++; + } + self._windowEnd = index; + debug('_fillWindow _windowEnd:'+self._windowEnd); +}; + +Dispatcher.prototype._createPushStream = function () { + var self = this; + + var sortedNodes = this.downloaders; + + if (sortedNodes.length === 0) return; + + var count = 0; + + var index = self._windowEnd; + + while (count !== self.maxLoaders){ + // debug('_fillWindow _windowLength:'+self._windowLength + ' downloadersLength:' + self.downloaders.length); + if (index >= self.chunks){ + break; + } + // debug('index:'+index); + // if (count >= sortedNodes.length) break; + + if (!self.bitfield.get(index)) { + + var pair = self._calRange(index); + // var node = self._getNodes(count); + // node.select(pair[0],pair[1]); + var node = sortedNodes[count % sortedNodes.length]; + // var node = sortedNodes[count]; + node.select(pair[0],pair[1]); + count ++; + } else { + + } + index ++; + } + self._windowEnd = index; +}; + +Dispatcher.prototype._setupHttp = function (hd) { + var self = this; + + hd.once('start',function () { + + }); + hd.once('done',function () { + + // debug('httpDownloader ondone'); + + }); + hd.once('error', function (error) { + + console.warn('http' + hd.uri + 'error!'); + + if (self.downloaders.length > self._windowLength) { + self.downloaders.removeObj(hd); + if (self._windowLength > 3) self._windowLength --; + } + self.checkoutDownloaders(); + }); + hd.on('data',function (buffer, start, end, speed) { + + var index = self._calIndex(start); + debug('httpDownloader' + hd.uri +' ondata range:'+start+'-'+end+' at index:'+index+' speed:'+hd.meanSpeed); + var size = end - start + 1; + if (!self.bitfield.get(index)){ + self.bitfield.set(index,true); + + self.store.put(index, buffer); + + + + self._checkDone(); + if (self.useMonitor) { + self.downloaded += size; + self.emit('downloaded', self.downloaded/self.fileSize); + // hd.downloaded += size; + self.emit('traffic', hd.mac, size, hd.type === 1 ? 'HTTP_Node' : 'HTTP_Server', hd.meanSpeed); + debug('ondata hd.type:' + hd.type +' index:' + index); + if (hd.type === 1) { //node + self.fogDownloaded += size; + var fogRatio = self.fogDownloaded/self.downloaded; + if (fogRatio >= self.fogRatio) { + self.emit('fograte', fogRatio); + } + self.emit('fogspeed', self.downloaders.getCurrentSpeed([1])); + // hd.type === 1 ? self.bufferSources[index] = 'n' : self.bufferSources[index] = 'b'; + hd.type === 1 ? self.bufferSources[index] = hd.id : self.bufferSources[index] = 'b'; //test + } else { + self.emit('cloudspeed', self.downloaders.getCurrentSpeed([0])); + // self.bufferSources[index] = 's' + self.bufferSources[index] = hd.id; //test + } + self.emit('buffersources', self.bufferSources); + self.emit('sourcemap', hd.type === 1 ? 'n' : 's', index); + } + // debug('bufferSources:'+self.bufferSources); + } else { + debug('重复下载'); + + } + }); + + return hd; +}; + +Dispatcher.prototype._setupDC = function (jd) { + var self = this; + + jd.once('start',function () { + // debug('DC start downloading'); + }); + + jd.on('data',function (buffer, start, end, speed) { + + var index = self._calIndex(start); + debug('pear_webrtc '+jd.dc_id+' ondata range:'+start+'-'+end+' at index:'+index+' speed:'+jd.meanSpeed); + var size = end - start + 1; + if (!self.bitfield.get(index)){ + self.bitfield.set(index,true); + + self.store.put(index, buffer); + + self._checkDone(); + if (self.useMonitor) { + self.downloaded += size; + self.fogDownloaded += size; + debug('downloaded:'+self.downloaded+' fogDownloaded:'+self.fogDownloaded); + self.emit('downloaded', self.downloaded/self.fileSize); + var fogRatio = self.fogDownloaded/self.downloaded; + if (fogRatio >= self.fogRatio) { + self.emit('fograte', fogRatio); + } + self.emit('fogspeed', self.downloaders.getCurrentSpeed([2])); + self.bufferSources[index] = 'd'; + self.emit('buffersources', self.bufferSources); + self.emit('sourcemap', 'd', index); + // jd.downloaded += size; + self.emit('traffic', jd.mac, size, 'WebRTC_Node', jd.meanSpeed); + } + } else { + debug('重复下载'); + for (var k=0;k= 2) { + this.emit('needsource'); + } + } +}; + +Dispatcher.prototype.addTorrent = function (torrent) { + var self = this; + // debug('torrent.pieces.length:'+torrent.pieces.length+' chunks:'+this.chunks); + if (torrent.pieces.length !== this.chunks) return; + this.torrent = torrent; + torrent.pear_downloaded = 0; + debug('addTorrent _windowOffset:' + self._windowOffset); + if (self._windowOffset + self._windowLength < torrent.pieces.length-1) { + debug('torrent.select:%d to %d', self._windowOffset+self._windowLength, torrent.pieces.length-1); + torrent.select(self._windowOffset+self._windowLength, torrent.pieces.length-1, 1000); + } + torrent.on('piecefromtorrent', function (index) { + + debug('piecefromtorrent:'+index); + if (self.useMonitor) { + self.downloaded += self.pieceLength; + self.fogDownloaded += self.pieceLength; + torrent.pear_downloaded += self.pieceLength; + self.emit('downloaded', self.downloaded/self.fileSize); + var fogRatio = self.fogDownloaded/self.downloaded; + if (fogRatio >= self.fogRatio) { + self.emit('fograte', fogRatio); + } + // debug('torrent.downloadSpeed:'+torrent.downloadSpeed/1024); + self.emit('fogspeed', torrent.downloadSpeed/1024); + self.bufferSources[index] = 'b'; + self.emit('buffersources', self.bufferSources); + self.emit('sourcemap', 'b', index); + self.emit('traffic', 'Webtorrent', self.pieceLength, 'WebRTC_Browser'); + } + }); + + torrent.on('done', function () { + debug('torrent done'); + }); +}; + +Dispatcher.prototype.addDataChannel = function (dc) { + + // this.downloaders.push(dc); + this.downloaders.splice(this._windowLength-1,0,dc); + // if (this._windowLength < 8 && this.downloaders.length > this._windowLength) { + // this._windowLength ++; + // } + this._setupDC(dc); + if (!this.sequencial && this._windowLength < 10) this._windowLength ++; // +}; + +Dispatcher.prototype.addNode = function (node) { //node是httpdownloader对象 + + this._setupHttp(node); + this.downloaders.push(node); + debug('dispatcher add node: '+node.uri); + if (!this.sequencial && this._windowLength < 10) this._windowLength ++; +}; + +Dispatcher.prototype.requestMoreNodes = function () { + + if (this.downloaders.length > 0) { //节点不够,重新请求 + this.emit('needmorenodes'); + } else { + this.emit('error'); + } +}; + +Dispatcher.prototype.requestMoreDataChannels = function () { + + if (this.downloaders.length > 0) { //节点不够,重新请求 + this.emit('needmoredatachannels'); + } else { + this.emit('error'); + } +}; + +Dispatcher.prototype.destroy = function () { + var self = this; + if (self.destroyed) return; + self.destroyed = true; + + for (var k=0;k= 0) { + sum+=this[i].meanSpeed; + length ++; + } + } + } else { + for (var i = 0; i < this.length; i++) { + sum+=this[i].meanSpeed; + length ++; + } + } + return Math.floor(sum/length); +}; + +Array.prototype.getCurrentSpeed = function (typeArr) { //根据传输的类型(不传则计算所有节点)来计算瞬时速度 + var sum = 0; + var length = 0; + if (typeArr) { + for (var i = 0; i < this.length; i++) { + if (typeArr.indexOf(this[i].type) >= 0) { + sum+=this[i].speed; + } + } + } else { + for (var i = 0; i < this.length; i++) { + sum+=this[i].speed; + } + } + return Math.floor(sum); +}; + - result.protocol = relative.protocol; - if (!relative.host && !hostlessProtocol[relative.protocol]) { - var relPath = (relative.pathname || '').split('/'); - while (relPath.length && !(relative.host = relPath.shift())); - if (!relative.host) relative.host = ''; - if (!relative.hostname) relative.hostname = ''; - if (relPath[0] !== '') relPath.unshift(''); - if (relPath.length < 2) relPath.unshift(''); - result.pathname = relPath.join('/'); - } else { - result.pathname = relative.pathname; - } - result.search = relative.search; - result.query = relative.query; - result.host = relative.host || ''; - result.auth = relative.auth; - result.hostname = relative.hostname || relative.host; - result.port = relative.port; - // to support http.request - if (result.pathname || result.search) { - var p = result.pathname || ''; - var s = result.search || ''; - result.path = p + s; - } - result.slashes = result.slashes || relative.slashes; - result.href = result.format(); - return result; - } +}).call(this,require('_process')) +},{"_process":15,"bitfield":73,"debug":87,"events":7,"fs-chunk-store":105,"immediate-chunk-store":95,"inherits":96}],176:[function(require,module,exports){ +module.exports = FileStream; + +var debug = require('debug')('pear:file-stream'); +var inherits = require('inherits'); +var stream = require('readable-stream'); + +inherits(FileStream, stream.Readable); + +/** + * Readable stream of a torrent file + * + * @param {File} file + * @param {Object} opts + * @param {number} opts.start stream slice of file, starting from this byte (inclusive) + * @param {number} opts.end stream slice of file, ending with this byte (inclusive) + */ +function FileStream (file, opts) { + stream.Readable.call(this, opts); + + this.destroyed = false; + this._dispatcher = file._dispatcher; + + var start = (opts && opts.start) || 0; + var end = (opts && opts.end && opts.end < file.length) + ? opts.end + : file.length - 1; + + var pieceLength = file._dispatcher.pieceLength; + this._startPiece = (start + file.offset) / pieceLength | 0; //start和end的单位应该是byte + this._endPiece = (end + file.offset) / pieceLength | 0; + + this._piece = this._startPiece; + this._offset = (start + file.offset) - (this._startPiece * pieceLength); + + this._missing = end - start + 1; + this._reading = false; + this._notifying = false; + this._criticalLength = Math.min((1024 * 1024 / pieceLength) | 0, 2); + + // debug('FileStream _startPiece:'+this._startPiece); + // debug('FileStream _endPiece:'+this._endPiece); + // debug('FileStream _offset:'+this._offset); + // debug('FileStream _missing:'+this._missing); +} + +FileStream.prototype._read = function () { + if (this._reading) return; + this._reading = true; + this._notify(); +}; + +FileStream.prototype._notify = function () { + var self = this; + + if (!self._reading || self._missing === 0) return; + if (!self._dispatcher.bitfield.get(self._piece)) { + // return self._dispatcher.critical(self._piece, self._piece + self._criticalLength) + return noop(); + } + + if (self._notifying) return; + + // self._ifCanPlay(); + // if (!this._dispatcher.enoughInitBuffer) return; + self._notifying = true; + + var p = self._piece; + // debug('FileStream get piece:' + p); + self._dispatcher.store.get(p, function (err, buffer) { + self._notifying = false; + if (self.destroyed) return; + if (err) return self._destroy(err); + // debug('read %s (length %s) (err %s)', p, buffer.length, err && err.message) + // debug('read '+p+' length:'+buffer.length); + if (self._offset) { + buffer = buffer.slice(self._offset); + self._offset = 0; + } + + if (self._missing < buffer.length) { + buffer = buffer.slice(0, self._missing); + } + self._missing -= buffer.length; + + // debug('pushing buffer of length:'+buffer.length); + self._reading = false; + self.push(buffer); + // if (p === self._dispatcher._windowLength/2) { + // self.emit('canplay'); + // } + if (self._missing === 0) self.push(null); + }); + self._piece += 1; +}; + +FileStream.prototype.destroy = function (onclose) { + this._destroy(null, onclose) +}; + +FileStream.prototype._destroy = function (err, onclose) { + if (this.destroyed) return; + this.destroyed = true; + + if (!this._dispatcher.destroyed) { + this._dispatcher.deselect(this._startPiece, this._endPiece, true); + } + debug('FileStream destroy'); + if (err) this.emit('error', err); + this.emit('close'); + if (onclose) onclose(); +}; + +// FileStream.prototype._ifCanPlay = function () { //缓存足够的buffer后才播放 +// if (this._dispatcher.enoughInitBuffer) return; +// var bitfield = this._dispatcher.bitfield; +// debug('this._dispatcher.normalWindowLength:'+this._dispatcher.normalWindowLength); +// for (var i=this._startPiece;i= 3) { + // this.clearQueue(); + // this.weight -= 0.1; + // if (this.weight < 0.1) { + // this.emit('error'); + // } + // } +}; + +HttpDownloader.prototype.abort = function () { + var self = this; + // debug('[HttpDownloader] readyState:'+self._xhr.readyState); + if (self._xhr && (self._xhr.readyState == 2 || self._xhr.readyState == 3)) { //如果正在下载,则停止 + self._xhr.abort(); + debug('HttpDownloader ' + self.uri +' aborted!'); + } + self.downloading = false; +}; + +HttpDownloader.prototype.clearQueue = function () { //清空下载队列 + + // this.downloading = false; + if (this.queue.length > 0) { + // debug('[HttpDownloader] clear queue!'); + this.queue = []; + } +}; + +HttpDownloader.prototype._getChunk = function (begin,end) { + var self = this; + debug('HttpDownloader _getChunk'); + self.downloading = true; + var xhr = new XMLHttpRequest(); + self._xhr = xhr; + xhr.open("GET", self.uri); + xhr.responseType = "arraybuffer"; + xhr.timeout = 2000; + self.startTime=(new Date()).getTime(); + // debug('get_file_index: start:'+begin+' end:'+end); + var range = "bytes="+begin+"-"+end; + // debug('request range: ' + range); + xhr.setRequestHeader("Range", range); + xhr.onload = function (event) { + if (this.status >= 200 || this.status < 300) { + self.downloading = false; + + self.endTime = (new Date()).getTime(); + // self.speed = Math.floor((event.total * 1000) / ((self.endTime - self.startTime) * 1024)); //单位: KB/s + self.speed = Math.floor(event.total / (self.endTime - self.startTime)); //单位: KB/s + debug('http speed:' + self.speed + 'KB/s'); + // self.meanSpeed = (self.meanSpeed*self.counter + self.speed)/(++self.counter); + if (self.meanSpeed == -1) self.meanSpeed = self.speed; + self.meanSpeed = 0.95*self.meanSpeed + 0.05*self.speed; + debug('http '+self.uri+' meanSpeed:' + self.meanSpeed + 'KB/s'); + if (!self.isAsync) { + if (self.queue.length > 0){ //如果下载队列不为空 + var pair = self.queue.shift(); + self._getChunk(pair[0], pair[1]); + } + } + var range = this.getResponseHeader("Content-Range").split(" ",2)[1].split('/',1)[0]; + // debug('xhr.onload range:'+range); + // self.emit('done'); + self._handleChunk(range,this.response); + } else { + self.emit('error'); + } + }; + xhr.onerror = function(_) { + + self.emit('error'); + }; + xhr.ontimeout = function (_) { + debug('HttpDownloader ' + self.uri + ' timeout'); + self.emit('error'); + }; + xhr.send(); +}; + +HttpDownloader.prototype._handleChunk = function (range,data) { + + var start = range.split('-')[0]; + var end = range.split('-')[1]; + var buffer = Buffer.from(data); + this.emit('data', buffer, start, end, this.speed); + +}; + + + + - // if the url is a non-slashed url, then relative - // links like ../.. should be able - // to crawl up to the hostname, as well. This is strange. - // result.protocol has already been set by now. - // Later on, put the first path part into the host field. - if (psychotic) { - result.hostname = ''; - result.port = null; - if (result.host) { - if (srcPath[0] === '') srcPath[0] = result.host; - else srcPath.unshift(result.host); - } - result.host = ''; - if (relative.protocol) { - relative.hostname = null; - relative.port = null; - if (relative.host) { - if (relPath[0] === '') relPath[0] = relative.host; - else relPath.unshift(relative.host); - } - relative.host = null; - } - mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); - } +},{"buffer/":82,"debug":87,"events":7,"inherits":96}],179:[function(require,module,exports){ +/** + * Created by XieTing on 17-6-6. + */ + +module.exports = PearDownloader; + +var debug = require('debug'); +var inherits = require('inherits'); +var Worker = require('./worker'); +var version = require('../package.json').version; + +inherits(PearDownloader, Worker); + +function PearDownloader(urlStr, token, opts) { + var self = this; + if (!(self instanceof PearDownloader)) return new PearDownloader(urlStr, token, opts); + // if (!(self instanceof PearPlayer)) return new PearPlayer(selector, opts); + if (typeof token === 'object') return PearDownloader(urlStr, '', token); + + //validate + if (opts.algorithm && ['push', 'pull'].indexOf(opts.algorithm) == -1) throw new Error('Algorithm ' + opts.algorithm + ' is not supported'); + + + if (!opts) opts = {}; + if (opts.debug) { + debug.enable('pear:*'); + } else { + debug.disable(); + } + self.version = version; + console.info('pear version:'+version); + + Worker.call(self, urlStr, token, opts); +} + +PearDownloader.isWebRTCSupported = function () { + + return Worker.isRTCSupported(); +}; + + - if (isRelAbs) { - // it's absolute. - result.host = (relative.host || relative.host === '') ? - relative.host : result.host; - result.hostname = (relative.hostname || relative.hostname === '') ? - relative.hostname : result.hostname; - result.search = relative.search; - result.query = relative.query; - srcPath = relPath; - // fall through to the dot-handling below. - } else if (relPath.length) { - // it's relative - // throw away the existing file, and take the new path instead. - if (!srcPath) srcPath = []; - srcPath.pop(); - srcPath = srcPath.concat(relPath); - result.search = relative.search; - result.query = relative.query; - } else if (!util.isNullOrUndefined(relative.search)) { - // just pull out the search. - // like href='?foo'. - // Put this after the other two cases because it simplifies the booleans - if (psychotic) { - result.hostname = result.host = srcPath.shift(); - //occationaly the auth can get stuck only in host - //this especially happens in cases like - //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = result.host && result.host.indexOf('@') > 0 ? - result.host.split('@') : false; - if (authInHost) { - result.auth = authInHost.shift(); - result.host = result.hostname = authInHost.shift(); - } - } - result.search = relative.search; - result.query = relative.query; - //to support http.request - if (!util.isNull(result.pathname) || !util.isNull(result.search)) { - result.path = (result.pathname ? result.pathname : '') + - (result.search ? result.search : ''); - } - result.href = result.format(); - return result; - } +},{"../package.json":174,"./worker":195,"debug":87,"inherits":96}],180:[function(require,module,exports){ +module.exports = FileStream + +var debug = require('debug')('webtorrent:file-stream') +var inherits = require('inherits') +var stream = require('readable-stream') + +inherits(FileStream, stream.Readable) + +/** + * Readable stream of a torrent file + * + * @param {File} file + * @param {Object} opts + * @param {number} opts.start stream slice of file, starting from this byte (inclusive) + * @param {number} opts.end stream slice of file, ending with this byte (inclusive) + */ +function FileStream (file, opts) { + stream.Readable.call(this, opts) + + this.destroyed = false + this._torrent = file._torrent + + var start = (opts && opts.start) || 0 + var end = (opts && opts.end && opts.end < file.length) + ? opts.end + : file.length - 1 + + var pieceLength = file._torrent.pieceLength + + this._startPiece = (start + file.offset) / pieceLength | 0 + this._endPiece = (end + file.offset) / pieceLength | 0 + + this._piece = this._startPiece + this._offset = (start + file.offset) - (this._startPiece * pieceLength) + + this._missing = end - start + 1 + this._reading = false + this._notifying = false + this._criticalLength = Math.min((1024 * 1024 / pieceLength) | 0, 2) +} + +FileStream.prototype._read = function () { + if (this._reading) return + this._reading = true + this._notify() +} + +FileStream.prototype._notify = function () { + var self = this + + if (!self._reading || self._missing === 0) return + if (!self._torrent.bitfield.get(self._piece)) { + return self._torrent.critical(self._piece, self._piece + self._criticalLength) + } + + if (self._notifying) return + self._notifying = true + + var p = self._piece + self._torrent.store.get(p, function (err, buffer) { + self._notifying = false + if (self.destroyed) return + if (err) return self._destroy(err) + debug('read %s (length %s) (err %s)', p, buffer.length, err && err.message) + + if (self._offset) { + buffer = buffer.slice(self._offset) + self._offset = 0 + } + + if (self._missing < buffer.length) { + buffer = buffer.slice(0, self._missing) + } + self._missing -= buffer.length + + debug('pushing buffer of length %s', buffer.length) + self._reading = false + self.push(buffer) + + if (self._missing === 0) self.push(null) + }) + self._piece += 1 +} + +FileStream.prototype.destroy = function (onclose) { + this._destroy(null, onclose) +} + +FileStream.prototype._destroy = function (err, onclose) { + if (this.destroyed) return + this.destroyed = true + + if (!this._torrent.destroyed) { + this._torrent.deselect(this._startPiece, this._endPiece, true) + } + + if (err) this.emit('error', err) + this.emit('close') + if (onclose) onclose() +} - if (!srcPath.length) { - // no path at all. easy. - // we've already handled the other stuff above. - result.pathname = null; - //to support http.request - if (result.search) { - result.path = '/' + result.search; - } else { - result.path = null; - } - result.href = result.format(); - return result; - } +},{"debug":87,"inherits":96,"readable-stream":132}],181:[function(require,module,exports){ +(function (process){ +module.exports = File + +var eos = require('end-of-stream') +var EventEmitter = require('events').EventEmitter +var FileStream = require('./file-stream') +var inherits = require('inherits') +var path = require('path') +// var render = require('render-media') +var stream = require('readable-stream') +// var streamToBlob = require('stream-to-blob') +// var streamToBlobURL = require('stream-to-blob-url') +var streamToBuffer = require('stream-with-known-length-to-buffer') + +inherits(File, EventEmitter) + +function File (torrent, file) { + EventEmitter.call(this) + + this._torrent = torrent + this._destroyed = false + + this.name = file.name + this.path = file.path + this.length = file.length + this.offset = file.offset + + this.done = false + + var start = file.offset + var end = start + file.length - 1 + + this._startPiece = start / this._torrent.pieceLength | 0 + this._endPiece = end / this._torrent.pieceLength | 0 + + if (this.length === 0) { + this.done = true + this.emit('done') + } +} + +Object.defineProperty(File.prototype, 'downloaded', { + get: function () { + if (!this._torrent.bitfield) return 0 + var downloaded = 0 + for (var index = this._startPiece; index <= this._endPiece; ++index) { + if (this._torrent.bitfield.get(index)) { + // verified data + downloaded += this._torrent.pieceLength + } else { + // "in progress" data + var piece = this._torrent.pieces[index] + downloaded += (piece.length - piece.missing) + } + } + return downloaded + } +}) + +Object.defineProperty(File.prototype, 'progress', { + get: function () { return this.length ? this.downloaded / this.length : 0 } +}) + +File.prototype.select = function (priority) { + if (this.length === 0) return + this._torrent.select(this._startPiece, this._endPiece, priority) +} + +File.prototype.deselect = function () { + if (this.length === 0) return + this._torrent.deselect(this._startPiece, this._endPiece, false) +} + +File.prototype.createReadStream = function (opts) { + var self = this + if (this.length === 0) { + var empty = new stream.PassThrough() + process.nextTick(function () { + empty.end() + }) + return empty + } + + var fileStream = new FileStream(self, opts) + self._torrent.select(fileStream._startPiece, fileStream._endPiece, true, function () { + fileStream._notify() + }) + eos(fileStream, function () { + if (self._destroyed) return + if (!self._torrent.destroyed) { + self._torrent.deselect(fileStream._startPiece, fileStream._endPiece, true) + } + }) + return fileStream +} + +File.prototype.getBuffer = function (cb) { + streamToBuffer(this.createReadStream(), this.length, cb) +} + +// File.prototype.getBlob = function (cb) { +// if (typeof window === 'undefined') throw new Error('browser-only method') +// streamToBlob(this.createReadStream(), this._getMimeType(), cb) +// } + +// File.prototype.getBlobURL = function (cb) { +// if (typeof window === 'undefined') throw new Error('browser-only method') +// streamToBlobURL(this.createReadStream(), this._getMimeType(), cb) +// } + +// File.prototype.appendTo = function (elem, opts, cb) { +// if (typeof window === 'undefined') throw new Error('browser-only method') +// render.append(this, elem, opts, cb) +// } + +// File.prototype.renderTo = function (elem, opts, cb) { +// if (typeof window === 'undefined') throw new Error('browser-only method') +// render.render(this, elem, opts, cb) +// } + +// File.prototype._getMimeType = function () { +// return render.mime[path.extname(this.name).toLowerCase()] +// } + +File.prototype._destroy = function () { + this._destroyed = true + this._torrent = null +} - // if a url ENDs in . or .., then it must get a trailing slash. - // however, if it ends in anything else non-slashy, - // then it must NOT get a trailing slash. - var last = srcPath.slice(-1)[0]; - var hasTrailingSlash = ( - (result.host || relative.host || srcPath.length > 1) && - (last === '.' || last === '..') || last === ''); +}).call(this,require('_process')) +},{"./file-stream":180,"_process":15,"end-of-stream":90,"events":7,"inherits":96,"path":13,"readable-stream":132,"stream-with-known-length-to-buffer":147}],182:[function(require,module,exports){ +var arrayRemove = require('unordered-array-remove') +var debug = require('debug')('webtorrent:peer') +var Wire = require('bittorrent-protocol') + +var WebConn = require('./webconn') + +var CONNECT_TIMEOUT_TCP = 5000 +var CONNECT_TIMEOUT_WEBRTC = 25000 +var HANDSHAKE_TIMEOUT = 25000 + +/** + * WebRTC peer connections start out connected, because WebRTC peers require an + * "introduction" (i.e. WebRTC signaling), and there's no equivalent to an IP address + * that lets you refer to a WebRTC endpoint. + */ +exports.createWebRTCPeer = function (conn, swarm) { + var peer = new Peer(conn.id, 'webrtc') + peer.conn = conn + peer.swarm = swarm + + if (peer.conn.connected) { + peer.onConnect() + } else { + peer.conn.once('connect', function () { peer.onConnect() }) + peer.conn.once('error', function (err) { peer.destroy(err) }) + peer.startConnectTimeout() + } + + return peer +} + +/** + * Incoming TCP peers start out connected, because the remote peer connected to the + * listening port of the TCP server. Until the remote peer sends a handshake, we don't + * know what swarm the connection is intended for. + */ +exports.createTCPIncomingPeer = function (conn) { + var addr = conn.remoteAddress + ':' + conn.remotePort + var peer = new Peer(addr, 'tcpIncoming') + peer.conn = conn + peer.addr = addr + + peer.onConnect() + + return peer +} + +/** + * Outgoing TCP peers start out with just an IP address. At some point (when there is an + * available connection), the client can attempt to connect to the address. + */ +exports.createTCPOutgoingPeer = function (addr, swarm) { + var peer = new Peer(addr, 'tcpOutgoing') + peer.addr = addr + peer.swarm = swarm + + return peer +} + +/** + * Peer that represents a Web Seed (BEP17 / BEP19). + */ +exports.createWebSeedPeer = function (url, swarm) { + var peer = new Peer(url, 'webSeed') + peer.swarm = swarm + peer.conn = new WebConn(url, swarm) + + peer.onConnect() + + return peer +} + +/** + * Peer. Represents a peer in the torrent swarm. + * + * @param {string} id "ip:port" string, peer id (for WebRTC peers), or url (for Web Seeds) + * @param {string} type the type of the peer + */ +function Peer (id, type) { + var self = this + self.id = id + self.type = type + + debug('new Peer %s', id) + + self.addr = null + self.conn = null + self.swarm = null + self.wire = null + + self.connected = false + self.destroyed = false + self.timeout = null // handshake timeout + self.retries = 0 // outgoing TCP connection retry count + + self.sentHandshake = false +} + +/** + * Called once the peer is connected (i.e. fired 'connect' event) + * @param {Socket} conn + */ +Peer.prototype.onConnect = function () { + var self = this + if (self.destroyed) return + self.connected = true + + debug('Peer %s connected', self.id) + + clearTimeout(self.connectTimeout) + + var conn = self.conn + conn.once('end', function () { + self.destroy() + }) + conn.once('close', function () { + self.destroy() + }) + conn.once('finish', function () { + self.destroy() + }) + conn.once('error', function (err) { + self.destroy(err) + }) + + var wire = self.wire = new Wire() + wire.type = self.type + wire.once('end', function () { + self.destroy() + }) + wire.once('close', function () { + self.destroy() + }) + wire.once('finish', function () { + self.destroy() + }) + wire.once('error', function (err) { + self.destroy(err) + }) + + wire.once('handshake', function (infoHash, peerId) { + self.onHandshake(infoHash, peerId) + }) + self.startHandshakeTimeout() + + conn.pipe(wire).pipe(conn) + if (self.swarm && !self.sentHandshake) self.handshake() +} + +/** + * Called when handshake is received from remote peer. + * @param {string} infoHash + * @param {string} peerId + */ +Peer.prototype.onHandshake = function (infoHash, peerId) { + var self = this + if (!self.swarm) return // `self.swarm` not set yet, so do nothing + if (self.destroyed) return + + if (self.swarm.destroyed) { + return self.destroy(new Error('swarm already destroyed')) + } + if (infoHash !== self.swarm.infoHash) { + return self.destroy(new Error('unexpected handshake info hash for this swarm')) + } + if (peerId === self.swarm.peerId) { + return self.destroy(new Error('refusing to connect to ourselves')) + } + + debug('Peer %s got handshake %s', self.id, infoHash) + + clearTimeout(self.handshakeTimeout) + + self.retries = 0 + + var addr = self.addr + if (!addr && self.conn.remoteAddress) { + addr = self.conn.remoteAddress + ':' + self.conn.remotePort + } + self.swarm._onWire(self.wire, addr) + + // swarm could be destroyed in user's 'wire' event handler + if (!self.swarm || self.swarm.destroyed) return + + if (!self.sentHandshake) self.handshake() +} + +Peer.prototype.handshake = function () { + var self = this + var opts = { + dht: self.swarm.private ? false : !!self.swarm.client.dht + } + self.wire.handshake(self.swarm.infoHash, self.swarm.client.peerId, opts) + self.sentHandshake = true +} + +Peer.prototype.startConnectTimeout = function () { + var self = this + clearTimeout(self.connectTimeout) + self.connectTimeout = setTimeout(function () { + self.destroy(new Error('connect timeout')) + }, self.type === 'webrtc' ? CONNECT_TIMEOUT_WEBRTC : CONNECT_TIMEOUT_TCP) + if (self.connectTimeout.unref) self.connectTimeout.unref() +} + +Peer.prototype.startHandshakeTimeout = function () { + var self = this + clearTimeout(self.handshakeTimeout) + self.handshakeTimeout = setTimeout(function () { + self.destroy(new Error('handshake timeout')) + }, HANDSHAKE_TIMEOUT) + if (self.handshakeTimeout.unref) self.handshakeTimeout.unref() +} + +Peer.prototype.destroy = function (err) { + var self = this + if (self.destroyed) return + self.destroyed = true + self.connected = false + + debug('destroy %s (error: %s)', self.id, err && (err.message || err)) + + clearTimeout(self.connectTimeout) + clearTimeout(self.handshakeTimeout) + + var swarm = self.swarm + var conn = self.conn + var wire = self.wire + + self.swarm = null + self.conn = null + self.wire = null + + if (swarm && wire) { + arrayRemove(swarm.wires, swarm.wires.indexOf(wire)) + } + if (conn) { + conn.on('error', noop) + conn.destroy() + } + if (wire) wire.destroy() + if (swarm) swarm.removePeer(self.id) +} + +function noop () {} - // strip single dots, resolve double dots to parent dir - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = srcPath.length; i >= 0; i--) { - last = srcPath[i]; - if (last === '.') { - srcPath.splice(i, 1); - } else if (last === '..') { - srcPath.splice(i, 1); - up++; - } else if (up) { - srcPath.splice(i, 1); - up--; - } - } +},{"./webconn":185,"bittorrent-protocol":74,"debug":87,"unordered-array-remove":157}],183:[function(require,module,exports){ +module.exports = RarityMap + +/** + * Mapping of torrent pieces to their respective availability in the torrent swarm. Used + * by the torrent manager for implementing the rarest piece first selection strategy. + */ +function RarityMap (torrent) { + var self = this + + self._torrent = torrent + self._numPieces = torrent.pieces.length + self._pieces = [] + + self._onWire = function (wire) { + self.recalculate() + self._initWire(wire) + } + self._onWireHave = function (index) { + self._pieces[index] += 1 + } + self._onWireBitfield = function () { + self.recalculate() + } + + self._torrent.wires.forEach(function (wire) { + self._initWire(wire) + }) + self._torrent.on('wire', self._onWire) + self.recalculate() +} + +/** + * Get the index of the rarest piece. Optionally, pass a filter function to exclude + * certain pieces (for instance, those that we already have). + * + * @param {function} pieceFilterFunc + * @return {number} index of rarest piece, or -1 + */ +RarityMap.prototype.getRarestPiece = function (pieceFilterFunc) { + if (!pieceFilterFunc) pieceFilterFunc = trueFn + + var candidates = [] + var min = Infinity + + for (var i = 0; i < this._numPieces; ++i) { + if (!pieceFilterFunc(i)) continue + + var availability = this._pieces[i] + if (availability === min) { + candidates.push(i) + } else if (availability < min) { + candidates = [ i ] + min = availability + } + } + + if (candidates.length > 0) { + // if there are multiple pieces with the same availability, choose one randomly + return candidates[Math.random() * candidates.length | 0] + } else { + return -1 + } +} + +RarityMap.prototype.destroy = function () { + var self = this + self._torrent.removeListener('wire', self._onWire) + self._torrent.wires.forEach(function (wire) { + self._cleanupWireEvents(wire) + }) + self._torrent = null + self._pieces = null + + self._onWire = null + self._onWireHave = null + self._onWireBitfield = null +} + +RarityMap.prototype._initWire = function (wire) { + var self = this + + wire._onClose = function () { + self._cleanupWireEvents(wire) + for (var i = 0; i < this._numPieces; ++i) { + self._pieces[i] -= wire.peerPieces.get(i) + } + } + + wire.on('have', self._onWireHave) + wire.on('bitfield', self._onWireBitfield) + wire.once('close', wire._onClose) +} + +/** + * Recalculates piece availability across all peers in the torrent. + */ +RarityMap.prototype.recalculate = function () { + var i + for (i = 0; i < this._numPieces; ++i) { + this._pieces[i] = 0 + } + + var numWires = this._torrent.wires.length + for (i = 0; i < numWires; ++i) { + var wire = this._torrent.wires[i] + for (var j = 0; j < this._numPieces; ++j) { + this._pieces[j] += wire.peerPieces.get(j) + } + } +} + +RarityMap.prototype._cleanupWireEvents = function (wire) { + wire.removeListener('have', this._onWireHave) + wire.removeListener('bitfield', this._onWireBitfield) + if (wire._onClose) wire.removeListener('close', wire._onClose) + wire._onClose = null +} + +function trueFn () { + return true +} - // if the path is allowed to go above the root, restore leading ..s - if (!mustEndAbs && !removeAllDots) { - for (; up--; up) { - srcPath.unshift('..'); - } - } +},{}],184:[function(require,module,exports){ +(function (process,global){ +/* global URL, Blob */ + +module.exports = Torrent + +var addrToIPPort = require('addr-to-ip-port') +var BitField = require('bitfield') +var ChunkStoreWriteStream = require('chunk-store-stream/write') +var debug = require('debug')('webtorrent:torrent') +var Discovery = require('torrent-discovery') +var EventEmitter = require('events').EventEmitter +var extend = require('xtend') +var extendMutable = require('xtend/mutable') +var fs = require('fs') +var FSChunkStore = require('fs-chunk-store') // browser: `memory-chunk-store` +var get = require('simple-get') +var ImmediateChunkStore = require('immediate-chunk-store') +var inherits = require('inherits') +var MultiStream = require('multistream') +var net = require('net') // browser exclude +var os = require('os') // browser exclude +var parallel = require('run-parallel') +var parallelLimit = require('run-parallel-limit') +var parseTorrent = require('parse-torrent') +var path = require('path') +var Piece = require('torrent-piece') +var pump = require('pump') +var randomIterate = require('random-iterate') +var sha1 = require('simple-sha1') +var speedometer = require('speedometer') +var uniq = require('uniq') +var utMetadata = require('ut_metadata') +var utPex = require('ut_pex') // browser exclude + +var File = require('./file') +var Peer = require('./peer') +var RarityMap = require('./rarity-map') +var Server = require('./server') // browser exclude + +var MAX_BLOCK_LENGTH = 128 * 1024 +var PIECE_TIMEOUT = 30000 +var CHOKE_TIMEOUT = 5000 +var SPEED_THRESHOLD = 3 * Piece.BLOCK_LENGTH + +var PIPELINE_MIN_DURATION = 0.5 +var PIPELINE_MAX_DURATION = 1 + +var RECHOKE_INTERVAL = 10000 // 10 seconds +var RECHOKE_OPTIMISTIC_DURATION = 2 // 30 seconds + +var FILESYSTEM_CONCURRENCY = 2 + +var RECONNECT_WAIT = [ 1000, 5000, 15000 ] + +var VERSION = require('../package.json').version +var USER_AGENT = 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' + +var TMP +try { + TMP = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent') +} catch (err) { + TMP = path.join(typeof os.tmpdir === 'function' ? os.tmpdir() : '/', 'webtorrent') +} + +inherits(Torrent, EventEmitter) + +function Torrent (torrentId, client, opts) { + EventEmitter.call(this) + + this._debugId = 'unknown infohash' + this.client = client + + this.announce = opts.announce + this.urlList = opts.urlList + + this.path = opts.path + this._store = FSChunkStore //pear modified + this._getAnnounceOpts = opts.getAnnounceOpts + + this.strategy = opts.strategy || 'sequential' + + this.maxWebConns = opts.maxWebConns || 4 + + this._rechokeNumSlots = (opts.uploads === false || opts.uploads === 0) + ? 0 + : (+opts.uploads || 10) + this._rechokeOptimisticWire = null + this._rechokeOptimisticTime = 0 + this._rechokeIntervalId = null + + this.ready = false + this.destroyed = false + this.paused = false + this.done = false + + this.metadata = null + this.store = opts.store //pear modified + this.bitfield = opts.bitfield //pear modified + this.files = [] + this.pieces = [] + + this._amInterested = false + this._selections = [] + this._critical = [] + + this.wires = [] // open wires (added *after* handshake) + + this._queue = [] // queue of outgoing tcp peers to connect to + this._peers = {} // connected peers (addr/peerId -> Peer) + this._peersLength = 0 // number of elements in `this._peers` (cache, for perf) + + // stats + this.received = 0 + this.uploaded = 0 + this._downloadSpeed = speedometer() + this._uploadSpeed = speedometer() + + // for cleanup + this._servers = [] + this._xsRequests = [] + + // TODO: remove this and expose a hook instead + // optimization: don't recheck every file if it hasn't changed + this._fileModtimes = opts.fileModtimes + + if (torrentId !== null) this._onTorrentId(torrentId) + + this._debug('new torrent') +} + +Object.defineProperty(Torrent.prototype, 'timeRemaining', { + get: function () { + if (this.done) return 0 + if (this.downloadSpeed === 0) return Infinity + return ((this.length - this.downloaded) / this.downloadSpeed) * 1000 + } +}) + +Object.defineProperty(Torrent.prototype, 'downloaded', { + get: function () { + if (!this.bitfield) return 0 + var downloaded = 0 + for (var index = 0, len = this.pieces.length; index < len; ++index) { + if (this.bitfield.get(index)) { // verified data + downloaded += (index === len - 1) ? this.lastPieceLength : this.pieceLength + } else { // "in progress" data + var piece = this.pieces[index] + downloaded += (piece.length - piece.missing) + } + } + return downloaded + } +}) + +// TODO: re-enable this. The number of missing pieces. Used to implement 'end game' mode. +// Object.defineProperty(Storage.prototype, 'numMissing', { +// get: function () { +// var self = this +// var numMissing = self.pieces.length +// for (var index = 0, len = self.pieces.length; index < len; index++) { +// numMissing -= self.bitfield.get(index) +// } +// return numMissing +// } +// }) + +Object.defineProperty(Torrent.prototype, 'downloadSpeed', { + get: function () { return this._downloadSpeed() } +}) + +Object.defineProperty(Torrent.prototype, 'uploadSpeed', { + get: function () { return this._uploadSpeed() } +}) + +Object.defineProperty(Torrent.prototype, 'progress', { + get: function () { return this.length ? this.downloaded / this.length : 0 } +}) + +Object.defineProperty(Torrent.prototype, 'ratio', { + get: function () { return this.uploaded / (this.received || 1) } +}) + +Object.defineProperty(Torrent.prototype, 'numPeers', { + get: function () { return this.wires.length } +}) + +Object.defineProperty(Torrent.prototype, 'torrentFileBlobURL', { + get: function () { + if (typeof window === 'undefined') throw new Error('browser-only property') + if (!this.torrentFile) return null + return URL.createObjectURL( + new Blob([ this.torrentFile ], { type: 'application/x-bittorrent' }) + ) + } +}) + +Object.defineProperty(Torrent.prototype, '_numQueued', { + get: function () { + return this._queue.length + (this._peersLength - this._numConns) + } +}) + +Object.defineProperty(Torrent.prototype, '_numConns', { + get: function () { + var self = this + var numConns = 0 + for (var id in self._peers) { + if (self._peers[id].connected) numConns += 1 + } + return numConns + } +}) + +// TODO: remove in v1 +Object.defineProperty(Torrent.prototype, 'swarm', { + get: function () { + console.warn('WebTorrent: `torrent.swarm` is deprecated. Use `torrent` directly instead.') + return this + } +}) + +Torrent.prototype._onTorrentId = function (torrentId) { + var self = this + if (self.destroyed) return + + var parsedTorrent + try { parsedTorrent = parseTorrent(torrentId) } catch (err) {} + if (parsedTorrent) { + // Attempt to set infoHash property synchronously + self.infoHash = parsedTorrent.infoHash + self._debugId = parsedTorrent.infoHash.toString('hex').substring(0, 7) + process.nextTick(function () { + if (self.destroyed) return + self._onParsedTorrent(parsedTorrent) + }) + } else { + // If torrentId failed to parse, it could be in a form that requires an async + // operation, i.e. http/https link, filesystem path, or Blob. + parseTorrent.remote(torrentId, function (err, parsedTorrent) { + if (self.destroyed) return + if (err) return self._destroy(err) + self._onParsedTorrent(parsedTorrent) + }) + } +} + +Torrent.prototype._onParsedTorrent = function (parsedTorrent) { + var self = this + if (self.destroyed) return + + self._processParsedTorrent(parsedTorrent) + + if (!self.infoHash) { + return self._destroy(new Error('Malformed torrent data: No info hash')) + } + + if (!self.path) self.path = path.join(TMP, self.infoHash) + + self._rechokeIntervalId = setInterval(function () { + self._rechoke() + }, RECHOKE_INTERVAL) + if (self._rechokeIntervalId.unref) self._rechokeIntervalId.unref() + + // Private 'infoHash' event allows client.add to check for duplicate torrents and + // destroy them before the normal 'infoHash' event is emitted. Prevents user + // applications from needing to deal with duplicate 'infoHash' events. + self.emit('_infoHash', self.infoHash) + if (self.destroyed) return + + self.emit('infoHash', self.infoHash) + if (self.destroyed) return // user might destroy torrent in event handler + + if (self.client.listening) { + self._onListening() + } else { + self.client.once('listening', function () { + self._onListening() + }) + } +} + +Torrent.prototype._processParsedTorrent = function (parsedTorrent) { + this._debugId = parsedTorrent.infoHash.toString('hex').substring(0, 7) + + if (this.announce) { + // Allow specifying trackers via `opts` parameter + parsedTorrent.announce = parsedTorrent.announce.concat(this.announce) + } + + if (this.client.tracker && global.WEBTORRENT_ANNOUNCE && !this.private) { + // So `webtorrent-hybrid` can force specific trackers to be used + parsedTorrent.announce = parsedTorrent.announce.concat(global.WEBTORRENT_ANNOUNCE) + } + + if (this.urlList) { + // Allow specifying web seeds via `opts` parameter + parsedTorrent.urlList = parsedTorrent.urlList.concat(this.urlList) + } + + uniq(parsedTorrent.announce) + uniq(parsedTorrent.urlList) + + extendMutable(this, parsedTorrent) + + this.magnetURI = parseTorrent.toMagnetURI(parsedTorrent) + this.torrentFile = parseTorrent.toTorrentFile(parsedTorrent) +} + +Torrent.prototype._onListening = function () { + var self = this + if (self.discovery || self.destroyed) return + + var trackerOpts = self.client.tracker + if (trackerOpts) { + trackerOpts = extend(self.client.tracker, { + getAnnounceOpts: function () { + var opts = { + uploaded: self.uploaded, + downloaded: self.downloaded, + left: Math.max(self.length - self.downloaded, 0) + } + if (self.client.tracker.getAnnounceOpts) { + extendMutable(opts, self.client.tracker.getAnnounceOpts()) + } + if (self._getAnnounceOpts) { + // TODO: consider deprecating this, as it's redundant with the former case + extendMutable(opts, self._getAnnounceOpts()) + } + return opts + } + }) + } + + // begin discovering peers via DHT and trackers + self.discovery = new Discovery({ + infoHash: self.infoHash, + announce: self.announce, + peerId: self.client.peerId, + dht: !self.private && self.client.dht, + tracker: trackerOpts, + port: self.client.torrentPort, + userAgent: USER_AGENT + }) + + self.discovery.on('error', onError) + self.discovery.on('peer', onPeer) + self.discovery.on('trackerAnnounce', onTrackerAnnounce) + self.discovery.on('dhtAnnounce', onDHTAnnounce) + self.discovery.on('warning', onWarning) + + function onError (err) { + self._destroy(err) + } + + function onPeer (peer) { + // Don't create new outgoing TCP connections when torrent is done + if (typeof peer === 'string' && self.done) return + self.addPeer(peer) + } + + function onTrackerAnnounce () { + self.emit('trackerAnnounce') + if (self.numPeers === 0) self.emit('noPeers', 'tracker') + } + + function onDHTAnnounce () { + self.emit('dhtAnnounce') + if (self.numPeers === 0) self.emit('noPeers', 'dht') + } + + function onWarning (err) { + self.emit('warning', err) + } + + if (self.info) { + // if full metadata was included in initial torrent id, use it immediately. Otherwise, + // wait for torrent-discovery to find peers and ut_metadata to get the metadata. + self._onMetadata(self) + } else if (self.xs) { + self._getMetadataFromServer() + } +} + +Torrent.prototype._getMetadataFromServer = function () { + var self = this + var urls = Array.isArray(self.xs) ? self.xs : [ self.xs ] + + var tasks = urls.map(function (url) { + return function (cb) { + getMetadataFromURL(url, cb) + } + }) + parallel(tasks) + + function getMetadataFromURL (url, cb) { + if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { + self.emit('warning', new Error('skipping non-http xs param: ' + url)) + return cb(null) + } + + var opts = { + url: url, + method: 'GET', + headers: { + 'user-agent': USER_AGENT + } + } + var req + try { + req = get.concat(opts, onResponse) + } catch (err) { + self.emit('warning', new Error('skipping invalid url xs param: ' + url)) + return cb(null) + } + + self._xsRequests.push(req) + + function onResponse (err, res, torrent) { + if (self.destroyed) return cb(null) + if (self.metadata) return cb(null) + + if (err) { + self.emit('warning', new Error('http error from xs param: ' + url)) + return cb(null) + } + if (res.statusCode !== 200) { + self.emit('warning', new Error('non-200 status code ' + res.statusCode + ' from xs param: ' + url)) + return cb(null) + } + + var parsedTorrent + try { + parsedTorrent = parseTorrent(torrent) + } catch (err) {} + + if (!parsedTorrent) { + self.emit('warning', new Error('got invalid torrent file from xs param: ' + url)) + return cb(null) + } + + if (parsedTorrent.infoHash !== self.infoHash) { + self.emit('warning', new Error('got torrent file with incorrect info hash from xs param: ' + url)) + return cb(null) + } + + self._onMetadata(parsedTorrent) + cb(null) + } + } +} + +/** + * Called when the full torrent metadata is received. + */ +Torrent.prototype._onMetadata = function (metadata) { + var self = this + if (self.metadata || self.destroyed) return + self._debug('got metadata') + + self._xsRequests.forEach(function (req) { + req.abort() + }) + self._xsRequests = [] + + var parsedTorrent + if (metadata && metadata.infoHash) { + // `metadata` is a parsed torrent (from parse-torrent module) + parsedTorrent = metadata + } else { + try { + parsedTorrent = parseTorrent(metadata) + } catch (err) { + return self._destroy(err) + } + } + + self._processParsedTorrent(parsedTorrent) + self.metadata = self.torrentFile + + // add web seed urls (BEP19) + if (self.client.enableWebSeeds) { + self.urlList.forEach(function (url) { + self.addWebSeed(url) + }) + } + + // start off selecting the entire torrent with low priority + // if (self.pieces.length !== 0) { //pear modified + // self.select(0, self.pieces.length - 1, false) + // } + + self._rarityMap = new RarityMap(self) + + // self.store = new ImmediateChunkStore( //pear modified + // new self._store(self.pieceLength, { + // torrent: { + // infoHash: self.infoHash + // }, + // files: self.files.map(function (file) { + // return { + // path: path.join(self.path, file.path), + // length: file.length, + // offset: file.offset + // } + // }), + // length: self.length + // }) + // ) + + self.files = self.files.map(function (file) { + return new File(self, file) + }) + + self._hashes = self.pieces + + self.pieces = self.pieces.map(function (hash, i) { + var pieceLength = (i === self.pieces.length - 1) + ? self.lastPieceLength + : self.pieceLength + return new Piece(pieceLength) + }) + + self._reservations = self.pieces.map(function () { + return [] + }) + + // self.bitfield = new BitField(self.pieces.length) //pear modified + + self.wires.forEach(function (wire) { + // If we didn't have the metadata at the time ut_metadata was initialized for this + // wire, we still want to make it available to the peer in case they request it. + if (wire.ut_metadata) wire.ut_metadata.setMetadata(self.metadata) + + self._onWireWithMetadata(wire) + }) + + self._debug('verifying existing torrent data') + if (self._fileModtimes && self._store === FSChunkStore) { + // don't verify if the files haven't been modified since we last checked + self.getFileModtimes(function (err, fileModtimes) { + if (err) return self._destroy(err) + + var unchanged = self.files.map(function (_, index) { + return fileModtimes[index] === self._fileModtimes[index] + }).every(function (x) { + return x + }) + + if (unchanged) { + for (var index = 0; index < self.pieces.length; index++) { + self._markVerified(index) + } + self._onStore() + } else { + self._verifyPieces() + } + }) + } else { + self._verifyPieces() + } + + self.emit('metadata') +} + +/* + * TODO: remove this + * Gets the last modified time of every file on disk for this torrent. + * Only valid in Node, not in the browser. + */ +Torrent.prototype.getFileModtimes = function (cb) { + var self = this + var ret = [] + parallelLimit(self.files.map(function (file, index) { + return function (cb) { + fs.stat(path.join(self.path, file.path), function (err, stat) { + if (err && err.code !== 'ENOENT') return cb(err) + ret[index] = stat && stat.mtime.getTime() + cb(null) + }) + } + }), FILESYSTEM_CONCURRENCY, function (err) { + self._debug('done getting file modtimes') + cb(err, ret) + }) +} + +Torrent.prototype._verifyPieces = function () { + var self = this + parallelLimit(self.pieces.map(function (_, index) { + return function (cb) { + if (self.destroyed) return cb(new Error('torrent is destroyed')) + + self.store.get(index, function (err, buf) { + if (self.destroyed) return cb(new Error('torrent is destroyed')) + + if (err) return process.nextTick(cb, null) // ignore error + sha1(buf, function (hash) { + if (self.destroyed) return cb(new Error('torrent is destroyed')) + + if (hash === self._hashes[index]) { + if (!self.pieces[index]) return + self._debug('piece verified %s', index) + self._markVerified(index) + } else { + self._debug('piece invalid %s', index) + } + cb(null) + }) + }) + } + }), FILESYSTEM_CONCURRENCY, function (err) { + if (err) return self._destroy(err) + self._debug('done verifying') + self._onStore() + }) +} + +Torrent.prototype._markVerified = function (index) { + this.pieces[index] = null + this._reservations[index] = null; + if (!this.bitfield.get(index)) { //pear modified + this.emit('piecefromtorrent', index); + } + this.bitfield.set(index, true) +} + +/** + * Called when the metadata, listening server, and underlying chunk store is initialized. + */ +Torrent.prototype._onStore = function () { + var self = this + if (self.destroyed) return + self._debug('on store') + + self.ready = true + self.emit('ready') + + // Files may start out done if the file was already in the store + self._checkDone() + + // In case any selections were made before torrent was ready + self._updateSelections() +} + +Torrent.prototype.destroy = function (cb) { + var self = this + self._destroy(null, cb) +} + +Torrent.prototype._destroy = function (err, cb) { + var self = this + if (self.destroyed) return + self.destroyed = true + self._debug('destroy') + + self.client._remove(self) + + clearInterval(self._rechokeIntervalId) + + self._xsRequests.forEach(function (req) { + req.abort() + }) + + if (self._rarityMap) { + self._rarityMap.destroy() + } + + for (var id in self._peers) { + self.removePeer(id) + } + + self.files.forEach(function (file) { + if (file instanceof File) file._destroy() + }) + + var tasks = self._servers.map(function (server) { + return function (cb) { + server.destroy(cb) + } + }) + + if (self.discovery) { + tasks.push(function (cb) { + self.discovery.destroy(cb) + }) + } + if (self.store) { + tasks.push(function (cb) { + self.store.close(cb) + }) + } + + parallel(tasks, cb) + + if (err) { + // Torrent errors are emitted at `torrent.on('error')`. If there are no 'error' + // event handlers on the torrent instance, then the error will be emitted at + // `client.on('error')`. This prevents throwing an uncaught exception + // (unhandled 'error' event), but it makes it impossible to distinguish client + // errors versus torrent errors. Torrent errors are not fatal, and the client + // is still usable afterwards. Therefore, always listen for errors in both + // places (`client.on('error')` and `torrent.on('error')`). + if (self.listenerCount('error') === 0) { + self.client.emit('error', err) + } else { + self.emit('error', err) + } + } + + self.emit('close') + + self.client = null + self.files = [] + self.discovery = null + self.store = null + self._rarityMap = null + self._peers = null + self._servers = null + self._xsRequests = null +} + +Torrent.prototype.addPeer = function (peer) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') + if (!self.infoHash) throw new Error('addPeer() must not be called before the `infoHash` event') + + if (self.client.blocked) { + var host + if (typeof peer === 'string') { + var parts + try { + parts = addrToIPPort(peer) + } catch (e) { + self._debug('ignoring peer: invalid %s', peer) + self.emit('invalidPeer', peer) + return false + } + host = parts[0] + } else if (typeof peer.remoteAddress === 'string') { + host = peer.remoteAddress + } + + if (host && self.client.blocked.contains(host)) { + self._debug('ignoring peer: blocked %s', peer) + if (typeof peer !== 'string') peer.destroy() + self.emit('blockedPeer', peer) + return false + } + } + + var wasAdded = !!self._addPeer(peer) + if (wasAdded) { + self.emit('peer', peer) + } else { + self.emit('invalidPeer', peer) + } + return wasAdded +} + +Torrent.prototype._addPeer = function (peer) { + var self = this + if (self.destroyed) { + if (typeof peer !== 'string') peer.destroy() + return null + } + if (typeof peer === 'string' && !self._validAddr(peer)) { + self._debug('ignoring peer: invalid %s', peer) + return null + } + + var id = (peer && peer.id) || peer + if (self._peers[id]) { + self._debug('ignoring peer: duplicate (%s)', id) + if (typeof peer !== 'string') peer.destroy() + return null + } + + if (self.paused) { + self._debug('ignoring peer: torrent is paused') + if (typeof peer !== 'string') peer.destroy() + return null + } + + self._debug('add peer %s', id) + + var newPeer + if (typeof peer === 'string') { + // `peer` is an addr ("ip:port" string) + newPeer = Peer.createTCPOutgoingPeer(peer, self) + } else { + // `peer` is a WebRTC connection (simple-peer) + newPeer = Peer.createWebRTCPeer(peer, self) + } + + self._peers[newPeer.id] = newPeer + self._peersLength += 1 + + if (typeof peer === 'string') { + // `peer` is an addr ("ip:port" string) + self._queue.push(newPeer) + self._drain() + } + + return newPeer +} + +Torrent.prototype.addWebSeed = function (url) { + if (this.destroyed) throw new Error('torrent is destroyed') + + if (!/^https?:\/\/.+/.test(url)) { + this.emit('warning', new Error('ignoring invalid web seed: ' + url)) + this.emit('invalidPeer', url) + return + } + + if (this._peers[url]) { + this.emit('warning', new Error('ignoring duplicate web seed: ' + url)) + this.emit('invalidPeer', url) + return + } + + this._debug('add web seed %s', url) + + var newPeer = Peer.createWebSeedPeer(url, this) + this._peers[newPeer.id] = newPeer + this._peersLength += 1 + + this.emit('peer', url) +} + +/** + * Called whenever a new incoming TCP peer connects to this torrent swarm. Called with a + * peer that has already sent a handshake. + */ +Torrent.prototype._addIncomingPeer = function (peer) { + var self = this + if (self.destroyed) return peer.destroy(new Error('torrent is destroyed')) + if (self.paused) return peer.destroy(new Error('torrent is paused')) + + this._debug('add incoming peer %s', peer.id) + + self._peers[peer.id] = peer + self._peersLength += 1 +} + +Torrent.prototype.removePeer = function (peer) { + var self = this + var id = (peer && peer.id) || peer + peer = self._peers[id] + + if (!peer) return + + this._debug('removePeer %s', id) + + delete self._peers[id] + self._peersLength -= 1 + + peer.destroy() + + // If torrent swarm was at capacity before, try to open a new connection now + self._drain() +} + +Torrent.prototype.select = function (start, end, priority, notify) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') + + if (start < 0 || end < start || self.pieces.length <= end) { + console.log('start:'+start + 'end:'+end + 'length:'+self.pieces.length); + throw new Error('invalid selection ', start, ':', end) + } + priority = Number(priority) || 0 + + self._debug('select %s-%s (priority %s)', start, end, priority) + + self._selections.push({ + from: start, + to: end, + offset: 0, + priority: priority, + notify: notify || noop + }) + + self._selections.sort(function (a, b) { + return b.priority - a.priority + }) + + self._updateSelections() +} + +Torrent.prototype.deselect = function (start, end, priority) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') + + priority = Number(priority) || 0 + self._debug('deselect %s-%s (priority %s)', start, end, priority) + + for (var i = 0; i < self._selections.length; ++i) { + var s = self._selections[i] + if (s.from === start && s.to === end && s.priority === priority) { + self._selections.splice(i, 1) + break + } + } + + self._updateSelections() +} + +Torrent.prototype.critical = function (start, end) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') + + self._debug('critical %s-%s', start, end) + + for (var i = start; i <= end; ++i) { + self._critical[i] = true + } + + self._updateSelections() +} + +Torrent.prototype._onWire = function (wire, addr) { + var self = this + self._debug('got wire %s (%s)', wire._debugId, addr || 'Unknown') + + wire.on('download', function (downloaded) { + if (self.destroyed) return + self.received += downloaded + self._downloadSpeed(downloaded) + self.client._downloadSpeed(downloaded) + self.emit('download', downloaded) + self.client.emit('download', downloaded) + }) + + wire.on('upload', function (uploaded) { + if (self.destroyed) return + self.uploaded += uploaded + self._uploadSpeed(uploaded) + self.client._uploadSpeed(uploaded) + self.emit('upload', uploaded) + self.client.emit('upload', uploaded) + }) + + self.wires.push(wire) + + if (addr) { + // Sometimes RTCPeerConnection.getStats() doesn't return an ip:port for peers + var parts = addrToIPPort(addr) + wire.remoteAddress = parts[0] + wire.remotePort = parts[1] + } + + // When peer sends PORT message, add that DHT node to routing table + if (self.client.dht && self.client.dht.listening) { + wire.on('port', function (port) { + if (self.destroyed || self.client.dht.destroyed) { + return + } + if (!wire.remoteAddress) { + return self._debug('ignoring PORT from peer with no address') + } + if (port === 0 || port > 65536) { + return self._debug('ignoring invalid PORT from peer') + } + + self._debug('port: %s (from %s)', port, addr) + self.client.dht.addNode({ host: wire.remoteAddress, port: port }) + }) + } + + wire.on('timeout', function () { + self._debug('wire timeout (%s)', addr) + // TODO: this might be destroying wires too eagerly + wire.destroy() + }) + + // Timeout for piece requests to this peer + wire.setTimeout(PIECE_TIMEOUT, true) + + // Send KEEP-ALIVE (every 60s) so peers will not disconnect the wire + wire.setKeepAlive(true) + + // use ut_metadata extension + wire.use(utMetadata(self.metadata)) + + wire.ut_metadata.on('warning', function (err) { + self._debug('ut_metadata warning: %s', err.message) + }) + + if (!self.metadata) { + wire.ut_metadata.on('metadata', function (metadata) { + self._debug('got metadata via ut_metadata') + self._onMetadata(metadata) + }) + wire.ut_metadata.fetch() + } + + // use ut_pex extension if the torrent is not flagged as private + if (typeof utPex === 'function' && !self.private) { + wire.use(utPex()) + + wire.ut_pex.on('peer', function (peer) { + // Only add potential new peers when we're not seeding + if (self.done) return + self._debug('ut_pex: got peer: %s (from %s)', peer, addr) + self.addPeer(peer) + }) + + wire.ut_pex.on('dropped', function (peer) { + // the remote peer believes a given peer has been dropped from the torrent swarm. + // if we're not currently connected to it, then remove it from the queue. + var peerObj = self._peers[peer] + if (peerObj && !peerObj.connected) { + self._debug('ut_pex: dropped peer: %s (from %s)', peer, addr) + self.removePeer(peer) + } + }) + + wire.once('close', function () { + // Stop sending updates to remote peer + wire.ut_pex.reset() + }) + } + + // Hook to allow user-defined `bittorrent-protocol` extensions + // More info: https://github.com/webtorrent/bittorrent-protocol#extension-api + self.emit('wire', wire, addr) + + if (self.metadata) { + process.nextTick(function () { + // This allows wire.handshake() to be called (by Peer.onHandshake) before any + // messages get sent on the wire + self._onWireWithMetadata(wire) + }) + } +} + +Torrent.prototype._onWireWithMetadata = function (wire) { + var self = this + var timeoutId = null + + function onChokeTimeout () { + if (self.destroyed || wire.destroyed) return + + if (self._numQueued > 2 * (self._numConns - self.numPeers) && + wire.amInterested) { + wire.destroy() + } else { + timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) + if (timeoutId.unref) timeoutId.unref() + } + } + + var i + function updateSeedStatus () { + if (wire.peerPieces.buffer.length !== self.bitfield.buffer.length) return + for (i = 0; i < self.pieces.length; ++i) { + if (!wire.peerPieces.get(i)) return + } + wire.isSeeder = true + wire.choke() // always choke seeders + } + + wire.on('bitfield', function () { + updateSeedStatus() + self._update() + }) + + wire.on('have', function () { + updateSeedStatus() + self._update() + }) + + wire.once('interested', function () { + wire.unchoke() + }) + + wire.once('close', function () { + clearTimeout(timeoutId) + }) + + wire.on('choke', function () { + clearTimeout(timeoutId) + timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) + if (timeoutId.unref) timeoutId.unref() + }) + + wire.on('unchoke', function () { + clearTimeout(timeoutId) + self._update() + }) + + wire.on('request', function (index, offset, length, cb) { + if (length > MAX_BLOCK_LENGTH) { + // Per spec, disconnect from peers that request >128KB + return wire.destroy() + } + if (self.pieces[index]) return + self.store.get(index, { offset: offset, length: length }, cb) + }) + + wire.bitfield(self.bitfield) // always send bitfield (required) + wire.interested() // always start out interested + + // Send PORT message to peers that support DHT + if (wire.peerExtensions.dht && self.client.dht && self.client.dht.listening) { + wire.port(self.client.dht.address().port) + } + + if (wire.type !== 'webSeed') { // do not choke on webseeds + timeoutId = setTimeout(onChokeTimeout, CHOKE_TIMEOUT) + if (timeoutId.unref) timeoutId.unref() + } + + wire.isSeeder = false + updateSeedStatus() +} + +/** + * Called on selection changes. + */ +Torrent.prototype._updateSelections = function () { + var self = this + if (!self.ready || self.destroyed) return + + process.nextTick(function () { + self._gcSelections() + }) + self._updateInterest() + self._update() +} + +/** + * Garbage collect selections with respect to the store's current state. + */ +Torrent.prototype._gcSelections = function () { + var self = this + + for (var i = 0; i < self._selections.length; ++i) { + var s = self._selections[i] + var oldOffset = s.offset + + // check for newly downloaded pieces in selection + while (self.bitfield.get(s.from + s.offset) && s.from + s.offset < s.to) { + s.offset += 1 + } + + if (oldOffset !== s.offset) s.notify() + if (s.to !== s.from + s.offset) continue + if (!self.bitfield.get(s.from + s.offset)) continue + + self._selections.splice(i, 1) // remove fully downloaded selection + i -= 1 // decrement i to offset splice + + s.notify() + self._updateInterest() + } + + if (!self._selections.length) self.emit('idle') +} + +/** + * Update interested status for all peers. + */ +Torrent.prototype._updateInterest = function () { + var self = this + + var prev = self._amInterested + self._amInterested = !!self._selections.length + + self.wires.forEach(function (wire) { + // TODO: only call wire.interested if the wire has at least one piece we need + if (self._amInterested) wire.interested() + else wire.uninterested() + }) + + if (prev === self._amInterested) return + if (self._amInterested) self.emit('interested') + else self.emit('uninterested') +} + +/** + * Heartbeat to update all peers and their requests. + */ +Torrent.prototype._update = function () { + var self = this + if (self.destroyed) return + + // update wires in random order for better request distribution + var ite = randomIterate(self.wires) + var wire + while ((wire = ite())) { + self._updateWire(wire) + } +} + +/** + * Attempts to update a peer's requests + */ +Torrent.prototype._updateWire = function (wire) { + var self = this + + if (wire.peerChoking) return + if (!wire.downloaded) return validateWire() + + var minOutstandingRequests = getBlockPipelineLength(wire, PIPELINE_MIN_DURATION) + if (wire.requests.length >= minOutstandingRequests) return + var maxOutstandingRequests = getBlockPipelineLength(wire, PIPELINE_MAX_DURATION) + + trySelectWire(false) || trySelectWire(true) + + function genPieceFilterFunc (start, end, tried, rank) { + return function (i) { + return i >= start && i <= end && !(i in tried) && wire.peerPieces.get(i) && (!rank || rank(i)) + } + } + + // TODO: Do we need both validateWire and trySelectWire? + function validateWire () { + if (wire.requests.length) return + + var i = self._selections.length + while (i--) { + var next = self._selections[i] + var piece + if (self.strategy === 'rarest') { + var start = next.from + next.offset + var end = next.to + var len = end - start + 1 + var tried = {} + var tries = 0 + var filter = genPieceFilterFunc(start, end, tried) + + while (tries < len) { + piece = self._rarityMap.getRarestPiece(filter) + if (piece < 0) break + if (self._request(wire, piece, false)) return + tried[piece] = true + tries += 1 + } + } else { + for (piece = next.to; piece >= next.from + next.offset; --piece) { + if (!wire.peerPieces.get(piece)) continue + if (self._request(wire, piece, false)) return + } + } + } + + // TODO: wire failed to validate as useful; should we close it? + // probably not, since 'have' and 'bitfield' messages might be coming + } + + function speedRanker () { + var speed = wire.downloadSpeed() || 1 + if (speed > SPEED_THRESHOLD) return function () { return true } + + var secs = Math.max(1, wire.requests.length) * Piece.BLOCK_LENGTH / speed + var tries = 10 + var ptr = 0 + + return function (index) { + if (!tries || self.bitfield.get(index)) return true + + var missing = self.pieces[index].missing + + for (; ptr < self.wires.length; ptr++) { + var otherWire = self.wires[ptr] + var otherSpeed = otherWire.downloadSpeed() + + if (otherSpeed < SPEED_THRESHOLD) continue + if (otherSpeed <= speed) continue + if (!otherWire.peerPieces.get(index)) continue + if ((missing -= otherSpeed * secs) > 0) continue + + tries-- + return false + } + + return true + } + } + + function shufflePriority (i) { + var last = i + for (var j = i; j < self._selections.length && self._selections[j].priority; j++) { + last = j + } + var tmp = self._selections[i] + self._selections[i] = self._selections[last] + self._selections[last] = tmp + } + + function trySelectWire (hotswap) { + if (wire.requests.length >= maxOutstandingRequests) return true + var rank = speedRanker() + + for (var i = 0; i < self._selections.length; i++) { + var next = self._selections[i] + + var piece + if (self.strategy === 'rarest') { + var start = next.from + next.offset + var end = next.to + var len = end - start + 1 + var tried = {} + var tries = 0 + var filter = genPieceFilterFunc(start, end, tried, rank) + + while (tries < len) { + piece = self._rarityMap.getRarestPiece(filter) + if (piece < 0) break + + // request all non-reserved blocks in this piece + while (self._request(wire, piece, self._critical[piece] || hotswap)) {} + + if (wire.requests.length < maxOutstandingRequests) { + tried[piece] = true + tries++ + continue + } + + if (next.priority) shufflePriority(i) + return true + } + } else { + for (piece = next.from + next.offset; piece <= next.to; piece++) { + if (!wire.peerPieces.get(piece) || !rank(piece)) continue + + // request all non-reserved blocks in piece + while (self._request(wire, piece, self._critical[piece] || hotswap)) {} + + if (wire.requests.length < maxOutstandingRequests) continue + + if (next.priority) shufflePriority(i) + return true + } + } + } + + return false + } +} + +/** + * Called periodically to update the choked status of all peers, handling optimistic + * unchoking as described in BEP3. + */ +Torrent.prototype._rechoke = function () { + var self = this + if (!self.ready) return + + if (self._rechokeOptimisticTime > 0) self._rechokeOptimisticTime -= 1 + else self._rechokeOptimisticWire = null + + var peers = [] + + self.wires.forEach(function (wire) { + if (!wire.isSeeder && wire !== self._rechokeOptimisticWire) { + peers.push({ + wire: wire, + downloadSpeed: wire.downloadSpeed(), + uploadSpeed: wire.uploadSpeed(), + salt: Math.random(), + isChoked: true + }) + } + }) + + peers.sort(rechokeSort) + + var unchokeInterested = 0 + var i = 0 + for (; i < peers.length && unchokeInterested < self._rechokeNumSlots; ++i) { + peers[i].isChoked = false + if (peers[i].wire.peerInterested) unchokeInterested += 1 + } + + // Optimistically unchoke a peer + if (!self._rechokeOptimisticWire && i < peers.length && self._rechokeNumSlots) { + var candidates = peers.slice(i).filter(function (peer) { return peer.wire.peerInterested }) + var optimistic = candidates[randomInt(candidates.length)] + + if (optimistic) { + optimistic.isChoked = false + self._rechokeOptimisticWire = optimistic.wire + self._rechokeOptimisticTime = RECHOKE_OPTIMISTIC_DURATION + } + } + + // Unchoke best peers + peers.forEach(function (peer) { + if (peer.wire.amChoking !== peer.isChoked) { + if (peer.isChoked) peer.wire.choke() + else peer.wire.unchoke() + } + }) + + function rechokeSort (peerA, peerB) { + // Prefer higher download speed + if (peerA.downloadSpeed !== peerB.downloadSpeed) { + return peerB.downloadSpeed - peerA.downloadSpeed + } + + // Prefer higher upload speed + if (peerA.uploadSpeed !== peerB.uploadSpeed) { + return peerB.uploadSpeed - peerA.uploadSpeed + } + + // Prefer unchoked + if (peerA.wire.amChoking !== peerB.wire.amChoking) { + return peerA.wire.amChoking ? 1 : -1 + } + + // Random order + return peerA.salt - peerB.salt + } +} + +/** + * Attempts to cancel a slow block request from another wire such that the + * given wire may effectively swap out the request for one of its own. + */ +Torrent.prototype._hotswap = function (wire, index) { + var self = this + + var speed = wire.downloadSpeed() + if (speed < Piece.BLOCK_LENGTH) return false + if (!self._reservations[index]) return false + + var r = self._reservations[index] + if (!r) { + return false + } + + var minSpeed = Infinity + var minWire + + var i + for (i = 0; i < r.length; i++) { + var otherWire = r[i] + if (!otherWire || otherWire === wire) continue + + var otherSpeed = otherWire.downloadSpeed() + if (otherSpeed >= SPEED_THRESHOLD) continue + if (2 * otherSpeed > speed || otherSpeed > minSpeed) continue + + minWire = otherWire + minSpeed = otherSpeed + } + + if (!minWire) return false + + for (i = 0; i < r.length; i++) { + if (r[i] === minWire) r[i] = null + } + + for (i = 0; i < minWire.requests.length; i++) { + var req = minWire.requests[i] + if (req.piece !== index) continue + + self.pieces[index].cancel((req.offset / Piece.BLOCK_LENGTH) | 0) + } + + self.emit('hotswap', minWire, wire, index) + return true +} + +/** + * Attempts to request a block from the given wire. + */ +Torrent.prototype._request = function (wire, index, hotswap) { + var self = this + var numRequests = wire.requests.length + var isWebSeed = wire.type === 'webSeed' + + if (self.bitfield.get(index)) return false + + var maxOutstandingRequests = isWebSeed + ? Math.min( + getPiecePipelineLength(wire, PIPELINE_MAX_DURATION, self.pieceLength), + self.maxWebConns + ) + : getBlockPipelineLength(wire, PIPELINE_MAX_DURATION) + + if (numRequests >= maxOutstandingRequests) return false + // var endGame = (wire.requests.length === 0 && self.store.numMissing < 30) + + var piece = self.pieces[index] + var reservation = isWebSeed ? piece.reserveRemaining() : piece.reserve() + + if (reservation === -1 && hotswap && self._hotswap(wire, index)) { + reservation = isWebSeed ? piece.reserveRemaining() : piece.reserve() + } + if (reservation === -1) return false + + var r = self._reservations[index] + if (!r) r = self._reservations[index] = [] + var i = r.indexOf(null) + if (i === -1) i = r.length + r[i] = wire + + var chunkOffset = piece.chunkOffset(reservation) + var chunkLength = isWebSeed ? piece.chunkLengthRemaining(reservation) : piece.chunkLength(reservation) + + wire.request(index, chunkOffset, chunkLength, function onChunk (err, chunk) { + if (self.destroyed) return + + // TODO: what is this for? + if (!self.ready) return self.once('ready', function () { onChunk(err, chunk) }) + + if (r[i] === wire) r[i] = null + + if (piece !== self.pieces[index]) return onUpdateTick() + + if (err) { + self._debug( + 'error getting piece %s (offset: %s length: %s) from %s: %s', + index, chunkOffset, chunkLength, wire.remoteAddress + ':' + wire.remotePort, + err.message + ) + isWebSeed ? piece.cancelRemaining(reservation) : piece.cancel(reservation) + onUpdateTick() + return + } + + self._debug( + 'got piece %s (offset: %s length: %s) from %s', + index, chunkOffset, chunkLength, wire.remoteAddress + ':' + wire.remotePort + ) + + if (!piece.set(reservation, chunk, wire)) return onUpdateTick() + + var buf = piece.flush() + + // TODO: might need to set self.pieces[index] = null here since sha1 is async + + sha1(buf, function (hash) { + if (self.destroyed) return + + if (hash === self._hashes[index]) { + if (!self.pieces[index]) return + self._debug('piece verified %s', index) + + self.pieces[index] = null + self._reservations[index] = null + if (!self.bitfield.get(index)) { //pear modified + self.emit('piecefromtorrent', index); + } + self.bitfield.set(index, true) + self.store.put(index, buf) + // console.log('self.store.put:'+index); + self.wires.forEach(function (wire) { + wire.have(index) + }) + + // We also check `self.destroyed` since `torrent.destroy()` could have been + // called in the `torrent.on('done')` handler, triggered by `_checkDone()`. + if (self._checkDone() && !self.destroyed) self.discovery.complete() + } else { + self.pieces[index] = new Piece(piece.length) + self.emit('warning', new Error('Piece ' + index + ' failed verification')) + } + onUpdateTick() + }) + }) + + function onUpdateTick () { + process.nextTick(function () { self._update() }) + } + + return true +} + +Torrent.prototype._checkDone = function () { + var self = this + if (self.destroyed) return + + // are any new files done? + self.files.forEach(function (file) { + if (file.done) return + for (var i = file._startPiece; i <= file._endPiece; ++i) { + if (!self.bitfield.get(i)) return + } + file.done = true + file.emit('done') + self._debug('file done: ' + file.name) + }) + + // is the torrent done? (if all current selections are satisfied, or there are + // no selections, then torrent is done) + var done = true + for (var i = 0; i < self._selections.length; i++) { + var selection = self._selections[i] + for (var piece = selection.from; piece <= selection.to; piece++) { + if (!self.bitfield.get(piece)) { + done = false + break + } + } + if (!done) break + } + if (!self.done && done) { + self.done = true + self._debug('torrent done: ' + self.infoHash) + self.emit('done') + } + self._gcSelections() + + return done +} + +Torrent.prototype.load = function (streams, cb) { + var self = this + if (self.destroyed) throw new Error('torrent is destroyed') + if (!self.ready) return self.once('ready', function () { self.load(streams, cb) }) + + if (!Array.isArray(streams)) streams = [ streams ] + if (!cb) cb = noop + + var readable = new MultiStream(streams) + var writable = new ChunkStoreWriteStream(self.store, self.pieceLength) + + pump(readable, writable, function (err) { + if (err) return cb(err) + self.pieces.forEach(function (piece, index) { + self.pieces[index] = null + self._reservations[index] = null; + if (!self.bitfield.get(index)) { //pear modified + self.emit('piecefromtorrent', index); + } + self.bitfield.set(index, true) + }) + self._checkDone() + cb(null) + }) +} + +Torrent.prototype.createServer = function (requestListener) { + if (typeof Server !== 'function') throw new Error('node.js-only method') + if (this.destroyed) throw new Error('torrent is destroyed') + var server = new Server(this, requestListener) + this._servers.push(server) + return server +} + +Torrent.prototype.pause = function () { + if (this.destroyed) return + this._debug('pause') + this.paused = true +} + +Torrent.prototype.resume = function () { + if (this.destroyed) return + this._debug('resume') + this.paused = false + this._drain() +} + +Torrent.prototype._debug = function () { + var args = [].slice.call(arguments) + args[0] = '[' + this.client._debugId + '] [' + this._debugId + '] ' + args[0] + debug.apply(null, args) +} + +/** + * Pop a peer off the FIFO queue and connect to it. When _drain() gets called, + * the queue will usually have only one peer in it, except when there are too + * many peers (over `this.maxConns`) in which case they will just sit in the + * queue until another connection closes. + */ +Torrent.prototype._drain = function () { + var self = this + this._debug('_drain numConns %s maxConns %s', self._numConns, self.client.maxConns) + if (typeof net.connect !== 'function' || self.destroyed || self.paused || + self._numConns >= self.client.maxConns) { + return + } + this._debug('drain (%s queued, %s/%s peers)', self._numQueued, self.numPeers, self.client.maxConns) + + var peer = self._queue.shift() + if (!peer) return // queue could be empty + + this._debug('tcp connect attempt to %s', peer.addr) + + var parts = addrToIPPort(peer.addr) + var opts = { + host: parts[0], + port: parts[1] + } + + var conn = peer.conn = net.connect(opts) + + conn.once('connect', function () { peer.onConnect() }) + conn.once('error', function (err) { peer.destroy(err) }) + peer.startConnectTimeout() + + // When connection closes, attempt reconnect after timeout (with exponential backoff) + conn.on('close', function () { + if (self.destroyed) return + + // TODO: If torrent is done, do not try to reconnect after a timeout + + if (peer.retries >= RECONNECT_WAIT.length) { + self._debug( + 'conn %s closed: will not re-add (max %s attempts)', + peer.addr, RECONNECT_WAIT.length + ) + return + } + + var ms = RECONNECT_WAIT[peer.retries] + self._debug( + 'conn %s closed: will re-add to queue in %sms (attempt %s)', + peer.addr, ms, peer.retries + 1 + ) + + var reconnectTimeout = setTimeout(function reconnectTimeout () { + var newPeer = self._addPeer(peer.addr) + if (newPeer) newPeer.retries = peer.retries + 1 + }, ms) + if (reconnectTimeout.unref) reconnectTimeout.unref() + }) +} + +/** + * Returns `true` if string is valid IPv4/6 address. + * @param {string} addr + * @return {boolean} + */ +Torrent.prototype._validAddr = function (addr) { + var parts + try { + parts = addrToIPPort(addr) + } catch (e) { + return false + } + var host = parts[0] + var port = parts[1] + return port > 0 && port < 65535 && + !(host === '127.0.0.1' && port === this.client.torrentPort) +} + +function getBlockPipelineLength (wire, duration) { + return 2 + Math.ceil(duration * wire.downloadSpeed() / Piece.BLOCK_LENGTH) +} + +function getPiecePipelineLength (wire, duration, pieceLength) { + return 1 + Math.ceil(duration * wire.downloadSpeed() / pieceLength) +} + +/** + * Returns a random integer in [0,high) + */ +function randomInt (high) { + return Math.random() * high | 0 +} + +function noop () {} - if (mustEndAbs && srcPath[0] !== '' && - (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { - srcPath.unshift(''); - } +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../package.json":188,"./file":181,"./peer":182,"./rarity-map":183,"./server":3,"_process":15,"addr-to-ip-port":42,"bitfield":73,"chunk-store-stream/write":83,"debug":87,"events":7,"fs":1,"fs-chunk-store":105,"immediate-chunk-store":95,"inherits":96,"multistream":113,"net":3,"os":3,"parse-torrent":117,"path":13,"pump":120,"random-iterate":121,"run-parallel":136,"run-parallel-limit":135,"simple-get":140,"simple-sha1":142,"speedometer":144,"torrent-discovery":152,"torrent-piece":153,"uniq":156,"ut_metadata":158,"ut_pex":3,"xtend":171,"xtend/mutable":172}],185:[function(require,module,exports){ +module.exports = WebConn + +var BitField = require('bitfield') +var Buffer = require('safe-buffer').Buffer +var debug = require('debug')('webtorrent:webconn') +var get = require('simple-get') +var inherits = require('inherits') +var sha1 = require('simple-sha1') +var Wire = require('bittorrent-protocol') + +var VERSION = require('../package.json').version + +inherits(WebConn, Wire) + +/** + * Converts requests for torrent blocks into http range requests. + * @param {string} url web seed url + * @param {Object} torrent + */ +function WebConn (url, torrent) { + Wire.call(this) + + this.url = url + this.webPeerId = sha1.sync(url) + this._torrent = torrent + + this._init() +} + +WebConn.prototype._init = function () { + var self = this + self.setKeepAlive(true) + + self.once('handshake', function (infoHash, peerId) { + if (self.destroyed) return + self.handshake(infoHash, self.webPeerId) + var numPieces = self._torrent.pieces.length + var bitfield = new BitField(numPieces) + for (var i = 0; i <= numPieces; i++) { + bitfield.set(i, true) + } + self.bitfield(bitfield) + }) + + self.once('interested', function () { + debug('interested') + self.unchoke() + }) + + self.on('uninterested', function () { debug('uninterested') }) + self.on('choke', function () { debug('choke') }) + self.on('unchoke', function () { debug('unchoke') }) + self.on('bitfield', function () { debug('bitfield') }) + + self.on('request', function (pieceIndex, offset, length, callback) { + debug('request pieceIndex=%d offset=%d length=%d', pieceIndex, offset, length) + self.httpRequest(pieceIndex, offset, length, callback) + }) +} + +WebConn.prototype.httpRequest = function (pieceIndex, offset, length, cb) { + var self = this + var pieceOffset = pieceIndex * self._torrent.pieceLength + var rangeStart = pieceOffset + offset /* offset within whole torrent */ + var rangeEnd = rangeStart + length - 1 + + // Web seed URL format: + // For single-file torrents, make HTTP range requests directly to the web seed URL + // For multi-file torrents, add the torrent folder and file name to the URL + var files = self._torrent.files + var requests + if (files.length <= 1) { + requests = [{ + url: self.url, + start: rangeStart, + end: rangeEnd + }] + } else { + var requestedFiles = files.filter(function (file) { + return file.offset <= rangeEnd && (file.offset + file.length) > rangeStart + }) + if (requestedFiles.length < 1) { + return cb(new Error('Could not find file corresponnding to web seed range request')) + } + + requests = requestedFiles.map(function (requestedFile) { + var fileEnd = requestedFile.offset + requestedFile.length - 1 + var url = self.url + + (self.url[self.url.length - 1] === '/' ? '' : '/') + + requestedFile.path + return { + url: url, + fileOffsetInRange: Math.max(requestedFile.offset - rangeStart, 0), + start: Math.max(rangeStart - requestedFile.offset, 0), + end: Math.min(fileEnd, rangeEnd - requestedFile.offset) + } + }) + } + + // Now make all the HTTP requests we need in order to load this piece + // Usually that's one requests, but sometimes it will be multiple + // Send requests in parallel and wait for them all to come back + var numRequestsSucceeded = 0 + var hasError = false + + var ret + if (requests.length > 1) { + ret = Buffer.alloc(length) + } + + requests.forEach(function (request) { + var url = request.url + var start = request.start + var end = request.end + debug( + 'Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d', + url, pieceIndex, offset, length, start, end + ) + var opts = { + url: url, + method: 'GET', + headers: { + 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)', + range: 'bytes=' + start + '-' + end + } + } + function onResponse (res, data) { + if (res.statusCode < 200 || res.statusCode >= 300) { + hasError = true + return cb(new Error('Unexpected HTTP status code ' + res.statusCode)) + } + debug('Got data of length %d', data.length) + + if (requests.length === 1) { + // Common case: fetch piece in a single HTTP request, return directly + cb(null, data) + } else { + // Rare case: reconstruct multiple HTTP requests across 2+ files into one + // piece buffer + data.copy(ret, request.fileOffsetInRange) + if (++numRequestsSucceeded === requests.length) { + cb(null, ret) + } + } + } + get.concat(opts, function (err, res, data) { + if (hasError) return + if (err) { + // Browsers allow HTTP redirects for simple cross-origin + // requests but not for requests that require preflight. + // Use a simple request to unravel any redirects and get the + // final URL. Retry the original request with the new URL if + // it's different. + // + // This test is imperfect but it's simple and good for common + // cases. It catches all cross-origin cases but matches a few + // same-origin cases too. + if (typeof window === 'undefined' || url.startsWith(window.location.origin + '/')) { + hasError = true + return cb(err) + } + + return get.head(url, function (errHead, res) { + if (hasError) return + if (errHead) { + hasError = true + return cb(errHead) + } + if (res.statusCode < 200 || res.statusCode >= 300) { + hasError = true + return cb(new Error('Unexpected HTTP status code ' + res.statusCode)) + } + if (res.url === url) { + hasError = true + return cb(err) + } + + opts.url = res.url + get.concat(opts, function (err, res, data) { + if (hasError) return + if (err) { + hasError = true + return cb(err) + } + onResponse(res, data) + }) + }) + } + onResponse(res, data) + }) + }) +} + +WebConn.prototype.destroy = function () { + Wire.prototype.destroy.call(this) + this._torrent = null +} - if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { - srcPath.push(''); - } +},{"../package.json":188,"bitfield":73,"bittorrent-protocol":74,"debug":87,"inherits":96,"safe-buffer":138,"simple-get":140,"simple-sha1":142}],186:[function(require,module,exports){ +/** + * 过滤掉不能下载的节点 + */ + +module.exports = NodeFilter; + +/* + nodesArray: {uri: string type: string capacity: number} + cb: function + range: {start: number end: number} + */ + +var debug = require('debug')('pear:node-filter'); + +function NodeFilter(nodesArray, cb, range) { + + // var ipArray = array.unique(); + var doneCount = 0; + var usefulNodes = []; + var fileLength = 0; + if (!range) { + range = { + start: 0, + end: nodesArray.length + } + } else if (range.end > nodesArray.length) { + range.end = nodesArray.length; + } + + for (var i=range.start;i= 200 && this.status<300) { + usefulNodes.push(node); + fileLength = xhr.getResponseHeader('content-length'); + } + chenkDone(); + }; + xhr.ontimeout = function() { + doneCount ++; + debug(node.uri + ' timeout'); + chenkDone(); + }; + xhr.onerror = function() { + doneCount ++; + chenkDone(); + }; + xhr.send(); + + }; + + function chenkDone() { + + // if (doneCount === nodesArray.length) { + // cb(usefulNodes, fileLength); + // } + + if (doneCount === (range.end-range.start)) { + + //根据capacity对节点进行排序 + usefulNodes.sort(function (a, b) { //按能力值从大到小排序 + return b.capacity - a.capacity; + }); + + for(var i = 0; i < usefulNodes.length; i++) { + debug('node ' + i + ' capacity ' + usefulNodes[i].capacity); + } + debug('length: ' + usefulNodes.filter(function (node) { + return node.capacity >= 5; + }).length); + + cb(usefulNodes, fileLength); + } + } + +}; - var isAbsolute = srcPath[0] === '' || - (srcPath[0] && srcPath[0].charAt(0) === '/'); +},{"debug":87}],187:[function(require,module,exports){ +/** + * 节点调度算法的默认实现 + */ + +/* + nodesProvider: Array //未排序的节点数组 + info:{ // 窗口滑动过程中的信息 + windowLength: number, //滑动窗口长度 + windowOffset: number, //滑动窗口的起始索引 + interval2BufPos: number, //当前播放点距离缓冲前沿的时间,单位秒 + slideInterval: number //当前播放点距离缓冲前沿多少秒时滑动窗口 + } + */ +var debug = require('debug')('pear:node-scheduler'); + +module.exports = { + + IdleFirst: function (nodesProvider, info) { + + var idles = nodesProvider.filter(function (item) { //空闲节点 + return item.downloading === false; + }); + + // for (var i=0;i info.windowLength) { + ret = ret.filter(function (item) { + return item.type !== 0 + }) + } + + + + return ret; + }, + + WebRTCFirst: function (nodesProvider, info) { + + var idles = nodesProvider.filter(function (item) { //datachannel优先级 > node > server + return item.downloading === false; + }).sort(function (a, b) { + return b.type - a.type; + }); + + var busys = nodesProvider.filter(function (item) { + return item.downloading === true && item.queue.length <= 1; + }).sort(function (a, b) { + return a.queue.length - b.queue.length; + }); + + var ret = idles.concat(busys); + // for (var i=0;i info.windowLength) { + ret = ret.filter(function (item) { + return item.type !== 0 + }) + } + + return ret; + }, + + CloudFirst: function (nodesProvider, info) { + + var idles = nodesProvider.filter(function (item) { //datachannel优先级 < node < server + return item.downloading === false; + }).sort(function (a, b) { + return a.type - b.type; + }); + + var busys = nodesProvider.filter(function (item) { + return item.downloading === true && item.queue.length <= 1; + }).sort(function (a, b) { + return a.queue.length - b.queue.length; + }); + + var ret = idles.concat(busys); + // for (var i=0;i info.windowLength) { + ret = ret.filter(function (item) { + return item.type !== 0 + }) + } + + return ret; + } + +}; - // put the host back - if (psychotic) { - result.hostname = result.host = isAbsolute ? '' : - srcPath.length ? srcPath.shift() : ''; - //occationaly the auth can get stuck only in host - //this especially happens in cases like - //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = result.host && result.host.indexOf('@') > 0 ? - result.host.split('@') : false; - if (authInHost) { - result.auth = authInHost.shift(); - result.host = result.hostname = authInHost.shift(); - } - } +},{"debug":87}],188:[function(require,module,exports){ +module.exports={ + "name": "webtorrent", + "description": "Streaming torrent client", + "version": "0.98.19", + "author": { + "name": "WebTorrent, LLC", + "email": "feross@webtorrent.io", + "url": "https://webtorrent.io" + }, + "browser": { + "./lib/server.js": false, + "./lib/tcp-pool.js": false, + "bittorrent-dht/client": false, + "fs-chunk-store": "memory-chunk-store", + "load-ip-set": false, + "net": false, + "os": false, + "ut_pex": false + }, + "browserify": { + "transform": [ + "package-json-versionify" + ] + }, + "bugs": { + "url": "https://github.com/webtorrent/webtorrent/issues" + }, + "dependencies": { + "addr-to-ip-port": "^1.4.2", + "bitfield": "^1.1.2", + "bittorrent-dht": "^7.2.2", + "bittorrent-protocol": "^2.1.5", + "chunk-store-stream": "^2.0.2", + "create-torrent": "^3.24.5", + "debug": "^2.2.0", + "end-of-stream": "^1.1.0", + "fs-chunk-store": "^1.6.2", + "immediate-chunk-store": "^1.0.8", + "inherits": "^2.0.1", + "load-ip-set": "^1.2.7", + "memory-chunk-store": "^1.2.0", + "mime": "^1.3.4", + "multistream": "^2.0.5", + "package-json-versionify": "^1.0.2", + "parse-torrent": "^5.8.0", + "pump": "^1.0.1", + "random-iterate": "^1.0.1", + "randombytes": "^2.0.3", + "range-parser": "^1.2.0", + "readable-stream": "^2.1.4", + "render-media": "^2.8.0", + "run-parallel": "^1.1.6", + "run-parallel-limit": "^1.0.3", + "safe-buffer": "^5.0.1", + "simple-concat": "^1.0.0", + "simple-get": "^2.2.1", + "simple-peer": "^8.0.0", + "simple-sha1": "^2.0.8", + "speedometer": "^1.0.0", + "stream-to-blob": "^1.0.0", + "stream-to-blob-url": "^2.1.0", + "stream-with-known-length-to-buffer": "^1.0.0", + "torrent-discovery": "^8.1.0", + "torrent-piece": "^1.1.0", + "uniq": "^1.0.1", + "unordered-array-remove": "^1.0.2", + "ut_metadata": "^3.0.8", + "ut_pex": "^1.1.1", + "xtend": "^4.0.1", + "zero-fill": "^2.2.3" + }, + "devDependencies": { + "babili": "^0.1.4", + "bittorrent-tracker": "^9.0.0", + "brfs": "^1.4.3", + "browserify": "^14.0.0", + "cross-spawn": "^5.0.1", + "electron-prebuilt": "^0.37.8", + "finalhandler": "^1.0.0", + "network-address": "^1.1.0", + "run-series": "^1.1.4", + "serve-static": "^1.11.1", + "standard": "*", + "tape": "^4.6.0", + "webtorrent-fixtures": "^1.5.0", + "zuul": "^3.10.1" + }, + "engines": { + "node": ">=4" + }, + "homepage": "https://webtorrent.io", + "keywords": [ + "bittorrent", + "bittorrent client", + "download", + "mad science", + "p2p", + "peer-to-peer", + "peers", + "streaming", + "swarm", + "torrent", + "web torrent", + "webrtc", + "webrtc data", + "webtorrent" + ], + "license": "MIT", + "main": "index.js", + "repository": { + "type": "git", + "url": "git://github.com/webtorrent/webtorrent.git" + }, + "scripts": { + "build": "browserify -s WebTorrent -e ./ | babili > webtorrent.min.js", + "build-debug": "browserify -s WebTorrent -e ./ > webtorrent.debug.js", + "size": "npm run build && cat webtorrent.min.js | gzip | wc -c", + "test": "standard && node ./bin/test.js", + "test-browser": "zuul -- test/*.js test/browser/*.js", + "test-browser-headless": "zuul --electron -- test/*.js test/browser/*.js", + "test-browser-local": "zuul --local -- test/*.js test/browser/*.js", + "test-node": "tape test/*.js test/node/*.js", + "update-authors": "./bin/update-authors.sh" + } +} - mustEndAbs = mustEndAbs || (result.host && srcPath.length); +},{}],189:[function(require,module,exports){ +(function (process,global){ +/* global FileList */ + +module.exports = WebTorrent + +var Buffer = require('safe-buffer').Buffer +var concat = require('simple-concat') +var createTorrent = require('create-torrent') +var debug = require('debug')('webtorrent') +var DHT = require('bittorrent-dht/client') // browser exclude +var EventEmitter = require('events').EventEmitter +var extend = require('xtend') +var inherits = require('inherits') +var loadIPSet = require('load-ip-set') // browser exclude +var parallel = require('run-parallel') +var parseTorrent = require('parse-torrent') +var path = require('path') +var Peer = require('simple-peer') +var randombytes = require('randombytes') +var speedometer = require('speedometer') +var zeroFill = require('zero-fill') + +var TCPPool = require('./lib/tcp-pool') // browser exclude +var Torrent = require('./lib/torrent') + +/** + * WebTorrent version. + */ +var VERSION = require('./package.json').version + +/** + * Version number in Azureus-style. Generated from major and minor semver version. + * For example: + * '0.16.1' -> '0016' + * '1.2.5' -> '0102' + */ +var VERSION_STR = VERSION.match(/([0-9]+)/g) + .slice(0, 2) + .map(function (v) { return zeroFill(2, v) }) + .join('') + +/** + * Version prefix string (used in peer ID). WebTorrent uses the Azureus-style + * encoding: '-', two characters for client id ('WW'), four ascii digits for version + * number, '-', followed by random numbers. + * For example: + * '-WW0102-'... + */ +var VERSION_PREFIX = '-WW' + VERSION_STR + '-' + +inherits(WebTorrent, EventEmitter) + +/** + * WebTorrent Client + * @param {Object=} opts + */ +function WebTorrent (opts) { + var self = this + if (!(self instanceof WebTorrent)) return new WebTorrent(opts) + EventEmitter.call(self) + + if (!opts) opts = {} + + if (typeof opts.peerId === 'string') { + self.peerId = opts.peerId + } else if (Buffer.isBuffer(opts.peerId)) { + self.peerId = opts.peerId.toString('hex') + } else { + self.peerId = Buffer.from(VERSION_PREFIX + randombytes(9).toString('base64')).toString('hex') + } + self.peerIdBuffer = Buffer.from(self.peerId, 'hex') + + if (typeof opts.nodeId === 'string') { + self.nodeId = opts.nodeId + } else if (Buffer.isBuffer(opts.nodeId)) { + self.nodeId = opts.nodeId.toString('hex') + } else { + self.nodeId = randombytes(20).toString('hex') + } + self.nodeIdBuffer = Buffer.from(self.nodeId, 'hex') + + self._debugId = self.peerId.toString('hex').substring(0, 7) + + self.destroyed = false + self.listening = false + self.torrentPort = opts.torrentPort || 0 + self.dhtPort = opts.dhtPort || 0 + self.tracker = opts.tracker !== undefined ? opts.tracker : {} + self.torrents = [] + self.maxConns = Number(opts.maxConns) || 55 + + self._debug( + 'new webtorrent (peerId %s, nodeId %s, port %s)', + self.peerId, self.nodeId, self.torrentPort + ) + + if (self.tracker) { + if (typeof self.tracker !== 'object') self.tracker = {} + if (opts.rtcConfig) { + // TODO: remove in v1 + console.warn('WebTorrent: opts.rtcConfig is deprecated. Use opts.tracker.rtcConfig instead') + self.tracker.rtcConfig = opts.rtcConfig + } + if (opts.wrtc) { + // TODO: remove in v1 + console.warn('WebTorrent: opts.wrtc is deprecated. Use opts.tracker.wrtc instead') + self.tracker.wrtc = opts.wrtc + } + if (global.WRTC && !self.tracker.wrtc) { + self.tracker.wrtc = global.WRTC + } + } + + if (typeof TCPPool === 'function') { + self._tcpPool = new TCPPool(self) + } else { + process.nextTick(function () { + self._onListening() + }) + } + + // stats + self._downloadSpeed = speedometer() + self._uploadSpeed = speedometer() + + if (opts.dht !== false && typeof DHT === 'function' /* browser exclude */) { + // use a single DHT instance for all torrents, so the routing table can be reused + self.dht = new DHT(extend({ nodeId: self.nodeId }, opts.dht)) + + self.dht.once('error', function (err) { + self._destroy(err) + }) + + self.dht.once('listening', function () { + var address = self.dht.address() + if (address) self.dhtPort = address.port + }) + + // Ignore warning when there are > 10 torrents in the client + self.dht.setMaxListeners(0) + + self.dht.listen(self.dhtPort) + } else { + self.dht = false + } + + // Enable or disable BEP19 (Web Seeds). Enabled by default: + self.enableWebSeeds = opts.webSeeds !== false + + if (typeof loadIPSet === 'function' && opts.blocklist != null) { + loadIPSet(opts.blocklist, { + headers: { + 'user-agent': 'WebTorrent/' + VERSION + ' (https://webtorrent.io)' + } + }, function (err, ipSet) { + if (err) return self.error('Failed to load blocklist: ' + err.message) + self.blocked = ipSet + ready() + }) + } else { + process.nextTick(ready) + } + + function ready () { + if (self.destroyed) return + self.ready = true + self.emit('ready') + } +} + +WebTorrent.WEBRTC_SUPPORT = Peer.WEBRTC_SUPPORT + +Object.defineProperty(WebTorrent.prototype, 'downloadSpeed', { + get: function () { return this._downloadSpeed() } +}) + +Object.defineProperty(WebTorrent.prototype, 'uploadSpeed', { + get: function () { return this._uploadSpeed() } +}) + +Object.defineProperty(WebTorrent.prototype, 'progress', { + get: function () { + var torrents = this.torrents.filter(function (torrent) { + return torrent.progress !== 1 + }) + var downloaded = torrents.reduce(function (total, torrent) { + return total + torrent.downloaded + }, 0) + var length = torrents.reduce(function (total, torrent) { + return total + (torrent.length || 0) + }, 0) || 1 + return downloaded / length + } +}) + +Object.defineProperty(WebTorrent.prototype, 'ratio', { + get: function () { + var uploaded = this.torrents.reduce(function (total, torrent) { + return total + torrent.uploaded + }, 0) + var received = this.torrents.reduce(function (total, torrent) { + return total + torrent.received + }, 0) || 1 + return uploaded / received + } +}) + +/** + * Returns the torrent with the given `torrentId`. Convenience method. Easier than + * searching through the `client.torrents` array. Returns `null` if no matching torrent + * found. + * + * @param {string|Buffer|Object|Torrent} torrentId + * @return {Torrent|null} + */ +WebTorrent.prototype.get = function (torrentId) { + var self = this + var i, torrent + var len = self.torrents.length + + if (torrentId instanceof Torrent) { + for (i = 0; i < len; i++) { + torrent = self.torrents[i] + if (torrent === torrentId) return torrent + } + } else { + var parsed + try { parsed = parseTorrent(torrentId) } catch (err) {} + + if (!parsed) return null + if (!parsed.infoHash) throw new Error('Invalid torrent identifier') + + for (i = 0; i < len; i++) { + torrent = self.torrents[i] + if (torrent.infoHash === parsed.infoHash) return torrent + } + } + return null +} + +// TODO: remove in v1 +WebTorrent.prototype.download = function (torrentId, opts, ontorrent) { + console.warn('WebTorrent: client.download() is deprecated. Use client.add() instead') + return this.add(torrentId, opts, ontorrent) +} + +/** + * Start downloading a new torrent. Aliased as `client.download`. + * @param {string|Buffer|Object} torrentId + * @param {Object} opts torrent-specific options + * @param {function=} ontorrent called when the torrent is ready (has metadata) + */ +WebTorrent.prototype.add = function (torrentId, opts, ontorrent) { + var self = this + if (self.destroyed) throw new Error('client is destroyed') + if (typeof opts === 'function') return self.add(torrentId, null, opts) + + self._debug('add') + opts = opts ? extend(opts) : {} + + var torrent = new Torrent(torrentId, self, opts) + self.torrents.push(torrent) + + torrent.once('_infoHash', onInfoHash) + torrent.once('ready', onReady) + torrent.once('close', onClose) + // torrent.on('piecefromtorrent', function (index) { + // self.emit('piecefromtorrent', index); + // }) + + function onInfoHash () { + if (self.destroyed) return + for (var i = 0, len = self.torrents.length; i < len; i++) { + var t = self.torrents[i] + if (t.infoHash === torrent.infoHash && t !== torrent) { + torrent._destroy(new Error('Cannot add duplicate torrent ' + torrent.infoHash)) + return + } + } + } + + function onReady () { + if (self.destroyed) return + if (typeof ontorrent === 'function') ontorrent(torrent) + self.emit('torrent', torrent) + } + + function onClose () { + torrent.removeListener('_infoHash', onInfoHash) + torrent.removeListener('ready', onReady) + torrent.removeListener('close', onClose) + } + + return torrent +} + +/** + * Start seeding a new file/folder. + * @param {string|File|FileList|Buffer|Array.} input + * @param {Object=} opts + * @param {function=} onseed called when torrent is seeding + */ +WebTorrent.prototype.seed = function (input, opts, onseed) { + var self = this + if (self.destroyed) throw new Error('client is destroyed') + if (typeof opts === 'function') return self.seed(input, null, opts) + + self._debug('seed') + opts = opts ? extend(opts) : {} + + // When seeding from fs path, initialize store from that path to avoid a copy + if (typeof input === 'string') opts.path = path.dirname(input) + if (!opts.createdBy) opts.createdBy = 'WebTorrent/' + VERSION_STR + + var torrent = self.add(null, opts, onTorrent) + var streams + + if (isFileList(input)) input = Array.prototype.slice.call(input) + if (!Array.isArray(input)) input = [ input ] + + parallel(input.map(function (item) { + return function (cb) { + if (isReadable(item)) concat(item, cb) + else cb(null, item) + } + }), function (err, input) { + if (self.destroyed) return + if (err) return torrent._destroy(err) + + createTorrent.parseInput(input, opts, function (err, files) { + if (self.destroyed) return + if (err) return torrent._destroy(err) + + streams = files.map(function (file) { + return file.getStream + }) + + createTorrent(input, opts, function (err, torrentBuf) { + if (self.destroyed) return + if (err) return torrent._destroy(err) + + var existingTorrent = self.get(torrentBuf) + if (existingTorrent) { + torrent._destroy(new Error('Cannot add duplicate torrent ' + existingTorrent.infoHash)) + } else { + torrent._onTorrentId(torrentBuf) + } + }) + }) + }) + + function onTorrent (torrent) { + var tasks = [ + function (cb) { + torrent.load(streams, cb) + } + ] + if (self.dht) { + tasks.push(function (cb) { + torrent.once('dhtAnnounce', cb) + }) + } + parallel(tasks, function (err) { + if (self.destroyed) return + if (err) return torrent._destroy(err) + _onseed(torrent) + }) + } + + function _onseed (torrent) { + self._debug('on seed') + if (typeof onseed === 'function') onseed(torrent) + torrent.emit('seed') + self.emit('seed', torrent) + } + + return torrent +} + +/** + * Remove a torrent from the client. + * @param {string|Buffer|Torrent} torrentId + * @param {function} cb + */ +WebTorrent.prototype.remove = function (torrentId, cb) { + this._debug('remove') + var torrent = this.get(torrentId) + if (!torrent) throw new Error('No torrent with id ' + torrentId) + this._remove(torrentId, cb) +} + +WebTorrent.prototype._remove = function (torrentId, cb) { + var torrent = this.get(torrentId) + if (!torrent) return + this.torrents.splice(this.torrents.indexOf(torrent), 1) + torrent.destroy(cb) +} + +WebTorrent.prototype.address = function () { + if (!this.listening) return null + return this._tcpPool + ? this._tcpPool.server.address() + : { address: '0.0.0.0', family: 'IPv4', port: 0 } +} + +/** + * Destroy the client, including all torrents and connections to peers. + * @param {function} cb + */ +WebTorrent.prototype.destroy = function (cb) { + if (this.destroyed) throw new Error('client already destroyed') + this._destroy(null, cb) +} + +WebTorrent.prototype._destroy = function (err, cb) { + var self = this + self._debug('client destroy') + self.destroyed = true + + var tasks = self.torrents.map(function (torrent) { + return function (cb) { + torrent.destroy(cb) + } + }) + + if (self._tcpPool) { + tasks.push(function (cb) { + self._tcpPool.destroy(cb) + }) + } + + if (self.dht) { + tasks.push(function (cb) { + self.dht.destroy(cb) + }) + } + + parallel(tasks, cb) + + if (err) self.emit('error', err) + + self.torrents = [] + self._tcpPool = null + self.dht = null +} + +WebTorrent.prototype._onListening = function () { + this._debug('listening') + this.listening = true + + if (this._tcpPool) { + // Sometimes server.address() returns `null` in Docker. + var address = this._tcpPool.server.address() + if (address) this.torrentPort = address.port + } + + this.emit('listening') +} + +WebTorrent.prototype._debug = function () { + var args = [].slice.call(arguments) + args[0] = '[' + this._debugId + '] ' + args[0] + debug.apply(null, args) +} + +/** + * Check if `obj` is a node Readable stream + * @param {*} obj + * @return {boolean} + */ +function isReadable (obj) { + return typeof obj === 'object' && obj != null && typeof obj.pipe === 'function' +} + +/** + * Check if `obj` is a W3C `FileList` object + * @param {*} obj + * @return {boolean} + */ +function isFileList (obj) { + return typeof FileList !== 'undefined' && obj instanceof FileList +} - if (mustEndAbs && !isAbsolute) { - srcPath.unshift(''); - } +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./lib/tcp-pool":3,"./lib/torrent":184,"./package.json":188,"_process":15,"bittorrent-dht/client":3,"create-torrent":86,"debug":87,"events":7,"inherits":96,"load-ip-set":3,"parse-torrent":117,"path":13,"randombytes":122,"run-parallel":136,"safe-buffer":138,"simple-concat":139,"simple-peer":141,"speedometer":144,"xtend":171,"zero-fill":173}],190:[function(require,module,exports){ +module.exports = peerId; + +function peerId() { + + //20位 + // var num = Math.floor(Math.random()*10000000000); + // // } + // // console.log(str.toString('base64')); + // var buffer = new ArrayBuffer() + // console.log(num.toString('base64')); + + var len = 9; + var timestamp = parseInt((new Date()).valueOf()/1000); + + + var x="0123456789qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"; + var tmp=""; + for(var i=0;i< len;i++) { + tmp += x.charAt(Math.ceil(Math.random()*100000000)%x.length); + } + + return timestamp+'-'+tmp; + + +} + +// console.log(peerId()); - if (!srcPath.length) { - result.pathname = null; - result.path = null; - } else { - result.pathname = srcPath.join('/'); - } +},{}],191:[function(require,module,exports){ +/** + * Created by xieting on 2017/12/7. + */ + +var debug = require('debug')('pear:reporter'); +var axios = require('axios'); + +axios.defaults.baseURL = 'https://statdapi.webrtc.win:9801'; + +var abilitiesURL = 'https://api.webrtc.win/v2/customer/stats/nodes/capacity'; + + +var totalReportTraffic = 0; + +function reportTraffic(uuid, fileSize, traffics) { + var temp = 0; + for (var i=0; i= totalReportTraffic + 10485760) { //如果流量增加大于10 + var body = { + uuid: uuid, + size: Number(fileSize), + traffic: traffics + }; + axios({ + method: 'post', + url: '/traffic', + data: body + }) + .then(function(response) { + // debug('reportTraffic response:'+JSON.stringify(response)+' temp:'+temp+' totalReportTraffic:'+totalReportTraffic); + if (response.status == 200) { + totalReportTraffic = temp; + } + }); + } +} + +function finalyReportTraffic(uuid, fileSize, traffics) { + var body = { + uuid: uuid, + size: Number(fileSize), + traffic: traffics + }; + axios({ + method: 'post', + url: '/traffic', + data: body + }) + .then(function(response) { + if (response.status == 200) { + debug('finalyReportTraffic'); + } + }); +} + +function reportAbilities(abilities) { + var benchmark = 0; + for (var mac in abilities) { + benchmark += abilities[mac]; + } + benchmark = benchmark/Object.getOwnPropertyNames(abilities).length; + var normalizeAbilities = {}; + for (var mac in abilities) { + normalizeAbilities[mac] = abilities[mac]/benchmark*5; + console.log('reportAbilities mac:'+mac+' ability:'+normalizeAbilities[mac]); + } + axios({ + method: 'post', + url: abilitiesURL, + data: normalizeAbilities + }) + .then(function(response) { + debug('reportAbilities response:'+JSON.stringify(response)); + }); +} + +module.exports = { + + reportTraffic : reportTraffic, + finalyReportTraffic: finalyReportTraffic, + reportAbilities: reportAbilities +}; + + + - //to support request.http - if (!util.isNull(result.pathname) || !util.isNull(result.search)) { - result.path = (result.pathname ? result.pathname : '') + - (result.search ? result.search : ''); - } - result.auth = relative.auth || result.auth; - result.slashes = result.slashes || relative.slashes; - result.href = result.format(); - return result; -}; +},{"axios":43,"debug":87}],192:[function(require,module,exports){ +/** + * Created by snow on 17-6-22. + */ + +module.exports = Set; + +function Set() { + this.items = {}; +} + +Set.prototype = { + constructer: Set, + has: function(value) { + return value in this.items; + }, + add: function(value) { + if (!this.has(value)) { + this.items[value] = value; + return true; + } + return false; + }, + remove: function(value) { + if (this.has(value)) { + delete this.items[value]; + return true; + } + return false; + }, + clear: function() { + this.items = {}; + }, + size: function() { + return Object.keys(this.items).length; + }, + values: function() { + return Object.keys(this.items); //values是数组 + }, + union: function(otherSet) { + var unionSet = new Set(); + var values = this.values(); + for (var i = 0; i < values.length; i++) { + unionSet.add(values[i]); + } + values = otherSet.values(); + for (var i = 0; i < values.length; i++) { + unionSet.add(values[i]); + } + return unionSet; + }, + intersection: function(otherSet) { + var intersectionSet = new Set(); + var values = this.values(); + for (var i = 0; i < values.length; i++) { + if (otherSet.has(values[i])) { + intersectionSet.add(values[i]); + } + } + return intersectionSet; + }, + difference: function(otherSet) { + var differenceSet = new Set(); + var values = otherSet.values(); + for (var i = 0; i < values.length; i++) { + if (!this.has(values[i])) { + differenceSet.add(values[i]); + } + } + return differenceSet; + }, + subset: function(otherSet) { + if (this.size() > otherSet.size()) { + return false; + } else { + var values = this.values(); + for (var i = 0; i < values.length; i++) { + if (!otherSet.has(values[i])) { + return false; + } + } + } + return true; + } +} -Url.prototype.parseHost = function() { - var host = this.host; - var port = portPattern.exec(host); - if (port) { - port = port[0]; - if (port !== ':') { - this.port = port.substr(1); - } - host = host.substr(0, host.length - port.length); - } - if (host) this.hostname = host; -}; +},{}],193:[function(require,module,exports){ +/** + * Created by snow on 17-6-19. + */ + +/* + config:{ + initiator : Boolean //true主动发送offer + stunServers: Array //stun服务器数组 + } + */ + +module.exports = SimpleRTC; + +var debug = require('debug')('pear:simple-RTC'); +var EventEmitter = require('events').EventEmitter; +var inherits = require('inherits'); + +inherits(SimpleRTC, EventEmitter); + +function SimpleRTC(config) { + EventEmitter.call(this); + + debug('start simpleRTC'); + var self = this; + self.config = config || {}; + + var wrtc = getBrowserRTC(); + + self.RTCPeerConnection = wrtc.RTCPeerConnection; + self.RTCSessionDescription = wrtc.RTCSessionDescription; + self.RTCIceCandidate = wrtc.RTCIceCandidate; + self.dataChannel = null; + self.peerConnection = null; + self.currentoffer = null; + self.sdp = ""; + + self.isDataChannelCreating = false; + self.iceServers = [ {urls:'stun:stun.miwifi.com'},{urls:'stun:stun.ekiga.net'},{urls:'stun:stun.ideasip.com'}]; + self.pc_config = { + iceServers: self.iceServers + }; + + self.createPeerConnect(); +} + +SimpleRTC.prototype.signal = function (event) { + + debug('[pear_webrtc] event.type' + event.type); + debug('event JSON: ' + JSON.stringify(event)); + if (event.type === 'offer') { + this.receiveOffer(event); + } else if (event.type === 'answer') { + this.receiveAnswer(event); + } + // else if (event.type === 'candidate') { + // ReceiveIceCandidate(event); + // } + else { + this.receiveIceCandidate(event); + // debug('err event.type: ' + JSON.stringify(event)); + } +}; + +SimpleRTC.prototype.createPeerConnect = function () { + var self = this; + + try { + this.peerConnection = new self.RTCPeerConnection(this.pc_config); + // debug('[simpleRTC] PeerConnection created!'); + if (this.config.initiator && this.config.initiator == true){ + debug('[pear_webrtc] sendOffer'); + this.sendOffer(); + + }else { + // createDatachannel(); + } + } + catch (e) { + debug("pc established error:"+e.message); + this.emit('error', e.message); + } + + this.peerConnection.onopen = function() { + debug("PeerConnection established"); + + }; + + this.peerConnection.onicecandidate = function (event) { + // debug('[pear_webrtc] onicecandidate: ' + JSON.stringify(event)); + if (event.candidate == null) { + if (self.sdp == "") { + debug("sdp error"); + self.emit('error', "sdp error"); + return; + } + return; + } else { + // socketSend(event.candidate); + // debug('[pear_webrtc] sendCandidate'); + self.emit('signal',event.candidate); + if (!self.config.initiator || self.config.initiator == false){ + // createDatachannel(); + } + } + // debug("iceGatheringState: "+ self.peerConnection.iceGatheringState); + }; + + this.peerConnection.oniceconnectionstatechange = function (evt) { + + debug("connectionState: "+ self.peerConnection.connectionState); + debug("signalingstate:"+ self.peerConnection.signalingState); + if (self.peerConnection.signalingState=="stable" && !self.isDataChannelCreating) + { + debug('[pear_webrtc] oniceconnectionstatechange stable'); + self.createDatachannel(); + self.isDataChannelCreating = true; + } + + + }; + + this.peerConnection.ondatachannel = function (evt) { + self.dataChannel = evt.channel; + debug(this.dataChannel.label+"dc state: "+ self.dataChannel.readyState); + self.dataChannelEvents(this.dataChannel); + }; + + this.peerConnection.onicegatheringstatechange = function() { + if (self.peerConnection.iceGatheringState === 'complete') { + self.emit('signal', { + "candidate":"completed" + }); + } + } +}; + +SimpleRTC.prototype.createDatachannel = function () { + + try { + this.dataChannel = this.peerConnection.createDataChannel('dataChannel', {reliable: true}); + debug("Channel [ " + this.dataChannel.label + " ] creating!"); + debug(this.dataChannel.label+" Datachannel state: "+ this.dataChannel.readyState); + } + catch (dce) { + debug("dc established error: "+dce.message); + this.emit('error', dce.message); + } + + this.dataChannelEvents(this.dataChannel); +}; + +SimpleRTC.prototype.dataChannelEvents = function (channel) { + var self = this; + + channel.onopen = function () { + debug("Datachannel opened, current stateis :\n" + self.dataChannel.readyState); + debug(channel); + self.emit('connect', self.dataChannel.readyState); + }; + + channel.onmessage = function (event) { + + var data = event.data; + if (data instanceof Blob) { //兼容firefox 将返回的Blob类型转为ArrayBuffer + var fileReader = new FileReader(); + fileReader.onload = function() { + // arrayBuffer = this.result; + self.emit('data', this.result); + }; + fileReader.readAsArrayBuffer(data); + } else { + self.emit('data', data); + } + }; + + channel.onerror = function (err) { + self.emit('error', err); + }; + + channel.onclose = function () { + debug("DataChannel is closed"); + clearInterval(self.timer); + self.timer = null; + } +}; + +SimpleRTC.prototype.receiveOffer = function (evt) { + var self = this; + + this.peerConnection.setRemoteDescription(new RTCSessionDescription(evt)); + // debug("Received Offer, and set as Remote Desc:\n"+ evt.sdp); + this.peerConnection.createAnswer(function(desc) { + self.peerConnection.setLocalDescription(desc); + self.currentoffer = desc; + self.sdp = desc.sdp; + // debug("Create Answer, and set as Local Desc:\n"+JSON.stringify(desc)); + // socketSend(desc); + self.emit('signal',desc); + },function (err) { + debug(err); + }); +}; + +SimpleRTC.prototype.sendOffer = function () { + + this.peerConnection.createOffer(function (desc) { + this.currentoffer = desc; + debug("Create an offer : \n"+JSON.stringify(desc)); + this.peerConnection.setLocalDescription(desc); + debug("Offer Set as Local Desc"); + // socketSend(desc); + this.emit('signal', desc); + this.sdp = desc.sdp; + debug("Send offer:\n"+JSON.stringify(this.sdp)); + },function(error) { + debug(error); + }); +}; + +SimpleRTC.prototype.receiveAnswer = function (answer) { + + debug("Received remote Answer: \n"+JSON.stringify(answer)); + this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer)); + debug("already set remote desc, current ice gather state: "+ this.peerConnection.iceGatheringState); +}; + +SimpleRTC.prototype.receiveIceCandidate = function (evt) { + + if (evt) { + debug("Received and add candidate:\n"+JSON.stringify(evt)); + this.peerConnection.addIceCandidate(new RTCIceCandidate(evt)); + } else{ + return; + } +}; + +SimpleRTC.prototype.connect = function () { + + this.createDatachannel(); +}; + +SimpleRTC.prototype.send = function (data) { + + try { + this.dataChannel.send(data); + debug("[pear_webrtc] send data:" + data); + } catch (e){ + debug("dataChannel send error:"+e.message); + } +}; + +SimpleRTC.prototype.close = function () { + + if (this.peerConnection){ + this.peerConnection.close(); + clearInterval(this.timer); + } +}; + +SimpleRTC.prototype.startHeartbeat = function () { + var self = this; + var heartbeat = { + action: 'ping' + }; + + this.timer = setInterval(function () { + debug(JSON.stringify(heartbeat)); + self.send(JSON.stringify(heartbeat)); + + }, 90*1000); +}; + + +function getBrowserRTC () { + if (typeof window === 'undefined') return null; + var wrtc = { + RTCPeerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection || + window.webkitRTCPeerConnection, + RTCSessionDescription: window.RTCSessionDescription || + window.mozRTCSessionDescription || window.webkitRTCSessionDescription, + RTCIceCandidate: window.RTCIceCandidate || window.mozRTCIceCandidate || + window.webkitRTCIceCandidate + }; + if (!wrtc.RTCPeerConnection) return null; + return wrtc +} + + -},{"./util":192,"punycode":171,"querystring":174}],192:[function(require,module,exports){ -'use strict'; +},{"debug":87,"events":7,"inherits":96}],194:[function(require,module,exports){ + +/* + config:{ + peer_id : + chunkSize : 每个块的大小,必须是32K的整数倍 (默认1M) + host : + uri : 文件路径, + fileSize : 文件大小 + useMonitor: 开启监控器 + } + */ + +module.exports = RTCDownloader; + +var debug = require('debug')('pear:webrtc-downloader-bin'); +var Buffer = require('buffer/').Buffer; +var SimpleRTC = require('./simple-RTC'); +var EventEmitter = require('events').EventEmitter; +var inherits = require('inherits'); + +inherits(RTCDownloader, EventEmitter); + +function RTCDownloader(config) { + EventEmitter.call(this); + + var self = this; + + self.type = 2; //datachannel + self.peer_id = config.peer_id; + self.chunkSize = config.chunkSize || 1*1024*1024; + self.uri = config.uri; + self.host = config.host; + self.fileSize = config.fileSize; + self.useMonitor = config.useMonitor || false; + self.chunkStore = []; + self.start = -1; + self.end = -1; + self.connectFlag = false; + self.downloading = false; //是否正在下载 + self.queue = []; //下载队列 + self.startTime = 0; + self.endTime = 0; + self.speed = 0; //当前速度 + self.meanSpeed = -1; //平均速度 + self.counter = 0; //记录下载的次数 + self.expectedLength = 1048576; //期望返回的buffer长度 + self.simpleRTC = new SimpleRTC(); + self._setupSimpleRTC(self.simpleRTC); + + self.dc_id = ''; //对等端的id + self.downloaded = 0; + self.mac = ''; + +}; + +RTCDownloader.prototype.offerFromWS = function (offer) { //由服务器传来的data channel的offer、peer_id、offer_id等信息 + var self = this; + + self.message = offer; + debug('[webrtc] messageFromDC:' + JSON.stringify(offer)); + self.dc_id = offer.peer_id; + self.simpleRTC.signal(offer.sdp); +}; + +RTCDownloader.prototype.candidatesFromWS = function (candidates) { + + for (var i=0; i= 3) { + // self.clearQueue(); + // self.weight -= 0.1; + // if (self.weight < 0.1) { + // self.emit('error'); + // } + // } +}; + +RTCDownloader.prototype.startDownloading = function (start, end) { + var self = this; + + self.downloading = true; + var str = { + "host":self.host, + "uri":self.uri, + "action":"get", + "response_type":"binary", + "start":start, + "end":end + // "end":10*1024*1024 + }; + debug("pear_send_file : " + JSON.stringify(str)); + self.startTime=(new Date()).getTime(); + self.expectedLength = end - start + 1; + self.simpleRTC.send(JSON.stringify(str)); +}; + +RTCDownloader.prototype._receive = function (chunk) { + var self = this; + // debug('[simpleRTC] chunk type:'+typeof chunk); + + var uint8 = new Uint8Array(chunk); + // debug('uint8.length:'+uint8.length); + // if (!uint8) { + // self.emit('error'); + // return; + // } + + var headerInfo = self._getHeaderInfo(uint8); + // debug('headerInfo:'+JSON.stringify(headerInfo)); + + if (headerInfo) { + + if (headerInfo.value){ + + // debug(self.mac+' headerInfo.start:'+headerInfo.start); + // if (headerInfo.start === self.lastChunkEnd + 1){ + // + // // self.chunkStore.push(uint8); + // self.lastChunkEnd = headerInfo.end; + // } else { + // console.error('RTCDownloader' +self.mac+ ' error start:' + headerInfo.start + ' lastChunkEnd:' + self.lastChunkEnd); + // // self.emit('error'); + // } + + self.chunkStore.push(uint8); + } else if (headerInfo.begin) { + // debug(self.mac+' headerInfo.begin:'+self.downloading); + self.emit('start'); + self.chunkStore = []; + } else if (headerInfo.done) { + // debug(self.mac+' headerInfo.done:'+self.downloading); + // debug('self.chunkStore done'); + var finalArray = [], length = self.chunkStore.length; + // self.downloading = false; + self.end = headerInfo.end; + + self.start = self._getHeaderInfo(self.chunkStore[0]).start; + + self.end = self._getHeaderInfo(self.chunkStore[self.chunkStore.length-1]).end; + + + self.endTime = (new Date()).getTime(); + // self.speed = Math.floor(((self.end - self.start) * 1000) / ((self.endTime - self.startTime) * 1024)); //单位: KB/s + self.speed = Math.floor((self.end - self.start + 1) / (self.endTime - self.startTime)); //单位: KB/s + debug('pear_webrtc speed:' + self.speed + 'KB/s'); + // self.meanSpeed = (self.meanSpeed*self.counter + self.speed)/(++self.counter); + if (self.meanSpeed == -1) self.meanSpeed = self.speed; + self.meanSpeed = 0.95*self.meanSpeed + 0.05*self.speed; + debug('datachannel '+self.dc_id+' meanSpeed:' + self.meanSpeed + 'KB/s'); + + for (var i = 0; i < length; i++) { + if (!!self.chunkStore[i]) { + var value = self.chunkStore[i].subarray(256); + // debug('value.length:'+value.length); + finalArray.push(Buffer.from(value)); + } + } + // debug('RTCDownloader' +self.mac+ ' emit data start:' + self.start + ' end:' + self.end); + var retBuf = Buffer.concat(finalArray); + if (retBuf.length === self.expectedLength) self.emit('data', retBuf, self.start, self.end, self.speed); + self.downloading = false; + if (self.queue.length>0) { //如果下载队列不为空 + var pair = self.queue.shift(); + self.startDownloading(pair[0], pair[1]); + } + } else if (headerInfo.action) { + //心跳信息 + } else { + debug('RTC error msg:'+JSON.stringify(headerInfo)); + self.emit('error'); + } + } else { + self.emit('error'); + } + + +}; + +RTCDownloader.prototype.abort = function () { + +}; + +RTCDownloader.prototype.close = function () { + var self = this; + + if (self.simpleRTC){ + self.simpleRTC.close(); + } +}; + +RTCDownloader.prototype.clearQueue = function () { //清空下载队列 + + // this.downloading = false; + if (this.queue.length > 0) { + this.queue = []; + } +}; + +RTCDownloader.prototype._getHeaderInfo = function (uint8arr) { + // debug('_getHeaderInfo mac:'+this.mac); + var sub = uint8arr.subarray(0, 256); + var headerString = String.fromCharCode.apply(String, sub); + // debug('headerString:'+headerString) + return JSON.parse(headerString.split('}')[0]+'}'); +}; + +RTCDownloader.prototype._setupSimpleRTC = function (simpleRTC) { + var self = this; + + simpleRTC.on('data', function (data) { + + self._receive(data); + }); + simpleRTC.on('error', function (err) + { + debug('[simpleRTC] error', err); + self.emit('error'); + }); + simpleRTC.on('signal', function (data) { + // debug('[simpleRTC] SIGNAL', JSON.stringify(data)); + + var message = { + "peer_id":self.peer_id, + "to_peer_id":self.message.peer_id, + "offer_id":self.message.offer_id + }; + self.mac = self.message.peer_id.replace(/:/g, ''); + // debug('webrtc mac:'+self.mac); + if (data.type == 'answer'){ + message.action = 'answer'; + message.sdps = data; + } else if(data.candidate){ + message.action = 'candidate'; + debug('signal candidate:'+JSON.stringify(data)); + message.candidates = data; + } + + // websocket.send(JSON.stringify(message)); + self.emit('signal',message); + }); + simpleRTC.on('connect', function (state) { + debug('[datachannel] '+self.dc_id+' CONNECT'); + // simpleRTC.send('[simpleRTC] PEER CONNECTED!'); + simpleRTC.startHeartbeat(); //开始周期性发送心跳信息 + if (!self.connectFlag){ + self.emit('connect',state); + self.connectFlag = true; + } + + }); +}; -module.exports = { - isString: function(arg) { - return typeof(arg) === 'string'; - }, - isObject: function(arg) { - return typeof(arg) === 'object' && arg !== null; - }, - isNull: function(arg) { - return arg === null; - }, - isNullOrUndefined: function(arg) { - return arg == null; - } -}; +},{"./simple-RTC":193,"buffer/":82,"debug":87,"events":7,"inherits":96}],195:[function(require,module,exports){ +/** + * Created by xieting on 2017/11/9. + */ + +module.exports = Worker; + +var debug = require('debug')('pear:worker'); +var md5 = require('blueimp-md5'); +var Dispatcher = require('./dispatcher'); +var HttpDownloader = require('./http-downloader'); +var RTCDownloader = require('./webrtc-downloader-bin'); +var getPeerId = require('./peerid-generator'); +var url = require('url'); +var File = require('./file'); +var nodeFilter = require('./node-filter'); +var inherits = require('inherits'); +var EventEmitter = require('events').EventEmitter; +var Set = require('./set'); +var PearTorrent = require('./pear-torrent'); +var Scheduler = require('./node-scheduler'); +var Reporter = require('./reporter'); + +// var WEBSOCKET_ADDR = 'ws://signal.webrtc.win:9600/ws'; //test +// var WEBSOCKET_ADDR = 'wss://signal.webrtc.win:7601/wss'; +var GETNODES_ADDR = 'https://api.webrtc.win:6601/v1/customer/nodes'; + +var BLOCK_LENGTH = 32 * 1024; + +inherits(Worker, EventEmitter); + +function Worker(urlStr, token, opts) { + var self = this; + + // if (!(self instanceof PearPlayer)) return new PearPlayer(selector, opts); + if (typeof token === 'object') return Worker(urlStr, '', token); + EventEmitter.call(self); + opts = opts || {}; + // token = ''; + // if (typeof token !== 'string') throw new Error('token must be a string!'); + // if (!(opts.type && opts.type === 'mp4')) throw new Error('only mp4 is supported!'); + // if (!((opts.src && typeof opts.src === 'string') || self.video.src)) throw new Error('video src is not valid!'); + // if (!(config.token && typeof config.token === 'string')) throw new Error('token is not valid!'); + + //player + self.websocketAddr = opts.websocketAddr || 'wss://signal.webrtc.win:7601/wss'; + self.render = opts.render; + self.selector = opts.selector; + self.autoplay = opts.autoplay === false ? false : true; + + self.src = urlStr; + self.urlObj = url.parse(self.src); + self.scheduler = opts.scheduler || 'IdleFirst'; + self.token = token; + self.useDataChannel = (opts.useDataChannel === false)? false : true; + self.useMonitor = (opts.useMonitor === false)? false : true; + self.useTorrent = (opts.useTorrent === false)? false : true; + self.magnetURI = opts.magnetURI || undefined; + self.trackers = opts.trackers && Array.isArray(opts.trackers) && opts.trackers.length > 0 ? opts.trackers : null; + self.sources = opts.sources && Array.isArray(opts.sources) && opts.sources.length > 0 ? opts.sources : null; + self.auto = (opts.auto === false) ? false : true; + self.dataChannels = opts.dataChannels || 20; + self.peerId = getPeerId(); + self.isPlaying = false; + self.fileLength = 0; + self.nodes = []; + self.websocket = null; + self.dispatcher = null; + self.JDMap = {}; //根据dc的peer_id来获取jd的map + self.nodeSet = new Set(); //保存node的set + self.tempDCQueue = []; //暂时保存data channel的队列 + self.fileName = self.urlObj.path.split('/').pop(); + self.file = null; + self.dispatcherConfig = { + + chunkSize: opts.chunkSize && (opts.chunkSize%BLOCK_LENGTH === 0 ? opts.chunkSize : Math.ceil(opts.chunkSize/BLOCK_LENGTH)*BLOCK_LENGTH), //每个chunk的大小,默认1M + interval: opts.interval ? opts.interval : (opts.sequencial ? 5000 : 2000), //滑动窗口的时间间隔,单位毫秒,默认10s, + auto: self.auto, + useMonitor: self.useMonitor, + scheduler: Scheduler[self.scheduler], + sequencial: opts.sequencial, + maxLoaders: opts.maxLoaders || 10, + algorithm: opts.algorithm || 'push' // push or pull + }; + + if (self.useDataChannel) { + self._pearSignalHandshake(); + } + //candidate + self.candidateMap = {}; + + //info + self.connectedDC = 0; + self.usefulDC = 0; + + self._debugInfo = { + totalDCs: 0, + connectedDCs: 0, + usefulDCs: 0, + totalHTTP: 0, + totalHTTPS: 0, + usefulHTTPAndHTTPS: 0, + windowOffset: 0, + windowLength: 0, + signalServerConnected: false, + traffics: {}, + abilities: {} + }; + + self._start(); +} + +Worker.isRTCSupported = function () { + + return !!getBrowserRTC(); +} + +Object.defineProperty(Worker.prototype, 'debugInfo', { + get: function () { return this._debugInfo } +}); + + + +Worker.prototype._start = function () { + var self = this; + if (!window.WebSocket) { + self.useDataChannel = false; + } + + if (self.sources) { //如果用户指定下载源 + + self.sources = self.sources.map(function (source) { + + return {uri: source, type: 'server'}; + }); + nodeFilter(self.sources, function (nodes, fileLength) { //筛选出可用的节点,以及回调文件大小 + + var length = nodes.length; + debug('nodes:'+JSON.stringify(nodes)); + + if (length) { + // self.fileLength = fileLength; + debug('nodeFilter fileLength:'+fileLength); + + self._startPlaying(nodes); + } else { + + self._fallBack(); + } + }, {start: 0, end: 30}); + } else { + + self._getNodes(self.token, function (nodes) { + debug('debug _getNodes: %j', nodes); + if (nodes) { + self._startPlaying(nodes); + //信令服务器开启关闭 + if (self.useDataChannel) { + self._pearSignalHandshake(); + } + } else { + self._fallBackToWRTC(); + } + }); + } +}; + +Worker.prototype._fallBack = function () { + + debug('PearDownloader _fallBack'); + + this.emit('fallback'); +}; + +Worker.prototype._fallBackToWRTC = function () { + var self = this; + debug('_fallBackToWRTC'); + if (self._debugInfo.signalServerConnected === true) { //如果websocket已经连接上 + nodeFilter([{uri: self.src, type: 'server'}], function (nodes, fileLength) { //筛选出可用的节点,以及回调文件大小 + + var length = nodes.length; + if (length) { + // self.fileLength = fileLength; + debug('nodeFilter fileLength:'+fileLength); + self.fileLength = fileLength; + self._startPlaying(nodes); + } else { + + self._fallBack(); + } + }); + } else { + self._fallBack(); + } + +}; + +Worker.prototype._getNodes = function (token, cb) { + var self = this; + + var postData = { + client_ip:'116.77.208.118', + host: self.urlObj.host, + uri: self.urlObj.path + }; + postData = (function(obj){ + var str = "?"; + + for(var prop in obj){ + str += prop + "=" + obj[prop] + "&" + } + return str; + })(postData); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", GETNODES_ADDR+postData); + xhr.timeout = 2000; + xhr.setRequestHeader('X-Pear-Token', self.token); + xhr.ontimeout = function() { + // self._fallBack(); + cb(null); + }; + xhr.onerror = function () { + self._fallBackToWRTC(); + }; + xhr.onload = function () { + if (this.status >= 200 && this.status < 300 || this.status == 304) { + + debug(this.response); + + var res = JSON.parse(this.response); + // debug(res.nodes); + if (res.size) { //如果filesize大于0 + self.fileLength = res.size; + //信令服务器开启关闭 + if (self.useDataChannel) { + self._pearSignalHandshake(); + } + + if (!res.nodes){ //如果没有可用节点则切换到纯webrtc模式 + // cb(null); + // cb([{uri: self.src, type: 'server'}]); + self._fallBackToWRTC(); + } else { + + var nodes = res.nodes; + var allNodes = []; + var isLocationHTTP = location.protocol === 'http:' ? true : false; + var httpsCount = 0; + var httpCount = 0; + for (var i=0; i= 20){ + nodes = nodes.slice(0, 20); + cb(nodes); + } else { + cb(nodes); + } + } else { + // self._fallBack(); + cb([{uri: self.src, type: 'server'}]); + } + }, {start: 0, end: 30}); + } + } else { + cb(null); + } + } else { //返回码不正常 + // self._fallBack(); + cb(null); + } + }; + xhr.send(); +}; + +Worker.prototype._pearSignalHandshake = function () { + var self = this; + var dcCount = 0; //目前建立的data channel数量 + debug('_pearSignalHandshake'); + var websocket = new WebSocket(self.websocketAddr); + self.websocket = websocket; + websocket.onopen = function() { + // debug('websocket connection opened!'); + self._debugInfo.signalServerConnected = true; + var hash = md5(self.urlObj.host + self.urlObj.path); + websocket.push(JSON.stringify({ + "action": "get", + "peer_id": self.peerId, + "host": self.urlObj.host, + "uri": self.urlObj.path, + "md5": hash + })); + // debug('peer_id:'+self.peerId); + }; + websocket.push = websocket.send; + websocket.send = function(data) { + if (websocket.readyState != 1) { + console.warn('websocket connection is not opened yet.'); + return setTimeout(function() { + websocket.send(data); + }, 1000); + } + // debug("send to signal is " + data); + websocket.push(data); + }; + websocket.onmessage = function(e) { + var message = JSON.parse(e.data); + // debug("[simpleRTC] websocket message is: " + JSON.stringify(message)); + // message = message.nodes[1]; + //字段名有误,Gooni修改 + if (message.action === 'candidate' && message.candidates.type === 'end') { + + for (var peerId in self.candidateMap) { + if (message.peer_id === peerId) { + // debug('self.candidateMap[peerId]:'+self.candidateMap[peerId]); + self.JDMap[peerId].candidatesFromWS(self.candidateMap[peerId]); + } + } + } else if (message.nodes) { + var nodes = message.nodes; + + self._debugInfo.totalDCs = nodes.length; + + for (var i=0;i 1.0 ? 1.0 : downloaded; + self.emit('progress', progress); + }); + d.on('meanspeed', function (meanSpeed) { + + + self.emit('meanspeed', meanSpeed); + }); + d.on('fograte', function (fogRate) { + + self.emit('fogratio', fogRate); + }); + d.on('fogspeed', function (speed) { + + self.emit('fogspeed', speed); + }); + d.on('cloudspeed', function (speed) { + + self.emit('cloudspeed', speed); + }); + d.on('buffersources', function (bufferSources) { //s: server n: node d: data channel b: browser + + self.emit('buffersources', bufferSources); + }); + d.on('sourcemap', function (sourceType, index) { //s: server n: node d: data channel b: browser + + self.emit('sourcemap', sourceType, index); + }); + d.on('traffic', function (mac, size, type, meanSpeed) { + + //流量上报 + if (!self._debugInfo.traffics[mac]) { + self._debugInfo.traffics[mac] = {}; + self._debugInfo.traffics[mac].mac = mac; + self._debugInfo.traffics[mac].traffic = size; + } else { + self._debugInfo.traffics[mac].traffic += size; + } + + //能力值上报 + if (meanSpeed) { + self._debugInfo.abilities[mac] = meanSpeed; + } + + self.emit('traffic', mac, size, type); + }); + d.on('datachannelerror', function () { + + self._debugInfo.usefulDCs --; + }); + d.on('fillwindow', function (windowOffset, windowLength) { + + self._debugInfo.windowOffset = windowOffset; + self._debugInfo.windowLength = windowLength; + }); +}; + +function getBrowserRTC () { + if (typeof window === 'undefined') return null; + var wrtc = { + RTCPeerConnection: window.RTCPeerConnection || window.mozRTCPeerConnection || + window.webkitRTCPeerConnection, + }; + if (!wrtc.RTCPeerConnection) return null; + return wrtc +} + +function makeCandidateArr(sdp) { + var rawArr = sdp.split('\r\n'); + + var ufrag_reg = /^(a=ice-ufrag)/; + var ice_ufrag; + for (var i=0; ir?r=3:15e}};d.headers={common:{Accept:"application/json, text/plain, */*"}},o.forEach(["delete","get","head"],function(e){d.headers[e]={}}),o.forEach(["post","put","patch"],function(e){d.headers[e]=o.merge(i)}),t.exports=d}).call(this,e("_process"))},{"./adapters/http":3,"./adapters/xhr":3,"./helpers/normalizeHeaderName":23,"./utils":26,_process:170}],16:[function(e,t){"use strict";t.exports=function(e,t){return function(){for(var n=Array(arguments.length),r=0;r>8-8*(o%1))){if(d=t.charCodeAt(o+=3/4),255=l?t(e/l)+"d":e>=c?t(e/c)+"h":e>=s?t(e/s)+"m":e>=p?t(e/p)+"s":e+"ms"}function i(e){return a(e,l,"day")||a(e,c,"hour")||a(e,s,"minute")||a(e,p,"second")||e+" ms"}function a(e,t,n){return e>18]+d[63&e>>12]+d[63&e>>6]+d[63&e]}function s(e,t,n){for(var r=[],s=t,i;s>16,d[c++]=255&s>>8,d[c++]=255&s;return 2===i?(s=a[e.charCodeAt(n)]<<2|a[e.charCodeAt(n+1)]>>4,d[c++]=255&s):1===i&&(s=a[e.charCodeAt(n)]<<10|a[e.charCodeAt(n+1)]<<4|a[e.charCodeAt(n+2)]>>2,d[c++]=255&s>>8,d[c++]=255&s),d},n.fromByteArray=function(e){for(var t=e.length,n=t%3,r="",o=[],a=16383,p=0,i=t-n,c;pi?i:p+a));return 1==n?(c=e[t-1],r+=d[c>>2],r+=d[63&c<<4],r+="=="):2==n&&(c=(e[t-2]<<8)+e[t-1],r+=d[c>>10],r+=d[63&c>>4],r+=d[63&c<<2],r+="="),o.push(r),o.join("")};for(var d=[],a=[],p="undefined"==typeof Uint8Array?Array:Uint8Array,c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",l=0,i=c.length;li&&48<=i){r=10*r+(i-48);continue}if(s!==t||43!==i){if(s===t&&45===i){o=-1;continue}if(46===i)break;throw new Error("not a number: buffer["+s+"] = "+i)}}return r*o}function o(t,n,r,s){return null==t||0===t.length?null:("number"!=typeof n&&null==s&&(s=n,n=void 0),"number"!=typeof r&&null==s&&(s=r,r=void 0),o.position=0,o.encoding=s||null,o.data=e.isBuffer(t)?t.slice(n,r):new e(t),o.bytes=o.data.length,o.next())}const s=101;o.bytes=0,o.position=0,o.data=null,o.encoding=null,o.next=function(){switch(o.data[o.position]){case 100:return o.dictionary();case 108:return o.list();case 105:return o.integer();default:return o.buffer();}},o.find=function(e){for(var t=o.position,n=o.data.length,s=o.data;tr||r>=e.length)throw new RangeError("invalid lower bound");if(void 0===o)o=e.length-1;else if(o|=0,o=e.length)throw new RangeError("invalid upper bound");for(;r<=o;)if(s=r+(o-r>>1),i=+n(e[s],t,s,e),0>i)r=s+1;else if(0>3;return 0!=e%8&&t++,t}var d="undefined"==typeof t?"undefined"==typeof Int8Array?function(e){for(var t=Array(e),n=0;n>3;return t>e%8)},o.prototype.set=function(t,r){var o=t>>3;r||1===arguments.length?(this.buffer.length>t%8):o>t%8))},o.prototype._grow=function(e){if(this.buffer.length=this._parserSize;){var r=1===this._buffer.length?this._buffer[0]:a.concat(this._buffer);this._bufferSize-=this._parserSize,this._buffer=this._bufferSize?[r.slice(this._parserSize)]:[],this._parser(r.slice(0,this._parserSize))}n(null)},r.prototype._callback=function(e,t,n){e&&(this._clearTimeout(),!this.peerChoking&&!this._finished&&this._updateTimeout(),e.callback(t,n))},r.prototype._clearTimeout=function(){this._timeout&&(clearTimeout(this._timeout),this._timeout=null)},r.prototype._updateTimeout=function(){var e=this;e._timeoutMs&&e.requests.length&&!e._timeout&&(e._timeout=setTimeout(function(){e._onTimeout()},e._timeoutMs),e._timeoutUnref&&e._timeout.unref&&e._timeout.unref())},r.prototype._parse=function(e,t){this._parserSize=e,this._parser=t},r.prototype._onMessageLength=function(e){var t=e.readUInt32BE(0);0=this.size;){var o=n.concat(this._buffered);this._bufferedBytes-=this.size,this.push(o.slice(0,this.size)),this._buffered=[o.slice(this.size,o.length)]}r()},r.prototype._flush=function(){if(this._bufferedBytes&&this._zeroPadding){var e=new n(this.size-this._bufferedBytes);e.fill(0),this._buffered.push(e),this.push(n.concat(this._buffered)),this._buffered=null}else this._bufferedBytes&&(this.push(n.concat(this._buffered)),this._buffered=null);this.push(null)}}).call(this,e("buffer").Buffer)},{buffer:159,defined:51,inherits:58,"readable-stream":92}],45:[function(e,t){(function(e){"use strict";function n(e,t){var n=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(n>>16)<<16|65535&n}function o(e,t){return e<>>32-t}function p(e,r,i,d,a,s){return n(o(n(n(r,e),n(d,s)),a),i)}function s(e,n,r,o,i,d,s){return p(n&r|~n&o,e,n,i,d,s)}function l(e,n,r,o,i,a,s){return p(n&o|r&~o,e,n,i,a,s)}function u(e,n,r,o,i,d,s){return p(n^r^o,e,n,i,d,s)}function f(e,n,r,o,i,a,s){return p(r^(n|~o),e,n,i,a,s)}function d(e,t){e[t>>5]|=128<>>9<<4)+14]=t;var r=1732584193,o=-271733879,a=-1732584194,p=271733878,d,i,c,h,m;for(d=0;d>5]>>>o%32);return t}function c(e){var t=[],n;for(t[(e.length>>2)-1]=void 0,n=0;n>5]|=(255&e.charCodeAt(n/8))<s;s+=1)r[s]=909522486^n[s],o[s]=1549556828^n[s];return i=d(r.concat(c(t)),512+8*t.length),a(d(o.concat(i),640))}function g(e){var t="0123456789abcdef",n="",r,o;for(o=0;o>>4)+t.charAt(15&r);return n}function _(e){return unescape(encodeURIComponent(e))}function y(e){return h(_(e))}function b(e){return g(y(e))}function w(e,t){return m(_(e),_(t))}function k(e,t){return g(w(e,t))}function x(e,t,n){return t?n?w(t,e):k(t,e):n?y(e):b(e)}"function"==typeof i&&i.amd?i(function(){return x}):"object"==typeof t&&t.exports?t.exports=x:e.md5=x})(this)},{}],46:[function(e,o,s){"use strict";function d(e){if(e>J)throw new RangeError("Invalid typed array length");var t=new Uint8Array(e);return t.__proto__=p.prototype,t}function p(e,t,n){if("number"==typeof e){if("string"==typeof t)throw new Error("If encoding is specified then the first argument must be a string");return l(e)}return i(e,t,n)}function i(e,t,n){if("number"==typeof e)throw new TypeError("\"value\" argument must not be a number");return e instanceof ArrayBuffer?h(e,t,n):"string"==typeof e?u(e,t):m(e)}function a(e){if("number"!=typeof e)throw new TypeError("\"size\" argument must be a number");else if(0>e)throw new RangeError("\"size\" argument must not be negative")}function c(e,t,n){return a(e),0>=e?d(e):void 0===t?d(e):"string"==typeof n?d(e).fill(t,n):d(e).fill(t)}function l(e){return a(e),d(0>e?0:0|g(e))}function u(e,t){if(("string"!=typeof t||""===t)&&(t="utf8"),!p.isEncoding(t))throw new TypeError("\"encoding\" must be a valid string encoding");var n=0|_(e,t),r=d(n),o=r.write(e,t);return o!==n&&(r=r.slice(0,o)),r}function f(e){for(var t=0>e.length?0:0|g(e.length),n=d(t),r=0;rt||e.byteLength=J)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+J.toString(16)+" bytes");return 0|e}function _(e,t){if(p.isBuffer(e))return e.length;if(K(e)||e instanceof ArrayBuffer)return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return F(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return V(e).length;default:if(r)return F(e).length;t=(""+t).toLowerCase(),r=!0;}}function y(e,t,n){var r=!1;if((void 0===t||0>t)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),0>=n)return"";if(n>>>=0,t>>>=0,n<=t)return"";for(e||(e="utf8");;)switch(e){case"hex":return A(this,t,n);case"utf8":case"utf-8":return L(this,t,n);case"ascii":return R(this,t,n);case"latin1":case"binary":return P(this,t,n);case"base64":return T(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return U(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0;}}function b(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function w(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):2147483647n&&(n=-2147483648),n=+n,X(n)&&(n=o?0:e.length-1),0>n&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(0>n)if(o)n=0;else return-1;if("string"==typeof t&&(t=p.from(t,r)),p.isBuffer(t))return 0===t.length?-1:k(e,t,n,r,o);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):k(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function k(e,t,n,r,o){function s(e,t){return 1==d?e[t]:e.readUInt16BE(t*d)}var d=1,a=e.length,p=t.length;if(void 0!==r&&(r=(r+"").toLowerCase(),"ucs2"===r||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(2>e.length||2>t.length)return-1;d=2,a/=2,p/=2,n/=2}var c;if(o){var i=-1;for(c=n;ca&&(n=a-p),c=n;0<=c;c--){for(var l=!0,u=0;uo&&(r=o)):r=o;var s=t.length;if(0!=s%2)throw new TypeError("Invalid hex string");r>s/2&&(r=s/2);for(var d=0,i;di&&(d=i):2==a?(p=e[s+1],128==(192&p)&&(u=(31&i)<<6|63&p,127u||57343u&&(d=u))):void 0}null===d?(d=65533,a=1):65535>>10),d=56320|1023&d),o.push(d),s+=a}return B(o)}function B(e){var t=e.length;if(t<=$)return r.apply(String,e);for(var n="",o=0;ot)&&(t=0),(!n||0>n||n>r)&&(n=r);for(var o="",s=t;se)throw new RangeError("offset is not uint");if(e+t>n)throw new RangeError("Trying to access beyond buffer length")}function H(e,t,n,r,o,s){if(!p.isBuffer(e))throw new TypeError("\"buffer\" argument must be a Buffer instance");if(t>o||te.length)throw new RangeError("Index out of range")}function q(e,t,n,r){if(n+r>e.length)throw new RangeError("Index out of range");if(0>n)throw new RangeError("Index out of range")}function j(e,t,n,r,o){return t=+t,n>>>=0,o||q(e,t,n,4,3.4028234663852886e38,-3.4028234663852886e38),Y.write(e,t,n,r,23,4),n+4}function D(e,t,n,r,o){return t=+t,n>>>=0,o||q(e,t,n,8,1.7976931348623157e308,-1.7976931348623157e308),Y.write(e,t,n,r,52,8),n+8}function M(e){if(e=e.trim().replace(Z,""),2>e.length)return"";for(;0!=e.length%4;)e+="=";return e}function N(e){return 16>e?"0"+e.toString(16):e.toString(16)}function F(e,t){t=t||Infinity;for(var n=e.length,r=null,o=[],s=0,i;si){if(!r){if(56319i){-1<(t-=3)&&o.push(239,191,189),r=i;continue}i=(r-55296<<10|i-56320)+65536}else r&&-1<(t-=3)&&o.push(239,191,189);if(r=null,128>i){if(0>(t-=1))break;o.push(i)}else if(2048>i){if(0>(t-=2))break;o.push(192|i>>6,128|63&i)}else if(65536>i){if(0>(t-=3))break;o.push(224|i>>12,128|63&i>>6,128|63&i)}else if(1114112>i){if(0>(t-=4))break;o.push(240|i>>18,128|63&i>>12,128|63&i>>6,128|63&i)}else throw new Error("Invalid code point")}return o}function W(e){for(var t=[],n=0;n(t-=2));++r)o=e.charCodeAt(r),s=o>>8,i=o%256,n.push(i),n.push(s);return n}function V(e){return Q.toByteArray(M(e))}function G(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function K(e){return"function"==typeof ArrayBuffer.isView&&ArrayBuffer.isView(e)}function X(e){return e!==e}var Q=e("base64-js"),Y=e("ieee754");s.Buffer=p,s.SlowBuffer=function(e){return+e!=e&&(e=0),p.alloc(+e)},s.INSPECT_MAX_BYTES=50;var J=2147483647;s.kMaxLength=J,p.TYPED_ARRAY_SUPPORT=function(){try{var e=new Uint8Array(1);return e.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}},42===e.foo()}catch(t){return!1}}(),p.TYPED_ARRAY_SUPPORT||"undefined"==typeof console||"function"!=typeof console.error||console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support."),"undefined"!=typeof Symbol&&Symbol.species&&p[Symbol.species]===p&&Object.defineProperty(p,Symbol.species,{value:null,configurable:!0,enumerable:!1,writable:!1}),p.poolSize=8192,p.from=function(e,t,n){return i(e,t,n)},p.prototype.__proto__=Uint8Array.prototype,p.__proto__=Uint8Array,p.alloc=function(e,t,n){return c(e,t,n)},p.allocUnsafe=function(e){return l(e)},p.allocUnsafeSlow=function(e){return l(e)},p.isBuffer=function(e){return null!=e&&!0===e._isBuffer},p.compare=function(e,t){if(!p.isBuffer(e)||!p.isBuffer(t))throw new TypeError("Arguments must be Buffers");if(e===t)return 0;for(var r=e.length,o=t.length,s=0,i=n(r,o);st&&(e+=" ... ")),""},p.prototype.compare=function(e,t,r,o,s){if(!p.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===o&&(o=0),void 0===s&&(s=this.length),0>t||r>e.length||0>o||s>this.length)throw new RangeError("out of range index");if(o>=s&&t>=r)return 0;if(o>=s)return-1;if(t>=r)return 1;if(t>>>=0,r>>>=0,o>>>=0,s>>>=0,this===e)return 0;for(var d=s-o,a=r-t,c=n(d,a),l=this.slice(o,s),u=e.slice(t,r),f=0;f>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0);else throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");var o=this.length-t;if((void 0===n||n>o)&&(n=o),0n||0>t)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var s=!1;;)switch(r){case"hex":return x(this,e,t,n);case"utf8":case"utf-8":return v(this,e,t,n);case"ascii":return C(this,e,t,n);case"latin1":case"binary":return S(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return I(this,e,t,n);default:if(s)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),s=!0;}},p.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var $=4096;p.prototype.slice=function(e,t){var n=this.length;e=~~e,t=void 0===t?n:~~t,0>e?(e+=n,0>e&&(e=0)):e>n&&(e=n),0>t?(t+=n,0>t&&(t=0)):t>n&&(t=n),t>>=0,t>>>=0,n||O(e,t,this.length);for(var r=this[e],o=1,s=0;++s>>=0,t>>>=0,n||O(e,t,this.length);for(var r=this[e+--t],o=1;0>>=0,t||O(e,1,this.length),this[e]},p.prototype.readUInt16LE=function(e,t){return e>>>=0,t||O(e,2,this.length),this[e]|this[e+1]<<8},p.prototype.readUInt16BE=function(e,t){return e>>>=0,t||O(e,2,this.length),this[e]<<8|this[e+1]},p.prototype.readUInt32LE=function(e,t){return e>>>=0,t||O(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},p.prototype.readUInt32BE=function(e,t){return e>>>=0,t||O(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},p.prototype.readIntLE=function(e,n,r){e>>>=0,n>>>=0,r||O(e,n,this.length);for(var o=this[e],s=1,d=0;++d=s&&(o-=t(2,8*n)),o},p.prototype.readIntBE=function(e,n,r){e>>>=0,n>>>=0,r||O(e,n,this.length);for(var o=n,s=1,i=this[e+--o];0=s&&(i-=t(2,8*n)),i},p.prototype.readInt8=function(e,t){return e>>>=0,t||O(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},p.prototype.readInt16LE=function(e,t){e>>>=0,t||O(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},p.prototype.readInt16BE=function(e,t){e>>>=0,t||O(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},p.prototype.readInt32LE=function(e,t){return e>>>=0,t||O(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},p.prototype.readInt32BE=function(e,t){return e>>>=0,t||O(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},p.prototype.readFloatLE=function(e,t){return e>>>=0,t||O(e,4,this.length),Y.read(this,e,!0,23,4)},p.prototype.readFloatBE=function(e,t){return e>>>=0,t||O(e,4,this.length),Y.read(this,e,!1,23,4)},p.prototype.readDoubleLE=function(e,t){return e>>>=0,t||O(e,8,this.length),Y.read(this,e,!0,52,8)},p.prototype.readDoubleBE=function(e,t){return e>>>=0,t||O(e,8,this.length),Y.read(this,e,!1,52,8)},p.prototype.writeUIntLE=function(e,n,r,o){if(e=+e,n>>>=0,r>>>=0,!o){var s=t(2,8*r)-1;H(this,e,n,r,s,0)}var d=1,a=0;for(this[n]=255&e;++a>>=0,r>>>=0,!o){var s=t(2,8*r)-1;H(this,e,n,r,s,0)}var d=r-1,i=1;for(this[n+d]=255&e;0<=--d&&(i*=256);)this[n+d]=255&e/i;return n+r},p.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,1,255,0),this[t]=255&e,t+1},p.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},p.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},p.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},p.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},p.prototype.writeIntLE=function(e,n,r,o){if(e=+e,n>>>=0,!o){var s=t(2,8*r-1);H(this,e,n,r,s-1,-s)}var d=0,i=1,a=0;for(this[n]=255&e;++de&&0==a&&0!==this[n+d-1]&&(a=1),this[n+d]=255&(e/i>>0)-a;return n+r},p.prototype.writeIntBE=function(e,n,r,o){if(e=+e,n>>>=0,!o){var s=t(2,8*r-1);H(this,e,n,r,s-1,-s)}var d=r-1,i=1,a=0;for(this[n+d]=255&e;0<=--d&&(i*=256);)0>e&&0==a&&0!==this[n+d+1]&&(a=1),this[n+d]=255&(e/i>>0)-a;return n+r},p.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,1,127,-128),0>e&&(e=255+e+1),this[t]=255&e,t+1},p.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},p.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},p.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},p.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||H(this,e,t,4,2147483647,-2147483648),0>e&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},p.prototype.writeFloatLE=function(e,t,n){return j(this,e,t,!0,n)},p.prototype.writeFloatBE=function(e,t,n){return j(this,e,t,!1,n)},p.prototype.writeDoubleLE=function(e,t,n){return D(this,e,t,!0,n)},p.prototype.writeDoubleBE=function(e,t,n){return D(this,e,t,!1,n)},p.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),0t)throw new RangeError("targetStart out of bounds");if(0>n||n>=this.length)throw new RangeError("sourceStart out of bounds");if(0>r)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-to)for(s=0;so&&(e=o)}if(void 0!==r&&"string"!=typeof r)throw new TypeError("encoding must be a string");if("string"==typeof r&&!p.isEncoding(r))throw new TypeError("Unknown encoding: "+r)}else"number"==typeof e&&(e&=255);if(0>t||this.length>>=0,n=n===void 0?this.length:n>>>0,e||(e=0);var s;if("number"==typeof e)for(s=t;s>1),p=t[a]-e,0>p?o=a+1:0t.path.length?d=null:0===n&&1this._size&&(r=this._size),n===this._size?(this.destroy(),void this.push(null)):void(t.onload=function(){e._offset=r,e.push(d(t.result))},t.onerror=function(){e.emit("error",t.error)},t.readAsArrayBuffer(this._file.slice(n,r)))},r.prototype.destroy=function(){if(this._file=null,this.reader){this.reader.onload=null,this.reader.onerror=null;try{this.reader.abort()}catch(t){}}this.reader=null}},{inherits:58,"readable-stream":92,"typedarray-to-buffer":114}],54:[function(e,t){t.exports=function(e,t){function n(e,r){return e.reduce(function(e,o){return Array.isArray(o)&&r>1,f=-7,h=o?p-1:0,i=o?-1:1,d=n[r+h],s,e;for(h+=i,s=d&(1<<-f)-1,d>>=-f,f+=c;0>=-f,f+=a;0>1,_=23===l?5.960464477539063e-8-6.617444900424222e-24:0,y=p?0:u-1,i=p?1:-1,d=0>r||0===r&&0>1/r?1:0,b,w,m;for(r=s(r),isNaN(r)||r===Infinity?(w=isNaN(r)?1:0,b=h):(b=o(Math.log(r)/Math.LN2),1>r*(m=t(2,-b))&&(b--,m*=2),r+=1<=b+g?_/m:_*t(2,1-g),2<=r*m&&(b++,m/=2),b+g>=h?(w=0,b=h):1<=b+g?(w=(r*m-1)*t(2,l),b+=g):(w=r*t(2,g-1)*t(2,l),b=0));8<=l;n[a+y]=255&w,y+=i,w/=256,l-=8);for(b=b<127)return!1;return!0}},{}],60:[function(e,t){"use strict";function n(e){return r.existsSync(e)&&r.statSync(e).isFile()}var r=e("fs");t.exports=function(e,t){return t?void r.stat(e,function(e,n){return e?t(e):t(null,n.isFile())}):n(e)},t.exports.sync=n},{fs:156}],61:[function(e,t){function n(e){return r(e)||o(e)}function r(e){return e instanceof Int8Array||e instanceof Int16Array||e instanceof Int32Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Uint16Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array}function o(e){return i[s.call(e)]}t.exports=n,n.strict=r,n.loose=o;var s=Object.prototype.toString,i={"[object Int8Array]":!0,"[object Int16Array]":!0,"[object Int32Array]":!0,"[object Uint8Array]":!0,"[object Uint8ClampedArray]":!0,"[object Uint16Array]":!0,"[object Uint32Array]":!0,"[object Float32Array]":!0,"[object Float64Array]":!0}},{}],62:[function(e,t){var n={}.toString;t.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},{}],63:[function(e,t,n){"use strict";n.regex=n.re=/^npm-debug\.log$|^\..*\.swp$|^\.DS_Store$|^\.AppleDouble$|^\.LSOverride$|^Icon\r$|^\._.*|^\.Spotlight-V100(?:$|\/)|\.Trashes|^__MACOSX$|~$|^Thumbs\.db$|^ehthumbs\.db$|^Desktop\.ini$|^@eaDir$/,n.is=(e)=>n.re.test(e),n.not=(e)=>!n.is(e)},{}],64:[function(e,t){function n(e){var t={},n=e.split("magnet:?")[1],s=n&&0<=n.length?n.split("&"):[];s.forEach(function(e){var n=e.split("=");if(2===n.length){var r=n[0],o=n[1];if("dn"===r&&(o=decodeURIComponent(o).replace(/\+/g," ")),("tr"===r||"xs"===r||"as"===r||"ws"===r)&&(o=decodeURIComponent(o)),"kt"===r&&(o=decodeURIComponent(o).split("+")),"ix"===r&&(o=+o),!t[r])t[r]=o;else if(Array.isArray(t[r]))t[r].push(o);else{var s=t[r];t[r]=[s,o]}}});var d;if(t.xt){var a=Array.isArray(t.xt)?t.xt:[t.xt];a.forEach(function(e){if(d=e.match(/^urn:btih:(.{40})/))t.infoHash=d[1].toLowerCase();else if(d=e.match(/^urn:btih:(.{32})/)){var n=r.decode(d[1]);t.infoHash=o.from(n,"binary").toString("hex")}})}return t.infoHash&&(t.infoHashBuffer=o.from(t.infoHash,"hex")),t.dn&&(t.name=t.dn),t.kt&&(t.keywords=t.kt),t.announce="string"==typeof t.tr?[t.tr]:Array.isArray(t.tr)?t.tr:[],t.urlList=[],("string"==typeof t.as||Array.isArray(t.as))&&(t.urlList=t.urlList.concat(t.as)),("string"==typeof t.ws||Array.isArray(t.ws))&&(t.urlList=t.urlList.concat(t.ws)),i(t.announce),i(t.urlList),t}t.exports=n,t.exports.decode=n,t.exports.encode=function(e){e=s(e),e.infoHashBuffer&&(e.xt="urn:btih:"+e.infoHashBuffer.toString("hex")),e.infoHash&&(e.xt="urn:btih:"+e.infoHash),e.name&&(e.dn=e.name),e.keywords&&(e.kt=e.keywords),e.announce&&(e.tr=e.announce),e.urlList&&(e.ws=e.urlList,delete e.as);var t="magnet:?";return Object.keys(e).filter(function(e){return 2===e.length}).forEach(function(n,r){var o=Array.isArray(e[n])?e[n]:[e[n]];o.forEach(function(e,o){(0e._bufferDuration)&&e._cb){var t=e._cb;e._cb=null,t()}};r.prototype._getBufferDuration=function(){for(var e=this,t=e._sourceBuffer.buffered,n=e._elem.currentTime,r=-1,o=0;on)break;else(0<=r||n<=i)&&(r=i)}var d=r-n;return 0>d&&(d=0),d}},{inherits:58,"readable-stream":92,"to-arraybuffer":111}],66:[function(e,t){(function(e){function n(e,t){if(!(this instanceof n))return new n(e,t);if(t||(t={}),this.chunkLength=+e,!this.chunkLength)throw new Error("First argument must be a chunk length");this.chunks=[],this.closed=!1,this.length=+t.length||Infinity,this.length!==Infinity&&(this.lastChunkLength=this.length%this.chunkLength||this.chunkLength,this.lastChunkIndex=d(this.length/this.chunkLength)-1)}function r(t,n,r){e.nextTick(function(){t&&t(n,r)})}t.exports=n,n.prototype.put=function(e,t,n){if(this.closed)return r(n,new Error("Storage is closed"));var o=e===this.lastChunkIndex;return o&&t.length!==this.lastChunkLength?r(n,new Error("Last chunk length must be "+this.lastChunkLength)):o||t.length===this.chunkLength?void(this.chunks[e]=t,r(n,null)):r(n,new Error("Chunk length must be "+this.chunkLength))},n.prototype.get=function(e,t,n){if("function"==typeof t)return this.get(e,null,t);if(this.closed)return r(n,new Error("Storage is closed"));var o=this.chunks[e];if(!o)return r(n,new Error("Chunk not found"));if(!t)return r(n,null,o);var s=t.offset||0,i=t.length||o.length-s;r(n,null,o.slice(s,i+s))},n.prototype.close=n.prototype.destroy=function(e){return this.closed?r(e,new Error("Storage is closed")):void(this.closed=!0,this.chunks=null,r(e,null))}}).call(this,e("_process"))},{_process:170}],67:[function(e,t,r){(function(t){function s(e,t,n){for(var r=t;r>3:0,c=null;return d&&(c=d.toString(16),p&&(c+="."+p)),{mimeCodec:c,buffer:new t(e.slice(0))}},r.esds.encodingLength=function(e){return e.buffer.length},r.stsz={},r.stsz.encode=function(e,n,o){var s=e.entries||[];n=n?n.slice(o):t(r.stsz.encodingLength(e)),n.writeUInt32BE(0,0),n.writeUInt32BE(s.length,4);for(var d=0;ds&&(p=1),t.writeUInt32BE(p,n),t.write(e.type,n+4,4,"ascii");var c=n+8;if(1===p&&(r.encode(e.length,t,c),c+=8),o.fullBoxes[a]&&(t.writeUInt32BE(e.flags||0,c),t.writeUInt8(e.version||0,c),c+=4),d[a]){var l=d[a];l.forEach(function(n){if(5===n.length){var r=e[n]||[];n=n.substr(0,4),r.forEach(function(e){i._encode(e,t,c),c+=i.encode.bytes})}else e[n]&&(i._encode(e[n],t,c),c+=i.encode.bytes)}),e.otherBoxes&&e.otherBoxes.forEach(function(e){i._encode(e,t,c),c+=i.encode.bytes})}else if(o[a]){var u=o[a].encode;u(e,t,c),c+=u.bytes}else if(e.buffer){var f=e.buffer;f.copy(t,c),c+=e.buffer.length}else throw new Error("Either `type` must be set to a known type (not'"+a+"') or `buffer` must be set");return i.encode.bytes=c-n,t},i.readHeaders=function(e,t,n){if(t=t||0,n=n||e.length,8>n-t)return 8;var s=e.readUInt32BE(t),i=e.toString("ascii",t+4,t+8),d=t+8;if(1===s){if(16>n-t)return 16;s=r.decode(e,d),d+=8}var a,p;return o.fullBoxes[i]&&(a=e.readUInt8(d),p=16777215&e.readUInt32BE(d),d+=4),{length:s,headersLen:d-t,contentLen:s-(d-t),type:i,version:a,flags:p}},i.decode=function(e,t,n){t=t||0,n=n||e.length;var r=i.readHeaders(e,t,n);if(!r||r.length>n-t)throw new Error("Data too short");return i.decodeWithoutHeaders(r,e,t+r.headersLen,t+r.length)},i.decodeWithoutHeaders=function(e,n,r,s){r=r||0,s=s||n.length;var a=e.type,p={};if(d[a]){p.otherBoxes=[];for(var c=d[a],l=r,u;8<=s-l;)if(u=i.decode(n,l,s),l+=u.length,0<=c.indexOf(u.type))p[u.type]=u;else if(0<=c.indexOf(u.type+"s")){var f=u.type+"s",h=p[f]=p[f]||[];h.push(u)}else p.otherBoxes.push(u)}else if(o[a]){var m=o[a].decode;p=m(n,r,s)}else p.buffer=new t(n.slice(r,s));return p.length=e.length,p.contentLen=e.contentLen,p.type=e.type,p.version=e.version,p.flags=e.flags,p},i.encodingLength=function(e){var t=e.type,n=8;if(o.fullBoxes[t]&&(n+=4),d[t]){var r=d[t];r.forEach(function(t){if(5===t.length){var r=e[t]||[];t=t.substr(0,4),r.forEach(function(e){e.type=t,n+=i.encodingLength(e)})}else if(e[t]){var o=e[t];o.type=t,n+=i.encodingLength(o)}}),e.otherBoxes&&e.otherBoxes.forEach(function(e){n+=i.encodingLength(e)})}else if(o[t])n+=o[t].encodingLength(e);else if(e.buffer)n+=e.buffer.length;else throw new Error("Either `type` must be set to a known type (not'"+t+"') or `buffer` must be set");return n>s&&(n+=8),e.length=n,n}}).call(this,e("buffer").Buffer)},{"./boxes":67,buffer:159,uint64be:115}],70:[function(e,t){(function(n){function r(){return this instanceof r?void(s.Writable.call(this),this.destroyed=!1,this._pending=0,this._missing=0,this._buf=null,this._str=null,this._cb=null,this._ondrain=null,this._writeBuffer=null,this._writeCb=null,this._ondrain=null,this._kick()):new r}function o(e){this._parent=e,this.destroyed=!1,s.PassThrough.call(this)}var s=e("readable-stream"),i=e("inherits"),d=e("next-event"),a=e("mp4-box-encoding"),p=new n(0);t.exports=r,i(r,s.Writable),r.prototype.destroy=function(e){this.destroyed||(this.destroyed=!0,e&&this.emit("error",e),this.emit("close"))},r.prototype._write=function(e,t,n){if(!this.destroyed){for(var r=!this._str||!this._str._writableState.needDrain;e.length&&!this.destroyed;){if(!this._missing)return this._writeBuffer=e,void(this._writeCb=n);var o=e.lengths++;)i.push(t(2,s));n.exports=function(e){return r(e/o,i)}},{"closest-to":48}],79:[function(e,t){(function(e){"use strict";t.exports=e.version&&0!==e.version.indexOf("v0.")&&(0!==e.version.indexOf("v1.")||0===e.version.indexOf("v1.8."))?e.nextTick:function(t,n,r,o){if("function"!=typeof t)throw new TypeError("\"callback\" argument must be a function");var s=arguments.length,d,a;switch(s){case 0:case 1:return e.nextTick(t);case 2:return e.nextTick(function(){t.call(null,n)});case 3:return e.nextTick(function(){t.call(null,n,r)});case 4:return e.nextTick(function(){t.call(null,n,r,o)});default:for(d=Array(s-1),a=0;ae.length)throw new Error("pump requires two streams per minimum");var n=e.map(function(o,s){var i=s=t.length)return o._position+=t.length,r(null);var p;if(a>t.length){o._position+=t.length,p=0===d?t:t.slice(d),s=i.stream.write(p)&&s;break}o._position+=a,p=0===d&&a===t.length?t:t.slice(d,a),s=i.stream.write(p)&&s,i.last&&i.stream.end(),t=t.slice(a),o._queue.shift()}s?r(null):i.stream.once("drain",r.bind(null,null))},r.prototype.slice=function(e){var t=this;if(t.destroyed)return null;e instanceof Array||(e=[e]);var n=new s.PassThrough;return e.forEach(function(r,o){t._queue.push({start:r.start,end:r.end,stream:n,last:o===e.length-1})}),t._buffer&&t._write(t._buffer,null,t._cb),n},r.prototype.destroy=function(e){var t=this;t.destroyed||(t.destroyed=!0,e&&t.emit("error",e))}},{inherits:58,"readable-stream":92}],84:[function(e,t){"use strict";function n(e){return this instanceof n?void(a.call(this,e),p.call(this,e),e&&!1===e.readable&&(this.readable=!1),e&&!1===e.writable&&(this.writable=!1),this.allowHalfOpen=!0,e&&!1===e.allowHalfOpen&&(this.allowHalfOpen=!1),this.once("end",r)):new n(e)}function r(){this.allowHalfOpen||this._writableState.ended||s(o,this)}function o(e){e.end()}var s=e("process-nextick-args"),i=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};t.exports=n;var d=e("core-util-is");d.inherits=e("inherits");var a=e("./_stream_readable"),p=e("./_stream_writable");d.inherits(n,a);for(var c=i(p.prototype),l=0,u;l=X?e=X:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}function m(e,t){return 0>=e||0===t.length&&t.ended?0:t.objectMode?1:e===e?(e>t.highWaterMark&&(t.highWaterMark=h(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0)):t.flowing&&t.length?t.buffer.head.data.length:t.length}function g(e,t){if(!t.ended){if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,_(e)}}function _(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(W("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?A(y,e):y(e))}function y(e){W("emit readable"),e.emit("readable"),S(e)}function b(e,t){t.readingMore||(t.readingMore=!0,A(w,e,t))}function w(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(r=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):r=I(e,t.buffer,t.decoder),r}function I(e,t,n){var r;return es.length?s.length:e;if(o+=i===s.length?s:s.slice(0,e),e-=i,0===e){i===s.length?(++r,t.head=n.next?n.next:t.tail=null):(t.head=n,n.data=s.slice(i));break}++r}return t.length-=r,o}function L(e,t){var n=D.allocUnsafe(e),r=t.head,o=1;for(r.data.copy(n),e-=r.data.length;r=r.next;){var s=r.data,i=e>s.length?s.length:e;if(s.copy(n,n.length-e,0,i),e-=i,0===e){i===s.length?(++o,t.head=r.next?r.next:t.tail=null):(t.head=r,r.data=s.slice(i));break}++o}return t.length-=o,n}function B(e){var t=e._readableState;if(0=t.highWaterMark||t.ended))return W("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?B(this):_(this),null;if(e=m(e,t),0===e&&t.ended)return 0===t.length&&B(this),null;var r=t.needReadable;W("need readable",r),(0===t.length||t.length-e>>0),n=this.head,s=0;n;)r(n.data,t,s),s+=n.data.length,n=n.next;return t},e}()},{"safe-buffer":98}],90:[function(e,t){"use strict";function n(e,t){e.emit("error",t)}var r=e("process-nextick-args");t.exports={destroy:function(e,t){var o=this,s=this._readableState&&this._readableState.destroyed,i=this._writableState&&this._writableState.destroyed;return s||i?void(t?t(e):e&&(!this._writableState||!this._writableState.errorEmitted)&&r(n,this,e)):void(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?(r(n,o,e),o._writableState&&(o._writableState.errorEmitted=!0)):t&&t(e)}))},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},{"process-nextick-args":79}],91:[function(e,t){t.exports=e("events").EventEmitter},{events:162}],92:[function(e,t,n){n=t.exports=e("./lib/_stream_readable.js"),n.Stream=n,n.Readable=n,n.Writable=e("./lib/_stream_writable.js"),n.Duplex=e("./lib/_stream_duplex.js"),n.Transform=e("./lib/_stream_transform.js"),n.PassThrough=e("./lib/_stream_passthrough.js")},{"./lib/_stream_duplex.js":84,"./lib/_stream_passthrough.js":85,"./lib/_stream_readable.js":86,"./lib/_stream_transform.js":87,"./lib/_stream_writable.js":88}],93:[function(e,t,n){function r(e,t,n,r){function s(){C.removeEventListener("loadstart",s),n.autoplay&&C.play()}function d(){C.removeEventListener("canplay",d),r(null,C)}function u(){C=t("iframe"),o(e,function(e,t){return e?w(e):void(C.src=t,".pdf"!==x&&(C.sandbox="allow-forms allow-scripts"),r(null,C))})}function w(t){t.message="Error rendering file \""+e.name+"\": "+t.message,a(t.message),r(t)}var x=l.extname(e.name).toLowerCase(),v=0,C;0<=g.indexOf(x)?function(){function r(){a("Use MediaSource API for "+e.name),g(),C.addEventListener("error",u),C.addEventListener("loadstart",s),C.addEventListener("canplay",d);var t=new c(C),n=t.createWriteStream(i(e.name));e.createReadStream().pipe(n),v&&(C.currentTime=v)}function p(){a("Use Blob URL for "+e.name),g(),C.addEventListener("error",w),C.addEventListener("loadstart",s),C.addEventListener("canplay",d),o(e,function(e,t){return e?w(e):void(C.src=t,v&&(C.currentTime=v))})}function l(e){a("videostream error: fallback to MediaSource API: %o",e.message||e),C.removeEventListener("error",l),C.removeEventListener("canplay",d),r()}function u(t){return a("MediaSource API error: fallback to Blob URL: %o",t.message||t),"number"==typeof e.length&&e.length>n.maxBlobLength?(a("File length too large for Blob URL approach: %d (max: %d)",e.length,n.maxBlobLength),w(new Error("File length too large for Blob URL approach: "+e.length+" (max: "+n.maxBlobLength+")"))):void(C.removeEventListener("error",u),C.removeEventListener("canplay",d),p())}function g(){C||(C=t(_),C.addEventListener("progress",function(){v=C.currentTime}))}var _=0<=m.indexOf(x)?"video":"audio";k?0<=h.indexOf(x)?function(){a("Use `videostream` package for "+e.name),g(),C.addEventListener("error",l),C.addEventListener("loadstart",s),C.addEventListener("canplay",d),f(e,C)}():r():p()}():0<=_.indexOf(x)?function(){C=t("audio"),o(e,function(e,t){return e?w(e):void(C.addEventListener("error",w),C.addEventListener("loadstart",s),C.addEventListener("canplay",d),C.src=t)})}():0<=y.indexOf(x)?function(){C=t("img"),o(e,function(t,n){return t?w(t):void(C.src=n,C.alt=e.name,r(null,C))})}():0<=b.indexOf(x)?u():function(){a("Unknown file extension \"%s\" - will attempt to render into iframe",x);var t="";e.createReadStream({start:0,end:1e3}).setEncoding("utf8").on("data",function(e){t+=e}).on("end",function(){p(t)?(a("File extension \"%s\" appears ascii, so will render.",x),u()):(a("File extension \"%s\" appears non-ascii, will not render.",x),r(new Error("Unsupported file type \""+x+"\": Cannot append to DOM")))}).on("error",r)}()}function o(e,t){var r=l.extname(e.name).toLowerCase();u(e.createReadStream(),n.mime[r],t)}function s(e){if(null==e)throw new Error("file cannot be null or undefined");if("string"!=typeof e.name)throw new Error("missing or invalid file.name property");if("function"!=typeof e.createReadStream)throw new Error("missing or invalid file.createReadStream property")}function i(e){var t=l.extname(e).toLowerCase();return{".m4a":"audio/mp4; codecs=\"mp4a.40.5\"",".m4v":"video/mp4; codecs=\"avc1.640029, mp4a.40.5\"",".mkv":"video/webm; codecs=\"avc1.640029, mp4a.40.5\"",".mp3":"audio/mpeg",".mp4":"video/mp4; codecs=\"avc1.640029, mp4a.40.5\"",".webm":"video/webm; codecs=\"vorbis, vp8\""}[t]}function d(e){null==e.autoplay&&(e.autoplay=!0),null==e.controls&&(e.controls=!0),null==e.maxBlobLength&&(e.maxBlobLength=w)}n.render=function(e,t,n,o){"function"==typeof n&&(o=n,n={}),n||(n={}),o||(o=function(){}),s(e),d(n),"string"==typeof t&&(t=document.querySelector(t)),r(e,function(n){if(t.nodeName!==n.toUpperCase()){var r=l.extname(e.name).toLowerCase();throw new Error("Cannot render \""+r+"\" inside a \""+t.nodeName.toLowerCase()+"\" element, expected \""+n+"\"")}return t},n,o)},n.append=function(e,t,n,o){function i(e){var r=a(e);return n.controls&&(r.controls=!0),n.autoplay&&(r.autoplay=!0),t.appendChild(r),r}function a(e){var n=document.createElement(e);return t.appendChild(n),n}if("function"==typeof n&&(o=n,n={}),n||(n={}),o||(o=function(){}),s(e),d(n),"string"==typeof t&&(t=document.querySelector(t)),t&&("VIDEO"===t.nodeName||"AUDIO"===t.nodeName))throw new Error("Invalid video/audio node argument. Argument must be root element that video/audio tag will be appended to.");r(e,function(e){return"video"===e||"audio"===e?i(e):a(e)},n,function(e,t){e&&t&&t.remove(),o(e,t)})},n.mime=e("./lib/mime.json");var a=e("debug")("render-media"),p=e("is-ascii"),c=e("mediasource"),l=e("path"),u=e("stream-to-blob-url"),f=e("videostream"),h=[".m4a",".m4v",".mp4"],m=[".m4v",".mkv",".mp4",".webm"],g=[].concat(m,[".m4a",".mp3"]),_=[".aac",".oga",".ogg",".wav"],y=[".bmp",".gif",".jpeg",".jpg",".png",".svg"],b=[".css",".html",".js",".md",".pdf",".txt"],w=200000000,k="undefined"!=typeof window&&window.MediaSource},{"./lib/mime.json":94,debug:27,"is-ascii":59,mediasource:65,path:168,"stream-to-blob-url":105,videostream:121}],94:[function(e,t){t.exports={".3gp":"video/3gpp",".aac":"audio/aac",".aif":"audio/x-aiff",".aiff":"audio/x-aiff",".atom":"application/atom+xml",".avi":"video/x-msvideo",".bmp":"image/bmp",".bz2":"application/x-bzip2",".conf":"text/plain",".css":"text/css",".csv":"text/plain",".diff":"text/x-diff",".doc":"application/msword",".flv":"video/x-flv",".gif":"image/gif",".gz":"application/x-gzip",".htm":"text/html",".html":"text/html",".ico":"image/vnd.microsoft.icon",".ics":"text/calendar",".iso":"application/octet-stream",".jar":"application/java-archive",".jpeg":"image/jpeg",".jpg":"image/jpeg",".js":"application/javascript",".json":"application/json",".less":"text/css",".log":"text/plain",".m3u":"audio/x-mpegurl",".m4a":"audio/mp4",".m4v":"video/mp4",".manifest":"text/cache-manifest",".markdown":"text/x-markdown",".mathml":"application/mathml+xml",".md":"text/x-markdown",".mid":"audio/midi",".midi":"audio/midi",".mov":"video/quicktime",".mp3":"audio/mpeg",".mp4":"video/mp4",".mp4v":"video/mp4",".mpeg":"video/mpeg",".mpg":"video/mpeg",".odp":"application/vnd.oasis.opendocument.presentation",".ods":"application/vnd.oasis.opendocument.spreadsheet",".odt":"application/vnd.oasis.opendocument.text",".oga":"audio/ogg",".ogg":"application/ogg",".pdf":"application/pdf",".png":"image/png",".pps":"application/vnd.ms-powerpoint",".ppt":"application/vnd.ms-powerpoint",".ps":"application/postscript",".psd":"image/vnd.adobe.photoshop",".qt":"video/quicktime",".rar":"application/x-rar-compressed",".rdf":"application/rdf+xml",".rss":"application/rss+xml",".rtf":"application/rtf",".svg":"image/svg+xml",".svgz":"image/svg+xml",".swf":"application/x-shockwave-flash",".tar":"application/x-tar",".tbz":"application/x-bzip-compressed-tar",".text":"text/plain",".tif":"image/tiff",".tiff":"image/tiff",".torrent":"application/x-bittorrent",".ttf":"application/x-font-ttf",".txt":"text/plain",".wav":"audio/wav",".webm":"video/webm",".wma":"audio/x-ms-wma",".wmv":"video/x-ms-wmv",".xls":"application/vnd.ms-excel",".xml":"application/xml",".yaml":"text/yaml",".yml":"text/yaml",".zip":"application/zip"}},{}],95:[function(e,t){(function(e){t.exports=function(t,n,r){function o(t){function n(){r&&r(t,d),r=null}i?e.nextTick(n):n()}function s(e,n,r){if(d[e]=r,n&&(l=!0),0==--p||n)o(n);else if(!l&&u>2)+1;s>2]|=128<<24-(t%4<<3),e[(-16&(t>>2)+2)+14]=0|n/536870912,e[(-16&(t>>2)+2)+15]=n<<3},c=function(e,t,n,r,o){var s=this,i=o%4,d=(r+i)%4,a=r-d,p;switch(i){case 0:e[o]=s.charCodeAt(n+3);case 1:e[0|o+1-(i<<1)]=s.charCodeAt(n+2);case 2:e[0|o+2-(i<<1)]=s.charCodeAt(n+1);case 3:e[0|o+3-(i<<1)]=s.charCodeAt(n);}if(!(r>2]=s.charCodeAt(n+p)<<24|s.charCodeAt(n+p+1)<<16|s.charCodeAt(n+p+2)<<8|s.charCodeAt(n+p+3);switch(d){case 3:e[0|o+a+1]=s.charCodeAt(n+a+2);case 2:e[0|o+a+2]=s.charCodeAt(n+a+1);case 1:e[0|o+a+3]=s.charCodeAt(n+a);}}},l=function(e,t,n,r,o){var s=this,i=o%4,d=(r+i)%4,a=r-d,p;switch(i){case 0:e[o]=s[n+3];case 1:e[0|o+1-(i<<1)]=s[n+2];case 2:e[0|o+2-(i<<1)]=s[n+1];case 3:e[0|o+3-(i<<1)]=s[n];}if(!(r>2]=s[n+p]<<24|s[n+p+1]<<16|s[n+p+2]<<8|s[n+p+3];switch(d){case 3:e[0|o+a+1]=s[n+a+2];case 2:e[0|o+a+2]=s[n+a+1];case 1:e[0|o+a+3]=s[n+a];}}},u=function(e,t,n,r,o){var i=this,d=o%4,a=(r+d)%4,p=r-a,c=new Uint8Array(s.readAsArrayBuffer(i.slice(n,n+r))),l;switch(d){case 0:e[o]=c[3];case 1:e[0|o+1-(d<<1)]=c[2];case 2:e[0|o+2-(d<<1)]=c[1];case 3:e[0|o+3-(d<<1)]=c[0];}if(!(r>2]=c[l]<<24|c[l+1]<<16|c[l+2]<<8|c[l+3];switch(a){case 3:e[0|o+p+1]=c[p+2];case 2:e[0|o+p+2]=c[p+1];case 1:e[0|o+p+3]=c[p];}}},f=function(e){switch(o.getDataType(e)){case"string":return c.bind(e);case"array":return l.bind(e);case"buffer":return l.bind(e);case"arraybuffer":return l.bind(new Uint8Array(e));case"view":return l.bind(new Uint8Array(e.buffer,e.byteOffset,e.byteLength));case"blob":return u.bind(e);}},h=Array(256),m=0;256>m;m++)h[m]=(16>m?"0":"")+m.toString(16);var i=function(e){for(var t=new Uint8Array(e),n=Array(e.byteLength),r=0;r=e)return 65536;if(16777216>e)for(t=1;t>2);return a(o,e),p(o,e,n),r},b=function(e,n,r,o){f(e)(t.h8,t.h32,n,r,o||0)},w=function(e,n,r,o,s){var i=r;b(e,n,r),s&&(i=y(r,o)),t.core.hash(i,t.padMaxChunkLen)},k=function(e,t){var n=new Int32Array(e,t+320,5),r=new Int32Array(5),o=new DataView(r.buffer);return o.setInt32(0,n[0],!1),o.setInt32(4,n[1],!1),o.setInt32(8,n[2],!1),o.setInt32(12,n[3],!1),o.setInt32(16,n[4],!1),r},x=this.rawDigest=function(e){var n=e.byteLength||e.length||e.size||0;_(t.heap,t.padMaxChunkLen);var r=0,o=t.maxChunkLen;for(r=0;n>r+o;r+=o)w(e,r,o,n,!1);return w(e,r,n-r,n,!0),k(t.heap,t.padMaxChunkLen)};this.digest=this.digestFromString=this.digestFromBuffer=this.digestFromArrayBuffer=function(e){return i(x(e).buffer)},this.resetState=function(){return _(t.heap,t.padMaxChunkLen),this},this.append=function(e){var r=0,o=e.byteLength||e.length||e.size||0,s=t.offset%t.maxChunkLen,i;for(t.offset+=o;r>2],d=0|r[t+324>>2],p=0|r[t+328>>2],l=0|r[t+332>>2],f=0|r[t+336>>2],n=0;(0|n)<(0|e);n=0|n+64){for(i=s,a=d,c=p,u=l,h=f,o=0;64>(0|o);o=0|o+4)g=0|r[n+o>>2],m=0|(0|(s<<5|s>>>27)+(d&p|~d&l))+(0|(0|g+f)+1518500249),f=l,l=p,p=d<<30|d>>>2,d=s,s=m,r[e+o>>2]=g;for(o=0|e+64;(0|o)<(0|e+80);o=0|o+4)g=(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])<<1|(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])>>>31,m=0|(0|(s<<5|s>>>27)+(d&p|~d&l))+(0|(0|g+f)+1518500249),f=l,l=p,p=d<<30|d>>>2,d=s,s=m,r[o>>2]=g;for(o=0|e+80;(0|o)<(0|e+160);o=0|o+4)g=(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])<<1|(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])>>>31,m=0|(0|(s<<5|s>>>27)+(d^p^l))+(0|(0|g+f)+1859775393),f=l,l=p,p=d<<30|d>>>2,d=s,s=m,r[o>>2]=g;for(o=0|e+160;(0|o)<(0|e+240);o=0|o+4)g=(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])<<1|(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])>>>31,m=0|(0|(s<<5|s>>>27)+(d&p|d&l|p&l))+(0|(0|g+f)-1894007588),f=l,l=p,p=d<<30|d>>>2,d=s,s=m,r[o>>2]=g;for(o=0|e+240;(0|o)<(0|e+320);o=0|o+4)g=(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])<<1|(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])>>>31,m=0|(0|(s<<5|s>>>27)+(d^p^l))+(0|(0|g+f)-899497514),f=l,l=p,p=d<<30|d>>>2,d=s,s=m,r[o>>2]=g;s=0|s+i,d=0|d+a,p=0|p+c,l=0|l+u,f=0|f+h}r[t+320>>2]=s,r[t+324>>2]=d,r[t+328>>2]=p,r[t+332>>2]=l,r[t+336>>2]=f}}},"undefined"==typeof t?"undefined"!=typeof window&&(window.Rusha=r):t.exports=r,"undefined"!=typeof FileReaderSync){var s=new FileReaderSync,i=function(e,t,n){try{return n(null,e.digest(t))}catch(t){return n(t)}},d=function(e,t,n,r,o){var s=new self.FileReader;s.onloadend=function(){var i=s.result;t+=s.result.byteLength;try{e.append(i)}catch(t){return void o(t)}tn.statusCode&&"location"in n.headers)return e.url=n.headers.location,n.resume(),void(0c?(r._debug("start backpressure: bufferedAmount %d",r._channel.bufferedAmount),r._cb=n):n(null)}else r._debug("write before connect"),r._chunk=e,r._cb=n},r.prototype._onFinish=function(){function e(){setTimeout(function(){t._destroy()},1e3)}var t=this;t.destroyed||(t.connected?e():t.once("connect",e))},r.prototype._createOffer=function(){var e=this;e.destroyed||e._pc.createOffer(function(t){function n(){var n=e._pc.localDescription||t;e._debug("signal"),e.emit("signal",{type:n.type,sdp:n.sdp})}e.destroyed||(t.sdp=e.sdpTransform(t.sdp),e._pc.setLocalDescription(t,function(){e.destroyed||(e.trickle||e._iceComplete?n():e.once("_iceComplete",n))},function(t){e._destroy(t)}))},function(t){e._destroy(t)},e.offerConstraints)},r.prototype._createAnswer=function(){var e=this;e.destroyed||e._pc.createAnswer(function(t){function n(){var n=e._pc.localDescription||t;e._debug("signal"),e.emit("signal",{type:n.type,sdp:n.sdp})}e.destroyed||(t.sdp=e.sdpTransform(t.sdp),e._pc.setLocalDescription(t,function(){e.destroyed||(e.trickle||e._iceComplete?n():e.once("_iceComplete",n))},function(t){e._destroy(t)}))},function(t){e._destroy(t)},e.answerConstraints)},r.prototype._onIceStateChange=function(){var e=this;if(!e.destroyed){var t=e._pc.iceConnectionState,n=e._pc.iceGatheringState;e._debug("iceStateChange (connection: %s) (gathering: %s)",t,n),e.emit("iceStateChange",t,n),("connected"===t||"completed"===t)&&(clearTimeout(e._reconnectTimeout),e._pcReady=!0,e._maybeReady()),"disconnected"===t&&(e.reconnectTimer?(clearTimeout(e._reconnectTimeout),e._reconnectTimeout=setTimeout(function(){e._destroy()},e.reconnectTimer)):e._destroy()),"failed"===t&&e._destroy(new Error("Ice connection failed.")),"closed"===t&&e._destroy()}},r.prototype.getStats=function(e){var t=this;0===t._pc.getStats.length?t._pc.getStats().then(function(t){var n=[];t.forEach(function(e){n.push(e)}),e(null,n)},function(t){e(t)}):t._isReactNativeWebrtc?t._pc.getStats(null,function(t){var n=[];t.forEach(function(e){n.push(e)}),e(null,n)},function(t){e(t)}):0c)&&this._onChannelBufferedAmountLow()},r.prototype._onSignalingStateChange=function(){var e=this;e.destroyed||(e._debug("signalingStateChange %s",e._pc.signalingState),e.emit("signalingStateChange",e._pc.signalingState))},r.prototype._onIceCandidate=function(e){var t=this;t.destroyed||(e.candidate&&t.trickle?t.emit("signal",{candidate:{candidate:e.candidate.candidate,sdpMLineIndex:e.candidate.sdpMLineIndex,sdpMid:e.candidate.sdpMid}}):!e.candidate&&(t._iceComplete=!0,t.emit("_iceComplete")))},r.prototype._onChannelMessage=function(e){var t=this;if(!t.destroyed){var r=e.data;r instanceof ArrayBuffer&&(r=n.from(r)),t.push(r)}},r.prototype._onChannelBufferedAmountLow=function(){var e=this;if(!e.destroyed&&e._cb){e._debug("ending backpressure: bufferedAmount %d",e._channel.bufferedAmount);var t=e._cb;e._cb=null,t(null)}},r.prototype._onChannelOpen=function(){var e=this;e.connected||e.destroyed||(e._debug("on channel open"),e._channelReady=!0,e._maybeReady())},r.prototype._onChannelClose=function(){var e=this;e.destroyed||(e._debug("on channel close"),e._destroy())},r.prototype._onAddStream=function(e){var t=this;t.destroyed||(t._debug("on add stream"),t.emit("stream",e.stream))},r.prototype._onTrack=function(e){var t=this;if(!t.destroyed){t._debug("on track");var n=e.streams[0].id;-1!==t._previousStreams.indexOf(n)||(t._previousStreams.push(n),t.emit("stream",e.streams[0]))}},r.prototype._debug=function(){var e=this,t=[].slice.call(arguments);t[0]="["+e._id+"] "+t[0],s.apply(null,t)},r.prototype._transformConstraints=function(e){var t=this;if(0===Object.keys(e).length)return e;if((e.mandatory||e.optional)&&!t._isChromium){var n=Object.assign({},e.optional,e.mandatory);return void 0!==n.OfferToReceiveVideo&&(n.offerToReceiveVideo=n.OfferToReceiveVideo,delete n.OfferToReceiveVideo),void 0!==n.OfferToReceiveAudio&&(n.offerToReceiveAudio=n.OfferToReceiveAudio,delete n.OfferToReceiveAudio),n}return e.mandatory||e.optional||!t._isChromium?e:(void 0!==e.offerToReceiveVideo&&(e.OfferToReceiveVideo=e.offerToReceiveVideo,delete e.offerToReceiveVideo),void 0!==e.offerToReceiveAudio&&(e.OfferToReceiveAudio=e.offerToReceiveAudio,delete e.offerToReceiveAudio),{mandatory:e})}}).call(this,e("buffer").Buffer)},{buffer:159,debug:27,"get-browser-rtc":55,inherits:58,randombytes:82,"readable-stream":92}],102:[function(e,t){function n(e){return i.digest(e)}function r(e){for(var t=e.length,n=new Uint8Array(t),r=0;r>>4).toString(16)),n.push((15&o).toString(16));return n.join("")}var s=e("rusha"),i=new s,d="undefined"==typeof window?self:window,a=d.crypto||d.msCrypto||{},p=a.subtle||a.webkitSubtle;try{p.digest({name:"sha-1"},new Uint8Array).catch(function(){p=!1})}catch(e){p=!1}t.exports=function(e,t){return p?void("string"==typeof e&&(e=r(e)),p.digest({name:"sha-1"},e).then(function(e){t(o(new Uint8Array(e)))},function(){t(n(e))})):void setTimeout(t,0,n(e))},t.exports.sync=n},{rusha:97}],103:[function(e,t){(function(n){function r(e){var t=this;if(!(t instanceof r))return new r(e);if(e||(e={}),"string"==typeof e&&(e={url:e}),null==e.url&&null==e.socket)throw new Error("Missing required `url` or `socket` option");if(null!=e.url&&null!=e.socket)throw new Error("Must specify either `url` or `socket` option, not both");if(t._id=d(4).toString("hex").slice(0,7),t._debug("new websocket: %o",e),e=Object.assign({allowHalfOpen:!1},e),a.Duplex.call(t,e),t.connected=!1,t.destroyed=!1,t._chunk=null,t._cb=null,t._interval=null,e.socket)t.url=e.socket.url,t._ws=e.socket;else{t.url=e.url;try{t._ws="function"==typeof p?new c(e.url,e):new c(e.url)}catch(e){return void n.nextTick(function(){t._destroy(e)})}}t._ws.binaryType="arraybuffer",t._ws.onopen=function(){t._onOpen()},t._ws.onmessage=function(e){t._onMessage(e)},t._ws.onclose=function(){t._onClose()},t._ws.onerror=function(){t._destroy(new Error("connection error to "+t.url))},t._onFinishBound=function(){t._onFinish()},t.once("finish",t._onFinishBound)}t.exports=r;var o=e("safe-buffer").Buffer,s=e("debug")("simple-websocket"),i=e("inherits"),d=e("randombytes"),a=e("readable-stream"),p=e("ws"),c="function"==typeof p?p:WebSocket,l=65536;i(r,a.Duplex),r.WEBSOCKET_SUPPORT=!!c,r.prototype.send=function(e){this._ws.send(e)},r.prototype.destroy=function(e){this._destroy(null,e)},r.prototype._destroy=function(e,t){var n=this;if(!n.destroyed){if(t&&n.once("close",t),n._debug("destroy (error: %s)",e&&(e.message||e)),n.readable=n.writable=!1,n._readableState.ended||n.push(null),n._writableState.finished||n.end(),n.connected=!1,n.destroyed=!0,clearInterval(n._interval),n._interval=null,n._chunk=null,n._cb=null,n._onFinishBound&&n.removeListener("finish",n._onFinishBound),n._onFinishBound=null,n._ws){var r=n._ws,o=function(){r.onclose=null};if(r.readyState===c.CLOSED)o();else try{r.onclose=o,r.close()}catch(e){o()}r.onopen=null,r.onmessage=null,r.onerror=null}n._ws=null,e&&n.emit("error",e),n.emit("close")}},r.prototype._read=function(){},r.prototype._write=function(e,t,n){if(this.destroyed)return n(new Error("cannot write after socket is destroyed"));if(this.connected){try{this.send(e)}catch(e){return this._destroy(e)}"function"!=typeof p&&this._ws.bufferedAmount>l?(this._debug("start backpressure: bufferedAmount %d",this._ws.bufferedAmount),this._cb=n):n(null)}else this._debug("write before connect"),this._chunk=e,this._cb=n},r.prototype._onFinish=function(){function e(){setTimeout(function(){t._destroy()},1e3)}var t=this;t.destroyed||(t.connected?e():t.once("connect",e))},r.prototype._onMessage=function(e){if(!this.destroyed){var t=e.data;t instanceof ArrayBuffer&&(t=o.from(t)),this.push(t)}},r.prototype._onOpen=function(){var e=this;if(!(e.connected||e.destroyed)){if(e.connected=!0,e._chunk){try{e.send(e._chunk)}catch(t){return e._destroy(t)}e._chunk=null,e._debug("sent chunk from \"write before connect\"");var t=e._cb;e._cb=null,t(null)}"function"!=typeof p&&(e._interval=setInterval(function(){e._onInterval()},150),e._interval.unref&&e._interval.unref()),e._debug("connect"),e.emit("connect")}},r.prototype._onInterval=function(){if(this._cb&&this._ws&&!(this._ws.bufferedAmount>l)){this._debug("ending backpressure: bufferedAmount %d",this._ws.bufferedAmount);var e=this._cb;this._cb=null,e(null)}},r.prototype._onClose=function(){this.destroyed||(this._debug("on close"),this._destroy())},r.prototype._debug=function(){var e=[].slice.call(arguments);e[0]="["+this._id+"] "+e[0],s.apply(null,e)}}).call(this,e("_process"))},{_process:170,debug:27,inherits:58,randombytes:82,"readable-stream":92,"safe-buffer":98,ws:158}],104:[function(e,t){var n=1,r=65535,o=4,s=setInterval(function(){n=n+1&r},0|1e3/o);s.unref&&s.unref(),t.exports=function(e){var t=o*(e||5),s=[0],i=1,d=n-1&r;return function(e){var a=n-d&r;for(a>t&&(a=t),d=n;a--;)i==t&&(i=0),s[i]=s[0==i?t-1:i-1],i++;e&&(s[i-1]+=e);var p=s[i-1],c=s.length=e)return 0;return 6==e>>5?2:14==e>>4?3:30==e>>3?4:-1}function a(e,t,n){var r=t.length-1;if(r=r)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function l(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function u(e,t){var r=(e.length-t)%3;return 0==r?e.toString("base64",t):(this.lastNeed=3-r,this.lastTotal=3,1==r?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-r))}function f(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function h(e){return e.toString(this.encoding)}function m(e){return e&&e.length?this.write(e):""}var g=e("safe-buffer").Buffer,_=g.isEncoding||function(e){switch(e=""+e,e&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1;}};n.StringDecoder=s,s.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(t=this.fillLast(e),void 0===t)return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n>s,s=(s+5)%8,i=i<>8-s,r++):(i=31&a>>8-(s+5),s=(s+5)%8,0==s&&r++),d[o]="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".charCodeAt(i),o++}for(r=o;r=n?(n=(n+5)%8,0==n?(a|=o,p[s]=a,s++,a=0):a|=255&o<<8-n):(n=(n+5)%8,a|=255&o>>>n,p[s]=a,s++,a=255&o<<8-n);else throw new Error("Invalid input - it is not base32 encoded string")}return p.slice(0,s)}}).call(this,e("buffer").Buffer)},{buffer:159}],111:[function(e,t){var n=e("buffer").Buffer;t.exports=function(e){if(e instanceof Uint8Array){if(0===e.byteOffset&&e.byteLength===e.buffer.byteLength)return e.buffer;if("function"==typeof e.buffer.slice)return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}if(n.isBuffer(e)){for(var t=new Uint8Array(e.length),r=e.length,o=0;o=e.length||0>t)){var n=e.pop();if(t=e.metadata_size?this.emit("warning",new Error("Peer gave invalid metadata size")):void(this._metadataSize=e.metadata_size,this._numPieces=d(this._metadataSize/l),this._remainingRejects=2*this._numPieces,this._fetching&&this._requestPieces()):this.emit("warning",new Error("Peer does not have metadata")):this.emit("warning",new Error("Peer does not support ut_metadata"))},t.prototype.onMessage=function(e){var t,r;try{var o=e.toString(),s=o.indexOf("ee")+2;t=n.decode(o.substring(0,s)),r=e.slice(s)}catch(e){return}switch(t.msg_type){case 0:this._onRequest(t.piece);break;case 1:this._onData(t.piece,r,t.total_size);break;case 2:this._onReject(t.piece);}},t.prototype.fetch=function(){this._metadataComplete||(this._fetching=!0,this._metadataSize&&this._requestPieces())},t.prototype.cancel=function(){this._fetching=!1},t.prototype.setMetadata=function(e){if(this._metadataComplete)return!0;s("set metadata");try{var t=n.decode(e).info;t&&(e=n.encode(t))}catch(e){}return this._infoHash&&this._infoHash!==p.sync(e)?!1:(this.cancel(),this.metadata=e,this._metadataComplete=!0,this._metadataSize=this.metadata.length,this._wire.extendedHandshake.metadata_size=this._metadataSize,this.emit("metadata",n.encode({info:n.decode(this.metadata)})),!0)},t.prototype._send=function(e,t){var r=n.encode(e);o.isBuffer(t)&&(r=o.concat([r,t])),this._wire.extended("ut_metadata",r)},t.prototype._request=function(e){this._send({msg_type:0,piece:e})},t.prototype._data=function(e,t,n){var r={msg_type:1,piece:e};"number"==typeof n&&(r.total_size=n),this._send(r,t)},t.prototype._reject=function(e){this._send({msg_type:2,piece:e})},t.prototype._onRequest=function(e){if(!this._metadataComplete)return void this._reject(e);var t=e*l,n=t+l;n>this._metadataSize&&(n=this._metadataSize);var r=this.metadata.slice(t,n);this._data(e,r,this._metadataSize)},t.prototype._onData=function(e,t){t.length>l||(t.copy(this.metadata,e*l),this._bitfield.set(e),this._checkDone())},t.prototype._onReject=function(e){0=e._entries[e._index][e._countName]&&(e._index++,e._offset=0),e.value=e._entries[e._index]},r.prototype._processMoov=function(e){var t=this,r=e.traks;t._tracks=[],t._hasVideo=!1,t._hasAudio=!1;for(var o=0;o=a.stsz.entries.length)break;if(g++,y+=S,g>=C.samplesPerChunk){g=0,y=0,_++;var L=a.stsc.entries[b+1];L&&_+1>=L.firstChunk&&b++}w+=E,k.inc(),x&&x.inc(),T&&v++}i.mdia.mdhd.duration=0,i.tkhd.duration=0;var B=C.sampleDescriptionId,R={type:"moov",mvhd:e.mvhd,traks:[{tkhd:i.tkhd,mdia:{mdhd:i.mdia.mdhd,hdlr:i.mdia.hdlr,elng:i.mdia.elng,minf:{vmhd:i.mdia.minf.vmhd,smhd:i.mdia.minf.smhd,dinf:i.mdia.minf.dinf,stbl:{stsd:a.stsd,stts:d(),ctts:d(),stsc:d(),stsz:d(),stco:d(),stss:d()}}}}],mvex:{mehd:{fragmentDuration:e.mvhd.duration},trexs:[{trackId:i.tkhd.trackId,defaultSampleDescriptionIndex:B,defaultSampleDuration:0,defaultSampleSize:0,defaultSampleFlags:0}]}};t._tracks.push({trackId:i.tkhd.trackId,timeScale:i.mdia.mdhd.timeScale,samples:h,currSample:null,currTime:null,moov:R,mime:f})}if(0===t._tracks.length)return void t.emit("error",new Error("no playable tracks"));e.mvhd.duration=0,t._ftyp={type:"ftyp",brand:"iso5",brandVersion:0,compatibleBrands:["iso5"]};var P=l.encode(t._ftyp),A=t._tracks.map(function(e){var t=l.encode(e.moov);return{mime:e.mime,init:n.concat([P,t])}});t.emit("ready",A)},r.prototype.seek=function(e){var t=this;if(!t._tracks)throw new Error("Not ready yet; wait for 'ready' event");t._fileStream&&(t._fileStream.destroy(),t._fileStream=null);var n=-1;if(t._tracks.map(function(r,o){function s(e){i.destroyed||i.box(e.moof,function(n){if(n)return t.emit("error",n);if(!i.destroyed){var d=r.inStream.slice(e.ranges);d.pipe(i.mediaData(e.length,function(e){if(e)return t.emit("error",e);if(!i.destroyed){var n=t._generateFragment(o);return n?void s(n):i.finalize()}}))}})}r.outStream&&r.outStream.destroy(),r.inStream&&(r.inStream.destroy(),r.inStream=null);var i=r.outStream=c.encode(),d=t._generateFragment(o,e);return d?void((-1==n||d.ranges[0].startd&&(d=-d-2);!r.samples[d].sync;)d--;return d};r.prototype._generateFragment=function(e,t){var n=this,r=n._tracks[e],o;if(o=void 0===t?r.currSample:n._findSampleBefore(e,t),o>=r.samples.length)return null;for(var s=r.samples[o].dts,i=0,d=[],a=o,p;a=r.timeScale*1));a++){i+=p.size;var c=d.length-1;0>c||d[c].end!==p.offset?d.push({start:p.offset,end:p.offset+p.size}):d[c].end+=p.size}return r.currSample=a,{moof:n._generateMoof(e,o,a),ranges:d,length:i}},r.prototype._generateMoof=function(e,t,n){for(var r=this,o=r._tracks[e],s=[],i=t,d;ie||t2*(r._numConns-r.numPeers)&&e.amInterested?e.destroy():(o=setTimeout(t,z),o.unref&&o.unref()))}function n(){if(e.peerPieces.buffer.length===r.bitfield.buffer.length){for(s=0;s131072?e.destroy():void(r.pieces[t]||r.store.get(t,{offset:n,length:o},s))}),e.bitfield(r.bitfield),e.interested(),e.peerExtensions.dht&&r.client.dht&&r.client.dht.listening&&e.port(r.client.dht.address().port),"webSeed"!==e.type&&(o=setTimeout(t,z),o.unref&&o.unref()),e.isSeeder=!1,n()},i.prototype._updateSelections=function(){var e=this;!e.ready||e.destroyed||(o.nextTick(function(){e._gcSelections()}),e._updateInterest(),e._update())},i.prototype._gcSelections=function(){for(var e=this,t=0;t=e&&s<=n&&!(s in r)&&t.peerPieces.get(s)&&(!o||o(s))}}function r(){if(!t.requests.length)for(var e=d._selections.length;e--;){var r=d._selections[e],o;if("rarest"===d.strategy)for(var s=r.from+r.offset,i=r.to,a={},p=0,c=n(s,i,a);po));){if(d._request(t,o,!1))return;a[o]=!0,p+=1}else for(o=r.to;o>=r.from+r.offset;--o)if(t.peerPieces.get(o)&&d._request(t,o,!1))return}}function o(){var n=t.downloadSpeed()||1;if(n>V)return function(){return!0};var r=e(1,t.requests.length)*P.BLOCK_LENGTH/n,o=10,s=0;return function(e){if(!o||d.bitfield.get(e))return!0;for(var t=d.pieces[e].missing;s=c)return!0;for(var r=o(),a=0;ap));){for(;d._request(t,p,d._critical[p]||e););if(t.requests.length=p)){var c=a(t,G);i(!1)||i(!0)}}},i.prototype._rechoke=function(){function e(e,t){return e.downloadSpeed===t.downloadSpeed?e.uploadSpeed===t.uploadSpeed?e.wire.amChoking===t.wire.amChoking?e.salt-t.salt:e.wire.amChoking?1:-1:t.uploadSpeed-e.uploadSpeed:t.downloadSpeed-e.downloadSpeed}var t=this;if(t.ready){0=V||2*c>o||c>d||(a=i,d=c)}}if(!a)return!1;for(p=0;p=f)return!1;var h=c.pieces[t],m=u?h.reserveRemaining():h.reserve();if(-1===m&&s&&c._hotswap(e,t)&&(m=u?h.reserveRemaining():h.reserve()),-1===m)return!1;var g=c._reservations[t];g||(g=c._reservations[t]=[]);var r=g.indexOf(null);-1===r&&(r=g.length),g[r]=e;var i=h.chunkOffset(m),_=u?h.chunkLengthRemaining(m):h.chunkLength(m);return e.request(t,i,_,function n(o,s){if(!c.destroyed){if(!c.ready)return c.once("ready",function(){n(o,s)});if(g[r]===e&&(g[r]=null),h!==c.pieces[t])return d();if(o)return c._debug("error getting piece %s (offset: %s length: %s) from %s: %s",t,i,_,e.remoteAddress+":"+e.remotePort,o.message),u?h.cancelRemaining(m):h.cancel(m),void d();if(c._debug("got piece %s (offset: %s length: %s) from %s",t,i,_,e.remoteAddress+":"+e.remotePort),!h.set(m,s,e))return d();var a=h.flush();O(a,function(e){if(!c.destroyed){if(e===c._hashes[t]){if(!c.pieces[t])return;c._debug("piece verified %s",t),c.pieces[t]=null,c._reservations[t]=null,c.bitfield.set(t,!0),c.store.put(t,a),c.wires.forEach(function(e){e.have(t)}),c._checkDone()&&!c.destroyed&&c.discovery.complete()}else c.pieces[t]=new P(h.length),c.emit("warning",new Error("Piece "+t+" failed verification"));d()}})}}),!0},i.prototype._checkDone=function(){var e=this;if(!e.destroyed){e.files.forEach(function(t){if(!t.done){for(var n=t._startPiece;n<=t._endPiece;++n)if(!e.bitfield.get(n))return;t.done=!0,t.emit("done"),e._debug("file done: "+t.name)}});for(var t=!0,n=0,r;n=e.client.maxConns)){this._debug("drain (%s queued, %s/%s peers)",e._numQueued,e.numPeers,e.client.maxConns);var t=e._queue.shift();if(t){this._debug("tcp connect attempt to %s",t.addr);var n=u(t.addr),r={host:n[0],port:n[1]},o=t.conn=E.connect(r);o.once("connect",function(){t.onConnect()}),o.once("error",function(e){t.destroy(e)}),t.startConnectTimeout(),o.on("close",function(){if(!e.destroyed){if(t.retries>=X.length)return void e._debug("conn %s closed: will not re-add (max %s attempts)",t.addr,X.length);var n=X[t.retries];e._debug("conn %s closed: will re-add to queue in %sms (attempt %s)",t.addr,n,t.retries+1);var r=setTimeout(function(){var n=e._addPeer(t.addr);n&&(n.retries=t.retries+1)},n);r.unref&&r.unref()}})}}},i.prototype._validAddr=function(e){var t;try{t=u(e)}catch(t){return!1}var n=t[0],r=t[1];return 0r&&("127.0.0.1"!==n||r!==this.client.torrentPort)}}).call(this,t("_process"),"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global)},{"../package.json":129,"./file":124,"./peer":125,"./rarity-map":126,"./server":158,_process:170,"addr-to-ip-port":31,bitfield:37,"chunk-store-stream/write":47,debug:27,events:162,fs:156,"fs-chunk-store":66,"immediate-chunk-store":57,inherits:58,multistream:73,net:158,os:158,"parse-torrent":77,path:168,pump:80,"random-iterate":81,"run-parallel":96,"run-parallel-limit":95,"simple-get":100,"simple-sha1":102,speedometer:104,"torrent-discovery":112,"torrent-piece":113,uniq:116,ut_metadata:118,ut_pex:158,xtend:131,"xtend/mutable":132}],128:[function(t,r){function o(e,t){l.call(this),this.url=e,this.webPeerId=c.sync(e),this._torrent=t,this._init()}r.exports=o;var s=t("bitfield"),i=t("safe-buffer").Buffer,d=t("debug")("webtorrent:webconn"),a=t("simple-get"),p=t("inherits"),c=t("simple-sha1"),l=t("bittorrent-protocol"),u=t("../package.json").version;p(o,l),o.prototype._init=function(){var e=this;e.setKeepAlive(!0),e.once("handshake",function(t){if(!e.destroyed){e.handshake(t,e.webPeerId);for(var n=e._torrent.pieces.length,r=new s(n),o=0;o<=n;o++)r.set(o,!0);e.bitfield(r)}}),e.once("interested",function(){d("interested"),e.unchoke()}),e.on("uninterested",function(){d("uninterested")}),e.on("choke",function(){d("choke")}),e.on("unchoke",function(){d("unchoke")}),e.on("bitfield",function(){d("bitfield")}),e.on("request",function(t,n,r,o){d("request pieceIndex=%d offset=%d length=%d",t,n,r),e.httpRequest(t,n,r,o)})},o.prototype.httpRequest=function(t,r,o,s){var p=this,c=t*p._torrent.pieceLength,l=c+r,f=l+o-1,h=p._torrent.files,m;if(1>=h.length)m=[{url:p.url,start:l,end:f}];else{var g=h.filter(function(e){return e.offset<=f&&e.offset+e.length>l});if(1>g.length)return s(new Error("Could not find file corresponnding to web seed range request"));m=g.map(function(t){var r=t.offset+t.length-1,o=p.url+("/"===p.url[p.url.length-1]?"":"/")+t.path;return{url:o,fileOffsetInRange:e(t.offset-l,0),start:e(l-t.offset,0),end:n(r,f-t.offset)}})}var _=0,y=!1,b;1t.statusCode||300<=t.statusCode?(y=!0,s(new Error("Unexpected HTTP status code "+t.statusCode))):void(d("Got data of length %d",n.length),1===m.length?s(null,n):(n.copy(b,e.fileOffsetInRange),++_===m.length&&s(null,b)))}var i=e.url,p=e.start,c=e.end;d("Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d",i,t,r,o,p,c);var l={url:i,method:"GET",headers:{"user-agent":"WebTorrent/"+u+" (https://webtorrent.io)",range:"bytes="+p+"-"+c}};a.concat(l,function(e,t,r){return y?void 0:e?"undefined"==typeof window||i.startsWith(window.location.origin+"/")?(y=!0,s(e)):a.head(i,function(t,r){return y?void 0:t?(y=!0,s(t)):200>r.statusCode||300<=r.statusCode?(y=!0,s(new Error("Unexpected HTTP status code "+r.statusCode))):r.url===i?(y=!0,s(e)):void(l.url=r.url,a.concat(l,function(e,t,r){return y?void 0:e?(y=!0,s(e)):void n(t,r)}))}):void n(t,r)})})},o.prototype.destroy=function(){l.prototype.destroy.call(this),this._torrent=null}},{"../package.json":129,bitfield:37,"bittorrent-protocol":38,debug:27,inherits:58,"safe-buffer":98,"simple-get":100,"simple-sha1":102}],129:[function(e,t){t.exports={version:"0.98.19"}},{}],130:[function(e,t){function n(e,t){function r(){for(var t=Array(arguments.length),n=0;n ./dist/pear-player.js","uglify-player":"browserify -s PearPlayer -e ./index.player.js | babili > ./dist/pear-player.min.js","pull-from-github":"git pull","push-to-github":"git add . && git commit -m 'update' && git push","npm-publish":"npm publish"},author:"Xie Ting Pear Limited",license:"MIT",homepage:"https://pear.hk",keywords:["WebRTC","video","player","p2p","peer-to-peer","peers","streaming","multiple source","torrent","web torrent","webrtc data channel","webtorrent"]}},{}],135:[function(e,t){(function(n){function r(e){a.call(this);var t=this;if(!(e.initialDownloaders&&e.fileSize&&e.scheduler))throw new Error("config is not completed");t.fileSize=e.fileSize,t.initialDownloaders=e.initialDownloaders,t.pieceLength=e.chunkSize||524288,t.interval=e.interval||5e3,t.auto=e.auto||!1,t.useMonitor=e.useMonitor||!1,t.downloaded=0,t.fogDownloaded=0,t._windowOffset=0,t.ready=!1,t.done=!1,t.destroyed=!1,t.chunks=0e||t=e.chunks)&&!(n>=t.length);){if(!e.bitfield.get(r)){var o=e._calRange(r),s=t[n%t.length];s.select(o[0],o[1]),n++}else;r++}e._windowEnd=r,d("_fillWindow _windowEnd:"+e._windowEnd)}}},r.prototype._createPushStream=function(){var e=this,t=this.downloaders;if(0!==t.length){for(var n=0,r=e._windowEnd;n!==e.maxLoaders&&!(r>=e.chunks);){if(!e.bitfield.get(r)){var o=e._calRange(r),s=t[n%t.length];s.select(o[0],o[1]),n++}else;r++}e._windowEnd=r}},r.prototype._setupHttp=function(e){var t=this;return e.once("start",function(){}),e.once("done",function(){}),e.once("error",function(){console.warn("http"+e.uri+"error!"),t.downloaders.length>t._windowLength&&(t.downloaders.removeObj(e),3=t.fogRatio&&t.emit("fograte",a),t.emit("fogspeed",t.downloaders.getCurrentSpeed([1])),t.bufferSources[s]=1===e.type?e.id:"b"}else t.emit("cloudspeed",t.downloaders.getCurrentSpeed([0])),t.bufferSources[s]=e.id;t.emit("buffersources",t.bufferSources),t.emit("sourcemap",1===e.type?"n":"s",s)}}),e},r.prototype._setupDC=function(e){var t=this;e.once("start",function(){}),e.on("data",function(n,r,o){var s=t._calIndex(r);d("pear_webrtc "+e.dc_id+" ondata range:"+r+"-"+o+" at index:"+s+" speed:"+e.meanSpeed);var i=o-r+1;if(!!t.bitfield.get(s)){d("\u91CD\u590D\u4E0B\u8F7D");for(var a=0;a=t.fogRatio&&t.emit("fograte",p),t.emit("fogspeed",t.downloaders.getCurrentSpeed([2])),t.bufferSources[s]="d",t.emit("buffersources",t.bufferSources),t.emit("sourcemap","d",s),t.emit("traffic",e.mac,i,"WebRTC_Node",e.meanSpeed)}}),e.once("error",function(){console.warn("webrtc error mac:"+e.mac),e.close(),t.downloaders.removeObj(e),t.downloaders.length=this.downloaders.length&&!this.noMoreNodes&&(this.requestMoreNodes(),this.requestMoreDataChannels(),2>=this.downloaders.length&&2<=this._windowLength/this.downloaders.length&&this.emit("needsource"))},r.prototype.addTorrent=function(e){var t=this;e.pieces.length!==this.chunks||(this.torrent=e,e.pear_downloaded=0,d("addTorrent _windowOffset:"+t._windowOffset),t._windowOffset+t._windowLength=t.fogRatio&&t.emit("fograte",r),t.emit("fogspeed",e.downloadSpeed/1024),t.bufferSources[n]="b",t.emit("buffersources",t.bufferSources),t.emit("sourcemap","b",n),t.emit("traffic","Webtorrent",t.pieceLength,"WebRTC_Browser")}}),e.on("done",function(){d("torrent done")}))},r.prototype.addDataChannel=function(e){this.downloaders.splice(this._windowLength-1,0,e),this._setupDC(e),!this.sequencial&&10>this._windowLength&&this._windowLength++},r.prototype.addNode=function(e){this._setupHttp(e),this.downloaders.push(e),d("dispatcher add node: "+e.uri),!this.sequencial&&10>this._windowLength&&this._windowLength++},r.prototype.requestMoreNodes=function(){0this.status){if(n.downloading=!1,n.endTime=new Date().getTime(),n.speed=o(e.total/(n.endTime-n.startTime)),r("http speed:"+n.speed+"KB/s"),-1==n.meanSpeed&&(n.meanSpeed=n.speed),n.meanSpeed=0.95*n.meanSpeed+0.05*n.speed,r("http "+n.uri+" meanSpeed:"+n.meanSpeed+"KB/s"),!n.isAsync&&0e||t2*(r._numConns-r.numPeers)&&e.amInterested?e.destroy():(o=setTimeout(t,z),o.unref&&o.unref()))}function n(){if(e.peerPieces.buffer.length===r.bitfield.buffer.length){for(s=0;s131072?e.destroy():void(r.pieces[t]||r.store.get(t,{offset:n,length:o},s))}),e.bitfield(r.bitfield),e.interested(),e.peerExtensions.dht&&r.client.dht&&r.client.dht.listening&&e.port(r.client.dht.address().port),"webSeed"!==e.type&&(o=setTimeout(t,z),o.unref&&o.unref()),e.isSeeder=!1,n()},i.prototype._updateSelections=function(){var e=this;!e.ready||e.destroyed||(o.nextTick(function(){e._gcSelections()}),e._updateInterest(),e._update())},i.prototype._gcSelections=function(){for(var e=this,t=0;t=e&&s<=n&&!(s in r)&&t.peerPieces.get(s)&&(!o||o(s))}}function r(){if(!t.requests.length)for(var e=d._selections.length;e--;){var r=d._selections[e],o;if("rarest"===d.strategy)for(var s=r.from+r.offset,i=r.to,a={},p=0,c=n(s,i,a);po));){if(d._request(t,o,!1))return;a[o]=!0,p+=1}else for(o=r.to;o>=r.from+r.offset;--o)if(t.peerPieces.get(o)&&d._request(t,o,!1))return}}function o(){var n=t.downloadSpeed()||1;if(n>V)return function(){return!0};var r=e(1,t.requests.length)*P.BLOCK_LENGTH/n,o=10,s=0;return function(e){if(!o||d.bitfield.get(e))return!0;for(var t=d.pieces[e].missing;s=c)return!0;for(var r=o(),a=0;ap));){for(;d._request(t,p,d._critical[p]||e););if(t.requests.length=p)){var c=a(t,G);i(!1)||i(!0)}}},i.prototype._rechoke=function(){function e(e,t){return e.downloadSpeed===t.downloadSpeed?e.uploadSpeed===t.uploadSpeed?e.wire.amChoking===t.wire.amChoking?e.salt-t.salt:e.wire.amChoking?1:-1:t.uploadSpeed-e.uploadSpeed:t.downloadSpeed-e.downloadSpeed}var t=this;if(t.ready){0=V||2*c>o||c>d||(a=i,d=c)}}if(!a)return!1;for(p=0;p=f)return!1;var h=c.pieces[t],m=u?h.reserveRemaining():h.reserve();if(-1===m&&s&&c._hotswap(e,t)&&(m=u?h.reserveRemaining():h.reserve()),-1===m)return!1;var g=c._reservations[t];g||(g=c._reservations[t]=[]);var r=g.indexOf(null);-1===r&&(r=g.length),g[r]=e;var i=h.chunkOffset(m),_=u?h.chunkLengthRemaining(m):h.chunkLength(m);return e.request(t,i,_,function n(o,s){if(!c.destroyed){if(!c.ready)return c.once("ready",function(){n(o,s)});if(g[r]===e&&(g[r]=null),h!==c.pieces[t])return d();if(o)return c._debug("error getting piece %s (offset: %s length: %s) from %s: %s",t,i,_,e.remoteAddress+":"+e.remotePort,o.message),u?h.cancelRemaining(m):h.cancel(m),void d();if(c._debug("got piece %s (offset: %s length: %s) from %s",t,i,_,e.remoteAddress+":"+e.remotePort),!h.set(m,s,e))return d();var a=h.flush();O(a,function(e){if(!c.destroyed){if(e===c._hashes[t]){if(!c.pieces[t])return;c._debug("piece verified %s",t),c.pieces[t]=null,c._reservations[t]=null,c.bitfield.get(t)||c.emit("piecefromtorrent",t),c.bitfield.set(t,!0),c.store.put(t,a),c.wires.forEach(function(e){e.have(t)}),c._checkDone()&&!c.destroyed&&c.discovery.complete()}else c.pieces[t]=new P(h.length),c.emit("warning",new Error("Piece "+t+" failed verification"));d()}})}}),!0},i.prototype._checkDone=function(){var e=this;if(!e.destroyed){e.files.forEach(function(t){if(!t.done){for(var n=t._startPiece;n<=t._endPiece;++n)if(!e.bitfield.get(n))return;t.done=!0,t.emit("done"),e._debug("file done: "+t.name)}});for(var t=!0,n=0,r;n=e.client.maxConns)){this._debug("drain (%s queued, %s/%s peers)",e._numQueued,e.numPeers,e.client.maxConns);var t=e._queue.shift();if(t){this._debug("tcp connect attempt to %s",t.addr);var n=u(t.addr),r={host:n[0],port:n[1]},o=t.conn=E.connect(r);o.once("connect",function(){t.onConnect()}),o.once("error",function(e){t.destroy(e)}),t.startConnectTimeout(),o.on("close",function(){if(!e.destroyed){if(t.retries>=X.length)return void e._debug("conn %s closed: will not re-add (max %s attempts)",t.addr,X.length);var n=X[t.retries];e._debug("conn %s closed: will re-add to queue in %sms (attempt %s)",t.addr,n,t.retries+1);var r=setTimeout(function(){var n=e._addPeer(t.addr);n&&(n.retries=t.retries+1)},n);r.unref&&r.unref()}})}}},i.prototype._validAddr=function(e){var t;try{t=u(e)}catch(t){return!1}var n=t[0],r=t[1];return 0r&&("127.0.0.1"!==n||r!==this.client.torrentPort)}}).call(this,t("_process"),"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global)},{"../package.json":148,"./file":141,"./peer":142,"./rarity-map":143,"./server":158,_process:170,"addr-to-ip-port":31,bitfield:37,"chunk-store-stream/write":47,debug:27,events:162,fs:156,"fs-chunk-store":66,"immediate-chunk-store":57,inherits:58,multistream:73,net:158,os:158,"parse-torrent":77,path:168,pump:80,"random-iterate":81,"run-parallel":96,"run-parallel-limit":95,"simple-get":100,"simple-sha1":102,speedometer:104,"torrent-discovery":112,"torrent-piece":113,uniq:116,ut_metadata:118,ut_pex:158,xtend:131,"xtend/mutable":132}],145:[function(e,t,n){arguments[4][128][0].apply(n,arguments)},{"../package.json":148,bitfield:37,"bittorrent-protocol":38,debug:27,dup:128,inherits:58,"safe-buffer":98,"simple-get":100,"simple-sha1":102}],146:[function(e,t){t.exports=function(e,t,r){function o(e){var t=new XMLHttpRequest;t.timeout=1e3,t.open("head",e.uri),t.onload=function(){d++,200<=this.status&&300>this.status&&(a.push(e),p=t.getResponseHeader("content-length")),s()},t.ontimeout=function(){d++,n(e.uri+" timeout"),s()},t.onerror=function(){d++,s()},t.send()}function s(){if(d==r.end-r.start){a.sort(function(e,t){return t.capacity-e.capacity});for(var e=0;ee.length&&(r.end=e.length):r={start:0,end:e.length};for(var c=r.start;c=e.queue.length}).sort(function(e,t){return e.queue.length-t.queue.length});r.sort(function(e,t){return t.meanSpeed-e.meanSpeed});var o=n.concat(r);return o.length>t.windowLength&&(o=o.filter(function(e){return 0!==e.type})),o},WebRTCFirst:function(e,t){var n=e.filter(function(e){return!1===e.downloading}).sort(function(e,t){return t.type-e.type}),r=e.filter(function(e){return!0===e.downloading&&1>=e.queue.length}).sort(function(e,t){return e.queue.length-t.queue.length}),o=n.concat(r);return o.length>t.windowLength&&(o=o.filter(function(e){return 0!==e.type})),o},CloudFirst:function(e,t){var n=e.filter(function(e){return!1===e.downloading}).sort(function(e,t){return e.type-t.type}),r=e.filter(function(e){return!0===e.downloading&&1>=e.queue.length}).sort(function(e,t){return e.queue.length-t.queue.length}),o=n.concat(r);return o.length>t.windowLength&&(o=o.filter(function(e){return 0!==e.type})),o}}},{debug:27}],148:[function(e,t){t.exports={name:"webtorrent",description:"Streaming torrent client",version:"0.98.19",author:{name:"WebTorrent, LLC",email:"feross@webtorrent.io",url:"https://webtorrent.io"},browser:{"./lib/server.js":!1,"./lib/tcp-pool.js":!1,"bittorrent-dht/client":!1,"fs-chunk-store":"memory-chunk-store","load-ip-set":!1,net:!1,os:!1,ut_pex:!1},browserify:{transform:["package-json-versionify"]},bugs:{url:"https://github.com/webtorrent/webtorrent/issues"},dependencies:{"addr-to-ip-port":"^1.4.2",bitfield:"^1.1.2","bittorrent-dht":"^7.2.2","bittorrent-protocol":"^2.1.5","chunk-store-stream":"^2.0.2","create-torrent":"^3.24.5",debug:"^2.2.0","end-of-stream":"^1.1.0","fs-chunk-store":"^1.6.2","immediate-chunk-store":"^1.0.8",inherits:"^2.0.1","load-ip-set":"^1.2.7","memory-chunk-store":"^1.2.0",mime:"^1.3.4",multistream:"^2.0.5","package-json-versionify":"^1.0.2","parse-torrent":"^5.8.0",pump:"^1.0.1","random-iterate":"^1.0.1",randombytes:"^2.0.3","range-parser":"^1.2.0","readable-stream":"^2.1.4","render-media":"^2.8.0","run-parallel":"^1.1.6","run-parallel-limit":"^1.0.3","safe-buffer":"^5.0.1","simple-concat":"^1.0.0","simple-get":"^2.2.1","simple-peer":"^8.0.0","simple-sha1":"^2.0.8",speedometer:"^1.0.0","stream-to-blob":"^1.0.0","stream-to-blob-url":"^2.1.0","stream-with-known-length-to-buffer":"^1.0.0","torrent-discovery":"^8.1.0","torrent-piece":"^1.1.0",uniq:"^1.0.1","unordered-array-remove":"^1.0.2",ut_metadata:"^3.0.8",ut_pex:"^1.1.1",xtend:"^4.0.1","zero-fill":"^2.2.3"},devDependencies:{babili:"^0.1.4","bittorrent-tracker":"^9.0.0",brfs:"^1.4.3",browserify:"^14.0.0","cross-spawn":"^5.0.1","electron-prebuilt":"^0.37.8",finalhandler:"^1.0.0","network-address":"^1.1.0","run-series":"^1.1.4","serve-static":"^1.11.1",standard:"*",tape:"^4.6.0","webtorrent-fixtures":"^1.5.0",zuul:"^3.10.1"},engines:{node:">=4"},homepage:"https://webtorrent.io",keywords:["bittorrent","bittorrent client","download","mad science","p2p","peer-to-peer","peers","streaming","swarm","torrent","web torrent","webrtc","webrtc data","webtorrent"],license:"MIT",main:"index.js",repository:{type:"git",url:"git://github.com/webtorrent/webtorrent.git"},scripts:{build:"browserify -s WebTorrent -e ./ | babili > webtorrent.min.js","build-debug":"browserify -s WebTorrent -e ./ > webtorrent.debug.js",size:"npm run build && cat webtorrent.min.js | gzip | wc -c",test:"standard && node ./bin/test.js","test-browser":"zuul -- test/*.js test/browser/*.js","test-browser-headless":"zuul --electron -- test/*.js test/browser/*.js","test-browser-local":"zuul --local -- test/*.js test/browser/*.js","test-node":"tape test/*.js test/node/*.js","update-authors":"./bin/update-authors.sh"}}},{}],149:[function(e,t){(function(n,r){function o(e){function t(){s.destroyed||(s.ready=!0,s.emit("ready"))}var s=this;return s instanceof o?void(u.call(s),!e&&(e={}),s.peerId="string"==typeof e.peerId?e.peerId:d.isBuffer(e.peerId)?e.peerId.toString("hex"):d.from(I+w(9).toString("base64")).toString("hex"),s.peerIdBuffer=d.from(s.peerId,"hex"),s.nodeId="string"==typeof e.nodeId?e.nodeId:d.isBuffer(e.nodeId)?e.nodeId.toString("hex"):w(20).toString("hex"),s.nodeIdBuffer=d.from(s.nodeId,"hex"),s._debugId=s.peerId.toString("hex").substring(0,7),s.destroyed=!1,s.listening=!1,s.torrentPort=e.torrentPort||0,s.dhtPort=e.dhtPort||0,s.tracker=e.tracker===void 0?{}:e.tracker,s.torrents=[],s.maxConns=+e.maxConns||55,s._debug("new webtorrent (peerId %s, nodeId %s, port %s)",s.peerId,s.nodeId,s.torrentPort),s.tracker&&("object"!=typeof s.tracker&&(s.tracker={}),e.rtcConfig&&(console.warn("WebTorrent: opts.rtcConfig is deprecated. Use opts.tracker.rtcConfig instead"),s.tracker.rtcConfig=e.rtcConfig),e.wrtc&&(console.warn("WebTorrent: opts.wrtc is deprecated. Use opts.tracker.wrtc instead"),s.tracker.wrtc=e.wrtc),r.WRTC&&!s.tracker.wrtc&&(s.tracker.wrtc=r.WRTC)),"function"==typeof v?s._tcpPool=new v(s):n.nextTick(function(){s._onListening()}),s._downloadSpeed=k(),s._uploadSpeed=k(),!1!==e.dht&&"function"==typeof l?(s.dht=new l(f({nodeId:s.nodeId},e.dht)),s.dht.once("error",function(e){s._destroy(e)}),s.dht.once("listening",function(){var e=s.dht.address();e&&(s.dhtPort=e.port)}),s.dht.setMaxListeners(0),s.dht.listen(s.dhtPort)):s.dht=!1,s.enableWebSeeds=!1!==e.webSeeds,"function"==typeof m&&null!=e.blocklist?m(e.blocklist,{headers:{"user-agent":"WebTorrent/"+S+" (https://webtorrent.io)"}},function(e,n){return e?s.error("Failed to load blocklist: "+e.message):void(s.blocked=n,t())}):n.nextTick(t)):new o(e)}function s(e){return"object"==typeof e&&null!=e&&"function"==typeof e.pipe}function i(e){return"undefined"!=typeof FileList&&e instanceof FileList}t.exports=o;var d=e("safe-buffer").Buffer,a=e("simple-concat"),p=e("create-torrent"),c=e("debug")("webtorrent"),l=e("bittorrent-dht/client"),u=e("events").EventEmitter,f=e("xtend"),h=e("inherits"),m=e("load-ip-set"),g=e("run-parallel"),_=e("parse-torrent"),y=e("path"),b=e("simple-peer"),w=e("randombytes"),k=e("speedometer"),x=e("zero-fill"),v=e("./lib/tcp-pool"),C=e("./lib/torrent"),S=e("./package.json").version,E=S.match(/([0-9]+)/g).slice(0,2).map(function(e){return x(2,e)}).join(""),I="-WW"+E+"-";h(o,u),o.WEBRTC_SUPPORT=b.WEBRTC_SUPPORT,Object.defineProperty(o.prototype,"downloadSpeed",{get:function(){return this._downloadSpeed()}}),Object.defineProperty(o.prototype,"uploadSpeed",{get:function(){return this._uploadSpeed()}}),Object.defineProperty(o.prototype,"progress",{get:function(){var e=this.torrents.filter(function(e){return 1!==e.progress}),t=e.reduce(function(e,t){return e+t.downloaded},0),n=e.reduce(function(e,t){return e+(t.length||0)},0)||1;return t/n}}),Object.defineProperty(o.prototype,"ratio",{get:function(){var e=this.torrents.reduce(function(e,t){return e+t.uploaded},0),t=this.torrents.reduce(function(e,t){return e+t.received},0)||1;return e/t}}),o.prototype.get=function(e){var t=this,n=t.torrents.length,r,o;if(e instanceof C){for(r=0;r=o+10485760){r({method:"post",url:"/traffic",data:{uuid:e,size:+t,traffic:n}}).then(function(e){200==e.status&&(o=s)})}},finalyReportTraffic:function(e,t,o){r({method:"post",url:"/traffic",data:{uuid:e,size:+t,traffic:o}}).then(function(e){200==e.status&&n("finalyReportTraffic")})},reportAbilities:function(e){var t=0;for(var o in e)t+=e[o];t/=Object.getOwnPropertyNames(e).length;var s={};for(var o in e)s[o]=5*(e[o]/t),console.log("reportAbilities mac:"+o+" ability:"+s[o]);r({method:"post",url:"https://api.webrtc.win/v2/customer/stats/nodes/capacity",data:s}).then(function(e){n("reportAbilities response:"+JSON.stringify(e))})}}},{axios:2,debug:27}],152:[function(e,t){function n(){this.items={}}t.exports=n,n.prototype={constructer:n,has:function(e){return e in this.items},add:function(e){return!this.has(e)&&(this.items[e]=e,!0)},remove:function(e){return!!this.has(e)&&(delete this.items[e],!0)},clear:function(){this.items={}},size:function(){return Object.keys(this.items).length},values:function(){return Object.keys(this.items)},union:function(e){for(var t=new n,r=this.values(),o=0;oe.size())return!1;for(var t=this.values(),n=0;nthis.status||304==this.status){s(this.response);var e=JSON.parse(this.response);if(!e.size)t(null);else if(n.fileLength=e.size,!e.nodes)n._fallBackToWRTC();else{for(var r=e.nodes,o=[],d="http:"===location.protocol,a=0,p=0,c=0,i;c=o?(e.push({uri:n.src,type:"server"}),t(e)):20<=e.length?(e=e.slice(0,20),t(e)):t(e)):t([{uri:n.src,type:"server"}])},{start:0,end:30})}}else t(null)},o.send()},n.prototype._pearSignalHandshake=function(){var e=this,t=0;s("_pearSignalHandshake");var n=new WebSocket("wss://signal.webrtc.win:7601/wss");e.websocket=n,n.onopen=function(){e._debugInfo.signalServerConnected=!0;var t=i(e.urlObj.host+e.urlObj.path);n.push(JSON.stringify({action:"get",peer_id:e.peerId,host:e.urlObj.host,uri:e.urlObj.path,md5:t}))},n.push=n.send,n.send=function(e){return 1==n.readyState?void n.push(e):(console.warn("websocket connection is not opened yet."),setTimeout(function(){n.send(e)},1e3))},n.onmessage=function(n){var r=JSON.parse(n.data);if("candidate"===r.action&&"end"===r.type)for(var d in e.candidateMap)r.peer_id===d&&e.JDMap[d].candidatesFromWS(e.candidateMap[d]);else if(r.nodes){var a=r.nodes;e._debugInfo.totalDCs=a.length;for(var p=0,i;pe||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,n,o,a,p,i;if(this._events||(this._events={}),"error"===e&&(!this._events.error||s(this._events.error)&&!this._events.error.length))if(t=arguments[1],t instanceof Error)throw t;else{var c=new Error("Uncaught, unspecified \"error\" event. ("+t+")");throw c.context=t,c}if(n=this._events[e],d(n))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:a=Array.prototype.slice.call(arguments,1),n.apply(this,a);}else if(s(n))for(a=Array.prototype.slice.call(arguments,1),i=n.slice(),o=i.length,p=0;po&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace())),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){function n(){this.removeListener(e,n),o||(o=!0,t.apply(this,arguments))}if(!r(t))throw TypeError("listener must be a function");var o=!1;return n.listener=t,this.on(e,n),this},n.prototype.removeListener=function(e,t){var n,o,d,a;if(!r(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(n=this._events[e],d=n.length,o=-1,n===t||r(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(s(n)){for(a=d;0o)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(o,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[e],r(n))this.removeListener(e,n);else if(n)for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?r(this._events[e])?[this._events[e]]:this._events[e].slice():[],t},n.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(r(t))return 1;if(t)return t.length}return 0},n.listenerCount=function(e,t){return e.listenerCount(t)}},{}],163:[function(e,t){function n(e){if("string"==typeof e&&(e=o.parse(e)),e.protocol||(e.protocol="https:"),"https:"!==e.protocol)throw new Error("Protocol \""+e.protocol+"\" not supported. Expected \"https:\"");return e}var r=e("http"),o=e("url"),s=t.exports;for(var i in r)r.hasOwnProperty(i)&&(s[i]=r[i]);s.request=function(e,t){return e=n(e),r.request.call(this,e,t)},s.get=function(e,t){return e=n(e),r.get.call(this,e,t)}},{http:185,url:191}],164:[function(e,t,n){arguments[4][56][0].apply(n,arguments)},{dup:56}],165:[function(e,t,n){arguments[4][58][0].apply(n,arguments)},{dup:58}],166:[function(e,t){function n(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function r(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&n(e.slice(0,0))}t.exports=function(e){return null!=e&&(n(e)||r(e)||!!e._isBuffer)}},{}],167:[function(e,t,n){arguments[4][62][0].apply(n,arguments)},{dup:62}],168:[function(e,t,r){(function(e){function t(e,t){for(var n=0,r=e.length-1,o;0<=r;r--)o=e[r],"."===o?e.splice(r,1):".."===o?(e.splice(r,1),n++):n&&(e.splice(r,1),n--);if(t)for(;n--;n)e.unshift("..");return e}function o(e,t){if(e.filter)return e.filter(t);for(var n=[],r=0;rn?[]:e.slice(t,n-t+1)}e=r.resolve(e).substr(1),t=r.resolve(t).substr(1);for(var s=o(e.split("/")),d=o(t.split("/")),a=n(s.length,d.length),p=a,c=0;c=o&&n>>10),e=56320|1023&e),t+=O(e),t}).join("")}function u(e){return 10>e-48?e-22:26>e-65?e-65:26>e-97?e-97:v}function f(e,t){return e+22+75*(26>e)-((0!=t)<<5)}function h(e,t,n){var r=0;for(e=n?U(e/E):e>>1,e+=U(e/t);e>A*S>>1;r+=v)e=U(e/A);return U(r+(A+1)*e/(e+w))}function m(e){var r=[],o=e.length,s=0,i=T,n=I,a,p,c,f,m,g,_,y,b,t;for(p=e.lastIndexOf(L),0>p&&(p=0),c=0;c=o&&d("invalid-input"),y=u(e.charCodeAt(f++)),(y>=v||y>U((x-s)/g))&&d("overflow"),s+=y*g,b=_<=n?C:_>=n+S?S:_-n,yU(x/t)&&d("overflow"),g*=t}a=r.length+1,n=h(s-m,a,0==m),U(s/a)>x-i&&d("overflow"),i+=U(s/a),s%=a,r.splice(s++,0,i)}return l(r)}function g(e){var r=[],o,n,s,i,a,p,l,u,m,g,t,_,y,b,w;for(e=c(e),_=e.length,o=T,n=0,a=I,p=0;p<_;++p)t=e[p],128>t&&r.push(O(t));for(s=i=r.length,i&&r.push(L);s<_;){for(l=x,p=0;p<_;++p)t=e[p],t>=o&&tU((x-n)/y)&&d("overflow"),n+=(l-o)*y,o=l,p=0;p<_;++p)if(t=e[p],tx&&d("overflow"),t==o){for(u=n,m=v;;m+=v){if(g=m<=a?C:m>=a+S?S:m-a,u= 0x80 (not a basic code point)","invalid-input":"Invalid input"},A=v-C,U=o,O=r,H,q;if(H={version:"1.4.1",ucs2:{decode:c,encode:l},decode:m,encode:g,toASCII:function(e){return p(e,function(e){return B.test(e)?"xn--"+g(e):e})},toUnicode:function(e){return p(e,function(e){return k.test(e)?m(e.slice(4).toLowerCase()):e})}},"function"==typeof i&&"object"==typeof i.amd&&i.amd)i("punycode",function(){return H});else if(!(_&&y))s.punycode=H;else if(t.exports==_)y.exports=H;else for(q in H)H.hasOwnProperty(q)&&(_[q]=H[q])})(this)}).call(this,"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global)},{}],172:[function(e,t){"use strict";function n(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.exports=function(e,t,o,s){t=t||"&",o=o||"=";var d={};if("string"!=typeof e||0===e.length)return d;var a=/\+/g;e=e.split(t);var p=1e3;s&&"number"==typeof s.maxKeys&&(p=s.maxKeys);var c=e.length;0p&&(c=p);for(var l=0;le._pos){var s=n.substr(e._pos);if("x-user-defined"===e._charset){for(var d=new o(s.length),p=0;pe._pos&&(e.push(new o(new Uint8Array(i.result.slice(e._pos)))),e._pos=i.result.byteLength)},i.onload=function(){e.push(null)},i.readAsArrayBuffer(n);}e._xhr.readyState===a.DONE&&"ms-stream"!==e._mode&&e.push(null)}}).call(this,e("_process"),"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global,e("buffer").Buffer)},{"./capability":186,_process:170,buffer:159,inherits:165,"readable-stream":183}],189:[function(e,t,n){arguments[4][108][0].apply(n,arguments)},{dup:108,"safe-buffer":184}],190:[function(e,t,n){arguments[4][111][0].apply(n,arguments)},{buffer:159,dup:111}],191:[function(e,t,n){"use strict";function r(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}function o(e,t,n){if(e&&d.isObject(e)&&e instanceof r)return e;var o=new r;return o.parse(e,t,n),o}var s=e("punycode"),d=e("./util");n.parse=o,n.resolve=function(e,t){return o(e,!1,!0).resolve(t)},n.resolveObject=function(e,t){return e?o(e,!1,!0).resolveObject(t):t},n.format=function(e){return d.isString(e)&&(e=o(e)),e instanceof r?e.format():r.prototype.format.call(e)},n.Url=r;var a=/^([a-z0-9.+-]+:)/i,i=/:[0-9]*$/,p=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,c=["{","}","|","\\","^","`"].concat(["<",">","\"","`"," ","\r","\n","\t"]),u=["'"].concat(c),l=["%","/","?",";","#"].concat(u),f=["/","?","#"],h=/^[+a-z0-9A-Z_-]{0,63}$/,m=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,g={javascript:!0,"javascript:":!0},_={javascript:!0,"javascript:":!0},y={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},b=e("querystring");r.prototype.parse=function(e,t,n){if(!d.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var r=e.indexOf("?"),o=-1!==r&&r255?"":this.hostname.toLowerCase(),R||(this.hostname=s.toASCII(this.hostname));var M=this.port?":"+this.port:"",N=this.hostname||"";this.host=N+M,this.href+=this.host,R&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==x[0]&&(x="/"+x))}if(!g[S])for(var T=0,A=u.length,F;Tb.length&&b.unshift(""),n.pathname=b.join("/")}else n.pathname=e.pathname;if(n.search=e.search,n.query=e.query,n.host=e.host||"",n.auth=e.auth,n.hostname=e.hostname||e.host,n.port=e.port,n.pathname||n.search){var w=n.pathname||"",p=n.search||"";n.path=w+p}return n.slashes=n.slashes||e.slashes,n.href=n.format(),n}var s=n.pathname&&"/"===n.pathname.charAt(0),k=e.host||e.pathname&&"/"===e.pathname.charAt(0),x=k||s||n.host&&e.pathname,v=x,C=n.pathname&&n.pathname.split("/")||[],b=e.pathname&&e.pathname.split("/")||[],S=n.protocol&&!y[n.protocol];if(S&&(n.hostname="",n.port=null,n.host&&(""===C[0]?C[0]=n.host:C.unshift(n.host)),n.host="",e.protocol&&(e.hostname=null,e.port=null,e.host&&(""===b[0]?b[0]=e.host:b.unshift(e.host)),e.host=null),x=x&&(""===b[0]||""===C[0])),k)n.host=e.host||""===e.host?e.host:n.host,n.hostname=e.hostname||""===e.hostname?e.hostname:n.hostname,n.search=e.search,n.query=e.query,C=b;else if(b.length)C||(C=[]),C.pop(),C=C.concat(b),n.search=e.search,n.query=e.query;else if(!d.isNullOrUndefined(e.search)){if(S){n.hostname=n.host=C.shift();var E=n.host&&0>18]+a[63&e>>12]+a[63&e>>6]+a[63&e]}function d(e,t,n){for(var r=[],o=t,i;o>16,d[a++]=255&i>>8,d[a++]=255&i;return 2===s&&(i=l[e.charCodeAt(u)]<<2|l[e.charCodeAt(u+1)]>>4,d[a++]=255&i),1===s&&(i=l[e.charCodeAt(u)]<<10|l[e.charCodeAt(u+1)]<<4|l[e.charCodeAt(u+2)]>>2,d[a++]=255&i>>8,d[a++]=255&i),d},n.fromByteArray=function(e){for(var t=e.length,n=t%3,r=[],o=16383,s=0,i=t-n,l;si?i:s+o));return 1==n?(l=e[t-1],r.push(a[l>>2]+a[63&l<<4]+"==")):2==n&&(l=(e[t-2]<<8)+e[t-1],r.push(a[l>>10]+a[63&l>>4]+a[63&l<<2]+"=")),r.join("")};for(var a=[],l=[],p="undefined"==typeof Uint8Array?Array:Uint8Array,c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",u=0,i=c.length;uJ)throw new RangeError("Invalid typed array length");var t=new Uint8Array(e);return t.__proto__=o.prototype,t}function o(e,t,n){if("number"==typeof e){if("string"==typeof t)throw new Error("If encoding is specified then the first argument must be a string");return c(e)}return i(e,t,n)}function i(e,t,n){if("number"==typeof e)throw new TypeError("\"value\" argument must not be a number");return K(e)||e&&K(e.buffer)?h(e,t,n):"string"==typeof e?u(e,t):g(e)}function s(e){if("number"!=typeof e)throw new TypeError("\"size\" argument must be of type number");else if(0>e)throw new RangeError("\"size\" argument must not be negative")}function p(e,t,n){return s(e),0>=e?r(e):void 0===t?r(e):"string"==typeof n?r(e).fill(t,n):r(e).fill(t)}function c(e){return s(e),r(0>e?0:0|m(e))}function u(e,t){if(("string"!=typeof t||""===t)&&(t="utf8"),!o.isEncoding(t))throw new TypeError("Unknown encoding: "+t);var n=0|_(e,t),i=r(n),s=i.write(e,t);return s!==n&&(i=i.slice(0,s)),i}function f(e){for(var t=0>e.length?0:0|m(e.length),n=r(t),o=0;ot||e.byteLength=J)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+J.toString(16)+" bytes");return 0|e}function _(e,t){if(o.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||K(e))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return W(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return V(e).length;default:if(r)return W(e).length;t=(""+t).toLowerCase(),r=!0;}}function y(e,t,n){var r=!1;if((void 0===t||0>t)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),0>=n)return"";if(n>>>=0,t>>>=0,n<=t)return"";for(e||(e="utf8");;)switch(e){case"hex":return A(this,t,n);case"utf8":case"utf-8":return L(this,t,n);case"ascii":return R(this,t,n);case"latin1":case"binary":return P(this,t,n);case"base64":return I(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return U(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0;}}function b(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function w(e,t,n,r,i){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):2147483647n&&(n=-2147483648),n=+n,X(n)&&(n=i?0:e.length-1),0>n&&(n=e.length+n),n>=e.length){if(i)return-1;n=e.length-1}else if(0>n)if(i)n=0;else return-1;if("string"==typeof t&&(t=o.from(t,r)),o.isBuffer(t))return 0===t.length?-1:k(e,t,n,r,i);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):k(e,[t],n,r,i);throw new TypeError("val must be string, number or Buffer")}function k(e,t,n,r,o){function s(e,t){return 1==d?e[t]:e.readUInt16BE(t*d)}var d=1,a=e.length,l=t.length;if(void 0!==r&&(r=(r+"").toLowerCase(),"ucs2"===r||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(2>e.length||2>t.length)return-1;d=2,a/=2,l/=2,n/=2}var p;if(o){var i=-1;for(p=n;pa&&(n=a-l),p=n;0<=p;p--){for(var c=!0,u=0;uo&&(r=o)):r=o;var s=t.length;r>s/2&&(r=s/2);for(var d=0,i;di&&(s=i):2==a?(l=e[o+1],128==(192&l)&&(u=(31&i)<<6|63&l,127u||57343u&&(s=u))):void 0}null===s?(s=65533,a=1):65535>>10),s=56320|1023&s),r.push(s),o+=a}return B(r)}function B(e){var t=e.length;if(t<=$)return l.apply(String,e);for(var n="",r=0;rt)&&(t=0),(!n||0>n||n>r)&&(n=r);for(var o="",s=t;se)throw new RangeError("offset is not uint");if(e+t>n)throw new RangeError("Trying to access beyond buffer length")}function M(e,t,n,r,i,s){if(!o.isBuffer(e))throw new TypeError("\"buffer\" argument must be a Buffer instance");if(t>i||te.length)throw new RangeError("Index out of range")}function q(e,t,n,r){if(n+r>e.length)throw new RangeError("Index out of range");if(0>n)throw new RangeError("Index out of range")}function j(e,t,n,r,o){return t=+t,n>>>=0,o||q(e,t,n,4,3.4028234663852886e38,-3.4028234663852886e38),Y.write(e,t,n,r,23,4),n+4}function H(e,t,n,r,o){return t=+t,n>>>=0,o||q(e,t,n,8,1.7976931348623157e308,-1.7976931348623157e308),Y.write(e,t,n,r,52,8),n+8}function D(e){if(e=e.split("=")[0],e=e.trim().replace(Z,""),2>e.length)return"";for(;0!=e.length%4;)e+="=";return e}function N(e){return 16>e?"0"+e.toString(16):e.toString(16)}function W(e,t){t=t||Infinity;for(var n=e.length,r=null,o=[],s=0,i;si){if(!r){if(56319i){-1<(t-=3)&&o.push(239,191,189),r=i;continue}i=(r-55296<<10|i-56320)+65536}else r&&-1<(t-=3)&&o.push(239,191,189);if(r=null,128>i){if(0>(t-=1))break;o.push(i)}else if(2048>i){if(0>(t-=2))break;o.push(192|i>>6,128|63&i)}else if(65536>i){if(0>(t-=3))break;o.push(224|i>>12,128|63&i>>6,128|63&i)}else if(1114112>i){if(0>(t-=4))break;o.push(240|i>>18,128|63&i>>12,128|63&i>>6,128|63&i)}else throw new Error("Invalid code point")}return o}function z(e){for(var t=[],n=0;n(t-=2));++r)o=e.charCodeAt(r),i=o>>8,s=o%256,n.push(s),n.push(i);return n}function V(e){return Q.toByteArray(D(e))}function G(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function K(e){return e instanceof ArrayBuffer||null!=e&&null!=e.constructor&&"ArrayBuffer"===e.constructor.name&&"number"==typeof e.byteLength}function X(e){return e!==e}var Q=e("base64-js"),Y=e("ieee754");n.Buffer=o,n.SlowBuffer=function(e){return+e!=e&&(e=0),o.alloc(+e)},n.INSPECT_MAX_BYTES=50;var J=2147483647;n.kMaxLength=J,o.TYPED_ARRAY_SUPPORT=function(){try{var e=new Uint8Array(1);return e.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}},42===e.foo()}catch(t){return!1}}(),o.TYPED_ARRAY_SUPPORT||"undefined"==typeof console||"function"!=typeof console.error||console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support."),Object.defineProperty(o.prototype,"parent",{get:function(){return this instanceof o?this.buffer:void 0}}),Object.defineProperty(o.prototype,"offset",{get:function(){return this instanceof o?this.byteOffset:void 0}}),"undefined"!=typeof Symbol&&Symbol.species&&o[Symbol.species]===o&&Object.defineProperty(o,Symbol.species,{value:null,configurable:!0,enumerable:!1,writable:!1}),o.poolSize=8192,o.from=function(e,t,n){return i(e,t,n)},o.prototype.__proto__=Uint8Array.prototype,o.__proto__=Uint8Array,o.alloc=function(e,t,n){return p(e,t,n)},o.allocUnsafe=function(e){return c(e)},o.allocUnsafeSlow=function(e){return c(e)},o.isBuffer=function(e){return null!=e&&!0===e._isBuffer},o.compare=function(e,t){if(!o.isBuffer(e)||!o.isBuffer(t))throw new TypeError("Arguments must be Buffers");if(e===t)return 0;for(var n=e.length,r=t.length,s=0,i=d(n,r);st&&(e+=" ... ")),""},o.prototype.compare=function(e,t,n,r,s){if(!o.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===s&&(s=this.length),0>t||n>e.length||0>r||s>this.length)throw new RangeError("out of range index");if(r>=s&&t>=n)return 0;if(r>=s)return-1;if(t>=n)return 1;if(t>>>=0,n>>>=0,r>>>=0,s>>>=0,this===e)return 0;for(var a=s-r,l=n-t,p=d(a,l),c=this.slice(r,s),u=e.slice(t,n),f=0;f>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0);else throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");var o=this.length-t;if((void 0===n||n>o)&&(n=o),0n||0>t)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return x(this,e,t,n);case"utf8":case"utf-8":return S(this,e,t,n);case"ascii":return v(this,e,t,n);case"latin1":case"binary":return C(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return T(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0;}},o.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var $=4096;o.prototype.slice=function(e,t){var n=this.length;e=~~e,t=void 0===t?n:~~t,0>e?(e+=n,0>e&&(e=0)):e>n&&(e=n),0>t?(t+=n,0>t&&(t=0)):t>n&&(t=n),t>>=0,t>>>=0,n||O(e,t,this.length);for(var r=this[e],o=1,s=0;++s>>=0,t>>>=0,n||O(e,t,this.length);for(var r=this[e+--t],o=1;0>>=0,t||O(e,1,this.length),this[e]},o.prototype.readUInt16LE=function(e,t){return e>>>=0,t||O(e,2,this.length),this[e]|this[e+1]<<8},o.prototype.readUInt16BE=function(e,t){return e>>>=0,t||O(e,2,this.length),this[e]<<8|this[e+1]},o.prototype.readUInt32LE=function(e,t){return e>>>=0,t||O(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},o.prototype.readUInt32BE=function(e,t){return e>>>=0,t||O(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},o.prototype.readIntLE=function(e,t,n){e>>>=0,t>>>=0,n||O(e,t,this.length);for(var r=this[e],o=1,s=0;++s=o&&(r-=a(2,8*t)),r},o.prototype.readIntBE=function(e,t,n){e>>>=0,t>>>=0,n||O(e,t,this.length);for(var r=t,o=1,i=this[e+--r];0=o&&(i-=a(2,8*t)),i},o.prototype.readInt8=function(e,t){return e>>>=0,t||O(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},o.prototype.readInt16LE=function(e,t){e>>>=0,t||O(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},o.prototype.readInt16BE=function(e,t){e>>>=0,t||O(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},o.prototype.readInt32LE=function(e,t){return e>>>=0,t||O(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},o.prototype.readInt32BE=function(e,t){return e>>>=0,t||O(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},o.prototype.readFloatLE=function(e,t){return e>>>=0,t||O(e,4,this.length),Y.read(this,e,!0,23,4)},o.prototype.readFloatBE=function(e,t){return e>>>=0,t||O(e,4,this.length),Y.read(this,e,!1,23,4)},o.prototype.readDoubleLE=function(e,t){return e>>>=0,t||O(e,8,this.length),Y.read(this,e,!0,52,8)},o.prototype.readDoubleBE=function(e,t){return e>>>=0,t||O(e,8,this.length),Y.read(this,e,!1,52,8)},o.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t>>>=0,n>>>=0,!r){var o=a(2,8*n)-1;M(this,e,t,n,o,0)}var s=1,d=0;for(this[t]=255&e;++d>>=0,n>>>=0,!r){var o=a(2,8*n)-1;M(this,e,t,n,o,0)}var s=n-1,i=1;for(this[t+s]=255&e;0<=--s&&(i*=256);)this[t+s]=255&e/i;return t+n},o.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,1,255,0),this[t]=255&e,t+1},o.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},o.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},o.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},o.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},o.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t>>>=0,!r){var o=a(2,8*n-1);M(this,e,t,n,o-1,-o)}var s=0,i=1,d=0;for(this[t]=255&e;++se&&0==d&&0!==this[t+s-1]&&(d=1),this[t+s]=255&(e/i>>0)-d;return t+n},o.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t>>>=0,!r){var o=a(2,8*n-1);M(this,e,t,n,o-1,-o)}var s=n-1,i=1,d=0;for(this[t+s]=255&e;0<=--s&&(i*=256);)0>e&&0==d&&0!==this[t+s+1]&&(d=1),this[t+s]=255&(e/i>>0)-d;return t+n},o.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,1,127,-128),0>e&&(e=255+e+1),this[t]=255&e,t+1},o.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},o.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},o.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},o.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,4,2147483647,-2147483648),0>e&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},o.prototype.writeFloatLE=function(e,t,n){return j(this,e,t,!0,n)},o.prototype.writeFloatBE=function(e,t,n){return j(this,e,t,!1,n)},o.prototype.writeDoubleLE=function(e,t,n){return H(this,e,t,!0,n)},o.prototype.writeDoubleBE=function(e,t,n){return H(this,e,t,!1,n)},o.prototype.copy=function(e,t,n,r){if(!o.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),0t)throw new RangeError("targetStart out of bounds");if(0>n||n>=this.length)throw new RangeError("Index out of range");if(0>r)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-ts||"latin1"===r)&&(e=s)}}else"number"==typeof e&&(e&=255);if(0>t||this.length>>=0,n=n===void 0?this.length:n>>>0,e||(e=0);var d;if("number"==typeof e)for(d=t;di)){d.warned=!0;var a=new Error("Possible EventEmitter memory leak detected. "+d.length+" \""+(t+"\" listeners added. Use emitter.setMaxListeners() to increase limit."));a.name="MaxListenersExceededWarning",a.emitter=e,a.type=t,a.count=d.length,"object"==typeof console&&console.warn&&console.warn("%s: %s",a.name,a.message)}return e}function c(){if(!this.fired)switch(this.target.removeListener(this.type,this.wrapFn),this.fired=!0,arguments.length){case 0:return this.listener.call(this.target);case 1:return this.listener.call(this.target,arguments[0]);case 2:return this.listener.call(this.target,arguments[0],arguments[1]);case 3:return this.listener.call(this.target,arguments[0],arguments[1],arguments[2]);default:for(var e=Array(arguments.length),t=0;te||e!==e)throw new TypeError("\"defaultMaxListeners\" must be a positive number");k=e}}):n.defaultMaxListeners=k,n.prototype.setMaxListeners=function(e){if("number"!=typeof e||0>e||isNaN(e))throw new TypeError("\"n\" argument must be a positive number");return this._maxListeners=e,this},n.prototype.getMaxListeners=function(){return r(this)},n.prototype.emit=function(e){var t="error"===e,n,r,o,c,u,i;if(i=this._events,i)t=t&&null==i.error;else if(!t)return!1;if(t){if(1o)return this;0===o?n.shift():g(n,o),1===n.length&&(r[e]=n[0]),r.removeListener&&this.emit("removeListener",e,i||t)}return this},n.prototype.removeAllListeners=function(e){var t,n,r;if(n=this._events,!n)return this;if(!n.removeListener)return 0===arguments.length?(this._events=y(null),this._eventsCount=0):n[e]&&(0==--this._eventsCount?this._events=y(null):delete n[e]),this;if(0===arguments.length){var o=b(n),i;for(r=0;r>1,f=-7,h=r?l-1:0,i=r?-1:1,d=t[n+h],s,e;for(h+=i,s=d&(1<<-f)-1,d>>=-f,f+=p;0>=-f,f+=o;0>1,b=23===f?5.960464477539063e-8-6.617444900424222e-24:0,w=u?0:h-1,i=u?1:-1,d=0>l||0===l&&0>1/l?1:0,k,x,m;for(l=s(l),isNaN(l)||l===Infinity?(x=isNaN(l)?1:0,k=_):(k=r(o(l)/n),1>l*(m=a(2,-k))&&(k--,m*=2),l+=1<=k+y?b/m:b*a(2,1-y),2<=l*m&&(k++,m/=2),k+y>=_?(x=0,k=_):1<=k+y?(x=(l*m-1)*a(2,f),k+=y):(x=l*a(2,y-1)*a(2,f),k=0));8<=f;t[p+w]=255&x,w+=i,x/=256,f-=8);for(k=k<n?[]:e.slice(t,n-t+1)}e=n.resolve(e).substr(1),t=n.resolve(t).substr(1);for(var o=r(e.split("/")),s=r(t.split("/")),a=d(o.length,s.length),l=a,p=0;p=o&&n>>10),e=56320|1023&e),t+=O(e),t}).join("")}function u(e){return 10>e-48?e-22:26>e-65?e-65:26>e-97?e-97:S}function f(e,t){return e+22+75*(26>e)-((0!=t)<<5)}function h(e,t,n){var r=0;for(e=n?U(e/E):e>>1,e+=U(e/t);e>A*C>>1;r+=S)e=U(e/A);return U(r+(A+1)*e/(e+w))}function g(e){var r=[],o=e.length,d=0,i=I,n=T,a,l,p,f,g,m,_,y,b,t;for(l=e.lastIndexOf(L),0>l&&(l=0),p=0;p=o&&s("invalid-input"),y=u(e.charCodeAt(f++)),(y>=S||y>U((x-d)/m))&&s("overflow"),d+=y*m,b=_<=n?v:_>=n+C?C:_-n,yU(x/t)&&s("overflow"),m*=t}a=r.length+1,n=h(d-g,a,0==g),U(d/a)>x-i&&s("overflow"),i+=U(d/a),d%=a,r.splice(d++,0,i)}return c(r)}function m(e){var r=[],o,n,i,d,a,l,c,u,g,m,t,_,y,b,w;for(e=p(e),_=e.length,o=I,n=0,a=T,l=0;l<_;++l)t=e[l],128>t&&r.push(O(t));for(i=d=r.length,d&&r.push(L);i<_;){for(c=x,l=0;l<_;++l)t=e[l],t>=o&&tU((x-n)/y)&&s("overflow"),n+=(c-o)*y,o=c,l=0;l<_;++l)if(t=e[l],tx&&s("overflow"),t==o){for(u=n,g=S;;g+=S){if(m=g<=a?v:g>=a+C?C:g-a,u= 0x80 (not a basic code point)","invalid-input":"Invalid input"},A=S-v,U=r,O=l,M,q;if(M={version:"1.4.1",ucs2:{decode:p,encode:c},decode:g,encode:m,toASCII:function(e){return a(e,function(e){return B.test(e)?"xn--"+m(e):e})},toUnicode:function(e){return a(e,function(e){return k.test(e)?g(e.slice(4).toLowerCase()):e})}},"function"==typeof i&&"object"==typeof i.amd&&i.amd)i("punycode",function(){return M});else if(!(_&&y))o.punycode=M;else if(t.exports==_)y.exports=M;else for(q in M)M.hasOwnProperty(q)&&(_[q]=M[q])})(this)}).call(this,"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global)},{}],17:[function(e,t){"use strict";function n(e,t){return Object.prototype.hasOwnProperty.call(e,t)}t.exports=function(e,t,o,s){t=t||"&",o=o||"=";var d={};if("string"!=typeof e||0===e.length)return d;var a=/\+/g;e=e.split(t);var l=1e3;s&&"number"==typeof s.maxKeys&&(l=s.maxKeys);var p=e.length;0l&&(p=l);for(var c=0;c=X?e=X:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}function g(e,t){return 0>=e||0===t.length&&t.ended?0:t.objectMode?1:e===e?(e>t.highWaterMark&&(t.highWaterMark=h(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0)):t.flowing&&t.length?t.buffer.head.data.length:t.length}function m(e,t){if(!t.ended){if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,_(e)}}function _(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(z("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?A.nextTick(y,e):y(e))}function y(e){z("emit readable"),e.emit("readable"),C(e)}function b(e,t){t.readingMore||(t.readingMore=!0,A.nextTick(w,e,t))}function w(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(r=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):r=T(e,t.buffer,t.decoder),r}function T(e,t,n){var r;return ei.length?i.length:e;if(o+=s===i.length?i:i.slice(0,e),e-=s,0===e){s===i.length?(++r,t.head=n.next?n.next:t.tail=null):(t.head=n,n.data=i.slice(s));break}++r}return t.length-=r,o}function L(e,t){var n=H.allocUnsafe(e),r=t.head,o=1;for(r.data.copy(n),e-=r.data.length;r=r.next;){var i=r.data,s=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,s),e-=s,0===e){s===i.length?(++o,t.head=r.next?r.next:t.tail=null):(t.head=r,r.data=i.slice(s));break}++o}return t.length-=o,n}function B(e){var t=e._readableState;if(0=t.highWaterMark||t.ended))return z("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?B(this):_(this),null;if(e=g(e,t),0===e&&t.ended)return 0===t.length&&B(this),null;var r=t.needReadable;z("need readable",r),(0===t.length||t.length-e>>0),n=this.head,s=0;n;)r(n.data,t,s),s+=n.data.length,n=n.next;return t},e}(),i&&i.inspect&&i.inspect.custom&&(t.exports.prototype[i.inspect.custom]=function(){var e=i.inspect({length:this.length});return this.constructor.name+" "+e})},{"safe-buffer":29,util:3}],26:[function(e,t){"use strict";function n(e,t){e.emit("error",t)}var r=e("process-nextick-args");t.exports={destroy:function(e,t){var o=this,i=this._readableState&&this._readableState.destroyed,s=this._writableState&&this._writableState.destroyed;return i||s?(t?t(e):e&&(!this._writableState||!this._writableState.errorEmitted)&&r.nextTick(n,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?(r.nextTick(n,o,e),o._writableState&&(o._writableState.errorEmitted=!0)):t&&t(e)}),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},{"process-nextick-args":14}],27:[function(e,t){t.exports=e("events").EventEmitter},{events:7}],28:[function(e,t,n){n=t.exports=e("./lib/_stream_readable.js"),n.Stream=n,n.Readable=n,n.Writable=e("./lib/_stream_writable.js"),n.Duplex=e("./lib/_stream_duplex.js"),n.Transform=e("./lib/_stream_transform.js"),n.PassThrough=e("./lib/_stream_passthrough.js")},{"./lib/_stream_duplex.js":20,"./lib/_stream_passthrough.js":21,"./lib/_stream_readable.js":22,"./lib/_stream_transform.js":23,"./lib/_stream_writable.js":24}],29:[function(e,t,n){function r(e,t){for(var n in e)t[n]=e[n]}function o(e,t,n){return s(e,t,n)}var i=e("buffer"),s=i.Buffer;s.from&&s.alloc&&s.allocUnsafe&&s.allocUnsafeSlow?t.exports=i:(r(i,n),n.Buffer=o),r(s,o),o.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return s(e,t,n)},o.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=s(e);return void 0===t?r.fill(0):"string"==typeof n?r.fill(t,n):r.fill(t),r},o.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return s(e)},o.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return i.SlowBuffer(e)}},{buffer:4}],30:[function(e,t,n){(function(t){var r=e("./lib/request"),o=e("./lib/response"),i=e("xtend"),s=e("builtin-status-codes"),d=e("url"),a=n;a.request=function(e,n){e="string"==typeof e?d.parse(e):i(e);var o=-1===t.location.protocol.search(/^https?:$/)?"http:":"",s=e.protocol||o,a=e.hostname||e.host,l=e.port,p=e.path||"/";a&&-1!==a.indexOf(":")&&(a="["+a+"]"),e.url=(a?s+"//"+a:"")+(l?":"+l:"")+p,e.method=(e.method||"GET").toUpperCase(),e.headers=e.headers||{};var c=new r(e);return n&&c.on("response",n),c},a.get=function(e,t){var n=a.request(e,t);return n.end(),n},a.ClientRequest=r,a.IncomingMessage=o.IncomingMessage,a.Agent=function(){},a.Agent.defaultMaxSockets=4,a.globalAgent=new a.Agent,a.STATUS_CODES=s,a.METHODS=["CHECKOUT","CONNECT","COPY","DELETE","GET","HEAD","LOCK","M-SEARCH","MERGE","MKACTIVITY","MKCOL","MOVE","NOTIFY","OPTIONS","PATCH","POST","PROPFIND","PROPPATCH","PURGE","PUT","REPORT","SEARCH","SUBSCRIBE","TRACE","UNLOCK","UNSUBSCRIBE"]}).call(this,"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global)},{"./lib/request":32,"./lib/response":33,"builtin-status-codes":5,url:37,xtend:40}],31:[function(e,t,n){(function(e){function t(){if(d!=void 0)return d;if(e.XMLHttpRequest){d=new e.XMLHttpRequest;try{d.open("GET",e.XDomainRequest?"/":"https://example.com")}catch(t){d=null}}else d=null;return d}function r(e){var n=t();if(!n)return!1;try{return n.responseType=e,n.responseType===e}catch(t){}return!1}function o(e){return"function"==typeof e}n.fetch=o(e.fetch)&&o(e.ReadableStream),n.writableStream=o(e.WritableStream),n.abortController=o(e.AbortController),n.blobConstructor=!1;try{new Blob([new ArrayBuffer(1)]),n.blobConstructor=!0}catch(t){}var i="undefined"!=typeof e.ArrayBuffer,s=i&&o(e.ArrayBuffer.prototype.slice),d;n.arraybuffer=n.fetch||i&&r("arraybuffer"),n.msstream=!n.fetch&&s&&r("ms-stream"),n.mozchunkedarraybuffer=!n.fetch&&i&&r("moz-chunked-arraybuffer"),n.overrideMimeType=n.fetch||!!t()&&o(t().overrideMimeType),n.vbArray=o(e.VBArray),d=null}).call(this,"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global)},{}],32:[function(e,t){(function(n,r,o){function i(e,t){return d.fetch&&t?"fetch":d.mozchunkedarraybuffer?"moz-chunked-arraybuffer":d.msstream?"ms-stream":d.arraybuffer&&e?"arraybuffer":d.vbArray&&e?"text:vbarray":"text"}function s(e){try{var t=e.status;return null!==t&&0!==t}catch(t){return!1}}var d=e("./capability"),a=e("inherits"),l=e("./response"),p=e("readable-stream"),c=e("to-arraybuffer"),u=l.IncomingMessage,f=l.readyStates,h=t.exports=function(e){var t=this;p.Writable.call(t),t._opts=e,t._body=[],t._headers={},e.auth&&t.setHeader("Authorization","Basic "+new o(e.auth).toString("base64")),Object.keys(e.headers).forEach(function(n){t.setHeader(n,e.headers[n])});var n=!0,r;if("disable-fetch"===e.mode||"requestTimeout"in e&&!d.abortController)n=!1,r=!0;else if("prefer-streaming"===e.mode)r=!1;else if("allow-wrong-content-type"===e.mode)r=!d.overrideMimeType;else if(!e.mode||"default"===e.mode||"prefer-fast"===e.mode)r=!0;else throw new Error("Invalid value for opts.mode");t._mode=i(r,n),t._fetchTimer=null,t.on("finish",function(){t._onFinish()})};a(h,p.Writable),h.prototype.setHeader=function(e,t){var n=this,r=e.toLowerCase();-1!==g.indexOf(r)||(n._headers[r]={name:e,value:t})},h.prototype.getHeader=function(e){var t=this._headers[e.toLowerCase()];return t?t.value:null},h.prototype.removeHeader=function(e){var t=this;delete t._headers[e.toLowerCase()]},h.prototype._onFinish=function(){var e=this;if(!e._destroyed){var t=e._opts,i=e._headers,s=null;"GET"!==t.method&&"HEAD"!==t.method&&(d.arraybuffer?s=c(o.concat(e._body)):d.blobConstructor?s=new r.Blob(e._body.map(function(e){return c(e)}),{type:(i["content-type"]||{}).value||""}):s=o.concat(e._body).toString());var a=[];if(Object.keys(i).forEach(function(e){var t=i[e].name,n=i[e].value;Array.isArray(n)?n.forEach(function(e){a.push([t,e])}):a.push([t,n])}),"fetch"===e._mode){var l=null;if(d.abortController){var p=new AbortController;l=p.signal,e._fetchAbortController=p,"requestTimeout"in t&&0!==t.requestTimeout&&(e._fetchTimer=r.setTimeout(function(){e.emit("requestTimeout"),e._fetchAbortController&&e._fetchAbortController.abort()},t.requestTimeout))}r.fetch(e._opts.url,{method:e._opts.method,headers:a,body:s||void 0,mode:"cors",credentials:t.withCredentials?"include":"same-origin",signal:l}).then(function(t){e._fetchResponse=t,e._connect()},function(t){r.clearTimeout(e._fetchTimer),e._destroyed||e.emit("error",t)})}else{var u=e._xhr=new r.XMLHttpRequest;try{u.open(e._opts.method,e._opts.url,!0)}catch(t){return void n.nextTick(function(){e.emit("error",t)})}"responseType"in u&&(u.responseType=e._mode.split(":")[0]),"withCredentials"in u&&(u.withCredentials=!!t.withCredentials),"text"===e._mode&&"overrideMimeType"in u&&u.overrideMimeType("text/plain; charset=x-user-defined"),"requestTimeout"in t&&(u.timeout=t.requestTimeout,u.ontimeout=function(){e.emit("requestTimeout")}),a.forEach(function(e){u.setRequestHeader(e[0],e[1])}),e._response=null,u.onreadystatechange=function(){switch(u.readyState){case f.LOADING:case f.DONE:e._onXHRProgress();}},"moz-chunked-arraybuffer"===e._mode&&(u.onprogress=function(){e._onXHRProgress()}),u.onerror=function(){e._destroyed||e.emit("error",new Error("XHR error"))};try{u.send(s)}catch(t){return void n.nextTick(function(){e.emit("error",t)})}}}},h.prototype._onXHRProgress=function(){var e=this;!s(e._xhr)||e._destroyed||(!e._response&&e._connect(),e._response._onXHRProgress())},h.prototype._connect=function(){var e=this;e._destroyed||(e._response=new u(e._xhr,e._fetchResponse,e._mode,e._fetchTimer),e._response.on("error",function(t){e.emit("error",t)}),e.emit("response",e._response))},h.prototype._write=function(e,t,n){var r=this;r._body.push(e),n()},h.prototype.abort=h.prototype.destroy=function(){var e=this;e._destroyed=!0,r.clearTimeout(e._fetchTimer),e._response&&(e._response._destroyed=!0),e._xhr?e._xhr.abort():e._fetchAbortController&&e._fetchAbortController.abort()},h.prototype.end=function(e,t,n){var r=this;"function"==typeof e&&(n=e,e=void 0),p.Writable.prototype.end.call(r,e,t,n)},h.prototype.flushHeaders=function(){},h.prototype.setTimeout=function(){},h.prototype.setNoDelay=function(){},h.prototype.setSocketKeepAlive=function(){};var g=["accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","cookie","cookie2","date","dnt","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","via"]}).call(this,e("_process"),"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global,e("buffer").Buffer)},{"./capability":31,"./response":33,_process:15,buffer:4,inherits:10,"readable-stream":28,"to-arraybuffer":36}],33:[function(e,t,n){(function(t,r,o){var i=e("./capability"),s=e("inherits"),d=e("readable-stream"),a=n.readyStates={UNSENT:0,OPENED:1,HEADERS_RECEIVED:2,LOADING:3,DONE:4},l=n.IncomingMessage=function(e,n,s,a){var l=this;if(d.Readable.call(l),l._mode=s,l.headers={},l.rawHeaders=[],l.trailers={},l.rawTrailers=[],l.on("end",function(){t.nextTick(function(){l.emit("close")})}),"fetch"===s){function e(){c.read().then(function(t){return l._destroyed?void 0:t.done?(r.clearTimeout(a),void l.push(null)):void(l.push(new o(t.value)),e())}).catch(function(e){r.clearTimeout(a),l._destroyed||l.emit("error",e)})}if(l._fetchResponse=n,l.url=n.url,l.statusCode=n.status,l.statusMessage=n.statusText,n.headers.forEach(function(e,t){l.headers[t.toLowerCase()]=e,l.rawHeaders.push(t,e)}),i.writableStream){var p=new WritableStream({write:function(e){return new Promise(function(t,n){l._destroyed?n():l.push(new o(e))?t():l._resumeFetch=t})},close:function(){r.clearTimeout(a),l._destroyed||l.push(null)},abort:function(e){l._destroyed||l.emit("error",e)}});try{return void n.body.pipeTo(p).catch(function(e){r.clearTimeout(a),l._destroyed||l.emit("error",e)})}catch(t){}}var c=n.body.getReader();e()}else{l._xhr=e,l._pos=0,l.url=e.responseURL,l.statusCode=e.status,l.statusMessage=e.statusText;var u=e.getAllResponseHeaders().split(/\r?\n/);if(u.forEach(function(e){var t=e.match(/^([^:]+):\s*(.*)/);if(t){var n=t[1].toLowerCase();"set-cookie"===n?(void 0===l.headers[n]&&(l.headers[n]=[]),l.headers[n].push(t[2])):void 0===l.headers[n]?l.headers[n]=t[2]:l.headers[n]+=", "+t[2],l.rawHeaders.push(t[1],t[2])}}),l._charset="x-user-defined",!i.overrideMimeType){var f=l.rawHeaders["mime-type"];if(f){var h=f.match(/;\s*charset=([^;])(;|$)/);h&&(l._charset=h[1].toLowerCase())}l._charset||(l._charset="utf-8")}}};s(l,d.Readable),l.prototype._read=function(){var e=this,t=e._resumeFetch;t&&(e._resumeFetch=null,t())},l.prototype._onXHRProgress=function(){var e=this,t=e._xhr,n=null;switch(e._mode){case"text:vbarray":if(t.readyState!==a.DONE)break;try{n=new r.VBArray(t.responseBody).toArray()}catch(t){}if(null!==n){e.push(new o(n));break}case"text":try{n=t.responseText}catch(t){e._mode="text:vbarray";break}if(n.length>e._pos){var s=n.substr(e._pos);if("x-user-defined"===e._charset){for(var d=new o(s.length),l=0;le._pos&&(e.push(new o(new Uint8Array(i.result.slice(e._pos)))),e._pos=i.result.byteLength)},i.onload=function(){e.push(null)},i.readAsArrayBuffer(n);}e._xhr.readyState===a.DONE&&"ms-stream"!==e._mode&&e.push(null)}}).call(this,e("_process"),"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global,e("buffer").Buffer)},{"./capability":31,_process:15,buffer:4,inherits:10,"readable-stream":28}],34:[function(e,t,n){"use strict";function r(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0;}}function o(e){var t=r(e);if("string"!=typeof t&&(m.isEncoding===_||!_(e)))throw new Error("Unknown encoding: "+e);return t||e}function i(e){this.encoding=o(e);var t;switch(this.encoding){case"utf16le":this.text=p,this.end=c,t=4;break;case"utf8":this.fillLast=l,t=4;break;case"base64":this.text=u,this.end=f,t=3;break;default:return this.write=h,void(this.end=g);}this.lastNeed=0,this.lastTotal=0,this.lastChar=m.allocUnsafe(t)}function s(e){if(127>=e)return 0;return 6==e>>5?2:14==e>>4?3:30==e>>3?4:2==e>>6?-1:-2}function d(e,t,n){var r=t.length-1;if(r=r)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function c(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function u(e,t){var r=(e.length-t)%3;return 0==r?e.toString("base64",t):(this.lastNeed=3-r,this.lastTotal=3,1==r?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-r))}function f(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function h(e){return e.toString(this.encoding)}function g(e){return e&&e.length?this.write(e):""}var m=e("safe-buffer").Buffer,_=m.isEncoding||function(e){switch(e=""+e,e&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1;}};n.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(t=this.fillLast(e),void 0===t)return"";n=this.lastNeed,this.lastNeed=0}else n=0;return narguments.length)&&d.call(arguments,1);return a[t]=!0,i(function(){a[t]&&(r?e.apply(null,r):e.call(null),n.clearImmediate(t))}),t},n.clearImmediate="function"==typeof r?r:function(e){delete a[e]}}).call(this,e("timers").setImmediate,e("timers").clearImmediate)},{"process/browser.js":15,timers:35}],36:[function(e,t){var n=e("buffer").Buffer;t.exports=function(e){if(e instanceof Uint8Array){if(0===e.byteOffset&&e.byteLength===e.buffer.byteLength)return e.buffer;if("function"==typeof e.buffer.slice)return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}if(n.isBuffer(e)){for(var t=new Uint8Array(e.length),r=e.length,o=0;o","\"","`"," ","\r","\n","\t"]),c=["'"].concat(p),u=["%","/","?",";","#"].concat(c),f=["/","?","#"],h=/^[+a-z0-9A-Z_-]{0,63}$/,g=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,m={javascript:!0,"javascript:":!0},_={javascript:!0,"javascript:":!0},y={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},b=e("querystring");r.prototype.parse=function(e,t,n){if(!d.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var r=e.indexOf("?"),o=-1!==r&&r255?"":this.hostname.toLowerCase(),P||(this.hostname=s.toASCII(this.hostname));var N=this.port?":"+this.port:"",p=this.hostname||"";this.host=p+N,this.href+=this.host,P&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==S[0]&&(S="/"+S))}if(!m[E])for(var L=0,U=c.length,W;Lb.length&&b.unshift(""),n.pathname=b.join("/")}else n.pathname=e.pathname;if(n.search=e.search,n.query=e.query,n.host=e.host||"",n.auth=e.auth,n.hostname=e.hostname||e.host,n.port=e.port,n.pathname||n.search){var w=n.pathname||"",p=n.search||"";n.path=w+p}return n.slashes=n.slashes||e.slashes,n.href=n.format(),n}var s=n.pathname&&"/"===n.pathname.charAt(0),k=e.host||e.pathname&&"/"===e.pathname.charAt(0),x=k||s||n.host&&e.pathname,S=x,v=n.pathname&&n.pathname.split("/")||[],b=e.pathname&&e.pathname.split("/")||[],C=n.protocol&&!y[n.protocol];if(C&&(n.hostname="",n.port=null,n.host&&(""===v[0]?v[0]=n.host:v.unshift(n.host)),n.host="",e.protocol&&(e.hostname=null,e.port=null,e.host&&(""===b[0]?b[0]=e.host:b.unshift(e.host)),e.host=null),x=x&&(""===b[0]||""===v[0])),k)n.host=e.host||""===e.host?e.host:n.host,n.hostname=e.hostname||""===e.hostname?e.hostname:n.hostname,n.search=e.search,n.query=e.query,v=b;else if(b.length)v||(v=[]),v.pop(),v=v.concat(b),n.search=e.search,n.query=e.query;else if(!d.isNullOrUndefined(e.search)){if(C){n.hostname=n.host=v.shift();var E=n.host&&0o?o=3:15e}};d.headers={common:{Accept:"application/json, text/plain, */*"}},o.forEach(["delete","get","head"],function(e){d.headers[e]={}}),o.forEach(["post","put","patch"],function(e){d.headers[e]=o.merge(s)}),t.exports=d}).call(this,e("_process"))},{"./adapters/http":44,"./adapters/xhr":44,"./helpers/normalizeHeaderName":64,"./utils":67,_process:15}],57:[function(e,t){"use strict";t.exports=function(e,t){return function(){for(var n=Array(arguments.length),r=0;r>8-8*(o%1))){if(d=t.charCodeAt(o+=3/4),255>18]+d[63&e>>12]+d[63&e>>6]+d[63&e]}function s(e,t,n){for(var r=[],s=t,i;s>16,d[l++]=255&i>>8,d[l++]=255&i;return 2===s?(i=a[e.charCodeAt(n)]<<2|a[e.charCodeAt(n+1)]>>4,d[l++]=255&i):1===s&&(i=a[e.charCodeAt(n)]<<10|a[e.charCodeAt(n+1)]<<4|a[e.charCodeAt(n+2)]>>2,d[l++]=255&i>>8,d[l++]=255&i),d},n.fromByteArray=function(e){for(var t=e.length,n=t%3,r="",o=[],a=16383,l=0,i=t-n,p;li?i:l+a));return 1==n?(p=e[t-1],r+=d[p>>2],r+=d[63&p<<4],r+="=="):2==n&&(p=(e[t-2]<<8)+e[t-1],r+=d[p>>10],r+=d[63&p>>4],r+=d[63&p<<2],r+="="),o.push(r),o.join("")};for(var d=[],a=[],p="undefined"==typeof Uint8Array?Array:Uint8Array,l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",c=0,i=l.length;ci&&48<=i){r=10*r+(i-48);continue}if(s!==t||43!==i){if(s===t&&45===i){o=-1;continue}if(46===i)break;throw new Error("not a number: buffer["+s+"] = "+i)}}return r*o}function r(t,n,o,i){return null==t||0===t.length?null:("number"!=typeof n&&null==i&&(i=n,n=void 0),"number"!=typeof o&&null==i&&(i=o,o=void 0),r.position=0,r.encoding=i||null,r.data=e.isBuffer(t)?t.slice(n,o):new e(t),r.bytes=r.data.length,r.next())}const o=101;r.bytes=0,r.position=0,r.data=null,r.encoding=null,r.next=function(){switch(r.data[r.position]){case 100:return r.dictionary();case 108:return r.list();case 105:return r.integer();default:return r.buffer();}},r.find=function(e){for(var t=r.position,n=r.data.length,o=r.data;tr||r>=e.length)throw new RangeError("invalid lower bound");if(void 0===o)o=e.length-1;else if(o|=0,o=e.length)throw new RangeError("invalid upper bound");for(;r<=o;)if(i=r+(o-r>>1),s=+n(e[i],t,i,e),0>s)r=i+1;else if(0>3;return 0!=e%8&&t++,t}var s="undefined"==typeof t?"undefined"==typeof Int8Array?function(e){for(var t=Array(e),n=0;n>3;return t>e%8)},r.prototype.set=function(t,n){var r=t>>3;n||1===arguments.length?(this.buffer.length>t%8):r>t%8))},r.prototype._grow=function(e){if(this.buffer.length=this._parserSize;){var r=1===this._buffer.length?this._buffer[0]:a.concat(this._buffer);this._bufferSize-=this._parserSize,this._buffer=this._bufferSize?[r.slice(this._parserSize)]:[],this._parser(r.slice(0,this._parserSize))}n(null)},r.prototype._callback=function(e,t,n){e&&(this._clearTimeout(),!this.peerChoking&&!this._finished&&this._updateTimeout(),e.callback(t,n))},r.prototype._clearTimeout=function(){this._timeout&&(clearTimeout(this._timeout),this._timeout=null)},r.prototype._updateTimeout=function(){var e=this;e._timeoutMs&&e.requests.length&&!e._timeout&&(e._timeout=setTimeout(function(){e._onTimeout()},e._timeoutMs),e._timeoutUnref&&e._timeout.unref&&e._timeout.unref())},r.prototype._parse=function(e,t){this._parserSize=e,this._parser=t},r.prototype._onMessageLength=function(e){var t=e.readUInt32BE(0);0=this.size;){var o=n.concat(this._buffered);this._bufferedBytes-=this.size,this.push(o.slice(0,this.size)),this._buffered=[o.slice(this.size,o.length)]}r()},r.prototype._flush=function(){if(this._bufferedBytes&&this._zeroPadding){var e=new n(this.size-this._bufferedBytes);e.fill(0),this._buffered.push(e),this.push(n.concat(this._buffered)),this._buffered=null}else this._bufferedBytes&&(this.push(n.concat(this._buffered)),this._buffered=null);this.push(null)}}).call(this,e("buffer").Buffer)},{buffer:4,defined:89,inherits:96,"readable-stream":132}],81:[function(e,t){(function(e){"use strict";function n(e,t){var n=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(n>>16)<<16|65535&n}function r(e,t){return e<>>32-t}function o(e,o,i,d,a,s){return n(r(n(n(o,e),n(d,s)),a),i)}function s(e,n,r,i,d,a,s){return o(n&r|~n&i,e,n,d,a,s)}function p(e,n,r,i,a,l,s){return o(n&i|r&~i,e,n,a,l,s)}function u(e,n,r,i,d,a,s){return o(n^r^i,e,n,d,a,s)}function f(e,n,r,i,a,l,s){return o(r^(n|~i),e,n,a,l,s)}function d(e,t){e[t>>5]|=128<>>9<<4)+14]=t;var r=1732584193,o=-271733879,a=-1732584194,l=271733878,d,i,c,h,g;for(d=0;d>5]>>>r%32);return t}function c(e){var t=[],n;for(t[(e.length>>2)-1]=void 0,n=0;n>5]|=(255&e.charCodeAt(n/8))<s;s+=1)r[s]=909522486^n[s],o[s]=1549556828^n[s];return i=d(r.concat(c(t)),512+8*t.length),a(d(o.concat(i),640))}function m(e){var t="0123456789abcdef",n="",r,o;for(o=0;o>>4)+t.charAt(15&r);return n}function _(e){return unescape(encodeURIComponent(e))}function y(e){return h(_(e))}function b(e){return m(y(e))}function w(e,t){return g(_(e),_(t))}function k(e,t){return m(w(e,t))}function x(e,t,n){return t?n?w(t,e):k(t,e):n?y(e):b(e)}"function"==typeof i&&i.amd?i(function(){return x}):"object"==typeof t&&t.exports?t.exports=x:e.md5=x})(this)},{}],82:[function(e,t,n){"use strict";function r(e){if(e>J)throw new RangeError("Invalid typed array length");var t=new Uint8Array(e);return t.__proto__=o.prototype,t}function o(e,t,n){if("number"==typeof e){if("string"==typeof t)throw new Error("If encoding is specified then the first argument must be a string");return c(e)}return i(e,t,n)}function i(e,t,n){if("number"==typeof e)throw new TypeError("\"value\" argument must not be a number");return e instanceof ArrayBuffer?h(e,t,n):"string"==typeof e?u(e,t):g(e)}function s(e){if("number"!=typeof e)throw new TypeError("\"size\" argument must be a number");else if(0>e)throw new RangeError("\"size\" argument must not be negative")}function p(e,t,n){return s(e),0>=e?r(e):void 0===t?r(e):"string"==typeof n?r(e).fill(t,n):r(e).fill(t)}function c(e){return s(e),r(0>e?0:0|m(e))}function u(e,t){if(("string"!=typeof t||""===t)&&(t="utf8"),!o.isEncoding(t))throw new TypeError("\"encoding\" must be a valid string encoding");var n=0|_(e,t),i=r(n),s=i.write(e,t);return s!==n&&(i=i.slice(0,s)),i}function f(e){for(var t=0>e.length?0:0|m(e.length),n=r(t),o=0;ot||e.byteLength=J)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+J.toString(16)+" bytes");return 0|e}function _(e,t){if(o.isBuffer(e))return e.length;if(K(e)||e instanceof ArrayBuffer)return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return W(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return V(e).length;default:if(r)return W(e).length;t=(""+t).toLowerCase(),r=!0;}}function y(e,t,n){var r=!1;if((void 0===t||0>t)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),0>=n)return"";if(n>>>=0,t>>>=0,n<=t)return"";for(e||(e="utf8");;)switch(e){case"hex":return A(this,t,n);case"utf8":case"utf-8":return L(this,t,n);case"ascii":return R(this,t,n);case"latin1":case"binary":return P(this,t,n);case"base64":return I(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return U(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0;}}function b(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function w(e,t,n,r,i){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):2147483647n&&(n=-2147483648),n=+n,X(n)&&(n=i?0:e.length-1),0>n&&(n=e.length+n),n>=e.length){if(i)return-1;n=e.length-1}else if(0>n)if(i)n=0;else return-1;if("string"==typeof t&&(t=o.from(t,r)),o.isBuffer(t))return 0===t.length?-1:k(e,t,n,r,i);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):k(e,[t],n,r,i);throw new TypeError("val must be string, number or Buffer")}function k(e,t,n,r,o){function s(e,t){return 1==d?e[t]:e.readUInt16BE(t*d)}var d=1,a=e.length,l=t.length;if(void 0!==r&&(r=(r+"").toLowerCase(),"ucs2"===r||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(2>e.length||2>t.length)return-1;d=2,a/=2,l/=2,n/=2}var p;if(o){var i=-1;for(p=n;pa&&(n=a-l),p=n;0<=p;p--){for(var c=!0,u=0;uo&&(r=o)):r=o;var s=t.length;if(0!=s%2)throw new TypeError("Invalid hex string");r>s/2&&(r=s/2);for(var d=0,i;di&&(s=i):2==a?(l=e[o+1],128==(192&l)&&(u=(31&i)<<6|63&l,127u||57343u&&(s=u))):void 0}null===s?(s=65533,a=1):65535>>10),s=56320|1023&s),r.push(s),o+=a}return B(r)}function B(e){var t=e.length;if(t<=$)return l.apply(String,e);for(var n="",r=0;rt)&&(t=0),(!n||0>n||n>r)&&(n=r);for(var o="",s=t;se)throw new RangeError("offset is not uint");if(e+t>n)throw new RangeError("Trying to access beyond buffer length")}function M(e,t,n,r,i,s){if(!o.isBuffer(e))throw new TypeError("\"buffer\" argument must be a Buffer instance");if(t>i||te.length)throw new RangeError("Index out of range")}function q(e,t,n,r){if(n+r>e.length)throw new RangeError("Index out of range");if(0>n)throw new RangeError("Index out of range")}function j(e,t,n,r,o){return t=+t,n>>>=0,o||q(e,t,n,4,3.4028234663852886e38,-3.4028234663852886e38),Y.write(e,t,n,r,23,4),n+4}function H(e,t,n,r,o){return t=+t,n>>>=0,o||q(e,t,n,8,1.7976931348623157e308,-1.7976931348623157e308),Y.write(e,t,n,r,52,8),n+8}function D(e){if(e=e.trim().replace(Z,""),2>e.length)return"";for(;0!=e.length%4;)e+="=";return e}function N(e){return 16>e?"0"+e.toString(16):e.toString(16)}function W(e,t){t=t||Infinity;for(var n=e.length,r=null,o=[],s=0,i;si){if(!r){if(56319i){-1<(t-=3)&&o.push(239,191,189),r=i;continue}i=(r-55296<<10|i-56320)+65536}else r&&-1<(t-=3)&&o.push(239,191,189);if(r=null,128>i){if(0>(t-=1))break;o.push(i)}else if(2048>i){if(0>(t-=2))break;o.push(192|i>>6,128|63&i)}else if(65536>i){if(0>(t-=3))break;o.push(224|i>>12,128|63&i>>6,128|63&i)}else if(1114112>i){if(0>(t-=4))break;o.push(240|i>>18,128|63&i>>12,128|63&i>>6,128|63&i)}else throw new Error("Invalid code point")}return o}function z(e){for(var t=[],n=0;n(t-=2));++r)o=e.charCodeAt(r),i=o>>8,s=o%256,n.push(s),n.push(i);return n}function V(e){return Q.toByteArray(D(e))}function G(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function K(e){return"function"==typeof ArrayBuffer.isView&&ArrayBuffer.isView(e)}function X(e){return e!==e}var Q=e("base64-js"),Y=e("ieee754");n.Buffer=o,n.SlowBuffer=function(e){return+e!=e&&(e=0),o.alloc(+e)},n.INSPECT_MAX_BYTES=50;var J=2147483647;n.kMaxLength=J,o.TYPED_ARRAY_SUPPORT=function(){try{var e=new Uint8Array(1);return e.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}},42===e.foo()}catch(t){return!1}}(),o.TYPED_ARRAY_SUPPORT||"undefined"==typeof console||"function"!=typeof console.error||console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support."),"undefined"!=typeof Symbol&&Symbol.species&&o[Symbol.species]===o&&Object.defineProperty(o,Symbol.species,{value:null,configurable:!0,enumerable:!1,writable:!1}),o.poolSize=8192,o.from=function(e,t,n){return i(e,t,n)},o.prototype.__proto__=Uint8Array.prototype,o.__proto__=Uint8Array,o.alloc=function(e,t,n){return p(e,t,n)},o.allocUnsafe=function(e){return c(e)},o.allocUnsafeSlow=function(e){return c(e)},o.isBuffer=function(e){return null!=e&&!0===e._isBuffer},o.compare=function(e,t){if(!o.isBuffer(e)||!o.isBuffer(t))throw new TypeError("Arguments must be Buffers");if(e===t)return 0;for(var n=e.length,r=t.length,s=0,i=d(n,r);st&&(e+=" ... ")),""},o.prototype.compare=function(e,t,n,r,s){if(!o.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===s&&(s=this.length),0>t||n>e.length||0>r||s>this.length)throw new RangeError("out of range index");if(r>=s&&t>=n)return 0;if(r>=s)return-1;if(t>=n)return 1;if(t>>>=0,n>>>=0,r>>>=0,s>>>=0,this===e)return 0;for(var a=s-r,l=n-t,p=d(a,l),c=this.slice(r,s),u=e.slice(t,n),f=0;f>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0);else throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");var o=this.length-t;if((void 0===n||n>o)&&(n=o),0n||0>t)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return x(this,e,t,n);case"utf8":case"utf-8":return S(this,e,t,n);case"ascii":return v(this,e,t,n);case"latin1":case"binary":return C(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return T(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0;}},o.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var $=4096;o.prototype.slice=function(e,t){var n=this.length;e=~~e,t=void 0===t?n:~~t,0>e?(e+=n,0>e&&(e=0)):e>n&&(e=n),0>t?(t+=n,0>t&&(t=0)):t>n&&(t=n),t>>=0,t>>>=0,n||O(e,t,this.length);for(var r=this[e],o=1,s=0;++s>>=0,t>>>=0,n||O(e,t,this.length);for(var r=this[e+--t],o=1;0>>=0,t||O(e,1,this.length),this[e]},o.prototype.readUInt16LE=function(e,t){return e>>>=0,t||O(e,2,this.length),this[e]|this[e+1]<<8},o.prototype.readUInt16BE=function(e,t){return e>>>=0,t||O(e,2,this.length),this[e]<<8|this[e+1]},o.prototype.readUInt32LE=function(e,t){return e>>>=0,t||O(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},o.prototype.readUInt32BE=function(e,t){return e>>>=0,t||O(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},o.prototype.readIntLE=function(e,t,n){e>>>=0,t>>>=0,n||O(e,t,this.length);for(var r=this[e],o=1,s=0;++s=o&&(r-=a(2,8*t)),r},o.prototype.readIntBE=function(e,t,n){e>>>=0,t>>>=0,n||O(e,t,this.length);for(var r=t,o=1,i=this[e+--r];0=o&&(i-=a(2,8*t)),i},o.prototype.readInt8=function(e,t){return e>>>=0,t||O(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},o.prototype.readInt16LE=function(e,t){e>>>=0,t||O(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},o.prototype.readInt16BE=function(e,t){e>>>=0,t||O(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},o.prototype.readInt32LE=function(e,t){return e>>>=0,t||O(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},o.prototype.readInt32BE=function(e,t){return e>>>=0,t||O(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},o.prototype.readFloatLE=function(e,t){return e>>>=0,t||O(e,4,this.length),Y.read(this,e,!0,23,4)},o.prototype.readFloatBE=function(e,t){return e>>>=0,t||O(e,4,this.length),Y.read(this,e,!1,23,4)},o.prototype.readDoubleLE=function(e,t){return e>>>=0,t||O(e,8,this.length),Y.read(this,e,!0,52,8)},o.prototype.readDoubleBE=function(e,t){return e>>>=0,t||O(e,8,this.length),Y.read(this,e,!1,52,8)},o.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t>>>=0,n>>>=0,!r){var o=a(2,8*n)-1;M(this,e,t,n,o,0)}var s=1,d=0;for(this[t]=255&e;++d>>=0,n>>>=0,!r){var o=a(2,8*n)-1;M(this,e,t,n,o,0)}var s=n-1,i=1;for(this[t+s]=255&e;0<=--s&&(i*=256);)this[t+s]=255&e/i;return t+n},o.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,1,255,0),this[t]=255&e,t+1},o.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},o.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},o.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},o.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},o.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t>>>=0,!r){var o=a(2,8*n-1);M(this,e,t,n,o-1,-o)}var s=0,i=1,d=0;for(this[t]=255&e;++se&&0==d&&0!==this[t+s-1]&&(d=1),this[t+s]=255&(e/i>>0)-d;return t+n},o.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t>>>=0,!r){var o=a(2,8*n-1);M(this,e,t,n,o-1,-o)}var s=n-1,i=1,d=0;for(this[t+s]=255&e;0<=--s&&(i*=256);)0>e&&0==d&&0!==this[t+s+1]&&(d=1),this[t+s]=255&(e/i>>0)-d;return t+n},o.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,1,127,-128),0>e&&(e=255+e+1),this[t]=255&e,t+1},o.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},o.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},o.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},o.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||M(this,e,t,4,2147483647,-2147483648),0>e&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},o.prototype.writeFloatLE=function(e,t,n){return j(this,e,t,!0,n)},o.prototype.writeFloatBE=function(e,t,n){return j(this,e,t,!1,n)},o.prototype.writeDoubleLE=function(e,t,n){return H(this,e,t,!0,n)},o.prototype.writeDoubleBE=function(e,t,n){return H(this,e,t,!1,n)},o.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),0t)throw new RangeError("targetStart out of bounds");if(0>n||n>=this.length)throw new RangeError("sourceStart out of bounds");if(0>r)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-to)for(s=0;ss&&(e=s)}if(void 0!==r&&"string"!=typeof r)throw new TypeError("encoding must be a string");if("string"==typeof r&&!o.isEncoding(r))throw new TypeError("Unknown encoding: "+r)}else"number"==typeof e&&(e&=255);if(0>t||this.length>>=0,n=n===void 0?this.length:n>>>0,e||(e=0);var d;if("number"==typeof e)for(d=t;d>1),l=t[a]-e,0>l?o=a+1:0t.path.length?s=null:0===n&&1this._size&&(r=this._size),n===this._size?(this.destroy(),void this.push(null)):void(t.onload=function(){e._offset=r,e.push(d(t.result))},t.onerror=function(){e.emit("error",t.error)},t.readAsArrayBuffer(this._file.slice(n,r)))},r.prototype.destroy=function(){if(this._file=null,this.reader){this.reader.onload=null,this.reader.onerror=null;try{this.reader.abort()}catch(t){}}this.reader=null}},{inherits:96,"readable-stream":132,"typedarray-to-buffer":154}],92:[function(e,t){t.exports=function(e,t){function n(e,r){return e.reduce(function(e,o){return Array.isArray(o)&&r>1,f=-7,h=r?l-1:0,i=r?-1:1,d=t[n+h],s,e;for(h+=i,s=d&(1<<-f)-1,d>>=-f,f+=p;0>=-f,f+=o;0>1,b=23===f?5.960464477539063e-8-6.617444900424222e-24:0,w=u?0:h-1,i=u?1:-1,d=0>l||0===l&&0>1/l?1:0,k,x,m;for(l=s(l),isNaN(l)||l===Infinity?(x=isNaN(l)?1:0,k=_):(k=r(o(l)/n),1>l*(m=a(2,-k))&&(k--,m*=2),l+=1<=k+y?b/m:b*a(2,1-y),2<=l*m&&(k++,m/=2),k+y>=_?(x=0,k=_):1<=k+y?(x=(l*m-1)*a(2,f),k+=y):(x=l*a(2,y-1)*a(2,f),k=0));8<=f;t[p+w]=255&x,w+=i,x/=256,f-=8);for(k=k<127)return!1;return!0}},{}],98:[function(e,t){function n(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function r(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&n(e.slice(0,0))}t.exports=function(e){return null!=e&&(n(e)||r(e)||!!e._isBuffer)}},{}],99:[function(e,t){"use strict";function n(e){return r.existsSync(e)&&r.statSync(e).isFile()}var r=e("fs");t.exports=function(e,t){return t?void r.stat(e,function(e,n){return e?t(e):t(null,n.isFile())}):n(e)},t.exports.sync=n},{fs:1}],100:[function(e,t){function n(e){return r(e)||o(e)}function r(e){return e instanceof Int8Array||e instanceof Int16Array||e instanceof Int32Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Uint16Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array}function o(e){return s[i.call(e)]}t.exports=n,n.strict=r,n.loose=o;var i=Object.prototype.toString,s={"[object Int8Array]":!0,"[object Int16Array]":!0,"[object Int32Array]":!0,"[object Uint8Array]":!0,"[object Uint8ClampedArray]":!0,"[object Uint16Array]":!0,"[object Uint32Array]":!0,"[object Float32Array]":!0,"[object Float64Array]":!0}},{}],101:[function(e,t,n){arguments[4][12][0].apply(n,arguments)},{dup:12}],102:[function(e,t,n){"use strict";n.regex=n.re=/^npm-debug\.log$|^\..*\.swp$|^\.DS_Store$|^\.AppleDouble$|^\.LSOverride$|^Icon\r$|^\._.*|^\.Spotlight-V100(?:$|\/)|\.Trashes|^__MACOSX$|~$|^Thumbs\.db$|^ehthumbs\.db$|^Desktop\.ini$|^@eaDir$/,n.is=(e)=>n.re.test(e),n.not=(e)=>!n.is(e)},{}],103:[function(e,t){function n(e){var t={},n=e.split("magnet:?")[1],i=n&&0<=n.length?n.split("&"):[];i.forEach(function(e){var n=e.split("=");if(2===n.length){var r=n[0],o=n[1];if("dn"===r&&(o=decodeURIComponent(o).replace(/\+/g," ")),("tr"===r||"xs"===r||"as"===r||"ws"===r)&&(o=decodeURIComponent(o)),"kt"===r&&(o=decodeURIComponent(o).split("+")),"ix"===r&&(o=+o),!t[r])t[r]=o;else if(Array.isArray(t[r]))t[r].push(o);else{var i=t[r];t[r]=[i,o]}}});var d;if(t.xt){var a=Array.isArray(t.xt)?t.xt:[t.xt];a.forEach(function(e){if(d=e.match(/^urn:btih:(.{40})/))t.infoHash=d[1].toLowerCase();else if(d=e.match(/^urn:btih:(.{32})/)){var n=r.decode(d[1]);t.infoHash=o.from(n,"binary").toString("hex")}})}return t.infoHash&&(t.infoHashBuffer=o.from(t.infoHash,"hex")),t.dn&&(t.name=t.dn),t.kt&&(t.keywords=t.kt),t.announce="string"==typeof t.tr?[t.tr]:Array.isArray(t.tr)?t.tr:[],t.urlList=[],("string"==typeof t.as||Array.isArray(t.as))&&(t.urlList=t.urlList.concat(t.as)),("string"==typeof t.ws||Array.isArray(t.ws))&&(t.urlList=t.urlList.concat(t.ws)),s(t.announce),s(t.urlList),t}t.exports=n,t.exports.decode=n,t.exports.encode=function(e){e=i(e),e.infoHashBuffer&&(e.xt="urn:btih:"+e.infoHashBuffer.toString("hex")),e.infoHash&&(e.xt="urn:btih:"+e.infoHash),e.name&&(e.dn=e.name),e.keywords&&(e.kt=e.keywords),e.announce&&(e.tr=e.announce),e.urlList&&(e.ws=e.urlList,delete e.as);var t="magnet:?";return Object.keys(e).filter(function(e){return 2===e.length}).forEach(function(n,r){var o=Array.isArray(e[n])?e[n]:[e[n]];o.forEach(function(e,o){(0e._bufferDuration)&&e._cb){var t=e._cb;e._cb=null,t()}};r.prototype._getBufferDuration=function(){for(var e=this,t=e._sourceBuffer.buffered,n=e._elem.currentTime,r=-1,o=0;on)break;else(0<=r||n<=s)&&(r=s)}var d=r-n;return 0>d&&(d=0),d}},{inherits:96,"readable-stream":132,"to-arraybuffer":151}],105:[function(e,n){(function(e){function r(e,n){if(!(this instanceof r))return new r(e,n);if(n||(n={}),this.chunkLength=+e,!this.chunkLength)throw new Error("First argument must be a chunk length");this.chunks=[],this.closed=!1,this.length=+n.length||Infinity,this.length!==Infinity&&(this.lastChunkLength=this.length%this.chunkLength||this.chunkLength,this.lastChunkIndex=t(this.length/this.chunkLength)-1)}function o(t,n,r){e.nextTick(function(){t&&t(n,r)})}n.exports=r,r.prototype.put=function(e,t,n){if(this.closed)return o(n,new Error("Storage is closed"));var r=e===this.lastChunkIndex;return r&&t.length!==this.lastChunkLength?o(n,new Error("Last chunk length must be "+this.lastChunkLength)):r||t.length===this.chunkLength?void(this.chunks[e]=t,o(n,null)):o(n,new Error("Chunk length must be "+this.chunkLength))},r.prototype.get=function(e,t,n){if("function"==typeof t)return this.get(e,null,t);if(this.closed)return o(n,new Error("Storage is closed"));var r=this.chunks[e];if(!r)return o(n,new Error("Chunk not found"));if(!t)return o(n,null,r);var i=t.offset||0,s=t.length||r.length-i;o(n,null,r.slice(i,s+i))},r.prototype.close=r.prototype.destroy=function(e){return this.closed?o(e,new Error("Storage is closed")):void(this.closed=!0,this.chunks=null,o(e,null))}}).call(this,e("_process"))},{_process:15}],106:[function(e,t,n){(function(t){function o(e,t,n){for(var r=t;r>3:0,p=null;return d&&(p=d.toString(16),l&&(p+="."+l)),{mimeCodec:p,buffer:new t(e.slice(0))}},n.esds.encodingLength=function(e){return e.buffer.length},n.stsz={},n.stsz.encode=function(e,r,o){var s=e.entries||[];r=r?r.slice(o):t(n.stsz.encodingLength(e)),r.writeUInt32BE(0,0),r.writeUInt32BE(s.length,4);for(var d=0;di&&(l=1),t.writeUInt32BE(l,n),t.write(e.type,n+4,4,"ascii");var p=n+8;if(1===l&&(r.encode(e.length,t,p),p+=8),o.fullBoxes[a]&&(t.writeUInt32BE(e.flags||0,p),t.writeUInt8(e.version||0,p),p+=4),d[a]){var c=d[a];c.forEach(function(n){if(5===n.length){var r=e[n]||[];n=n.substr(0,4),r.forEach(function(e){s._encode(e,t,p),p+=s.encode.bytes})}else e[n]&&(s._encode(e[n],t,p),p+=s.encode.bytes)}),e.otherBoxes&&e.otherBoxes.forEach(function(e){s._encode(e,t,p),p+=s.encode.bytes})}else if(o[a]){var u=o[a].encode;u(e,t,p),p+=u.bytes}else if(e.buffer){var f=e.buffer;f.copy(t,p),p+=e.buffer.length}else throw new Error("Either `type` must be set to a known type (not'"+a+"') or `buffer` must be set");return s.encode.bytes=p-n,t},s.readHeaders=function(e,t,n){if(t=t||0,n=n||e.length,8>n-t)return 8;var i=e.readUInt32BE(t),s=e.toString("ascii",t+4,t+8),d=t+8;if(1===i){if(16>n-t)return 16;i=r.decode(e,d),d+=8}var a,l;return o.fullBoxes[s]&&(a=e.readUInt8(d),l=16777215&e.readUInt32BE(d),d+=4),{length:i,headersLen:d-t,contentLen:i-(d-t),type:s,version:a,flags:l}},s.decode=function(e,t,n){t=t||0,n=n||e.length;var r=s.readHeaders(e,t,n);if(!r||r.length>n-t)throw new Error("Data too short");return s.decodeWithoutHeaders(r,e,t+r.headersLen,t+r.length)},s.decodeWithoutHeaders=function(e,n,r,i){r=r||0,i=i||n.length;var a=e.type,l={};if(d[a]){l.otherBoxes=[];for(var p=d[a],c=r,u;8<=i-c;)if(u=s.decode(n,c,i),c+=u.length,0<=p.indexOf(u.type))l[u.type]=u;else if(0<=p.indexOf(u.type+"s")){var f=u.type+"s",h=l[f]=l[f]||[];h.push(u)}else l.otherBoxes.push(u)}else if(o[a]){var g=o[a].decode;l=g(n,r,i)}else l.buffer=new t(n.slice(r,i));return l.length=e.length,l.contentLen=e.contentLen,l.type=e.type,l.version=e.version,l.flags=e.flags,l},s.encodingLength=function(e){var t=e.type,n=8;if(o.fullBoxes[t]&&(n+=4),d[t]){var r=d[t];r.forEach(function(t){if(5===t.length){var r=e[t]||[];t=t.substr(0,4),r.forEach(function(e){e.type=t,n+=s.encodingLength(e)})}else if(e[t]){var o=e[t];o.type=t,n+=s.encodingLength(o)}}),e.otherBoxes&&e.otherBoxes.forEach(function(e){n+=s.encodingLength(e)})}else if(o[t])n+=o[t].encodingLength(e);else if(e.buffer)n+=e.buffer.length;else throw new Error("Either `type` must be set to a known type (not'"+t+"') or `buffer` must be set");return n>i&&(n+=8),e.length=n,n}}).call(this,e("buffer").Buffer)},{"./boxes":106,buffer:4,uint64be:155}],109:[function(e,t){(function(n){function r(){return this instanceof r?void(i.Writable.call(this),this.destroyed=!1,this._pending=0,this._missing=0,this._buf=null,this._str=null,this._cb=null,this._ondrain=null,this._writeBuffer=null,this._writeCb=null,this._ondrain=null,this._kick()):new r}function o(e){this._parent=e,this.destroyed=!1,i.PassThrough.call(this)}var i=e("readable-stream"),s=e("inherits"),d=e("next-event"),a=e("mp4-box-encoding"),l=new n(0);t.exports=r,s(r,i.Writable),r.prototype.destroy=function(e){this.destroyed||(this.destroyed=!0,e&&this.emit("error",e),this.emit("close"))},r.prototype._write=function(e,t,n){if(!this.destroyed){for(var r=!this._str||!this._str._writableState.needDrain;e.length&&!this.destroyed;){if(!this._missing)return this._writeBuffer=e,void(this._writeCb=n);var o=e.length=u?t(e/u)+"d":e>=c?t(e/c)+"h":e>=s?t(e/s)+"m":e>=p?t(e/p)+"s":e+"ms"}function a(e){return l(e,u,"day")||l(e,c,"hour")||l(e,s,"minute")||l(e,p,"second")||e+" ms"}function l(e,o,n){return eo++;)i.push(a(2,o));t.exports=function(e){return n(e/r,i)}},{"closest-to":84}],119:[function(e,t){(function(e){"use strict";t.exports=e.version&&0!==e.version.indexOf("v0.")&&(0!==e.version.indexOf("v1.")||0===e.version.indexOf("v1.8."))?e.nextTick:function(t,n,r,o){if("function"!=typeof t)throw new TypeError("\"callback\" argument must be a function");var s=arguments.length,d,a;switch(s){case 0:case 1:return e.nextTick(t);case 2:return e.nextTick(function(){t.call(null,n)});case 3:return e.nextTick(function(){t.call(null,n,r)});case 4:return e.nextTick(function(){t.call(null,n,r,o)});default:for(d=Array(s-1),a=0;ae.length)throw new Error("pump requires two streams per minimum");var n=e.map(function(o,s){var i=s=t.length)return o._position+=t.length,r(null);var l;if(a>t.length){o._position+=t.length,l=0===d?t:t.slice(d),i=s.stream.write(l)&&i;break}o._position+=a,l=0===d&&a===t.length?t:t.slice(d,a),i=s.stream.write(l)&&i,s.last&&s.stream.end(),t=t.slice(a),o._queue.shift()}i?r(null):s.stream.once("drain",r.bind(null,null))},r.prototype.slice=function(e){var t=this;if(t.destroyed)return null;e instanceof Array||(e=[e]);var n=new i.PassThrough;return e.forEach(function(r,o){t._queue.push({start:r.start,end:r.end,stream:n,last:o===e.length-1})}),t._buffer&&t._write(t._buffer,null,t._cb),n},r.prototype.destroy=function(e){var t=this;t.destroyed||(t.destroyed=!0,e&&t.emit("error",e))}},{inherits:96,"readable-stream":132}],124:[function(e,t){"use strict";function n(e){return this instanceof n?void(a.call(this,e),l.call(this,e),e&&!1===e.readable&&(this.readable=!1),e&&!1===e.writable&&(this.writable=!1),this.allowHalfOpen=!0,e&&!1===e.allowHalfOpen&&(this.allowHalfOpen=!1),this.once("end",r)):new n(e)}function r(){this.allowHalfOpen||this._writableState.ended||i(o,this)}function o(e){e.end()}var i=e("process-nextick-args"),s=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};t.exports=n;var d=e("core-util-is");d.inherits=e("inherits");var a=e("./_stream_readable"),l=e("./_stream_writable");d.inherits(n,a);for(var p=s(l.prototype),c=0,u;c=X?e=X:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}function g(e,t){return 0>=e||0===t.length&&t.ended?0:t.objectMode?1:e===e?(e>t.highWaterMark&&(t.highWaterMark=h(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0)):t.flowing&&t.length?t.buffer.head.data.length:t.length}function m(e,t){if(!t.ended){if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,_(e)}}function _(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(z("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?A(y,e):y(e))}function y(e){z("emit readable"),e.emit("readable"),C(e)}function b(e,t){t.readingMore||(t.readingMore=!0,A(w,e,t))}function w(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(r=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):r=T(e,t.buffer,t.decoder),r}function T(e,t,n){var r;return ei.length?i.length:e;if(o+=s===i.length?i:i.slice(0,e),e-=s,0===e){s===i.length?(++r,t.head=n.next?n.next:t.tail=null):(t.head=n,n.data=i.slice(s));break}++r}return t.length-=r,o}function L(e,t){var n=H.allocUnsafe(e),r=t.head,o=1;for(r.data.copy(n),e-=r.data.length;r=r.next;){var i=r.data,s=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,s),e-=s,0===e){s===i.length?(++o,t.head=r.next?r.next:t.tail=null):(t.head=r,r.data=i.slice(s));break}++o}return t.length-=o,n}function B(e){var t=e._readableState;if(0=t.highWaterMark||t.ended))return z("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?B(this):_(this),null;if(e=g(e,t),0===e&&t.ended)return 0===t.length&&B(this),null;var r=t.needReadable;z("need readable",r),(0===t.length||t.length-e>>0),n=this.head,s=0;n;)r(n.data,t,s),s+=n.data.length,n=n.next;return t},e}()},{"safe-buffer":138}],130:[function(e,t){"use strict";function n(e,t){e.emit("error",t)}var r=e("process-nextick-args");t.exports={destroy:function(e,t){var o=this,i=this._readableState&&this._readableState.destroyed,s=this._writableState&&this._writableState.destroyed;return i||s?void(t?t(e):e&&(!this._writableState||!this._writableState.errorEmitted)&&r(n,this,e)):void(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?(r(n,o,e),o._writableState&&(o._writableState.errorEmitted=!0)):t&&t(e)}))},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},{"process-nextick-args":119}],131:[function(e,t,n){arguments[4][27][0].apply(n,arguments)},{dup:27,events:7}],132:[function(e,t,n){arguments[4][28][0].apply(n,arguments)},{"./lib/_stream_duplex.js":124,"./lib/_stream_passthrough.js":125,"./lib/_stream_readable.js":126,"./lib/_stream_transform.js":127,"./lib/_stream_writable.js":128,dup:28}],133:[function(e,t,n){function r(e,t,n,r){function i(){v.removeEventListener("loadstart",i),n.autoplay&&v.play()}function d(){v.removeEventListener("canplay",d),r(null,v)}function u(){v=t("iframe"),o(e,function(e,t){return e?w(e):void(v.src=t,".pdf"!==x&&(v.sandbox="allow-forms allow-scripts"),r(null,v))})}function w(t){t.message="Error rendering file \""+e.name+"\": "+t.message,a(t.message),r(t)}var x=c.extname(e.name).toLowerCase(),S=0,v;0<=m.indexOf(x)?function(){function r(){a("Use MediaSource API for "+e.name),m(),v.addEventListener("error",u),v.addEventListener("loadstart",i),v.addEventListener("canplay",d);var t=new p(v),n=t.createWriteStream(s(e.name));e.createReadStream().pipe(n),S&&(v.currentTime=S)}function l(){a("Use Blob URL for "+e.name),m(),v.addEventListener("error",w),v.addEventListener("loadstart",i),v.addEventListener("canplay",d),o(e,function(e,t){return e?w(e):void(v.src=t,S&&(v.currentTime=S))})}function c(e){a("videostream error: fallback to MediaSource API: %o",e.message||e),v.removeEventListener("error",c),v.removeEventListener("canplay",d),r()}function u(t){return a("MediaSource API error: fallback to Blob URL: %o",t.message||t),"number"==typeof e.length&&e.length>n.maxBlobLength?(a("File length too large for Blob URL approach: %d (max: %d)",e.length,n.maxBlobLength),w(new Error("File length too large for Blob URL approach: "+e.length+" (max: "+n.maxBlobLength+")"))):void(v.removeEventListener("error",u),v.removeEventListener("canplay",d),l())}function m(){v||(v=t(_),v.addEventListener("progress",function(){S=v.currentTime}))}var _=0<=g.indexOf(x)?"video":"audio";k?0<=h.indexOf(x)?function(){a("Use `videostream` package for "+e.name),m(),v.addEventListener("error",c),v.addEventListener("loadstart",i),v.addEventListener("canplay",d),f(e,v)}():r():l()}():0<=_.indexOf(x)?function(){v=t("audio"),o(e,function(e,t){return e?w(e):void(v.addEventListener("error",w),v.addEventListener("loadstart",i),v.addEventListener("canplay",d),v.src=t)})}():0<=y.indexOf(x)?function(){v=t("img"),o(e,function(t,n){return t?w(t):void(v.src=n,v.alt=e.name,r(null,v))})}():0<=b.indexOf(x)?u():function(){a("Unknown file extension \"%s\" - will attempt to render into iframe",x);var t="";e.createReadStream({start:0,end:1e3}).setEncoding("utf8").on("data",function(e){t+=e}).on("end",function(){l(t)?(a("File extension \"%s\" appears ascii, so will render.",x),u()):(a("File extension \"%s\" appears non-ascii, will not render.",x),r(new Error("Unsupported file type \""+x+"\": Cannot append to DOM")))}).on("error",r)}()}function o(e,t){var r=c.extname(e.name).toLowerCase();u(e.createReadStream(),n.mime[r],t)}function i(e){if(null==e)throw new Error("file cannot be null or undefined");if("string"!=typeof e.name)throw new Error("missing or invalid file.name property");if("function"!=typeof e.createReadStream)throw new Error("missing or invalid file.createReadStream property")}function s(e){var t=c.extname(e).toLowerCase();return{".m4a":"audio/mp4; codecs=\"mp4a.40.5\"",".m4v":"video/mp4; codecs=\"avc1.640029, mp4a.40.5\"",".mkv":"video/webm; codecs=\"avc1.640029, mp4a.40.5\"",".mp3":"audio/mpeg",".mp4":"video/mp4; codecs=\"avc1.640029, mp4a.40.5\"",".webm":"video/webm; codecs=\"vorbis, vp8\""}[t]}function d(e){null==e.autoplay&&(e.autoplay=!0),null==e.controls&&(e.controls=!0),null==e.maxBlobLength&&(e.maxBlobLength=w)}n.render=function(e,t,n,o){"function"==typeof n&&(o=n,n={}),n||(n={}),o||(o=function(){}),i(e),d(n),"string"==typeof t&&(t=document.querySelector(t)),r(e,function(n){if(t.nodeName!==n.toUpperCase()){var r=c.extname(e.name).toLowerCase();throw new Error("Cannot render \""+r+"\" inside a \""+t.nodeName.toLowerCase()+"\" element, expected \""+n+"\"")}return t},n,o)},n.append=function(e,t,n,o){function s(e){var r=a(e);return n.controls&&(r.controls=!0),n.autoplay&&(r.autoplay=!0),t.appendChild(r),r}function a(e){var n=document.createElement(e);return t.appendChild(n),n}if("function"==typeof n&&(o=n,n={}),n||(n={}),o||(o=function(){}),i(e),d(n),"string"==typeof t&&(t=document.querySelector(t)),t&&("VIDEO"===t.nodeName||"AUDIO"===t.nodeName))throw new Error("Invalid video/audio node argument. Argument must be root element that video/audio tag will be appended to.");r(e,function(e){return"video"===e||"audio"===e?s(e):a(e)},n,function(e,t){e&&t&&t.remove(),o(e,t)})},n.mime=e("./lib/mime.json");var a=e("debug")("render-media"),l=e("is-ascii"),p=e("mediasource"),c=e("path"),u=e("stream-to-blob-url"),f=e("videostream"),h=[".m4a",".m4v",".mp4"],g=[".m4v",".mkv",".mp4",".webm"],m=[].concat(g,[".m4a",".mp3"]),_=[".aac",".oga",".ogg",".wav"],y=[".bmp",".gif",".jpeg",".jpg",".png",".svg"],b=[".css",".html",".js",".md",".pdf",".txt"],w=200000000,k="undefined"!=typeof window&&window.MediaSource},{"./lib/mime.json":134,debug:87,"is-ascii":97,mediasource:104,path:13,"stream-to-blob-url":145,videostream:161}],134:[function(e,t){t.exports={".3gp":"video/3gpp",".aac":"audio/aac",".aif":"audio/x-aiff",".aiff":"audio/x-aiff",".atom":"application/atom+xml",".avi":"video/x-msvideo",".bmp":"image/bmp",".bz2":"application/x-bzip2",".conf":"text/plain",".css":"text/css",".csv":"text/plain",".diff":"text/x-diff",".doc":"application/msword",".flv":"video/x-flv",".gif":"image/gif",".gz":"application/x-gzip",".htm":"text/html",".html":"text/html",".ico":"image/vnd.microsoft.icon",".ics":"text/calendar",".iso":"application/octet-stream",".jar":"application/java-archive",".jpeg":"image/jpeg",".jpg":"image/jpeg",".js":"application/javascript",".json":"application/json",".less":"text/css",".log":"text/plain",".m3u":"audio/x-mpegurl",".m4a":"audio/mp4",".m4v":"video/mp4",".manifest":"text/cache-manifest",".markdown":"text/x-markdown",".mathml":"application/mathml+xml",".md":"text/x-markdown",".mid":"audio/midi",".midi":"audio/midi",".mov":"video/quicktime",".mp3":"audio/mpeg",".mp4":"video/mp4",".mp4v":"video/mp4",".mpeg":"video/mpeg",".mpg":"video/mpeg",".odp":"application/vnd.oasis.opendocument.presentation",".ods":"application/vnd.oasis.opendocument.spreadsheet",".odt":"application/vnd.oasis.opendocument.text",".oga":"audio/ogg",".ogg":"application/ogg",".pdf":"application/pdf",".png":"image/png",".pps":"application/vnd.ms-powerpoint",".ppt":"application/vnd.ms-powerpoint",".ps":"application/postscript",".psd":"image/vnd.adobe.photoshop",".qt":"video/quicktime",".rar":"application/x-rar-compressed",".rdf":"application/rdf+xml",".rss":"application/rss+xml",".rtf":"application/rtf",".svg":"image/svg+xml",".svgz":"image/svg+xml",".swf":"application/x-shockwave-flash",".tar":"application/x-tar",".tbz":"application/x-bzip-compressed-tar",".text":"text/plain",".tif":"image/tiff",".tiff":"image/tiff",".torrent":"application/x-bittorrent",".ttf":"application/x-font-ttf",".txt":"text/plain",".wav":"audio/wav",".webm":"video/webm",".wma":"audio/x-ms-wma",".wmv":"video/x-ms-wmv",".xls":"application/vnd.ms-excel",".xml":"application/xml",".yaml":"text/yaml",".yml":"text/yaml",".zip":"application/zip"}},{}],135:[function(e,t){(function(e){t.exports=function(t,n,r){function o(t){function n(){r&&r(t,d),r=null}i?e.nextTick(n):n()}function s(e,n,r){if(d[e]=r,n&&(c=!0),0==--l||n)o(n);else if(!c&&u>2)+1;i>2]|=128<<24-(t%4<<3),e[(-16&(t>>2)+2)+14]=0|n/536870912,e[(-16&(t>>2)+2)+15]=n<<3},p=function(e,t,n,r,o){var i=this,s=o%4,d=(r+s)%4,a=r-d,l;switch(s){case 0:e[o]=i.charCodeAt(n+3);case 1:e[0|o+1-(s<<1)]=i.charCodeAt(n+2);case 2:e[0|o+2-(s<<1)]=i.charCodeAt(n+1);case 3:e[0|o+3-(s<<1)]=i.charCodeAt(n);}if(!(r>2]=i.charCodeAt(n+l)<<24|i.charCodeAt(n+l+1)<<16|i.charCodeAt(n+l+2)<<8|i.charCodeAt(n+l+3);switch(d){case 3:e[0|o+a+1]=i.charCodeAt(n+a+2);case 2:e[0|o+a+2]=i.charCodeAt(n+a+1);case 1:e[0|o+a+3]=i.charCodeAt(n+a);}}},c=function(e,t,n,r,o){var i=this,s=o%4,d=(r+s)%4,a=r-d,l;switch(s){case 0:e[o]=i[n+3];case 1:e[0|o+1-(s<<1)]=i[n+2];case 2:e[0|o+2-(s<<1)]=i[n+1];case 3:e[0|o+3-(s<<1)]=i[n];}if(!(r>2]=i[n+l]<<24|i[n+l+1]<<16|i[n+l+2]<<8|i[n+l+3];switch(d){case 3:e[0|o+a+1]=i[n+a+2];case 2:e[0|o+a+2]=i[n+a+1];case 1:e[0|o+a+3]=i[n+a];}}},u=function(e,t,n,r,i){var s=this,d=i%4,a=(r+d)%4,l=r-a,p=new Uint8Array(o.readAsArrayBuffer(s.slice(n,n+r))),c;switch(d){case 0:e[i]=p[3];case 1:e[0|i+1-(d<<1)]=p[2];case 2:e[0|i+2-(d<<1)]=p[1];case 3:e[0|i+3-(d<<1)]=p[0];}if(!(r>2]=p[c]<<24|p[c+1]<<16|p[c+2]<<8|p[c+3];switch(a){case 3:e[0|i+l+1]=p[l+2];case 2:e[0|i+l+2]=p[l+1];case 1:e[0|i+l+3]=p[l];}}},f=function(e){switch(r.getDataType(e)){case"string":return p.bind(e);case"array":return c.bind(e);case"buffer":return c.bind(e);case"arraybuffer":return c.bind(new Uint8Array(e));case"view":return c.bind(new Uint8Array(e.buffer,e.byteOffset,e.byteLength));case"blob":return u.bind(e);}},h=Array(256),g=0;256>g;g++)h[g]=(16>g?"0":"")+g.toString(16);var i=function(e){for(var t=new Uint8Array(e),n=Array(e.byteLength),r=0;r=e)return 65536;if(16777216>e)for(t=1;t>2);return a(o,e),l(o,e,n),r},b=function(e,n,r,o){f(e)(t.h8,t.h32,n,r,o||0)},w=function(e,n,r,o,i){var s=r;b(e,n,r),i&&(s=y(r,o)),t.core.hash(s,t.padMaxChunkLen)},k=function(e,t){var n=new Int32Array(e,t+320,5),r=new Int32Array(5),o=new DataView(r.buffer);return o.setInt32(0,n[0],!1),o.setInt32(4,n[1],!1),o.setInt32(8,n[2],!1),o.setInt32(12,n[3],!1),o.setInt32(16,n[4],!1),r},x=this.rawDigest=function(e){var n=e.byteLength||e.length||e.size||0;_(t.heap,t.padMaxChunkLen);var r=0,o=t.maxChunkLen;for(r=0;n>r+o;r+=o)w(e,r,o,n,!1);return w(e,r,n-r,n,!0),k(t.heap,t.padMaxChunkLen)};this.digest=this.digestFromString=this.digestFromBuffer=this.digestFromArrayBuffer=function(e){return i(x(e).buffer)},this.resetState=function(){return _(t.heap,t.padMaxChunkLen),this},this.append=function(e){var n=0,r=e.byteLength||e.length||e.size||0,o=t.offset%t.maxChunkLen,i;for(t.offset+=r;n>2],d=0|r[t+324>>2],l=0|r[t+328>>2],c=0|r[t+332>>2],f=0|r[t+336>>2],n=0;(0|n)<(0|e);n=0|n+64){for(s=i,a=d,p=l,u=c,h=f,o=0;64>(0|o);o=0|o+4)m=0|r[n+o>>2],g=0|(0|(i<<5|i>>>27)+(d&l|~d&c))+(0|(0|m+f)+1518500249),f=c,c=l,l=d<<30|d>>>2,d=i,i=g,r[e+o>>2]=m;for(o=0|e+64;(0|o)<(0|e+80);o=0|o+4)m=(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])<<1|(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])>>>31,g=0|(0|(i<<5|i>>>27)+(d&l|~d&c))+(0|(0|m+f)+1518500249),f=c,c=l,l=d<<30|d>>>2,d=i,i=g,r[o>>2]=m;for(o=0|e+80;(0|o)<(0|e+160);o=0|o+4)m=(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])<<1|(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])>>>31,g=0|(0|(i<<5|i>>>27)+(d^l^c))+(0|(0|m+f)+1859775393),f=c,c=l,l=d<<30|d>>>2,d=i,i=g,r[o>>2]=m;for(o=0|e+160;(0|o)<(0|e+240);o=0|o+4)m=(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])<<1|(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])>>>31,g=0|(0|(i<<5|i>>>27)+(d&l|d&c|l&c))+(0|(0|m+f)-1894007588),f=c,c=l,l=d<<30|d>>>2,d=i,i=g,r[o>>2]=m;for(o=0|e+240;(0|o)<(0|e+320);o=0|o+4)m=(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])<<1|(r[o-12>>2]^r[o-32>>2]^r[o-56>>2]^r[o-64>>2])>>>31,g=0|(0|(i<<5|i>>>27)+(d^l^c))+(0|(0|m+f)-899497514),f=c,c=l,l=d<<30|d>>>2,d=i,i=g,r[o>>2]=m;i=0|i+s,d=0|d+a,l=0|l+p,c=0|c+u,f=0|f+h}r[t+320>>2]=i,r[t+324>>2]=d,r[t+328>>2]=l,r[t+332>>2]=c,r[t+336>>2]=f}}},"undefined"==typeof t?"undefined"!=typeof window&&(window.Rusha=n):t.exports=n,"undefined"!=typeof FileReaderSync){var o=new FileReaderSync,i=function(e,t,n){try{return n(null,e.digest(t))}catch(t){return n(t)}},s=function(e,t,n,r,o){var i=new self.FileReader;i.onloadend=function(){var d=i.result;t+=i.result.byteLength;try{e.append(d)}catch(t){return void o(t)}tn.statusCode&&"location"in n.headers)return e.url=n.headers.location,n.resume(),void(0p?(r._debug("start backpressure: bufferedAmount %d",r._channel.bufferedAmount),r._cb=n):n(null)}else r._debug("write before connect"),r._chunk=e,r._cb=n},r.prototype._onFinish=function(){function e(){setTimeout(function(){t._destroy()},1e3)}var t=this;t.destroyed||(t.connected?e():t.once("connect",e))},r.prototype._createOffer=function(){var e=this;e.destroyed||e._pc.createOffer(function(t){function n(){var n=e._pc.localDescription||t;e._debug("signal"),e.emit("signal",{type:n.type,sdp:n.sdp})}e.destroyed||(t.sdp=e.sdpTransform(t.sdp),e._pc.setLocalDescription(t,function(){e.destroyed||(e.trickle||e._iceComplete?n():e.once("_iceComplete",n))},function(t){e._destroy(t)}))},function(t){e._destroy(t)},e.offerConstraints)},r.prototype._createAnswer=function(){var e=this;e.destroyed||e._pc.createAnswer(function(t){function n(){var n=e._pc.localDescription||t;e._debug("signal"),e.emit("signal",{type:n.type,sdp:n.sdp})}e.destroyed||(t.sdp=e.sdpTransform(t.sdp),e._pc.setLocalDescription(t,function(){e.destroyed||(e.trickle||e._iceComplete?n():e.once("_iceComplete",n))},function(t){e._destroy(t)}))},function(t){e._destroy(t)},e.answerConstraints)},r.prototype._onIceStateChange=function(){var e=this;if(!e.destroyed){var t=e._pc.iceConnectionState,n=e._pc.iceGatheringState;e._debug("iceStateChange (connection: %s) (gathering: %s)",t,n),e.emit("iceStateChange",t,n),("connected"===t||"completed"===t)&&(clearTimeout(e._reconnectTimeout),e._pcReady=!0,e._maybeReady()),"disconnected"===t&&(e.reconnectTimer?(clearTimeout(e._reconnectTimeout),e._reconnectTimeout=setTimeout(function(){e._destroy()},e.reconnectTimer)):e._destroy()),"failed"===t&&e._destroy(new Error("Ice connection failed.")),"closed"===t&&e._destroy()}},r.prototype.getStats=function(e){var t=this;0===t._pc.getStats.length?t._pc.getStats().then(function(t){var n=[];t.forEach(function(e){n.push(e)}),e(null,n)},function(t){e(t)}):t._isReactNativeWebrtc?t._pc.getStats(null,function(t){var n=[];t.forEach(function(e){n.push(e)}),e(null,n)},function(t){e(t)}):0p)&&this._onChannelBufferedAmountLow()},r.prototype._onSignalingStateChange=function(){var e=this;e.destroyed||(e._debug("signalingStateChange %s",e._pc.signalingState),e.emit("signalingStateChange",e._pc.signalingState))},r.prototype._onIceCandidate=function(e){var t=this;t.destroyed||(e.candidate&&t.trickle?t.emit("signal",{candidate:{candidate:e.candidate.candidate,sdpMLineIndex:e.candidate.sdpMLineIndex,sdpMid:e.candidate.sdpMid}}):!e.candidate&&(t._iceComplete=!0,t.emit("_iceComplete")))},r.prototype._onChannelMessage=function(e){var t=this;if(!t.destroyed){var r=e.data;r instanceof ArrayBuffer&&(r=n.from(r)),t.push(r)}},r.prototype._onChannelBufferedAmountLow=function(){var e=this;if(!e.destroyed&&e._cb){e._debug("ending backpressure: bufferedAmount %d",e._channel.bufferedAmount);var t=e._cb;e._cb=null,t(null)}},r.prototype._onChannelOpen=function(){var e=this;e.connected||e.destroyed||(e._debug("on channel open"),e._channelReady=!0,e._maybeReady())},r.prototype._onChannelClose=function(){var e=this;e.destroyed||(e._debug("on channel close"),e._destroy())},r.prototype._onAddStream=function(e){var t=this;t.destroyed||(t._debug("on add stream"),t.emit("stream",e.stream))},r.prototype._onTrack=function(e){var t=this;if(!t.destroyed){t._debug("on track");var n=e.streams[0].id;-1!==t._previousStreams.indexOf(n)||(t._previousStreams.push(n),t.emit("stream",e.streams[0]))}},r.prototype._debug=function(){var e=this,t=[].slice.call(arguments);t[0]="["+e._id+"] "+t[0],i.apply(null,t)},r.prototype._transformConstraints=function(e){var t=this;if(0===Object.keys(e).length)return e;if((e.mandatory||e.optional)&&!t._isChromium){var n=Object.assign({},e.optional,e.mandatory);return void 0!==n.OfferToReceiveVideo&&(n.offerToReceiveVideo=n.OfferToReceiveVideo,delete n.OfferToReceiveVideo),void 0!==n.OfferToReceiveAudio&&(n.offerToReceiveAudio=n.OfferToReceiveAudio,delete n.OfferToReceiveAudio),n}return e.mandatory||e.optional||!t._isChromium?e:(void 0!==e.offerToReceiveVideo&&(e.OfferToReceiveVideo=e.offerToReceiveVideo,delete e.offerToReceiveVideo),void 0!==e.offerToReceiveAudio&&(e.OfferToReceiveAudio=e.offerToReceiveAudio,delete e.offerToReceiveAudio),{mandatory:e})}}).call(this,e("buffer").Buffer)},{buffer:4,debug:87,"get-browser-rtc":93,inherits:96,randombytes:122,"readable-stream":132}],142:[function(e,t){function n(e){return s.digest(e)}function r(e){for(var t=e.length,n=new Uint8Array(t),r=0;r>>4).toString(16)),n.push((15&o).toString(16));return n.join("")}var i=e("rusha"),s=new i,d="undefined"==typeof window?self:window,a=d.crypto||d.msCrypto||{},l=a.subtle||a.webkitSubtle;try{l.digest({name:"sha-1"},new Uint8Array).catch(function(){l=!1})}catch(e){l=!1}t.exports=function(e,t){return l?void("string"==typeof e&&(e=r(e)),l.digest({name:"sha-1"},e).then(function(e){t(o(new Uint8Array(e)))},function(){t(n(e))})):void setTimeout(t,0,n(e))},t.exports.sync=n},{rusha:137}],143:[function(e,t){(function(n){function r(e){var t=this;if(!(t instanceof r))return new r(e);if(e||(e={}),"string"==typeof e&&(e={url:e}),null==e.url&&null==e.socket)throw new Error("Missing required `url` or `socket` option");if(null!=e.url&&null!=e.socket)throw new Error("Must specify either `url` or `socket` option, not both");if(t._id=d(4).toString("hex").slice(0,7),t._debug("new websocket: %o",e),e=Object.assign({allowHalfOpen:!1},e),a.Duplex.call(t,e),t.connected=!1,t.destroyed=!1,t._chunk=null,t._cb=null,t._interval=null,e.socket)t.url=e.socket.url,t._ws=e.socket;else{t.url=e.url;try{t._ws="function"==typeof l?new p(e.url,e):new p(e.url)}catch(e){return void n.nextTick(function(){t._destroy(e)})}}t._ws.binaryType="arraybuffer",t._ws.onopen=function(){t._onOpen()},t._ws.onmessage=function(e){t._onMessage(e)},t._ws.onclose=function(){t._onClose()},t._ws.onerror=function(){t._destroy(new Error("connection error to "+t.url))},t._onFinishBound=function(){t._onFinish()},t.once("finish",t._onFinishBound)}t.exports=r;var o=e("safe-buffer").Buffer,i=e("debug")("simple-websocket"),s=e("inherits"),d=e("randombytes"),a=e("readable-stream"),l=e("ws"),p="function"==typeof l?l:WebSocket,c=65536;s(r,a.Duplex),r.WEBSOCKET_SUPPORT=!!p,r.prototype.send=function(e){this._ws.send(e)},r.prototype.destroy=function(e){this._destroy(null,e)},r.prototype._destroy=function(e,t){var n=this;if(!n.destroyed){if(t&&n.once("close",t),n._debug("destroy (error: %s)",e&&(e.message||e)),n.readable=n.writable=!1,n._readableState.ended||n.push(null),n._writableState.finished||n.end(),n.connected=!1,n.destroyed=!0,clearInterval(n._interval),n._interval=null,n._chunk=null,n._cb=null,n._onFinishBound&&n.removeListener("finish",n._onFinishBound),n._onFinishBound=null,n._ws){var r=n._ws,o=function(){r.onclose=null};if(r.readyState===p.CLOSED)o();else try{r.onclose=o,r.close()}catch(e){o()}r.onopen=null,r.onmessage=null,r.onerror=null}n._ws=null,e&&n.emit("error",e),n.emit("close")}},r.prototype._read=function(){},r.prototype._write=function(e,t,n){if(this.destroyed)return n(new Error("cannot write after socket is destroyed"));if(this.connected){try{this.send(e)}catch(e){return this._destroy(e)}"function"!=typeof l&&this._ws.bufferedAmount>c?(this._debug("start backpressure: bufferedAmount %d",this._ws.bufferedAmount),this._cb=n):n(null)}else this._debug("write before connect"),this._chunk=e,this._cb=n},r.prototype._onFinish=function(){function e(){setTimeout(function(){t._destroy()},1e3)}var t=this;t.destroyed||(t.connected?e():t.once("connect",e))},r.prototype._onMessage=function(e){if(!this.destroyed){var t=e.data;t instanceof ArrayBuffer&&(t=o.from(t)),this.push(t)}},r.prototype._onOpen=function(){var e=this;if(!(e.connected||e.destroyed)){if(e.connected=!0,e._chunk){try{e.send(e._chunk)}catch(t){return e._destroy(t)}e._chunk=null,e._debug("sent chunk from \"write before connect\"");var t=e._cb;e._cb=null,t(null)}"function"!=typeof l&&(e._interval=setInterval(function(){e._onInterval()},150),e._interval.unref&&e._interval.unref()),e._debug("connect"),e.emit("connect")}},r.prototype._onInterval=function(){if(this._cb&&this._ws&&!(this._ws.bufferedAmount>c)){this._debug("ending backpressure: bufferedAmount %d",this._ws.bufferedAmount);var e=this._cb;this._cb=null,e(null)}},r.prototype._onClose=function(){this.destroyed||(this._debug("on close"),this._destroy())},r.prototype._debug=function(){var e=[].slice.call(arguments);e[0]="["+this._id+"] "+e[0],i.apply(null,e)}}).call(this,e("_process"))},{_process:15,debug:87,inherits:96,randombytes:122,"readable-stream":132,"safe-buffer":138,ws:3}],144:[function(e,t){var n=1,r=65535,o=4,i=setInterval(function(){n=n+1&r},0|1e3/o);i.unref&&i.unref(),t.exports=function(e){var t=o*(e||5),i=[0],s=1,d=n-1&r;return function(e){var a=n-d&r;for(a>t&&(a=t),d=n;a--;)s==t&&(s=0),i[s]=i[0==s?t-1:s-1],s++;e&&(i[s-1]+=e);var l=i[s-1],p=i.length=e)return 0;return 6==e>>5?2:14==e>>4?3:30==e>>3?4:-1}function d(e,t,n){var r=t.length-1;if(r=r)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function c(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function u(e,t){var r=(e.length-t)%3;return 0==r?e.toString("base64",t):(this.lastNeed=3-r,this.lastTotal=3,1==r?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-r))}function f(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function h(e){return e.toString(this.encoding)}function g(e){return e&&e.length?this.write(e):""}var m=e("safe-buffer").Buffer,_=m.isEncoding||function(e){switch(e=""+e,e&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1;}};n.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(t=this.fillLast(e),void 0===t)return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n>i,i=(i+5)%8,s=s<>8-i,r++):(s=31&a>>8-(i+5),i=(i+5)%8,0==i&&r++),d[o]="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".charCodeAt(s),o++}for(r=o;r=r?(r=(r+5)%8,0==r?(a|=o,l[d]=a,d++,a=0):a|=255&o<<8-r):(r=(r+5)%8,a|=255&o>>>r,l[d]=a,d++,a=255&o<<8-r);else throw new Error("Invalid input - it is not base32 encoded string")}return l.slice(0,d)}}).call(this,e("buffer").Buffer)},{buffer:4}],151:[function(e,t,n){arguments[4][36][0].apply(n,arguments)},{buffer:4,dup:36}],152:[function(e,t){(function(n){function o(e){function t(e,t){var n=new s(t);return n.on("warning",r._onWarning),n.on("error",r._onError),n.listen(e),r._internalDHT=!0,n}var r=this;if(!(r instanceof o))return new o(e);if(d.call(r),!e.peerId)throw new Error("Option `peerId` is required");if(!e.infoHash)throw new Error("Option `infoHash` is required");if(!n.browser&&!e.port)throw new Error("Option `port` is required");r.peerId="string"==typeof e.peerId?e.peerId:e.peerId.toString("hex"),r.infoHash="string"==typeof e.infoHash?e.infoHash:e.infoHash.toString("hex"),r._port=e.port,r._userAgent=e.userAgent,r.destroyed=!1,r._announce=e.announce||[],r._intervalMs=e.intervalMs||900000,r._trackerOpts=null,r._dhtAnnouncing=!1,r._dhtTimeout=!1,r._internalDHT=!1,r._onWarning=function(e){r.emit("warning",e)},r._onError=function(e){r.emit("error",e)},r._onDHTPeer=function(e,t){t.toString("hex")!==r.infoHash||r.emit("peer",e.host+":"+e.port,"dht")},r._onTrackerPeer=function(e){r.emit("peer",e,"tracker")},r._onTrackerAnnounce=function(){r.emit("trackerAnnounce")},!1===e.tracker?r.tracker=null:e.tracker&&"object"==typeof e.tracker?(r._trackerOpts=a(e.tracker),r.tracker=r._createTracker()):r.tracker=r._createTracker(),r.dht=!1===e.dht||"function"!=typeof s?null:e.dht&&"function"==typeof e.dht.addNode?e.dht:e.dht&&"object"==typeof e.dht?t(e.dhtPort,e.dht):t(e.dhtPort),r.dht&&(r.dht.on("peer",r._onDHTPeer),r._dhtAnnounce())}t.exports=o;var i=e("debug")("torrent-discovery"),s=e("bittorrent-dht/client"),d=e("events").EventEmitter,a=e("xtend"),l=e("inherits"),p=e("run-parallel"),c=e("bittorrent-tracker/client");l(o,d),o.prototype.updatePort=function(e){var t=this;e===t._port||(t._port=e,t.dht&&t._dhtAnnounce(),t.tracker&&(t.tracker.stop(),t.tracker.destroy(function(){t.tracker=t._createTracker()})))},o.prototype.complete=function(e){this.tracker&&this.tracker.complete(e)},o.prototype.destroy=function(e){var t=this;if(!t.destroyed){t.destroyed=!0,clearTimeout(t._dhtTimeout);var n=[];t.tracker&&(t.tracker.stop(),t.tracker.removeListener("warning",t._onWarning),t.tracker.removeListener("error",t._onError),t.tracker.removeListener("peer",t._onTrackerPeer),t.tracker.removeListener("update",t._onTrackerAnnounce),n.push(function(e){t.tracker.destroy(e)})),t.dht&&t.dht.removeListener("peer",t._onDHTPeer),t._internalDHT&&(t.dht.removeListener("warning",t._onWarning),t.dht.removeListener("error",t._onError),n.push(function(e){t.dht.destroy(e)})),p(n,e),t.dht=null,t.tracker=null,t._announce=null}},o.prototype._createTracker=function(){var e=a(this._trackerOpts,{infoHash:this.infoHash,announce:this._announce,peerId:this.peerId,port:this._port,userAgent:this._userAgent}),t=new c(e);return t.on("warning",this._onWarning),t.on("error",this._onError),t.on("peer",this._onTrackerPeer),t.on("update",this._onTrackerAnnounce),t.setInterval(this._intervalMs),t.start(),t},o.prototype._dhtAnnounce=function(){function e(){return t._intervalMs+r(Math.random()*t._intervalMs/5)}var t=this;t._dhtAnnouncing||(i("dht announce"),t._dhtAnnouncing=!0,clearTimeout(t._dhtTimeout),t.dht.announce(t.infoHash,t._port,function(n){t._dhtAnnouncing=!1,i("dht announce complete"),n&&t.emit("warning",n),t.emit("dhtAnnounce"),t.destroyed||(t._dhtTimeout=setTimeout(function(){t._dhtAnnounce()},e()),t._dhtTimeout.unref&&t._dhtTimeout.unref())}))}}).call(this,e("_process"))},{_process:15,"bittorrent-dht/client":3,"bittorrent-tracker/client":75,debug:87,events:7,inherits:96,"run-parallel":136,xtend:171}],153:[function(e,n){(function(e){function r(e){return this instanceof r?void(this.length=e,this.missing=e,this.sources=null,this._chunks=t(e/o),this._remainder=e%o||o,this._buffered=0,this._buffer=null,this._cancellations=null,this._reservations=0,this._flushed=!1):new r(e)}n.exports=r;var o=16384;r.BLOCK_LENGTH=o,r.prototype.chunkLength=function(e){return e===this._chunks-1?this._remainder:o},r.prototype.chunkLengthRemaining=function(e){return this.length-e*o},r.prototype.chunkOffset=function(e){return e*o},r.prototype.reserve=function(){return this.init()?this._cancellations.length?this._cancellations.pop():this._reservations=e.length||0>t)){var n=e.pop();if(t=e.metadata_size?this.emit("warning",new Error("Peer gave invalid metadata size")):void(this._metadataSize=e.metadata_size,this._numPieces=t(this._metadataSize/c),this._remainingRejects=2*this._numPieces,this._fetching&&this._requestPieces()):this.emit("warning",new Error("Peer does not have metadata")):this.emit("warning",new Error("Peer does not support ut_metadata"))},n.prototype.onMessage=function(e){var t,n;try{var o=e.toString(),i=o.indexOf("ee")+2;t=r.decode(o.substring(0,i)),n=e.slice(i)}catch(e){return}switch(t.msg_type){case 0:this._onRequest(t.piece);break;case 1:this._onData(t.piece,n,t.total_size);break;case 2:this._onReject(t.piece);}},n.prototype.fetch=function(){this._metadataComplete||(this._fetching=!0,this._metadataSize&&this._requestPieces())},n.prototype.cancel=function(){this._fetching=!1},n.prototype.setMetadata=function(e){if(this._metadataComplete)return!0;s("set metadata");try{var t=r.decode(e).info;t&&(e=r.encode(t))}catch(e){}return this._infoHash&&this._infoHash!==l.sync(e)?!1:(this.cancel(),this.metadata=e,this._metadataComplete=!0,this._metadataSize=this.metadata.length,this._wire.extendedHandshake.metadata_size=this._metadataSize,this.emit("metadata",r.encode({info:r.decode(this.metadata)})),!0)},n.prototype._send=function(e,t){var n=r.encode(e);i.isBuffer(t)&&(n=i.concat([n,t])),this._wire.extended("ut_metadata",n)},n.prototype._request=function(e){this._send({msg_type:0,piece:e})},n.prototype._data=function(e,t,n){var r={msg_type:1,piece:e};"number"==typeof n&&(r.total_size=n),this._send(r,t)},n.prototype._reject=function(e){this._send({msg_type:2,piece:e})},n.prototype._onRequest=function(e){if(!this._metadataComplete)return void this._reject(e);var t=e*c,n=t+c;n>this._metadataSize&&(n=this._metadataSize);var r=this.metadata.slice(t,n);this._data(e,r,this._metadataSize)},n.prototype._onData=function(e,t){t.length>c||(t.copy(this.metadata,e*c),this._bitfield.set(e),this._checkDone())},n.prototype._onReject=function(e){0=e._entries[e._index][e._countName]&&(e._index++,e._offset=0),e.value=e._entries[e._index]},o.prototype._processMoov=function(e){var t=this,r=e.traks;t._tracks=[],t._hasVideo=!1,t._hasAudio=!1;for(var o=0;o=a.stsz.entries.length)break;if(m++,y+=C,m>=v.samplesPerChunk){m=0,y=0,_++;var L=a.stsc.entries[b+1];L&&_+1>=L.firstChunk&&b++}w+=E,k.inc(),x&&x.inc(),I&&S++}i.mdia.mdhd.duration=0,i.tkhd.duration=0;var B=v.sampleDescriptionId,R={type:"moov",mvhd:e.mvhd,traks:[{tkhd:i.tkhd,mdia:{mdhd:i.mdia.mdhd,hdlr:i.mdia.hdlr,elng:i.mdia.elng,minf:{vmhd:i.mdia.minf.vmhd,smhd:i.mdia.minf.smhd,dinf:i.mdia.minf.dinf,stbl:{stsd:a.stsd,stts:d(),ctts:d(),stsc:d(),stsz:d(),stco:d(),stss:d()}}}}],mvex:{mehd:{fragmentDuration:e.mvhd.duration},trexs:[{trackId:i.tkhd.trackId,defaultSampleDescriptionIndex:B,defaultSampleDuration:0,defaultSampleSize:0,defaultSampleFlags:0}]}};t._tracks.push({trackId:i.tkhd.trackId,timeScale:i.mdia.mdhd.timeScale,samples:h,currSample:null,currTime:null,moov:R,mime:f})}if(0===t._tracks.length)return void t.emit("error",new Error("no playable tracks"));e.mvhd.duration=0,t._ftyp={type:"ftyp",brand:"iso5",brandVersion:0,compatibleBrands:["iso5"]};var P=c.encode(t._ftyp),A=t._tracks.map(function(e){var t=c.encode(e.moov);return{mime:e.mime,init:n.concat([P,t])}});t.emit("ready",A)},o.prototype.seek=function(e){var t=this;if(!t._tracks)throw new Error("Not ready yet; wait for 'ready' event");t._fileStream&&(t._fileStream.destroy(),t._fileStream=null);var n=-1;if(t._tracks.map(function(r,o){function i(e){s.destroyed||s.box(e.moof,function(n){if(n)return t.emit("error",n);if(!s.destroyed){var d=r.inStream.slice(e.ranges);d.pipe(s.mediaData(e.length,function(e){if(e)return t.emit("error",e);if(!s.destroyed){var n=t._generateFragment(o);return n?void i(n):s.finalize()}}))}})}r.outStream&&r.outStream.destroy(),r.inStream&&(r.inStream.destroy(),r.inStream=null);var s=r.outStream=p.encode(),d=t._generateFragment(o,e);return d?void((-1==n||d.ranges[0].startd&&(d=-d-2);!o.samples[d].sync;)d--;return d};o.prototype._generateFragment=function(e,t){var n=this,r=n._tracks[e],o;if(o=void 0===t?r.currSample:n._findSampleBefore(e,t),o>=r.samples.length)return null;for(var i=r.samples[o].dts,s=0,d=[],a=o,l;a=r.timeScale*1));a++){s+=l.size;var p=d.length-1;0>p||d[p].end!==l.offset?d.push({start:l.offset,end:l.offset+l.size}):d[p].end+=l.size}return r.currSample=a,{moof:n._generateMoof(e,o,a),ranges:d,length:s}},o.prototype._generateMoof=function(e,t,n){for(var r=this,o=r._tracks[e],i=[],s=t,d;se||t2*(r._numConns-r.numPeers)&&e.amInterested?e.destroy():(o=setTimeout(t,F),o.unref&&o.unref()))}function n(){if(e.peerPieces.buffer.length===r.bitfield.buffer.length){for(s=0;s131072?e.destroy():void(r.pieces[t]||r.store.get(t,{offset:n,length:o},i))}),e.bitfield(r.bitfield),e.interested(),e.peerExtensions.dht&&r.client.dht&&r.client.dht.listening&&e.port(r.client.dht.address().port),"webSeed"!==e.type&&(o=setTimeout(t,F),o.unref&&o.unref()),e.isSeeder=!1,n()},s.prototype._updateSelections=function(){var e=this;!e.ready||e.destroyed||(o.nextTick(function(){e._gcSelections()}),e._updateInterest(),e._update())},s.prototype._gcSelections=function(){for(var e=this,t=0;t=e&&s<=n&&!(s in r)&&t.peerPieces.get(s)&&(!o||o(s))}}function r(){if(!t.requests.length)for(var e=d._selections.length;e--;){var r=d._selections[e],o;if("rarest"===d.strategy)for(var i=r.from+r.offset,s=r.to,a={},l=0,p=n(i,s,a);lo));){if(d._request(t,o,!1))return;a[o]=!0,l+=1}else for(o=r.to;o>=r.from+r.offset;--o)if(t.peerPieces.get(o)&&d._request(t,o,!1))return}}function o(){var n=t.downloadSpeed()||1;if(n>V)return function(){return!0};var r=e(1,t.requests.length)*P.BLOCK_LENGTH/n,o=10,i=0;return function(e){if(!o||d.bitfield.get(e))return!0;for(var t=d.pieces[e].missing;i=p)return!0;for(var r=o(),a=0;al));){for(;d._request(t,l,d._critical[l]||e););if(t.requests.length=l)){var p=a(t,G);i(!1)||i(!0)}}},s.prototype._rechoke=function(){function e(e,t){return e.downloadSpeed===t.downloadSpeed?e.uploadSpeed===t.uploadSpeed?e.wire.amChoking===t.wire.amChoking?e.salt-t.salt:e.wire.amChoking?1:-1:t.uploadSpeed-e.uploadSpeed:t.downloadSpeed-e.downloadSpeed}var t=this;if(t.ready){0=V||2*p>o||p>d||(a=i,d=p)}}if(!a)return!1;for(l=0;l=f)return!1;var h=p.pieces[t],g=u?h.reserveRemaining():h.reserve();if(-1===g&&n&&p._hotswap(e,t)&&(g=u?h.reserveRemaining():h.reserve()),-1===g)return!1;var m=p._reservations[t];m||(m=p._reservations[t]=[]);var r=m.indexOf(null);-1===r&&(r=m.length),m[r]=e;var i=h.chunkOffset(g),_=u?h.chunkLengthRemaining(g):h.chunkLength(g);return e.request(t,i,_,function n(o,d){if(!p.destroyed){if(!p.ready)return p.once("ready",function(){n(o,d)});if(m[r]===e&&(m[r]=null),h!==p.pieces[t])return s();if(o)return p._debug("error getting piece %s (offset: %s length: %s) from %s: %s",t,i,_,e.remoteAddress+":"+e.remotePort,o.message),u?h.cancelRemaining(g):h.cancel(g),void s();if(p._debug("got piece %s (offset: %s length: %s) from %s",t,i,_,e.remoteAddress+":"+e.remotePort),!h.set(g,d,e))return s();var a=h.flush();O(a,function(e){if(!p.destroyed){if(e===p._hashes[t]){if(!p.pieces[t])return;p._debug("piece verified %s",t),p.pieces[t]=null,p._reservations[t]=null,p.bitfield.set(t,!0),p.store.put(t,a),p.wires.forEach(function(e){e.have(t)}),p._checkDone()&&!p.destroyed&&p.discovery.complete()}else p.pieces[t]=new P(h.length),p.emit("warning",new Error("Piece "+t+" failed verification"));s()}})}}),!0},s.prototype._checkDone=function(){var e=this;if(!e.destroyed){e.files.forEach(function(t){if(!t.done){for(var n=t._startPiece;n<=t._endPiece;++n)if(!e.bitfield.get(n))return;t.done=!0,t.emit("done"),e._debug("file done: "+t.name)}});for(var t=!0,n=0,r;n=e.client.maxConns)){this._debug("drain (%s queued, %s/%s peers)",e._numQueued,e.numPeers,e.client.maxConns);var t=e._queue.shift();if(t){this._debug("tcp connect attempt to %s",t.addr);var n=u(t.addr),r={host:n[0],port:n[1]},o=t.conn=E.connect(r);o.once("connect",function(){t.onConnect()}),o.once("error",function(e){t.destroy(e)}),t.startConnectTimeout(),o.on("close",function(){if(!e.destroyed){if(t.retries>=X.length)return void e._debug("conn %s closed: will not re-add (max %s attempts)",t.addr,X.length);var n=X[t.retries];e._debug("conn %s closed: will re-add to queue in %sms (attempt %s)",t.addr,n,t.retries+1);var r=setTimeout(function(){var n=e._addPeer(t.addr);n&&(n.retries=t.retries+1)},n);r.unref&&r.unref()}})}}},s.prototype._validAddr=function(e){var t;try{t=u(e)}catch(t){return!1}var n=t[0],r=t[1];return 0r&&("127.0.0.1"!==n||r!==this.client.torrentPort)}}).call(this,n("_process"),"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global)},{"../package.json":169,"./file":164,"./peer":165,"./rarity-map":166,"./server":3,_process:15,"addr-to-ip-port":42,bitfield:73,"chunk-store-stream/write":83,debug:87,events:7,fs:1,"fs-chunk-store":105,"immediate-chunk-store":95,inherits:96,multistream:113,net:3,os:3,"parse-torrent":117,path:13,pump:120,"random-iterate":121,"run-parallel":136,"run-parallel-limit":135,"simple-get":140,"simple-sha1":142,speedometer:144,"torrent-discovery":152,"torrent-piece":153,uniq:156,ut_metadata:158,ut_pex:3,xtend:171,"xtend/mutable":172}],168:[function(t,n){function r(e,t){c.call(this),this.url=e,this.webPeerId=p.sync(e),this._torrent=t,this._init()}n.exports=r;var o=t("bitfield"),i=t("safe-buffer").Buffer,s=t("debug")("webtorrent:webconn"),a=t("simple-get"),l=t("inherits"),p=t("simple-sha1"),c=t("bittorrent-protocol"),u=t("../package.json").version;l(r,c),r.prototype._init=function(){var e=this;e.setKeepAlive(!0),e.once("handshake",function(t){if(!e.destroyed){e.handshake(t,e.webPeerId);for(var n=e._torrent.pieces.length,r=new o(n),s=0;s<=n;s++)r.set(s,!0);e.bitfield(r)}}),e.once("interested",function(){s("interested"),e.unchoke()}),e.on("uninterested",function(){s("uninterested")}),e.on("choke",function(){s("choke")}),e.on("unchoke",function(){s("unchoke")}),e.on("bitfield",function(){s("bitfield")}),e.on("request",function(t,n,r,o){s("request pieceIndex=%d offset=%d length=%d",t,n,r),e.httpRequest(t,n,r,o)})},r.prototype.httpRequest=function(t,n,r,o){var l=this,p=t*l._torrent.pieceLength,c=p+n,f=c+r-1,h=l._torrent.files,g;if(1>=h.length)g=[{url:l.url,start:c,end:f}];else{var m=h.filter(function(e){return e.offset<=f&&e.offset+e.length>c});if(1>m.length)return o(new Error("Could not find file corresponnding to web seed range request"));g=m.map(function(t){var n=t.offset+t.length-1,r=l.url+("/"===l.url[l.url.length-1]?"":"/")+t.path;return{url:r,fileOffsetInRange:e(t.offset-c,0),start:e(c-t.offset,0),end:d(n,f-t.offset)}})}var _=0,y=!1,b;1t.statusCode||300<=t.statusCode?(y=!0,o(new Error("Unexpected HTTP status code "+t.statusCode))):void(s("Got data of length %d",n.length),1===g.length?o(null,n):(n.copy(b,e.fileOffsetInRange),++_===g.length&&o(null,b)))}var d=e.url,l=e.start,p=e.end;s("Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d",d,t,n,r,l,p);var c={url:d,method:"GET",headers:{"user-agent":"WebTorrent/"+u+" (https://webtorrent.io)",range:"bytes="+l+"-"+p}};a.concat(c,function(e,t,n){return y?void 0:e?"undefined"==typeof window||d.startsWith(window.location.origin+"/")?(y=!0,o(e)):a.head(d,function(t,n){return y?void 0:t?(y=!0,o(t)):200>n.statusCode||300<=n.statusCode?(y=!0,o(new Error("Unexpected HTTP status code "+n.statusCode))):n.url===d?(y=!0,o(e)):void(c.url=n.url,a.concat(c,function(e,t,n){return y?void 0:e?(y=!0,o(e)):void i(t,n)}))}):void i(t,n)})})},r.prototype.destroy=function(){c.prototype.destroy.call(this),this._torrent=null}},{"../package.json":169,bitfield:73,"bittorrent-protocol":74,debug:87,inherits:96,"safe-buffer":138,"simple-get":140,"simple-sha1":142}],169:[function(e,t){t.exports={version:"0.98.19"}},{}],170:[function(e,t){function n(e,t){function r(){for(var t=Array(arguments.length),n=0;n ./dist/pear-player.js","uglify-player":"browserify -s PearPlayer -e ./index.player.js | babili > ./dist/pear-player.min.js","pull-from-github":"git pull","push-to-github":"git add . && git commit -m 'update' && git push","npm-publish":"npm publish"},author:"Xie Ting Pear Limited",license:"MIT",homepage:"https://pear.hk",keywords:["WebRTC","video","player","p2p","peer-to-peer","peers","streaming","multiple source","torrent","web torrent","webrtc data channel","webtorrent"]}},{}],175:[function(e,t){(function(n){function o(e){a.call(this);var t=this;if(!(e.initialDownloaders&&e.fileSize&&e.scheduler))throw new Error("config is not completed");t.fileSize=e.fileSize,t.initialDownloaders=e.initialDownloaders,t.pieceLength=e.chunkSize||524288,t.interval=e.interval||5e3,t.auto=e.auto||!1,t.useMonitor=e.useMonitor||!1,t.downloaded=0,t.fogDownloaded=0,t._windowOffset=0,t.ready=!1,t.done=!1,t.destroyed=!1,t.chunks=0e||t=e.chunks)&&!(n>=t.length);){if(!e.bitfield.get(r)){var o=e._calRange(r),i=t[n%t.length];i.select(o[0],o[1]),n++}else;r++}e._windowEnd=r,d("_fillWindow _windowEnd:"+e._windowEnd)}}},o.prototype._createPushStream=function(){var e=this,t=this.downloaders;if(0!==t.length){for(var n=0,r=e._windowEnd;n!==e.maxLoaders&&!(r>=e.chunks);){if(!e.bitfield.get(r)){var o=e._calRange(r),i=t[n%t.length];i.select(o[0],o[1]),n++}else;r++}e._windowEnd=r}},o.prototype._setupHttp=function(e){var t=this;return e.once("start",function(){}),e.once("done",function(){}),e.once("error",function(){console.warn("http"+e.uri+"error!"),t.downloaders.length>t._windowLength&&(t.downloaders.removeObj(e),3=t.fogRatio&&t.emit("fograte",a),t.emit("fogspeed",t.downloaders.getCurrentSpeed([1])),t.bufferSources[i]=1===e.type?e.id:"b"}else t.emit("cloudspeed",t.downloaders.getCurrentSpeed([0])),t.bufferSources[i]=e.id;t.emit("buffersources",t.bufferSources),t.emit("sourcemap",1===e.type?"n":"s",i)}}),e},o.prototype._setupDC=function(e){var t=this;e.once("start",function(){}),e.on("data",function(n,r,o){var i=t._calIndex(r);d("pear_webrtc "+e.dc_id+" ondata range:"+r+"-"+o+" at index:"+i+" speed:"+e.meanSpeed);var s=o-r+1;if(!!t.bitfield.get(i)){d("\u91CD\u590D\u4E0B\u8F7D");for(var a=0;a=t.fogRatio&&t.emit("fograte",l),t.emit("fogspeed",t.downloaders.getCurrentSpeed([2])),t.bufferSources[i]="d",t.emit("buffersources",t.bufferSources),t.emit("sourcemap","d",i),t.emit("traffic",e.mac,s,"WebRTC_Node",e.meanSpeed)}}),e.once("error",function(){console.warn("webrtc error mac:"+e.mac),e.close(),t.downloaders.removeObj(e),t.downloaders.length=this.downloaders.length&&!this.noMoreNodes&&(this.requestMoreNodes(),this.requestMoreDataChannels(),2>=this.downloaders.length&&2<=this._windowLength/this.downloaders.length&&this.emit("needsource"))},o.prototype.addTorrent=function(e){var t=this;e.pieces.length!==this.chunks||(this.torrent=e,e.pear_downloaded=0,d("addTorrent _windowOffset:"+t._windowOffset),t._windowOffset+t._windowLength=t.fogRatio&&t.emit("fograte",r),t.emit("fogspeed",e.downloadSpeed/1024),t.bufferSources[n]="b",t.emit("buffersources",t.bufferSources),t.emit("sourcemap","b",n),t.emit("traffic","Webtorrent",t.pieceLength,"WebRTC_Browser")}}),e.on("done",function(){d("torrent done")}))},o.prototype.addDataChannel=function(e){this.downloaders.splice(this._windowLength-1,0,e),this._setupDC(e),!this.sequencial&&10>this._windowLength&&this._windowLength++},o.prototype.addNode=function(e){this._setupHttp(e),this.downloaders.push(e),d("dispatcher add node: "+e.uri),!this.sequencial&&10>this._windowLength&&this._windowLength++},o.prototype.requestMoreNodes=function(){0this.status){if(n.downloading=!1,n.endTime=new Date().getTime(),n.speed=r(e.total/(n.endTime-n.startTime)),o("http speed:"+n.speed+"KB/s"),-1==n.meanSpeed&&(n.meanSpeed=n.speed),n.meanSpeed=0.95*n.meanSpeed+0.05*n.speed,o("http "+n.uri+" meanSpeed:"+n.meanSpeed+"KB/s"),!n.isAsync&&0e||t2*(r._numConns-r.numPeers)&&e.amInterested?e.destroy():(o=setTimeout(t,F),o.unref&&o.unref()))}function n(){if(e.peerPieces.buffer.length===r.bitfield.buffer.length){for(s=0;s131072?e.destroy():void(r.pieces[t]||r.store.get(t,{offset:n,length:o},i))}),e.bitfield(r.bitfield),e.interested(),e.peerExtensions.dht&&r.client.dht&&r.client.dht.listening&&e.port(r.client.dht.address().port),"webSeed"!==e.type&&(o=setTimeout(t,F),o.unref&&o.unref()),e.isSeeder=!1,n()},s.prototype._updateSelections=function(){var e=this;!e.ready||e.destroyed||(o.nextTick(function(){e._gcSelections()}),e._updateInterest(),e._update())},s.prototype._gcSelections=function(){for(var e=this,t=0;t=e&&s<=n&&!(s in r)&&t.peerPieces.get(s)&&(!o||o(s))}}function r(){if(!t.requests.length)for(var e=d._selections.length;e--;){var r=d._selections[e],o;if("rarest"===d.strategy)for(var i=r.from+r.offset,s=r.to,a={},l=0,p=n(i,s,a);lo));){if(d._request(t,o,!1))return;a[o]=!0,l+=1}else for(o=r.to;o>=r.from+r.offset;--o)if(t.peerPieces.get(o)&&d._request(t,o,!1))return}}function o(){var n=t.downloadSpeed()||1;if(n>V)return function(){return!0};var r=e(1,t.requests.length)*P.BLOCK_LENGTH/n,o=10,i=0;return function(e){if(!o||d.bitfield.get(e))return!0;for(var t=d.pieces[e].missing;i=p)return!0;for(var r=o(),a=0;al));){for(;d._request(t,l,d._critical[l]||e););if(t.requests.length=l)){var p=a(t,G);i(!1)||i(!0)}}},s.prototype._rechoke=function(){function e(e,t){return e.downloadSpeed===t.downloadSpeed?e.uploadSpeed===t.uploadSpeed?e.wire.amChoking===t.wire.amChoking?e.salt-t.salt:e.wire.amChoking?1:-1:t.uploadSpeed-e.uploadSpeed:t.downloadSpeed-e.downloadSpeed}var t=this;if(t.ready){0=V||2*p>o||p>d||(a=i,d=p)}}if(!a)return!1;for(l=0;l=f)return!1;var h=p.pieces[t],g=u?h.reserveRemaining():h.reserve();if(-1===g&&n&&p._hotswap(e,t)&&(g=u?h.reserveRemaining():h.reserve()),-1===g)return!1;var m=p._reservations[t];m||(m=p._reservations[t]=[]);var r=m.indexOf(null);-1===r&&(r=m.length),m[r]=e;var i=h.chunkOffset(g),_=u?h.chunkLengthRemaining(g):h.chunkLength(g);return e.request(t,i,_,function n(o,d){if(!p.destroyed){if(!p.ready)return p.once("ready",function(){n(o,d)});if(m[r]===e&&(m[r]=null),h!==p.pieces[t])return s();if(o)return p._debug("error getting piece %s (offset: %s length: %s) from %s: %s",t,i,_,e.remoteAddress+":"+e.remotePort,o.message),u?h.cancelRemaining(g):h.cancel(g),void s();if(p._debug("got piece %s (offset: %s length: %s) from %s",t,i,_,e.remoteAddress+":"+e.remotePort),!h.set(g,d,e))return s();var a=h.flush();O(a,function(e){if(!p.destroyed){if(e===p._hashes[t]){if(!p.pieces[t])return;p._debug("piece verified %s",t),p.pieces[t]=null,p._reservations[t]=null,p.bitfield.get(t)||p.emit("piecefromtorrent",t),p.bitfield.set(t,!0),p.store.put(t,a),p.wires.forEach(function(e){e.have(t)}),p._checkDone()&&!p.destroyed&&p.discovery.complete()}else p.pieces[t]=new P(h.length),p.emit("warning",new Error("Piece "+t+" failed verification"));s()}})}}),!0},s.prototype._checkDone=function(){var e=this;if(!e.destroyed){e.files.forEach(function(t){if(!t.done){for(var n=t._startPiece;n<=t._endPiece;++n)if(!e.bitfield.get(n))return;t.done=!0,t.emit("done"),e._debug("file done: "+t.name)}});for(var t=!0,n=0,r;n=e.client.maxConns)){this._debug("drain (%s queued, %s/%s peers)",e._numQueued,e.numPeers,e.client.maxConns);var t=e._queue.shift();if(t){this._debug("tcp connect attempt to %s",t.addr);var n=u(t.addr),r={host:n[0],port:n[1]},o=t.conn=E.connect(r);o.once("connect",function(){t.onConnect()}),o.once("error",function(e){t.destroy(e)}),t.startConnectTimeout(),o.on("close",function(){if(!e.destroyed){if(t.retries>=X.length)return void e._debug("conn %s closed: will not re-add (max %s attempts)",t.addr,X.length);var n=X[t.retries];e._debug("conn %s closed: will re-add to queue in %sms (attempt %s)",t.addr,n,t.retries+1);var r=setTimeout(function(){var n=e._addPeer(t.addr);n&&(n.retries=t.retries+1)},n);r.unref&&r.unref()}})}}},s.prototype._validAddr=function(e){var t;try{t=u(e)}catch(t){return!1}var n=t[0],r=t[1];return 0r&&("127.0.0.1"!==n||r!==this.client.torrentPort)}}).call(this,n("_process"),"undefined"==typeof global?"undefined"==typeof self?"undefined"==typeof window?{}:window:self:global)},{"../package.json":188,"./file":181,"./peer":182,"./rarity-map":183,"./server":3,_process:15,"addr-to-ip-port":42,bitfield:73,"chunk-store-stream/write":83,debug:87,events:7,fs:1,"fs-chunk-store":105,"immediate-chunk-store":95,inherits:96,multistream:113,net:3,os:3,"parse-torrent":117,path:13,pump:120,"random-iterate":121,"run-parallel":136,"run-parallel-limit":135,"simple-get":140,"simple-sha1":142,speedometer:144,"torrent-discovery":152,"torrent-piece":153,uniq:156,ut_metadata:158,ut_pex:3,xtend:171,"xtend/mutable":172}],185:[function(t,n){function r(e,t){c.call(this),this.url=e,this.webPeerId=p.sync(e),this._torrent=t,this._init()}n.exports=r;var o=t("bitfield"),i=t("safe-buffer").Buffer,s=t("debug")("webtorrent:webconn"),a=t("simple-get"),l=t("inherits"),p=t("simple-sha1"),c=t("bittorrent-protocol"),u=t("../package.json").version;l(r,c),r.prototype._init=function(){var e=this;e.setKeepAlive(!0),e.once("handshake",function(t){if(!e.destroyed){e.handshake(t,e.webPeerId);for(var n=e._torrent.pieces.length,r=new o(n),s=0;s<=n;s++)r.set(s,!0);e.bitfield(r)}}),e.once("interested",function(){s("interested"),e.unchoke()}),e.on("uninterested",function(){s("uninterested")}),e.on("choke",function(){s("choke")}),e.on("unchoke",function(){s("unchoke")}),e.on("bitfield",function(){s("bitfield")}),e.on("request",function(t,n,r,o){s("request pieceIndex=%d offset=%d length=%d",t,n,r),e.httpRequest(t,n,r,o)})},r.prototype.httpRequest=function(t,n,r,o){var l=this,p=t*l._torrent.pieceLength,c=p+n,f=c+r-1,h=l._torrent.files,g;if(1>=h.length)g=[{url:l.url,start:c,end:f}];else{var m=h.filter(function(e){return e.offset<=f&&e.offset+e.length>c});if(1>m.length)return o(new Error("Could not find file corresponnding to web seed range request"));g=m.map(function(t){var n=t.offset+t.length-1,r=l.url+("/"===l.url[l.url.length-1]?"":"/")+t.path;return{url:r,fileOffsetInRange:e(t.offset-c,0),start:e(c-t.offset,0),end:d(n,f-t.offset)}})}var _=0,y=!1,b;1t.statusCode||300<=t.statusCode?(y=!0,o(new Error("Unexpected HTTP status code "+t.statusCode))):void(s("Got data of length %d",n.length),1===g.length?o(null,n):(n.copy(b,e.fileOffsetInRange),++_===g.length&&o(null,b)))}var d=e.url,l=e.start,p=e.end;s("Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d",d,t,n,r,l,p);var c={url:d,method:"GET",headers:{"user-agent":"WebTorrent/"+u+" (https://webtorrent.io)",range:"bytes="+l+"-"+p}};a.concat(c,function(e,t,n){return y?void 0:e?"undefined"==typeof window||d.startsWith(window.location.origin+"/")?(y=!0,o(e)):a.head(d,function(t,n){return y?void 0:t?(y=!0,o(t)):200>n.statusCode||300<=n.statusCode?(y=!0,o(new Error("Unexpected HTTP status code "+n.statusCode))):n.url===d?(y=!0,o(e)):void(c.url=n.url,a.concat(c,function(e,t,n){return y?void 0:e?(y=!0,o(e)):void i(t,n)}))}):void i(t,n)})})},r.prototype.destroy=function(){c.prototype.destroy.call(this),this._torrent=null}},{"../package.json":188,bitfield:73,"bittorrent-protocol":74,debug:87,inherits:96,"safe-buffer":138,"simple-get":140,"simple-sha1":142}],186:[function(e,t){t.exports=function(e,t,r){function o(e){var t=new XMLHttpRequest;t.timeout=1e3,t.open("head",e.uri),t.onload=function(){d++,200<=this.status&&300>this.status&&(a.push(e),l=t.getResponseHeader("content-length")),s()},t.ontimeout=function(){d++,n(e.uri+" timeout"),s()},t.onerror=function(){d++,s()},t.send()}function s(){if(d==r.end-r.start){a.sort(function(e,t){return t.capacity-e.capacity});for(var e=0;ee.length&&(r.end=e.length):r={start:0,end:e.length};for(var p=r.start;p=e.queue.length}).sort(function(e,t){return e.queue.length-t.queue.length});r.sort(function(e,t){return t.meanSpeed-e.meanSpeed});var o=n.concat(r);return o.length>t.windowLength&&(o=o.filter(function(e){return 0!==e.type})),o},WebRTCFirst:function(e,t){var n=e.filter(function(e){return!1===e.downloading}).sort(function(e,t){return t.type-e.type}),r=e.filter(function(e){return!0===e.downloading&&1>=e.queue.length}).sort(function(e,t){return e.queue.length-t.queue.length}),o=n.concat(r);return o.length>t.windowLength&&(o=o.filter(function(e){return 0!==e.type})),o},CloudFirst:function(e,t){var n=e.filter(function(e){return!1===e.downloading}).sort(function(e,t){return e.type-t.type}),r=e.filter(function(e){return!0===e.downloading&&1>=e.queue.length}).sort(function(e,t){return e.queue.length-t.queue.length}),o=n.concat(r);return o.length>t.windowLength&&(o=o.filter(function(e){return 0!==e.type})),o}}},{debug:87}],188:[function(e,t){t.exports={name:"webtorrent",description:"Streaming torrent client",version:"0.98.19",author:{name:"WebTorrent, LLC",email:"feross@webtorrent.io",url:"https://webtorrent.io"},browser:{"./lib/server.js":!1,"./lib/tcp-pool.js":!1,"bittorrent-dht/client":!1,"fs-chunk-store":"memory-chunk-store","load-ip-set":!1,net:!1,os:!1,ut_pex:!1},browserify:{transform:["package-json-versionify"]},bugs:{url:"https://github.com/webtorrent/webtorrent/issues"},dependencies:{"addr-to-ip-port":"^1.4.2",bitfield:"^1.1.2","bittorrent-dht":"^7.2.2","bittorrent-protocol":"^2.1.5","chunk-store-stream":"^2.0.2","create-torrent":"^3.24.5",debug:"^2.2.0","end-of-stream":"^1.1.0","fs-chunk-store":"^1.6.2","immediate-chunk-store":"^1.0.8",inherits:"^2.0.1","load-ip-set":"^1.2.7","memory-chunk-store":"^1.2.0",mime:"^1.3.4",multistream:"^2.0.5","package-json-versionify":"^1.0.2","parse-torrent":"^5.8.0",pump:"^1.0.1","random-iterate":"^1.0.1",randombytes:"^2.0.3","range-parser":"^1.2.0","readable-stream":"^2.1.4","render-media":"^2.8.0","run-parallel":"^1.1.6","run-parallel-limit":"^1.0.3","safe-buffer":"^5.0.1","simple-concat":"^1.0.0","simple-get":"^2.2.1","simple-peer":"^8.0.0","simple-sha1":"^2.0.8",speedometer:"^1.0.0","stream-to-blob":"^1.0.0","stream-to-blob-url":"^2.1.0","stream-with-known-length-to-buffer":"^1.0.0","torrent-discovery":"^8.1.0","torrent-piece":"^1.1.0",uniq:"^1.0.1","unordered-array-remove":"^1.0.2",ut_metadata:"^3.0.8",ut_pex:"^1.1.1",xtend:"^4.0.1","zero-fill":"^2.2.3"},devDependencies:{babili:"^0.1.4","bittorrent-tracker":"^9.0.0",brfs:"^1.4.3",browserify:"^14.0.0","cross-spawn":"^5.0.1","electron-prebuilt":"^0.37.8",finalhandler:"^1.0.0","network-address":"^1.1.0","run-series":"^1.1.4","serve-static":"^1.11.1",standard:"*",tape:"^4.6.0","webtorrent-fixtures":"^1.5.0",zuul:"^3.10.1"},engines:{node:">=4"},homepage:"https://webtorrent.io",keywords:["bittorrent","bittorrent client","download","mad science","p2p","peer-to-peer","peers","streaming","swarm","torrent","web torrent","webrtc","webrtc data","webtorrent"],license:"MIT",main:"index.js",repository:{type:"git",url:"git://github.com/webtorrent/webtorrent.git"},scripts:{build:"browserify -s WebTorrent -e ./ | babili > webtorrent.min.js","build-debug":"browserify -s WebTorrent -e ./ > webtorrent.debug.js",size:"npm run build && cat webtorrent.min.js | gzip | wc -c",test:"standard && node ./bin/test.js","test-browser":"zuul -- test/*.js test/browser/*.js","test-browser-headless":"zuul --electron -- test/*.js test/browser/*.js","test-browser-local":"zuul --local -- test/*.js test/browser/*.js","test-node":"tape test/*.js test/node/*.js","update-authors":"./bin/update-authors.sh"}}},{}],189:[function(e,t){(function(n,r){function o(e){function t(){i.destroyed||(i.ready=!0,i.emit("ready"))}var i=this;return i instanceof o?void(u.call(i),!e&&(e={}),i.peerId="string"==typeof e.peerId?e.peerId:d.isBuffer(e.peerId)?e.peerId.toString("hex"):d.from(T+w(9).toString("base64")).toString("hex"),i.peerIdBuffer=d.from(i.peerId,"hex"),i.nodeId="string"==typeof e.nodeId?e.nodeId:d.isBuffer(e.nodeId)?e.nodeId.toString("hex"):w(20).toString("hex"),i.nodeIdBuffer=d.from(i.nodeId,"hex"),i._debugId=i.peerId.toString("hex").substring(0,7),i.destroyed=!1,i.listening=!1,i.torrentPort=e.torrentPort||0,i.dhtPort=e.dhtPort||0,i.tracker=e.tracker===void 0?{}:e.tracker,i.torrents=[],i.maxConns=+e.maxConns||55,i._debug("new webtorrent (peerId %s, nodeId %s, port %s)",i.peerId,i.nodeId,i.torrentPort),i.tracker&&("object"!=typeof i.tracker&&(i.tracker={}),e.rtcConfig&&(console.warn("WebTorrent: opts.rtcConfig is deprecated. Use opts.tracker.rtcConfig instead"),i.tracker.rtcConfig=e.rtcConfig),e.wrtc&&(console.warn("WebTorrent: opts.wrtc is deprecated. Use opts.tracker.wrtc instead"),i.tracker.wrtc=e.wrtc),r.WRTC&&!i.tracker.wrtc&&(i.tracker.wrtc=r.WRTC)),"function"==typeof S?i._tcpPool=new S(i):n.nextTick(function(){i._onListening()}),i._downloadSpeed=k(),i._uploadSpeed=k(),!1!==e.dht&&"function"==typeof c?(i.dht=new c(f({nodeId:i.nodeId},e.dht)),i.dht.once("error",function(e){i._destroy(e)}),i.dht.once("listening",function(){var e=i.dht.address();e&&(i.dhtPort=e.port)}),i.dht.setMaxListeners(0),i.dht.listen(i.dhtPort)):i.dht=!1,i.enableWebSeeds=!1!==e.webSeeds,"function"==typeof g&&null!=e.blocklist?g(e.blocklist,{headers:{"user-agent":"WebTorrent/"+C+" (https://webtorrent.io)"}},function(e,n){return e?i.error("Failed to load blocklist: "+e.message):void(i.blocked=n,t())}):n.nextTick(t)):new o(e)}function i(e){return"object"==typeof e&&null!=e&&"function"==typeof e.pipe}function s(e){return"undefined"!=typeof FileList&&e instanceof FileList}t.exports=o;var d=e("safe-buffer").Buffer,a=e("simple-concat"),l=e("create-torrent"),p=e("debug")("webtorrent"),c=e("bittorrent-dht/client"),u=e("events").EventEmitter,f=e("xtend"),h=e("inherits"),g=e("load-ip-set"),m=e("run-parallel"),_=e("parse-torrent"),y=e("path"),b=e("simple-peer"),w=e("randombytes"),k=e("speedometer"),x=e("zero-fill"),S=e("./lib/tcp-pool"),v=e("./lib/torrent"),C=e("./package.json").version,E=C.match(/([0-9]+)/g).slice(0,2).map(function(e){return x(2,e)}).join(""),T="-WW"+E+"-";h(o,u),o.WEBRTC_SUPPORT=b.WEBRTC_SUPPORT,Object.defineProperty(o.prototype,"downloadSpeed",{get:function(){return this._downloadSpeed()}}),Object.defineProperty(o.prototype,"uploadSpeed",{get:function(){return this._uploadSpeed()}}),Object.defineProperty(o.prototype,"progress",{get:function(){var e=this.torrents.filter(function(e){return 1!==e.progress}),t=e.reduce(function(e,t){return e+t.downloaded},0),n=e.reduce(function(e,t){return e+(t.length||0)},0)||1;return t/n}}),Object.defineProperty(o.prototype,"ratio",{get:function(){var e=this.torrents.reduce(function(e,t){return e+t.uploaded},0),t=this.torrents.reduce(function(e,t){return e+t.received},0)||1;return e/t}}),o.prototype.get=function(e){var t=this,n=t.torrents.length,r,o;if(e instanceof v){for(r=0;r=o+10485760){r({method:"post",url:"/traffic",data:{uuid:e,size:+t,traffic:n}}).then(function(e){200==e.status&&(o=s)})}},finalyReportTraffic:function(e,t,o){r({method:"post",url:"/traffic",data:{uuid:e,size:+t,traffic:o}}).then(function(e){200==e.status&&n("finalyReportTraffic")})},reportAbilities:function(e){var t=0;for(var o in e)t+=e[o];t/=Object.getOwnPropertyNames(e).length;var i={};for(var o in e)i[o]=5*(e[o]/t),console.log("reportAbilities mac:"+o+" ability:"+i[o]);r({method:"post",url:"https://api.webrtc.win/v2/customer/stats/nodes/capacity",data:i}).then(function(e){n("reportAbilities response:"+JSON.stringify(e))})}}},{axios:43,debug:87}],192:[function(e,t){function n(){this.items={}}t.exports=n,n.prototype={constructer:n,has:function(e){return e in this.items},add:function(e){return!this.has(e)&&(this.items[e]=e,!0)},remove:function(e){return!!this.has(e)&&(delete this.items[e],!0)},clear:function(){this.items={}},size:function(){return Object.keys(this.items).length},values:function(){return Object.keys(this.items)},union:function(e){for(var t=new n,r=this.values(),o=0;oe.size())return!1;for(var t=this.values(),n=0;nthis.status||304==this.status){s(this.response);var e=JSON.parse(this.response);if(!e.size)t(null);else if(n.fileLength=e.size,n.useDataChannel&&n._pearSignalHandshake(),!e.nodes)n._fallBackToWRTC();else{for(var r=e.nodes,o=[],d="http:"===location.protocol,a=0,l=0,p=0,i;p=o?(e.push({uri:n.src,type:"server"}),t(e)):20<=e.length?(e=e.slice(0,20),t(e)):t(e)):t([{uri:n.src,type:"server"}])},{start:0,end:30})}}else t(null)},o.send()},r.prototype._pearSignalHandshake=function(){var e=this,t=0;s("_pearSignalHandshake");var n=new WebSocket(e.websocketAddr);e.websocket=n,n.onopen=function(){e._debugInfo.signalServerConnected=!0;var t=d(e.urlObj.host+e.urlObj.path);n.push(JSON.stringify({action:"get",peer_id:e.peerId,host:e.urlObj.host,uri:e.urlObj.path,md5:t}))},n.push=n.send,n.send=function(e){return 1==n.readyState?void n.push(e):(console.warn("websocket connection is not opened yet."),setTimeout(function(){n.send(e)},1e3))},n.onmessage=function(n){var r=JSON.parse(n.data);if("candidate"===r.action&&"end"===r.candidates.type)for(var o in e.candidateMap)r.peer_id===o&&e.JDMap[o].candidatesFromWS(e.candidateMap[o]);else if(r.nodes){var d=r.nodes;e._debugInfo.totalDCs=d.length;for(var a=0,l;a