timesync
Version:
Time synchronization between peers
1,669 lines (1,433 loc) • 47.4 kB
JavaScript
(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){
;
// 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 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){
;
module.exports = require('./lib')
},{"./lib":8}],4:[function(require,module,exports){
;
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){
;
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){
;
//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){
;
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){
;
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){
;
// 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){
;
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){
;
module.exports = typeof window === 'undefined' || typeof window.Promise === 'undefined' ? require('promise') : window.Promise;
},{"promise":3}],12:[function(require,module,exports){
;
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){
;
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){
;
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){
;
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){
;
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){
;
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){
;
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)
});