UNPKG

clocksy

Version:

Transport-agnostic client-server clock synchronization

208 lines (177 loc) 10.2 kB
'use strict'; 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;