/* eslint-disable */ //IE8 doesn't have Date.now() Date.now = Date.now || function() { return +new Date; }; TimeSync = { loggingEnabled: true }; function log(/* arguments */) { if (TimeSync.loggingEnabled) { Meteor._debug.apply(this, arguments); } } var defaultInterval = 1000; // Internal values, exported for testing SyncInternals = { offset: undefined, roundTripTime: undefined, offsetDep: new Deps.Dependency(), timeTick: {}, timeCheck: function (lastTime, currentTime, interval, tolerance) { if (Math.abs(currentTime - lastTime - interval) < tolerance) { // Everything is A-OK return true; } // We're no longer in sync. return false; } }; SyncInternals.timeTick[defaultInterval] = new Deps.Dependency(); var maxAttempts = 5; var attempts = 0; /* This is an approximation of http://en.wikipedia.org/wiki/Network_Time_Protocol If this turns out to be more accurate under the connect handlers, we should try taking multiple measurements. */ // Only use Meteor.absoluteUrl for Cordova; see // https://github.com/meteor/meteor/issues/4696 // https://github.com/mizzao/meteor-timesync/issues/30 var syncUrl = "/_timesync"; if (__meteor_runtime_config__.ROOT_URL_PATH_PREFIX) { syncUrl = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + syncUrl; } if (Meteor.isCordova) { syncUrl = Meteor.absoluteUrl("_timesync"); } var updateOffset = function() { var t0 = Date.now(); HTTP.get(syncUrl, function(err, response) { var t3 = Date.now(); // Grab this now if (err) { // We'll still use our last computed offset if is defined log("Error syncing to server time: ", err); if (++attempts <= maxAttempts) Meteor.setTimeout(TimeSync.resync, 1000); else log("Max number of time sync attempts reached. Giving up."); return; } attempts = 0; // It worked var ts = parseInt(response.content); SyncInternals.offset = Math.round(((ts - t0) + (ts - t3)) / 2); SyncInternals.roundTripTime = t3 - t0; // - (ts - ts) which is 0 SyncInternals.offsetDep.changed(); }); }; // Reactive variable for server time that updates every second. TimeSync.serverTime = function(clientTime, interval) { check(interval, Match.Optional(Match.Integer)); // If we don't know the offset, we can't provide the server time. if ( !TimeSync.isSynced() ) return undefined; // If a client time is provided, we don't need to depend on the tick. if ( !clientTime ) getTickDependency(interval || defaultInterval).depend(); // SyncInternals.offsetDep.depend(); implicit as we call isSynced() // Convert Date argument to epoch as necessary return (+clientTime || Date.now()) + SyncInternals.offset; }; // Reactive variable for the difference between server and client time. TimeSync.serverOffset = function() { SyncInternals.offsetDep.depend(); return SyncInternals.offset; }; TimeSync.roundTripTime = function() { SyncInternals.offsetDep.depend(); return SyncInternals.roundTripTime; }; TimeSync.isSynced = function() { SyncInternals.offsetDep.depend(); return SyncInternals.offset !== undefined; }; var resyncIntervalId = null; TimeSync.resync = function() { if (resyncIntervalId !== null) Meteor.clearInterval(resyncIntervalId); updateOffset(); resyncIntervalId = Meteor.setInterval(updateOffset, 600000); }; // Run this as soon as we load, even before Meteor.startup() // Run again whenever we reconnect after losing connection var wasConnected = false; Deps.autorun(function() { var connected = Meteor.status().connected; if ( connected && !wasConnected ) TimeSync.resync(); wasConnected = connected; }); // Resync if unexpected change by more than a few seconds. This needs to be // somewhat lenient, or a CPU-intensive operation can trigger a re-sync even // when the offset is still accurate. In any case, we're not going to be able to // catch very small system-initiated NTP adjustments with this, anyway. var tickCheckTolerance = 5000; var lastClientTime = Date.now(); // Set up a new interval for any amount of reactivity. function getTickDependency(interval) { if ( !SyncInternals.timeTick[interval] ) { var dep = new Deps.Dependency(); Meteor.setInterval(function() { dep.changed(); }, interval); SyncInternals.timeTick[interval] = dep; } return SyncInternals.timeTick[interval]; } // Set up special interval for the default tick, which also watches for re-sync Meteor.setInterval(function() { var currentClientTime = Date.now(); if ( SyncInternals.timeCheck( lastClientTime, currentClientTime, defaultInterval, tickCheckTolerance) ) { // No problem here, just keep ticking along SyncInternals.timeTick[defaultInterval].changed(); } else { // resync on major client clock changes // based on http://stackoverflow.com/a/3367542/1656818 log("Clock discrepancy detected. Attempting re-sync."); // Refuse to compute server time. SyncInternals.offset = undefined; SyncInternals.offsetDep.changed(); TimeSync.resync(); } lastClientTime = currentClientTime; }, defaultInterval);