UNPKG

timesync

Version:

Time synchronization between peers

1,669 lines (1,433 loc) 47.4 kB
(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.timesync = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; // rawAsap provides everything we need except exception management. var rawAsap = require("./raw"); // RawTasks are recycled to reduce GC churn. var freeTasks = []; // We queue errors to ensure they are thrown in right order (FIFO). // Array-as-queue is good enough here, since we are just dealing with exceptions. var pendingErrors = []; var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError); function throwFirstError() { if (pendingErrors.length) { throw pendingErrors.shift(); } } /** * Calls a task as soon as possible after returning, in its own event, with priority * over other events like animation, reflow, and repaint. An error thrown from an * event will not interrupt, nor even substantially slow down the processing of * other events, but will be rather postponed to a lower priority event. * @param {{call}} task A callable object, typically a function that takes no * arguments. */ module.exports = asap; function asap(task) { var rawTask; if (freeTasks.length) { rawTask = freeTasks.pop(); } else { rawTask = new RawTask(); } rawTask.task = task; rawAsap(rawTask); } // We wrap tasks with recyclable task objects. A task object implements // `call`, just like a function. function RawTask() { this.task = null; } // The sole purpose of wrapping the task is to catch the exception and recycle // the task object after its single use. RawTask.prototype.call = function () { try { this.task.call(); } catch (error) { if (asap.onerror) { // This hook exists purely for testing purposes. // Its name will be periodically randomized to break any code that // depends on its existence. asap.onerror(error); } else { // In a web browser, exceptions are not fatal. However, to avoid // slowing down the queue of pending tasks, we rethrow the error in a // lower priority turn. pendingErrors.push(error); requestErrorThrow(); } } finally { this.task = null; freeTasks[freeTasks.length] = this; } }; },{"./raw":2}],2:[function(require,module,exports){ "use strict"; // Use the fastest means possible to execute a task in its own turn, with // priority over other events including IO, animation, reflow, and redraw // events in browsers. // // An exception thrown by a task will permanently interrupt the processing of // subsequent tasks. The higher level `asap` function ensures that if an // exception is thrown by a task, that the task queue will continue flushing as // soon as possible, but if you use `rawAsap` directly, you are responsible to // either ensure that no exceptions are thrown from your task, or to manually // call `rawAsap.requestFlush` if an exception is thrown. module.exports = rawAsap; function rawAsap(task) { if (!queue.length) { requestFlush(); flushing = true; } // Equivalent to push, but avoids a function call. queue[queue.length] = task; } var queue = []; // Once a flush has been requested, no further calls to `requestFlush` are // necessary until the next `flush` completes. var flushing = false; // `requestFlush` is an implementation-specific method that attempts to kick // off a `flush` event as quickly as possible. `flush` will attempt to exhaust // the event queue before yielding to the browser's own event loop. var requestFlush; // The position of the next task to execute in the task queue. This is // preserved between calls to `flush` so that it can be resumed if // a task throws an exception. var index = 0; // If a task schedules additional tasks recursively, the task queue can grow // unbounded. To prevent memory exhaustion, the task queue will periodically // truncate already-completed tasks. var capacity = 1024; // The flush function processes all tasks that have been scheduled with // `rawAsap` unless and until one of those tasks throws an exception. // If a task throws an exception, `flush` ensures that its state will remain // consistent and will resume where it left off when called again. // However, `flush` does not make any arrangements to be called again if an // exception is thrown. function flush() { while (index < queue.length) { var currentIndex = index; // Advance the index before calling the task. This ensures that we will // begin flushing on the next task the task throws an error. index = index + 1; queue[currentIndex].call(); // Prevent leaking memory for long chains of recursive calls to `asap`. // If we call `asap` within tasks scheduled by `asap`, the queue will // grow, but to avoid an O(n) walk for every task we execute, we don't // shift tasks off the queue after they have been executed. // Instead, we periodically shift 1024 tasks off the queue. if (index > capacity) { // Manually shift all values starting at the index back to the // beginning of the queue. for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) { queue[scan] = queue[scan + index]; } queue.length -= index; index = 0; } } queue.length = 0; index = 0; flushing = false; } // `requestFlush` is implemented using a strategy based on data collected from // every available SauceLabs Selenium web driver worker at time of writing. // https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593 // Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that // have WebKitMutationObserver but not un-prefixed MutationObserver. // Must use `global` or `self` instead of `window` to work in both frames and web // workers. `global` is a provision of Browserify, Mr, Mrs, or Mop. /* globals self */ var scope = typeof global !== "undefined" ? global : self; var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver; // MutationObservers are desirable because they have high priority and work // reliably everywhere they are implemented. // They are implemented in all modern browsers. // // - Android 4-4.3 // - Chrome 26-34 // - Firefox 14-29 // - Internet Explorer 11 // - iPad Safari 6-7.1 // - iPhone Safari 7-7.1 // - Safari 6-7 if (typeof BrowserMutationObserver === "function") { requestFlush = makeRequestCallFromMutationObserver(flush); // MessageChannels are desirable because they give direct access to the HTML // task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera // 11-12, and in web workers in many engines. // Although message channels yield to any queued rendering and IO tasks, they // would be better than imposing the 4ms delay of timers. // However, they do not work reliably in Internet Explorer or Safari. // Internet Explorer 10 is the only browser that has setImmediate but does // not have MutationObservers. // Although setImmediate yields to the browser's renderer, it would be // preferrable to falling back to setTimeout since it does not have // the minimum 4ms penalty. // Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and // Desktop to a lesser extent) that renders both setImmediate and // MessageChannel useless for the purposes of ASAP. // https://github.com/kriskowal/q/issues/396 // Timers are implemented universally. // We fall back to timers in workers in most engines, and in foreground // contexts in the following browsers. // However, note that even this simple case requires nuances to operate in a // broad spectrum of browsers. // // - Firefox 3-13 // - Internet Explorer 6-9 // - iPad Safari 4.3 // - Lynx 2.8.7 } else { requestFlush = makeRequestCallFromTimer(flush); } // `requestFlush` requests that the high priority event queue be flushed as // soon as possible. // This is useful to prevent an error thrown in a task from stalling the event // queue if the exception handled by Node.js’s // `process.on("uncaughtException")` or by a domain. rawAsap.requestFlush = requestFlush; // To request a high priority event, we induce a mutation observer by toggling // the text of a text node between "1" and "-1". function makeRequestCallFromMutationObserver(callback) { var toggle = 1; var observer = new BrowserMutationObserver(callback); var node = document.createTextNode(""); observer.observe(node, {characterData: true}); return function requestCall() { toggle = -toggle; node.data = toggle; }; } // The message channel technique was discovered by Malte Ubl and was the // original foundation for this library. // http://www.nonblocking.io/2011/06/windownexttick.html // Safari 6.0.5 (at least) intermittently fails to create message ports on a // page's first load. Thankfully, this version of Safari supports // MutationObservers, so we don't need to fall back in that case. // function makeRequestCallFromMessageChannel(callback) { // var channel = new MessageChannel(); // channel.port1.onmessage = callback; // return function requestCall() { // channel.port2.postMessage(0); // }; // } // For reasons explained above, we are also unable to use `setImmediate` // under any circumstances. // Even if we were, there is another bug in Internet Explorer 10. // It is not sufficient to assign `setImmediate` to `requestFlush` because // `setImmediate` must be called *by name* and therefore must be wrapped in a // closure. // Never forget. // function makeRequestCallFromSetImmediate(callback) { // return function requestCall() { // setImmediate(callback); // }; // } // Safari 6.0 has a problem where timers will get lost while the user is // scrolling. This problem does not impact ASAP because Safari 6.0 supports // mutation observers, so that implementation is used instead. // However, if we ever elect to use timers in Safari, the prevalent work-around // is to add a scroll event listener that calls for a flush. // `setTimeout` does not call the passed callback if the delay is less than // approximately 7 in web workers in Firefox 8 through 18, and sometimes not // even then. function makeRequestCallFromTimer(callback) { return function requestCall() { // We dispatch a timeout with a specified delay of 0 for engines that // can reliably accommodate that request. This will usually be snapped // to a 4 milisecond delay, but once we're flushing, there's no delay // between events. var timeoutHandle = setTimeout(handleTimer, 0); // However, since this timer gets frequently dropped in Firefox // workers, we enlist an interval handle that will try to fire // an event 20 times per second until it succeeds. var intervalHandle = setInterval(handleTimer, 50); function handleTimer() { // Whichever timer succeeds will cancel both timers and // execute the callback. clearTimeout(timeoutHandle); clearInterval(intervalHandle); callback(); } }; } // This is for `asap.js` only. // Its name will be periodically randomized to break any code that depends on // its existence. rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer; // ASAP was originally a nextTick shim included in Q. This was factored out // into this ASAP package. It was later adapted to RSVP which made further // amendments. These decisions, particularly to marginalize MessageChannel and // to capture the MutationObserver implementation in a closure, were integrated // back into ASAP proper. // https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js },{}],3:[function(require,module,exports){ 'use strict'; module.exports = require('./lib') },{"./lib":8}],4:[function(require,module,exports){ 'use strict'; var asap = require('asap/raw'); function noop() {} // States: // // 0 - pending // 1 - fulfilled with _value // 2 - rejected with _value // 3 - adopted the state of another promise, _value // // once the state is no longer pending (0) it is immutable // All `_` prefixed properties will be reduced to `_{random number}` // at build time to obfuscate them and discourage their use. // We don't use symbols or Object.defineProperty to fully hide them // because the performance isn't good enough. // to avoid using try/catch inside critical functions, we // extract them to here. var LAST_ERROR = null; var IS_ERROR = {}; function getThen(obj) { try { return obj.then; } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } function tryCallOne(fn, a) { try { return fn(a); } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } function tryCallTwo(fn, a, b) { try { fn(a, b); } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } module.exports = Promise; function Promise(fn) { if (typeof this !== 'object') { throw new TypeError('Promises must be constructed via new'); } if (typeof fn !== 'function') { throw new TypeError('Promise constructor\'s argument is not a function'); } this._1 = 0; this._2 = 0; this._3 = null; this._4 = null; if (fn === noop) return; doResolve(fn, this); } Promise._5 = null; Promise._6 = null; Promise._7 = noop; Promise.prototype.then = function(onFulfilled, onRejected) { if (this.constructor !== Promise) { return safeThen(this, onFulfilled, onRejected); } var res = new Promise(noop); handle(this, new Handler(onFulfilled, onRejected, res)); return res; }; function safeThen(self, onFulfilled, onRejected) { return new self.constructor(function (resolve, reject) { var res = new Promise(noop); res.then(resolve, reject); handle(self, new Handler(onFulfilled, onRejected, res)); }); } function handle(self, deferred) { while (self._2 === 3) { self = self._3; } if (Promise._5) { Promise._5(self); } if (self._2 === 0) { if (self._1 === 0) { self._1 = 1; self._4 = deferred; return; } if (self._1 === 1) { self._1 = 2; self._4 = [self._4, deferred]; return; } self._4.push(deferred); return; } handleResolved(self, deferred); } function handleResolved(self, deferred) { asap(function() { var cb = self._2 === 1 ? deferred.onFulfilled : deferred.onRejected; if (cb === null) { if (self._2 === 1) { resolve(deferred.promise, self._3); } else { reject(deferred.promise, self._3); } return; } var ret = tryCallOne(cb, self._3); if (ret === IS_ERROR) { reject(deferred.promise, LAST_ERROR); } else { resolve(deferred.promise, ret); } }); } function resolve(self, newValue) { // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === self) { return reject( self, new TypeError('A promise cannot be resolved with itself.') ); } if ( newValue && (typeof newValue === 'object' || typeof newValue === 'function') ) { var then = getThen(newValue); if (then === IS_ERROR) { return reject(self, LAST_ERROR); } if ( then === self.then && newValue instanceof Promise ) { self._2 = 3; self._3 = newValue; finale(self); return; } else if (typeof then === 'function') { doResolve(then.bind(newValue), self); return; } } self._2 = 1; self._3 = newValue; finale(self); } function reject(self, newValue) { self._2 = 2; self._3 = newValue; if (Promise._6) { Promise._6(self, newValue); } finale(self); } function finale(self) { if (self._1 === 1) { handle(self, self._4); self._4 = null; } if (self._1 === 2) { for (var i = 0; i < self._4.length; i++) { handle(self, self._4[i]); } self._4 = null; } } function Handler(onFulfilled, onRejected, promise){ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; this.onRejected = typeof onRejected === 'function' ? onRejected : null; this.promise = promise; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. */ function doResolve(fn, promise) { var done = false; var res = tryCallTwo(fn, function (value) { if (done) return; done = true; resolve(promise, value); }, function (reason) { if (done) return; done = true; reject(promise, reason); }); if (!done && res === IS_ERROR) { done = true; reject(promise, LAST_ERROR); } } },{"asap/raw":2}],5:[function(require,module,exports){ 'use strict'; var Promise = require('./core.js'); module.exports = Promise; Promise.prototype.done = function (onFulfilled, onRejected) { var self = arguments.length ? this.then.apply(this, arguments) : this; self.then(null, function (err) { setTimeout(function () { throw err; }, 0); }); }; },{"./core.js":4}],6:[function(require,module,exports){ 'use strict'; //This file contains the ES6 extensions to the core Promises/A+ API var Promise = require('./core.js'); module.exports = Promise; /* Static Functions */ var TRUE = valuePromise(true); var FALSE = valuePromise(false); var NULL = valuePromise(null); var UNDEFINED = valuePromise(undefined); var ZERO = valuePromise(0); var EMPTYSTRING = valuePromise(''); function valuePromise(value) { var p = new Promise(Promise._7); p._2 = 1; p._3 = value; return p; } Promise.resolve = function (value) { if (value instanceof Promise) return value; if (value === null) return NULL; if (value === undefined) return UNDEFINED; if (value === true) return TRUE; if (value === false) return FALSE; if (value === 0) return ZERO; if (value === '') return EMPTYSTRING; if (typeof value === 'object' || typeof value === 'function') { try { var then = value.then; if (typeof then === 'function') { return new Promise(then.bind(value)); } } catch (ex) { return new Promise(function (resolve, reject) { reject(ex); }); } } return valuePromise(value); }; var iterableToArray = function (iterable) { if (typeof Array.from === 'function') { // ES2015+, iterables exist iterableToArray = Array.from; return Array.from(iterable); } // ES5, only arrays and array-likes exist iterableToArray = function (x) { return Array.prototype.slice.call(x); }; return Array.prototype.slice.call(iterable); } Promise.all = function (arr) { var args = iterableToArray(arr); return new Promise(function (resolve, reject) { if (args.length === 0) return resolve([]); var remaining = args.length; function res(i, val) { if (val && (typeof val === 'object' || typeof val === 'function')) { if (val instanceof Promise && val.then === Promise.prototype.then) { while (val._2 === 3) { val = val._3; } if (val._2 === 1) return res(i, val._3); if (val._2 === 2) reject(val._3); val.then(function (val) { res(i, val); }, reject); return; } else { var then = val.then; if (typeof then === 'function') { var p = new Promise(then.bind(val)); p.then(function (val) { res(i, val); }, reject); return; } } } args[i] = val; if (--remaining === 0) { resolve(args); } } for (var i = 0; i < args.length; i++) { res(i, args[i]); } }); }; function onSettledFulfill(value) { return { status: 'fulfilled', value: value }; } function onSettledReject(reason) { return { status: 'rejected', reason: reason }; } function mapAllSettled(item) { if(item && (typeof item === 'object' || typeof item === 'function')){ if(item instanceof Promise && item.then === Promise.prototype.then){ return item.then(onSettledFulfill, onSettledReject); } var then = item.then; if (typeof then === 'function') { return new Promise(then.bind(item)).then(onSettledFulfill, onSettledReject) } } return onSettledFulfill(item); } Promise.allSettled = function (iterable) { return Promise.all(iterableToArray(iterable).map(mapAllSettled)); }; Promise.reject = function (value) { return new Promise(function (resolve, reject) { reject(value); }); }; Promise.race = function (values) { return new Promise(function (resolve, reject) { iterableToArray(values).forEach(function(value){ Promise.resolve(value).then(resolve, reject); }); }); }; /* Prototype Methods */ Promise.prototype['catch'] = function (onRejected) { return this.then(null, onRejected); }; },{"./core.js":4}],7:[function(require,module,exports){ 'use strict'; var Promise = require('./core.js'); module.exports = Promise; Promise.prototype.finally = function (f) { return this.then(function (value) { return Promise.resolve(f()).then(function () { return value; }); }, function (err) { return Promise.resolve(f()).then(function () { throw err; }); }); }; },{"./core.js":4}],8:[function(require,module,exports){ 'use strict'; module.exports = require('./core.js'); require('./done.js'); require('./finally.js'); require('./es6-extensions.js'); require('./node-extensions.js'); require('./synchronous.js'); },{"./core.js":4,"./done.js":5,"./es6-extensions.js":6,"./finally.js":7,"./node-extensions.js":9,"./synchronous.js":10}],9:[function(require,module,exports){ 'use strict'; // This file contains then/promise specific extensions that are only useful // for node.js interop var Promise = require('./core.js'); var asap = require('asap'); module.exports = Promise; /* Static Functions */ Promise.denodeify = function (fn, argumentCount) { if ( typeof argumentCount === 'number' && argumentCount !== Infinity ) { return denodeifyWithCount(fn, argumentCount); } else { return denodeifyWithoutCount(fn); } }; var callbackFn = ( 'function (err, res) {' + 'if (err) { rj(err); } else { rs(res); }' + '}' ); function denodeifyWithCount(fn, argumentCount) { var args = []; for (var i = 0; i < argumentCount; i++) { args.push('a' + i); } var body = [ 'return function (' + args.join(',') + ') {', 'var self = this;', 'return new Promise(function (rs, rj) {', 'var res = fn.call(', ['self'].concat(args).concat([callbackFn]).join(','), ');', 'if (res &&', '(typeof res === "object" || typeof res === "function") &&', 'typeof res.then === "function"', ') {rs(res);}', '});', '};' ].join(''); return Function(['Promise', 'fn'], body)(Promise, fn); } function denodeifyWithoutCount(fn) { var fnLength = Math.max(fn.length - 1, 3); var args = []; for (var i = 0; i < fnLength; i++) { args.push('a' + i); } var body = [ 'return function (' + args.join(',') + ') {', 'var self = this;', 'var args;', 'var argLength = arguments.length;', 'if (arguments.length > ' + fnLength + ') {', 'args = new Array(arguments.length + 1);', 'for (var i = 0; i < arguments.length; i++) {', 'args[i] = arguments[i];', '}', '}', 'return new Promise(function (rs, rj) {', 'var cb = ' + callbackFn + ';', 'var res;', 'switch (argLength) {', args.concat(['extra']).map(function (_, index) { return ( 'case ' + (index) + ':' + 'res = fn.call(' + ['self'].concat(args.slice(0, index)).concat('cb').join(',') + ');' + 'break;' ); }).join(''), 'default:', 'args[argLength] = cb;', 'res = fn.apply(self, args);', '}', 'if (res &&', '(typeof res === "object" || typeof res === "function") &&', 'typeof res.then === "function"', ') {rs(res);}', '});', '};' ].join(''); return Function( ['Promise', 'fn'], body )(Promise, fn); } Promise.nodeify = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); var callback = typeof args[args.length - 1] === 'function' ? args.pop() : null; var ctx = this; try { return fn.apply(this, arguments).nodeify(callback, ctx); } catch (ex) { if (callback === null || typeof callback == 'undefined') { return new Promise(function (resolve, reject) { reject(ex); }); } else { asap(function () { callback.call(ctx, ex); }) } } } }; Promise.prototype.nodeify = function (callback, ctx) { if (typeof callback != 'function') return this; this.then(function (value) { asap(function () { callback.call(ctx, null, value); }); }, function (err) { asap(function () { callback.call(ctx, err); }); }); }; },{"./core.js":4,"asap":1}],10:[function(require,module,exports){ 'use strict'; var Promise = require('./core.js'); module.exports = Promise; Promise.enableSynchronous = function () { Promise.prototype.isPending = function() { return this.getState() == 0; }; Promise.prototype.isFulfilled = function() { return this.getState() == 1; }; Promise.prototype.isRejected = function() { return this.getState() == 2; }; Promise.prototype.getValue = function () { if (this._2 === 3) { return this._3.getValue(); } if (!this.isFulfilled()) { throw new Error('Cannot get a value of an unfulfilled promise.'); } return this._3; }; Promise.prototype.getReason = function () { if (this._2 === 3) { return this._3.getReason(); } if (!this.isRejected()) { throw new Error('Cannot get a rejection reason of a non-rejected promise.'); } return this._3; }; Promise.prototype.getState = function () { if (this._2 === 3) { return this._3.getState(); } if (this._2 === -1 || this._2 === -2) { return 0; } return this._2; }; }; Promise.disableSynchronous = function() { Promise.prototype.isPending = undefined; Promise.prototype.isFulfilled = undefined; Promise.prototype.isRejected = undefined; Promise.prototype.getValue = undefined; Promise.prototype.getReason = undefined; Promise.prototype.getState = undefined; }; },{"./core.js":4}],11:[function(require,module,exports){ "use strict"; module.exports = typeof window === 'undefined' || typeof window.Promise === 'undefined' ? require('promise') : window.Promise; },{"promise":3}],12:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = emitter; /** * Turn an object into an event emitter. Attaches methods `on`, `off`, * `emit`, and `list` * @param {Object} obj * @return {Object} Returns the original object, extended with emitter functions */ function emitter(obj) { var _callbacks = {}; obj.emit = function (event, data) { var callbacks = _callbacks[event]; callbacks && callbacks.forEach(function (callback) { return callback(data); }); }; obj.on = function (event, callback) { var callbacks = _callbacks[event] || (_callbacks[event] = []); callbacks.push(callback); return obj; }; obj.off = function (event, callback) { if (callback) { var callbacks = _callbacks[event]; var index = callbacks.indexOf(callback); if (index !== -1) { callbacks.splice(index, 1); } if (callbacks.length === 0) { delete _callbacks[event]; } } else { delete _callbacks[event]; } return obj; }; obj.list = function (event) { return _callbacks[event] || []; }; return obj; } },{}],13:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.fetch = fetch; exports.post = post; function fetch(method, url, body, headers, callback, timeout) { try { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { var contentType = xhr.getResponseHeader('Content-Type'); if (contentType && contentType.indexOf('json') !== -1) { try { // return JSON object var response = JSON.parse(xhr.responseText); callback(null, response, xhr.status); } catch (err) { callback(err, null, xhr.status); } } else { // return text callback(null, xhr.responseText, xhr.status); } } }; if (headers) { for (var name in headers) { if (headers.hasOwnProperty(name)) { xhr.setRequestHeader(name, headers[name]); } } } xhr.ontimeout = function (err) { callback(err, null, 0); }; xhr.open(method, url, true); xhr.timeout = timeout; if (typeof body === 'string') { xhr.send(body); } else if (body) { // body is an object xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(JSON.stringify(body)); } else { xhr.send(); } } catch (err) { callback(err, null, 0); } } function post(url, body, timeout) { return new Promise(function (resolve, reject) { var callback = function callback(err, res, status) { if (err) { return reject(err); } resolve([res, status]); }; fetch('POST', url, body, null, callback, timeout); }); } },{}],14:[function(require,module,exports){ "use strict"; var isBrowser = typeof document !== 'undefined'; var isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative'; // FIXME: how to do conditional loading this with ES6 modules? module.exports = isBrowser || isReactNative ? require('./request.browser') : require('./request.node'); },{"./request.browser":13,"./request.node":15}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.post = post; var _http = _interopRequireDefault(require("http")); var _https = _interopRequireDefault(require("https")); var _url = _interopRequireDefault(require("url")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } var parseUrl = _url["default"].parse; function post(url, body, timeout) { return new Promise(function (resolve, reject) { var data = body === 'string' ? body : JSON.stringify(body); var urlObj = parseUrl(url); // An object of options to indicate where to post to var options = { host: urlObj.hostname, port: urlObj.port, path: urlObj.path, method: 'POST', headers: { 'Content-Length': data.length } }; if (body !== 'string') { options.headers['Content-Type'] = 'application/json'; } var proto = urlObj.protocol === 'https:' ? _https["default"] : _http["default"]; var req = proto.request(options, function (res) { res.setEncoding('utf8'); var result = ''; res.on('data', function (data) { result += data; }); res.on('end', function () { var contentType = res.headers['content-type']; var isJSON = contentType && contentType.indexOf('json') !== -1; try { var body = isJSON ? JSON.parse(result) : result; resolve([body, res.statusCode]); } catch (err) { reject(err); } }); }); req.on('error', reject); req.on('socket', function (socket) { socket.setTimeout(timeout, function () { req.abort(); }); }); req.write(data); req.end(); }); } },{"http":undefined,"https":undefined,"url":undefined}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.add = add; exports.compare = compare; exports.mean = mean; exports.median = median; exports.std = std; exports.sum = sum; exports.variance = variance; // basic statistical functions function compare(a, b) { return a > b ? 1 : a < b ? -1 : 0; } function add(a, b) { return a + b; } function sum(arr) { return arr.reduce(add); } function mean(arr) { return sum(arr) / arr.length; } function std(arr) { return Math.sqrt(variance(arr)); } function variance(arr) { if (arr.length < 2) return 0; var _mean = mean(arr); return arr.map(function (x) { return Math.pow(x - _mean, 2); }).reduce(add) / (arr.length - 1); } function median(arr) { if (arr.length < 2) return arr[0]; var sorted = arr.slice().sort(compare); if (sorted.length % 2 === 0) { // even return (sorted[arr.length / 2 - 1] + sorted[arr.length / 2]) / 2; } else { // odd return sorted[(arr.length - 1) / 2]; } } },{}],17:[function(require,module,exports){ "use strict"; function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } Object.defineProperty(exports, "__esModule", { value: true }); exports.create = create; var util = _interopRequireWildcard(require("./util.js")); var stat = _interopRequireWildcard(require("./stat.js")); var request = _interopRequireWildcard(require("./request/request")); var _emitter = _interopRequireDefault(require("./emitter.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * timesync * * Time synchronization between peers * * https://github.com/enmasseio/timesync */ var Promise = require('./Promise'); /** * Factory function to create a timesync instance * @param {Object} [options] TODO: describe options * @return {Object} Returns a new timesync instance */ function create(options) { var timesync = { // configurable options options: { interval: 60 * 60 * 1000, // interval for doing synchronizations in ms. Set to null to disable auto sync timeout: 10000, // timeout for requests to fail in ms delay: 1000, // delay between requests in ms repeat: 5, // number of times to do a request to one peer peers: [], // uri's or id's of the peers server: null, // uri of a single server (master/slave configuration) now: Date.now // function returning the system time }, /** @type {number} The current offset from system time */ offset: 0, // ms /** @type {number} Contains the timeout for the next synchronization */ _timeout: null, /** @type {Object.<string, function>} Contains a map with requests in progress */ _inProgress: {}, /** * @type {boolean} * This property used to immediately apply the first ever received offset. * After that, it's set to false and not used anymore. */ _isFirst: true, /** * Send a message to a peer * This method must be overridden when using timesync * @param {string} to * @param {*} data */ send: function send(to, data, timeout) { return request.post(to, data, timeout).then(function (val) { var res = val[0]; if (val[1] !== 200) { throw new Error('Send failure: bad HTTP StatusCode (' + val[1] + ')'); } timesync.receive(to, res); })["catch"](function (err) { emitError(err); }); }, /** * Receive method to be called when a reply comes in * @param {string | undefined} [from] * @param {*} data */ receive: function receive(from, data) { if (data === undefined) { data = from; from = undefined; } if (data && data.id in timesync._inProgress) { // this is a reply timesync._inProgress[data.id](data.result); } else if (data && data.id !== undefined) { // this is a request from an other peer // reply with our current time timesync.send(from, { jsonrpc: '2.0', id: data.id, result: timesync.now() }); } }, _handleRPCSendError: function _handleRPCSendError(id, reject, err) { delete timesync._inProgress[id]; reject(new Error('Send failure')); }, /** * Send a JSON-RPC message and retrieve a response * @param {string} to * @param {string} method * @param {*} [params] * @returns {Promise} */ rpc: function rpc(to, method, params) { var id = util.nextId(); var resolve, reject; var deferred = new Promise(function (res, rej) { resolve = res; reject = rej; }); timesync._inProgress[id] = function (data) { delete timesync._inProgress[id]; resolve(data); }; var sendResult; try { sendResult = timesync.send(to, { jsonrpc: '2.0', id: id, method: method, params: params }, timesync.options.timeout); } catch (err) { timesync._handleRPCSendError(id, reject, err); } if (sendResult && (sendResult instanceof Promise || sendResult.then && sendResult["catch"])) { sendResult["catch"](timesync._handleRPCSendError.bind(this, id, reject)); } else { console.warn('Send should return a promise'); } return deferred; }, /** * Synchronize now with all configured peers * Docs: http://www.mine-control.com/zack/timesync/timesync.html */ sync: function sync() { timesync.emit('sync', 'start'); var peers = timesync.options.server ? [timesync.options.server] : timesync.options.peers; return Promise.all(peers.map(function (peer) { return timesync._syncWithPeer(peer); })).then(function (all) { var offsets = all.filter(function (offset) { return timesync._validOffset(offset); }); if (offsets.length > 0) { // take the average of all peers (excluding self) as new offset timesync.offset = stat.mean(offsets); timesync.emit('change', timesync.offset); } timesync.emit('sync', 'end'); }); }, /** * Test whether given offset is a valid number (not NaN, Infinite, or null) * @param {number} offset * @returns {boolean} * @private */ _validOffset: function _validOffset(offset) { return offset !== null && !isNaN(offset) && isFinite(offset); }, /** * Sync one peer * @param {string} peer * @return {Promise.<number | null>} Resolves with the offset to this peer, * or null if failed to sync with this peer. * @private */ _syncWithPeer: function _syncWithPeer(peer) { // retrieve the offset of a peer, then wait 1 sec var all = []; function sync() { return timesync._getOffset(peer).then(function (result) { return all.push(result); }); } function waitAndSync() { return util.wait(timesync.options.delay).then(sync); } function notDone() { return all.length < timesync.options.repeat; } return sync().then(function () { return util.whilst(notDone, waitAndSync); }).then(function () { // filter out null results var results = all.filter(function (result) { return result !== null; }); // calculate the limit for outliers var roundtrips = results.map(function (result) { return result.roundtrip; }); var limit = stat.median(roundtrips) + stat.std(roundtrips); // filter all results which have a roundtrip smaller than the mean+std var filtered = results.filter(function (result) { return result.roundtrip < limit; }); var offsets = filtered.map(function (result) { return result.offset; }); // return the new offset return offsets.length > 0 ? stat.mean(offsets) : null; }); }, /** * Retrieve the offset from one peer by doing a single call to the peer * @param {string} peer * @returns {Promise.<{roundtrip: number, offset: number} | null>} * @private */ _getOffset: function _getOffset(peer) { var start = timesync.options.now(); // local system time return timesync.rpc(peer, 'timesync').then(function (timestamp) { var end = timesync.options.now(); // local system time var roundtrip = end - start; var offset = timestamp - end + roundtrip / 2; // offset from local system time // apply the first ever retrieved offset immediately. if (timesync._isFirst) { timesync._isFirst = false; timesync.offset = offset; timesync.emit('change', offset); } return { roundtrip: roundtrip, offset: offset }; })["catch"](function (err) { // just ignore failed requests, return null return null; }); }, /** * Get the current time * @returns {number} Returns a timestamp */ now: function now() { return timesync.options.now() + timesync.offset; }, /** * Destroy the timesync instance. Stops automatic synchronization. * If timesync is currently executing a synchronization, this * synchronization will be finished first. */ destroy: function destroy() { clearTimeout(timesync._timeout); } }; // apply provided options if (options) { if (options.server && options.peers) { throw new Error('Configure either option "peers" or "server", not both.'); } for (var prop in options) { if (options.hasOwnProperty(prop)) { if (prop === 'peers' && typeof options.peers === 'string') { // split a comma separated string with peers into an array timesync.options.peers = options.peers.split(',').map(function (peer) { return peer.trim(); }).filter(function (peer) { return peer !== ''; }); } else { timesync.options[prop] = options[prop]; } } } } // turn into an event emitter (0, _emitter["default"])(timesync); /** * Emit an error message. If there are no listeners, the error is outputted * to the console. * @param {Error} err */ function emitError(err) { if (timesync.list('error').length > 0) { timesync.emit('error', err); } else { console.log('Error', err); } } if (timesync.options.interval !== null) { // start an interval to automatically run a synchronization once per interval timesync._timeout = setInterval(timesync.sync, timesync.options.interval); // synchronize immediately on the next tick (allows to attach event // handlers before the timesync starts). setTimeout(function () { timesync.sync()["catch"](function (err) { return emitError(err); }); }, 0); } return timesync; } },{"./Promise":11,"./emitter.js":12,"./request/request":14,"./stat.js":16,"./util.js":18}],18:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.nextId = nextId; exports.repeat = repeat; exports.wait = wait; exports.whilst = whilst; var Promise = require('./Promise'); /** * Resolve a promise after a delay * @param {number} delay A delay in milliseconds * @returns {Promise} Resolves after given delay */ function wait(delay) { return new Promise(function (resolve) { setTimeout(resolve, delay); }); } /** * Repeat a given asynchronous function a number of times * @param {function} fn A function returning a promise * @param {number} times * @return {Promise} */ function repeat(fn, times) { return new Promise(function (resolve, reject) { var count = 0; var results = []; function recurse() { if (count < times) { count++; fn().then(function (result) { results.push(result); recurse(); }); } else { resolve(results); } } recurse(); }); } /** * Repeat an asynchronous callback function whilst * @param {function} condition A function returning true or false * @param {function} callback A callback returning a Promise * @returns {Promise} */ function whilst(condition, callback) { return new Promise(function (resolve, reject) { function recurse() { if (condition()) { callback().then(function () { return recurse(); }); } else { resolve(); } } recurse(); }); } /** * Simple id generator * @returns {number} Returns a new id */ function nextId() { return _id++; } var _id = 0; },{"./Promise":11}]},{},[17])(17) });