clocksy
Version:
Transport-agnostic client-server clock synchronization
208 lines (177 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ClocksyServer = exports.ClocksyClient = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /*!
* Clocksy
*
* Transport-agnostic client-server clock synchronization.
*
* @copyright Guillermo Grau Panea 2016
* @license MIT
*/
var _timm = require('timm');
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var DEFAULT_PERIOD = 10000;
var DEFAULT_ALPHA = 0.2;
//-------------------------------------
// ClocksyServer
//-------------------------------------
/* --
### Server
Example usage with socket.io: just call `ClocksyServer.processRequest()`
and return the result to the client as fast as you can:
```js
import { ClocksyServer } from 'clocksy'; // const { ClocksyServer } = require('clocksy');
const clocksy = new ClocksyServer();
// ...
socket.on('MSG', msg => {
const { type, data } = msg;
if (type === 'CLOCKSY') {
socket.emit('MSG', {
type: 'CLOCKSY',
data: clocksy.processRequest(data),
});
return;
}
// ...
})
```
-- */
var ClocksyServer = function () {
function ClocksyServer() {
_classCallCheck(this, ClocksyServer);
}
_createClass(ClocksyServer, [{
key: 'processRequest',
value: function processRequest(req) {
return (0, _timm.set)(req, 'tServer', new Date().getTime());
}
}]);
return ClocksyServer;
}();
//-------------------------------------
// ClocksyClient
//-------------------------------------
/* --
### Client
Enable/disable automatic requests by using `ClocksyServer.start|stop()`
and pass any response you get from the server to Clocksy, as fast as you can:
```js
import { ClocksyClient } from 'clocksy'; // const { ClocksyClient } = require('clocksy');
const socket = socketio.connect(url);
const clocksy = new ClocksyClient({
sendRequest: req => socket.emit('MSG', { type: 'CLOCKSY', data: req }),
// Other parameters and their default values:
// alpha: 0.2, // higher levels accelerate convergence but decrease accuracy
// updatePeriod: 10000, // [ms] how often should Clocksy estimate clock error
});
socket.on('connect', () => clocksy.start());
socket.on('disconnect', () => clocksy.stop());
socket.on('MSG', msg => {
const { type, data } = msg;
if (type === 'CLOCKSY') {
const tDelta = clocksy.processResponse(data);
// tDelta is the estimated server time minus the local time.
// Use this delta for whatever purpose you want, e.g.
// correcting the local time for graphs or changing the timestamps
// of data downloaded from the server...
// If you don't need the delta immediately, you can also obtain it later
// calling clocksy.getDelta())
return;
}
});
```
-- */
var ClocksyClient = function () {
function ClocksyClient() {
var _this = this;
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref$alpha = _ref.alpha;
var alpha = _ref$alpha === undefined ? DEFAULT_ALPHA : _ref$alpha;
var _ref$updatePeriod = _ref.updatePeriod;
var updatePeriod = _ref$updatePeriod === undefined ? DEFAULT_PERIOD : _ref$updatePeriod;
var sendRequest = _ref.sendRequest;
_classCallCheck(this, ClocksyClient);
this.tDelta = null;
this.rtt = null;
this.timer = null;
this.alpha = alpha;
this.updatePeriod = updatePeriod;
this.sendRequest = sendRequest || function () {};
// Keep track of the tab's shown/hidden status. Chrome
// goes bonkers with the timers in hidden windows, so RTT
// calculations are not reliable. Automatic requests are
// not sent while the tab is hidden.
this.fHiddenTab = false;
try {
this.fHiddenTab = document.hidden;
document.addEventListener('visibilitychange', function () {
_this.fHiddenTab = document.hidden; // change tab text for demo
});
} catch (err) {/* ignore */}
}
_createClass(ClocksyClient, [{
key: 'createRequest',
value: function createRequest() {
return { tTx: new Date().getTime() };
}
}, {
key: 'processResponse',
value: function processResponse(rsp) {
var tRx = new Date().getTime();
var tTx = rsp.tTx;
var tServer = rsp.tServer;
var rtt = tRx - tTx;
var tDelta = tServer - rtt / 2 - tTx;
this.tDelta = this.calcNewDelta(tDelta);
this.rtt = rtt;
return this.tDelta;
}
}, {
key: 'calcNewDelta',
value: function calcNewDelta(tDelta) {
var tDelta0 = this.tDelta;
var alpha = this.alpha;
return tDelta0 != null ? alpha * tDelta + (1 - alpha) * tDelta0 : tDelta;
}
}, {
key: 'getDelta',
value: function getDelta() {
return this.tDelta;
}
}, {
key: 'getRtt',
value: function getRtt() {
return this.rtt;
}
}, {
key: 'start',
value: function start() {
if (this.timer != null) this.stop();
this.sendAutoRequest(true);
this.timer = setInterval(this.sendAutoRequest.bind(this), this.updatePeriod);
}
}, {
key: 'stop',
value: function stop() {
if (this.timer == null) return;
clearInterval(this.timer);
this.timer = null;
}
}, {
key: 'sendAutoRequest',
value: function sendAutoRequest(fForce) {
if (this.fHiddenTab && !fForce) return;
var req = this.createRequest();
this.sendRequest(req);
}
}]);
return ClocksyClient;
}();
//-------------------------------------
// API
//-------------------------------------
exports.ClocksyClient = ClocksyClient;
exports.ClocksyServer = ClocksyServer;