From 566e80931ece458de497738d5fb49b076e937b62 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Mon, 18 Mar 2019 18:20:58 -0400 Subject: [PATCH 1/9] Initial commit of 0.6.0 refactoring: - Code Format Refactor - Bumped minimum package API version to Meteor 1.3 (to support Tracker) - Changed Deps to Tracker (#49) - Only show log output in development --- History.md | 11 ++- client/index.js | 6 ++ .../timesync-client.js | 98 ++++++++++--------- package.js | 27 ++--- server/index.js | 1 + .../timesync-server.js | 20 ++-- tests/client.js | 28 +++--- 7 files changed, 108 insertions(+), 83 deletions(-) create mode 100644 client/index.js rename timesync-client.js => client/timesync-client.js (65%) create mode 100644 server/index.js rename timesync-server.js => server/timesync-server.js (62%) diff --git a/History.md b/History.md index 9546424..2d7ae2c 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,12 @@ ## vNEXT +## v0.6.0 + +- Code Format Refactor +- Bumped minimum package API version to Meteor 1.3 (to support Tracker) +- Changed Deps to Tracker (#49) +- Only show log output in development + ## v0.5.1 - Fix an issue where `TimeSync.ServerTime` returned NaN when executed in meteor-desktop. @@ -15,11 +22,11 @@ ## v0.3.4 -- Explicitly pull in client-side `check` for Meteor 1.2 apps. +- Explicitly pull in client-side `check` for Meteor 1.2 apps. ## v0.3.3 -- Be more robust with sync url when outside of Cordova. (#30) +- Be more robust with sync url when outside of Cordova. (#30) ## v0.3.2 diff --git a/client/index.js b/client/index.js new file mode 100644 index 0000000..9bc8c21 --- /dev/null +++ b/client/index.js @@ -0,0 +1,6 @@ +import { TimeSync, SyncInternals } from './timesync-client'; + +export { + TimeSync, + SyncInternals, +}; \ No newline at end of file diff --git a/timesync-client.js b/client/timesync-client.js similarity index 65% rename from timesync-client.js rename to client/timesync-client.js index 8929055..207304c 100644 --- a/timesync-client.js +++ b/client/timesync-client.js @@ -1,24 +1,30 @@ -//IE8 doesn't have Date.now() -Date.now = Date.now || function() { return +new Date; }; +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; +import { HTTP } from 'meteor/http'; + +// IE8 doesn't have Date.now() +Date.now = Date.now || function () { + return +new Date; +}; TimeSync = { - loggingEnabled: true + loggingEnabled: Meteor.isDevelopment }; -function log(/* arguments */) { +function log( /* arguments */ ) { if (TimeSync.loggingEnabled) { Meteor._debug.apply(this, arguments); } } -var defaultInterval = 1000; +const defaultInterval = 1000; // Internal values, exported for testing SyncInternals = { offset: undefined, roundTripTime: undefined, - offsetDep: new Deps.Dependency(), - syncDep: new Deps.Dependency(), + offsetDep: new Tracker.Dependency(), + syncDep: new Tracker.Dependency(), isSynced: false, timeTick: {}, getDiscrepancy: function (lastTime, currentTime, interval) { @@ -26,10 +32,10 @@ SyncInternals = { } }; -SyncInternals.timeTick[defaultInterval] = new Deps.Dependency(); +SyncInternals.timeTick[defaultInterval] = new Tracker.Dependency(); -var maxAttempts = 5; -var attempts = 0; +let maxAttempts = 5; +let attempts = 0; /* This is an approximation of @@ -39,39 +45,39 @@ var attempts = 0; we should try taking multiple measurements. */ -var syncUrl; +let syncUrl; if (Meteor.isCordova || Meteor.isDesktop) { // Only use Meteor.absoluteUrl for Cordova and Desktop; see // https://github.com/meteor/meteor/issues/4696 // https://github.com/mizzao/meteor-timesync/issues/30 // Cordova should never be running out of a subdirectory... - syncUrl = Meteor.absoluteUrl("_timesync"); -} -else { + syncUrl = Meteor.absoluteUrl('_timesync'); +} else { // Support Meteor running in relative paths, based on computed root url prefix // https://github.com/mizzao/meteor-timesync/pull/40 const basePath = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''; - syncUrl = basePath + "/_timesync"; + syncUrl = basePath + '/_timesync'; } -var updateOffset = function() { - var t0 = Date.now(); +const updateOffset = function () { + const t0 = Date.now(); - HTTP.get(syncUrl, function(err, response) { - var t3 = Date.now(); // Grab this now + HTTP.get(syncUrl, function (err, response) { + const 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) + // 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."); + } else { + log('Max number of time sync attempts reached. Giving up.'); + } return; } attempts = 0; // It worked - var ts = parseInt(response.content); + const ts = parseInt(response.content, 10); SyncInternals.isSynced = true; SyncInternals.offset = Math.round(((ts - t0) + (ts - t3)) / 2); SyncInternals.roundTripTime = t3 - t0; // - (ts - ts) which is 0 @@ -80,10 +86,10 @@ var updateOffset = function() { }; // Reactive variable for server time that updates every second. -TimeSync.serverTime = function(clientTime, interval) { +TimeSync.serverTime = function (clientTime, interval) { check(interval, Match.Optional(Match.Integer)); // If a client time is provided, we don't need to depend on the tick. - if ( !clientTime ) getTickDependency(interval || defaultInterval).depend(); + if (!clientTime) getTickDependency(interval || defaultInterval).depend(); SyncInternals.offsetDep.depend(); // depend on offset to enable reactivity // Convert Date argument to epoch as necessary @@ -91,24 +97,24 @@ TimeSync.serverTime = function(clientTime, interval) { }; // Reactive variable for the difference between server and client time. -TimeSync.serverOffset = function() { +TimeSync.serverOffset = function () { SyncInternals.offsetDep.depend(); return SyncInternals.offset; }; -TimeSync.roundTripTime = function() { +TimeSync.roundTripTime = function () { SyncInternals.offsetDep.depend(); return SyncInternals.roundTripTime; }; -TimeSync.isSynced = function() { +TimeSync.isSynced = function () { SyncInternals.offsetDep.depend(); return SyncInternals.isSynced; }; -var resyncIntervalId = null; +let resyncIntervalId = null; -TimeSync.resync = function() { +TimeSync.resync = function () { if (resyncIntervalId !== null) Meteor.clearInterval(resyncIntervalId); updateOffset(); resyncIntervalId = Meteor.setInterval(updateOffset, 600000); @@ -116,11 +122,11 @@ TimeSync.resync = function() { // Run this as soon as we load, even before Meteor.startup() // Run again whenever we reconnect after losing connection -var wasConnected = false; +let wasConnected = false; -Deps.autorun(function() { - var connected = Meteor.status().connected; - if ( connected && !wasConnected ) TimeSync.resync(); +Tracker.autorun(function () { + const connected = Meteor.status().connected; + if (connected && !wasConnected) TimeSync.resync(); wasConnected = connected; }); @@ -128,17 +134,17 @@ Deps.autorun(function() { // 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; +const tickCheckTolerance = 5000; -var lastClientTime = Date.now(); +let 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(); + if (!SyncInternals.timeTick[interval]) { + let dep = new Tracker.Dependency(); - Meteor.setInterval(function() { + Meteor.setInterval(function () { dep.changed(); }, interval); @@ -149,17 +155,17 @@ function getTickDependency(interval) { } // Set up special interval for the default tick, which also watches for re-sync -Meteor.setInterval(function() { - var currentClientTime = Date.now(); +Meteor.setInterval(function () { + let currentClientTime = Date.now(); - var discrepancy = SyncInternals.getDiscrepancy(lastClientTime, currentClientTime, defaultInterval); + let discrepancy = SyncInternals.getDiscrepancy(lastClientTime, currentClientTime, defaultInterval); if (Math.abs(discrepancy) < 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."); + log('Clock discrepancy detected. Attempting re-sync.'); // Refuse to compute server time and try to guess new server offset. Guessing only works if the server time hasn't changed. SyncInternals.offset = SyncInternals.offset - discrepancy; SyncInternals.isSynced = false; @@ -168,4 +174,4 @@ Meteor.setInterval(function() { } lastClientTime = currentClientTime; -}, defaultInterval); +}, defaultInterval); \ No newline at end of file diff --git a/package.js b/package.js index 9598300..fc375f6 100644 --- a/package.js +++ b/package.js @@ -1,12 +1,12 @@ Package.describe({ - name: "mizzao:timesync", - summary: "NTP-style time synchronization between server and client", - version: "0.5.1", - git: "https://github.com/mizzao/meteor-timesync.git" + name: 'mizzao:timesync', + summary: 'NTP-style time synchronization between server and client', + version: '0.6.0', + git: 'https://github.com/mizzao/meteor-timesync.git' }); Package.onUse(function (api) { - api.versionsFrom("1.2.0.1"); + api.versionsFrom('METEOR@1.3'); api.use([ 'check', @@ -14,27 +14,30 @@ Package.onUse(function (api) { 'http' ], 'client'); - api.use('webapp', 'server'); + api.use(['webapp'], 'server'); - api.use('ecmascript'); + api.use(['ecmascript']); // Our files - api.addFiles('timesync-server.js', 'server'); - api.addFiles('timesync-client.js', 'client'); + api.addFiles('server/index.js', 'server'); + api.addFiles('client/index.js', 'client'); api.export('TimeSync', 'client'); - api.export('SyncInternals', 'client', {testOnly: true} ); + api.export('SyncInternals', 'client', { + testOnly: true + }); }); Package.onTest(function (api) { api.use([ + 'ecmascript', 'tinytest', 'test-helpers' ]); - api.use(["tracker", "underscore"], 'client'); + api.use(['tracker'], 'client'); - api.use("mizzao:timesync"); + api.use('mizzao:timesync'); api.addFiles('tests/client.js', 'client'); }); diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..ab8e570 --- /dev/null +++ b/server/index.js @@ -0,0 +1 @@ +import './timesync-server'; \ No newline at end of file diff --git a/timesync-server.js b/server/timesync-server.js similarity index 62% rename from timesync-server.js rename to server/timesync-server.js index c362697..0ba2d05 100644 --- a/timesync-server.js +++ b/server/timesync-server.js @@ -1,16 +1,16 @@ // Use rawConnectHandlers so we get a response as quickly as possible // https://github.com/meteor/meteor/blob/devel/packages/webapp/webapp_server.js -WebApp.rawConnectHandlers.use("/_timesync", - function(req, res, next) { +WebApp.rawConnectHandlers.use('/_timesync', + function (req, res, next) { // Never ever cache this, otherwise weird times are shown on reload // http://stackoverflow.com/q/18811286/586086 - res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - res.setHeader("Pragma", "no-cache"); - res.setHeader("Expires", 0); + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', 0); // Avoid MIME type warnings in browsers - res.setHeader("Content-Type", "text/plain"); + res.setHeader('Content-Type', 'text/plain'); // Cordova lives in a local webserver, so it does CORS // we need to bless it's requests in order for it to accept our results @@ -18,12 +18,12 @@ WebApp.rawConnectHandlers.use("/_timesync", // and http://meteor.local for earlier versions const origin = req.headers.origin; - if (origin && ( origin === 'http://meteor.local' || - origin === 'meteor://desktop' || - /^http:\/\/localhost:1[23]\d\d\d$/.test(origin) ) ) { + if (origin && (origin === 'http://meteor.local' || + origin === 'meteor://desktop' || + /^http:\/\/localhost:1[23]\d\d\d$/.test(origin))) { res.setHeader('Access-Control-Allow-Origin', origin); } res.end(Date.now().toString()); } -); +); \ No newline at end of file diff --git a/tests/client.js b/tests/client.js index 3a015b1..a10b271 100644 --- a/tests/client.js +++ b/tests/client.js @@ -1,4 +1,4 @@ -Tinytest.add("timesync - tick check - normal tick", function(test) { +Tinytest.add("timesync - tick check - normal tick", function (test) { var lastTime = 5000; var currentTime = 6000; var interval = 1000; @@ -6,7 +6,7 @@ Tinytest.add("timesync - tick check - normal tick", function(test) { test.equal(SyncInternals.getDiscrepancy(lastTime, currentTime, interval), 0); }); -Tinytest.add("timesync - tick check - slightly off", function(test) { +Tinytest.add("timesync - tick check - slightly off", function (test) { var lastTime = 5000; var currentTime = 6500; var interval = 1000; @@ -18,7 +18,7 @@ Tinytest.add("timesync - tick check - slightly off", function(test) { test.equal(SyncInternals.getDiscrepancy(lastTime, currentTime, interval), -500); }); -Tinytest.add("timesync - tick check - big jump", function(test) { +Tinytest.add("timesync - tick check - big jump", function (test) { var lastTime = 5000; var currentTime = 0; var interval = 1000; @@ -34,7 +34,7 @@ Tinytest.add("timesync - tick check - big jump", function(test) { TODO: add tests for proper dependencies in reactive functions */ -Tinytest.addAsync("timesync - basic - initial sync", function(test, next) { +Tinytest.addAsync("timesync - basic - initial sync", function (test, next) { function success() { var syncedTime = TimeSync.serverTime(); @@ -46,7 +46,7 @@ Tinytest.addAsync("timesync - basic - initial sync", function(test, next) { // always be true in PhantomJS tests where client/server are the same // machine, although it might fail in development environments, for example // when the server and client are different VMs. - test.isTrue( Math.abs(syncedTime - Date.now()) < 1000 ); + test.isTrue(Math.abs(syncedTime - Date.now()) < 1000); next(); } @@ -59,24 +59,26 @@ Tinytest.addAsync("timesync - basic - initial sync", function(test, next) { simplePoll(TimeSync.isSynced, success, fail, 5000, 100); }); -Tinytest.addAsync("timesync - basic - serverTime format", function(test, next) { +Tinytest.addAsync("timesync - basic - serverTime format", function (test, next) { - test.isTrue(_.isNumber( TimeSync.serverTime() )); + test.isTrue(_.isNumber(TimeSync.serverTime())); - test.isTrue(_.isNumber( TimeSync.serverTime(null) )); + test.isTrue(_.isNumber(TimeSync.serverTime(null))); // Accept Date as client time - test.isTrue(_.isNumber( TimeSync.serverTime(new Date()) )); + test.isTrue(_.isNumber(TimeSync.serverTime(new Date()))); // Accept epoch as client time - test.isTrue(_.isNumber( TimeSync.serverTime(Date.now()) )); + test.isTrue(_.isNumber(TimeSync.serverTime(Date.now()))); next(); }); -Tinytest.addAsync("timesync - basic - different sync intervals", function(test, next) { +Tinytest.addAsync("timesync - basic - different sync intervals", function (test, next) { - var aCount = 0, bCount = 0, cCount = 0; + var aCount = 0, + bCount = 0, + cCount = 0; var a = Tracker.autorun(function () { TimeSync.serverTime(null, 500); @@ -95,7 +97,7 @@ Tinytest.addAsync("timesync - basic - different sync intervals", function(test, var testInterval = 4990; - Meteor.setTimeout(function() { + Meteor.setTimeout(function () { test.equal(aCount, 10); // 0, 500, 1000, 1500 ... // not going to be 5 since the first tick won't generate this dep From af96690f9832b02872eb60ac3bb3226c4cd25d8e Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Mon, 18 Mar 2019 18:46:06 -0400 Subject: [PATCH 2/9] More refactoring Replaced internal references from Dep to Tracker Changed some non-changing variables from let to const --- client/timesync-client.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/client/timesync-client.js b/client/timesync-client.js index 207304c..603bf67 100644 --- a/client/timesync-client.js +++ b/client/timesync-client.js @@ -23,8 +23,8 @@ const defaultInterval = 1000; SyncInternals = { offset: undefined, roundTripTime: undefined, - offsetDep: new Tracker.Dependency(), - syncDep: new Tracker.Dependency(), + offsetTracker: new Tracker.Dependency(), + syncTracker: new Tracker.Dependency(), isSynced: false, timeTick: {}, getDiscrepancy: function (lastTime, currentTime, interval) { @@ -34,7 +34,7 @@ SyncInternals = { SyncInternals.timeTick[defaultInterval] = new Tracker.Dependency(); -let maxAttempts = 5; +const maxAttempts = 5; let attempts = 0; /* @@ -81,7 +81,7 @@ const updateOffset = function () { SyncInternals.isSynced = true; SyncInternals.offset = Math.round(((ts - t0) + (ts - t3)) / 2); SyncInternals.roundTripTime = t3 - t0; // - (ts - ts) which is 0 - SyncInternals.offsetDep.changed(); + SyncInternals.offsetTracker.changed(); }); }; @@ -91,24 +91,24 @@ TimeSync.serverTime = function (clientTime, interval) { // If a client time is provided, we don't need to depend on the tick. if (!clientTime) getTickDependency(interval || defaultInterval).depend(); - SyncInternals.offsetDep.depend(); // depend on offset to enable reactivity + SyncInternals.offsetTracker.depend(); // depend on offset to enable reactivity // 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(); + SyncInternals.offsetTracker.depend(); return SyncInternals.offset; }; TimeSync.roundTripTime = function () { - SyncInternals.offsetDep.depend(); + SyncInternals.offsetTracker.depend(); return SyncInternals.roundTripTime; }; TimeSync.isSynced = function () { - SyncInternals.offsetDep.depend(); + SyncInternals.offsetTracker.depend(); return SyncInternals.isSynced; }; @@ -142,7 +142,7 @@ let lastClientTime = Date.now(); function getTickDependency(interval) { if (!SyncInternals.timeTick[interval]) { - let dep = new Tracker.Dependency(); + const dep = new Tracker.Dependency(); Meteor.setInterval(function () { dep.changed(); @@ -156,9 +156,9 @@ function getTickDependency(interval) { // Set up special interval for the default tick, which also watches for re-sync Meteor.setInterval(function () { - let currentClientTime = Date.now(); - - let discrepancy = SyncInternals.getDiscrepancy(lastClientTime, currentClientTime, defaultInterval); + const currentClientTime = Date.now(); + const discrepancy = SyncInternals.getDiscrepancy(lastClientTime, currentClientTime, defaultInterval); + if (Math.abs(discrepancy) < tickCheckTolerance) { // No problem here, just keep ticking along SyncInternals.timeTick[defaultInterval].changed(); @@ -169,7 +169,7 @@ Meteor.setInterval(function () { // Refuse to compute server time and try to guess new server offset. Guessing only works if the server time hasn't changed. SyncInternals.offset = SyncInternals.offset - discrepancy; SyncInternals.isSynced = false; - SyncInternals.offsetDep.changed(); + SyncInternals.offsetTracker.changed(); TimeSync.resync(); } From cb9f1573448b4469c659ead712cce7f551158a2c Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Tue, 19 Mar 2019 02:36:00 -0400 Subject: [PATCH 3/9] DDP TimeSync + Tests - Added _timeSync Meteor Method for doing timesync over DDP instead of HTTP - Auto switch to DDP after initial HTTP timesync to improve subsequent round trip times - Added option TimeSync.forceDDP to always use DDP, even for first sync (which may be slow!) - Shortened resync interval from 1 minute to 30 seconds when using DDP. - Added tests for DDP and HTTP sync - Added option to set the timesync URL using `TimeSync.setSyncUrl` --- History.md | 9 +++- client/timesync-client.js | 90 ++++++++++++++++++++++++--------------- package.js | 2 +- server/timesync-server.js | 11 ++++- tests/client.js | 40 +++++++++++++++++ 5 files changed, 115 insertions(+), 37 deletions(-) diff --git a/History.md b/History.md index 2d7ae2c..b7da94a 100644 --- a/History.md +++ b/History.md @@ -5,7 +5,14 @@ - Code Format Refactor - Bumped minimum package API version to Meteor 1.3 (to support Tracker) - Changed Deps to Tracker (#49) -- Only show log output in development +- Only show log output if running in development +- Added _timeSync Meteor Method for doing timesync over DDP instead of HTTP +- Auto switch to DDP after initial HTTP timesync to improve subsequent round trip times +- Added option TimeSync.forceDDP to always use DDP, even for first sync (which may be slow!) +- Shortened resync interval from 1 minute to 30 seconds when using DDP. +- Added tests for DDP and HTTP sync +- Added option to set the timesync URL using `TimeSync.setSyncUrl` +- ... more to come, v0.6.0 is a work in progress! ## v0.5.1 diff --git a/client/timesync-client.js b/client/timesync-client.js index 603bf67..e651279 100644 --- a/client/timesync-client.js +++ b/client/timesync-client.js @@ -8,7 +8,8 @@ Date.now = Date.now || function () { }; TimeSync = { - loggingEnabled: Meteor.isDevelopment + loggingEnabled: Meteor.isDevelopment, + forceDDP: false }; function log( /* arguments */ ) { @@ -26,6 +27,7 @@ SyncInternals = { offsetTracker: new Tracker.Dependency(), syncTracker: new Tracker.Dependency(), isSynced: false, + usingDDP: false, timeTick: {}, getDiscrepancy: function (lastTime, currentTime, interval) { return currentTime - (lastTime + interval) @@ -46,44 +48,62 @@ let attempts = 0; */ let syncUrl; -if (Meteor.isCordova || Meteor.isDesktop) { - // Only use Meteor.absoluteUrl for Cordova and Desktop; see - // https://github.com/meteor/meteor/issues/4696 - // https://github.com/mizzao/meteor-timesync/issues/30 - // Cordova should never be running out of a subdirectory... - syncUrl = Meteor.absoluteUrl('_timesync'); -} else { - // Support Meteor running in relative paths, based on computed root url prefix - // https://github.com/mizzao/meteor-timesync/pull/40 - const basePath = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''; - syncUrl = basePath + '/_timesync'; + +TimeSync.setSyncUrl = function (url) { + if (url) { + syncUrl = url; + } else if (Meteor.isCordova || Meteor.isDesktop) { + // Only use Meteor.absoluteUrl for Cordova and Desktop; see + // https://github.com/meteor/meteor/issues/4696 + // https://github.com/mizzao/meteor-timesync/issues/30 + // Cordova should never be running out of a subdirectory... + syncUrl = Meteor.absoluteUrl('_timesync'); + } else { + // Support Meteor running in relative paths, based on computed root url prefix + // https://github.com/mizzao/meteor-timesync/pull/40 + const basePath = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''; + syncUrl = basePath + '/_timesync'; + } +}; +TimeSync.getSyncUrl = function () { + return syncUrl; } +TimeSync.setSyncUrl(); const updateOffset = function () { const t0 = Date.now(); + if (TimeSync.forceDDP || SyncInternals.useDDP) { + Meteor.call('_timeSync', function (err, res) { + handleResponse(t0, err, res); + }); + } else { + HTTP.get(syncUrl, function (err, res) { + handleResponse(t0, err, res); + }); + } +}; - HTTP.get(syncUrl, function (err, response) { - const 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; +const handleResponse = function (t0, err, res) { + const 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 - - const ts = parseInt(response.content, 10); - SyncInternals.isSynced = true; - SyncInternals.offset = Math.round(((ts - t0) + (ts - t3)) / 2); - SyncInternals.roundTripTime = t3 - t0; // - (ts - ts) which is 0 - SyncInternals.offsetTracker.changed(); - }); -}; + attempts = 0; // It worked + const response = res.content || res; + const ts = parseInt(response, 10); + SyncInternals.isSynced = true; + SyncInternals.offset = Math.round(((ts - t0) + (ts - t3)) / 2); + SyncInternals.roundTripTime = t3 - t0; // - (ts - ts) which is 0 + SyncInternals.offsetTracker.changed(); +} // Reactive variable for server time that updates every second. TimeSync.serverTime = function (clientTime, interval) { @@ -116,8 +136,9 @@ let resyncIntervalId = null; TimeSync.resync = function () { if (resyncIntervalId !== null) Meteor.clearInterval(resyncIntervalId); + updateOffset(); - resyncIntervalId = Meteor.setInterval(updateOffset, 600000); + resyncIntervalId = Meteor.setInterval(updateOffset, (SyncInternals.useDDP) ? 300000 : 600000); }; // Run this as soon as we load, even before Meteor.startup() @@ -128,6 +149,7 @@ Tracker.autorun(function () { const connected = Meteor.status().connected; if (connected && !wasConnected) TimeSync.resync(); wasConnected = connected; + SyncInternals.useDDP = connected; }); // Resync if unexpected change by more than a few seconds. This needs to be @@ -158,7 +180,7 @@ function getTickDependency(interval) { Meteor.setInterval(function () { const currentClientTime = Date.now(); const discrepancy = SyncInternals.getDiscrepancy(lastClientTime, currentClientTime, defaultInterval); - + if (Math.abs(discrepancy) < tickCheckTolerance) { // No problem here, just keep ticking along SyncInternals.timeTick[defaultInterval].changed(); diff --git a/package.js b/package.js index fc375f6..9be493a 100644 --- a/package.js +++ b/package.js @@ -35,7 +35,7 @@ Package.onTest(function (api) { 'test-helpers' ]); - api.use(['tracker'], 'client'); + api.use(['tracker', 'underscore'], 'client'); api.use('mizzao:timesync'); diff --git a/server/timesync-server.js b/server/timesync-server.js index 0ba2d05..6a0d886 100644 --- a/server/timesync-server.js +++ b/server/timesync-server.js @@ -1,3 +1,5 @@ +import { Meteor } from "meteor/meteor"; + // Use rawConnectHandlers so we get a response as quickly as possible // https://github.com/meteor/meteor/blob/devel/packages/webapp/webapp_server.js @@ -26,4 +28,11 @@ WebApp.rawConnectHandlers.use('/_timesync', res.end(Date.now().toString()); } -); \ No newline at end of file +); + +Meteor.methods({ + _timeSync: function () { + this.unblock(); + return Date.now(); + } +}); \ No newline at end of file diff --git a/tests/client.js b/tests/client.js index a10b271..15265eb 100644 --- a/tests/client.js +++ b/tests/client.js @@ -1,3 +1,7 @@ +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; +import { HTTP } from 'meteor/http'; + Tinytest.add("timesync - tick check - normal tick", function (test) { var lastTime = 5000; var currentTime = 6000; @@ -118,3 +122,39 @@ Tinytest.addAsync("timesync - basic - different sync intervals", function (test, }, testInterval); }); + +Tinytest.addAsync("timesync - basic - DDP timeSync", function (test, next) { + Meteor.call('_timeSync', function (err, res) { + if (err) { + test.fail(); + next(); + } + test.isTrue(_.isNumber(res)); + + // Make sure it's close to the current time on the client. This should + // always be true in PhantomJS tests where client/server are the same + // machine, although it might fail in development environments, for example + // when the server and client are different VMs. + test.isTrue(Math.abs(res - Date.now()) < 1000); + + next(); + }); +}); + +Tinytest.addAsync("timesync - basic - HTTP timeSync", function (test, next) { + var syncUrl = TimeSync.getSyncUrl(); + + test.isNotNull(syncUrl); + + HTTP.get(syncUrl, function (err, res) { + if (err) { + test.fail(); + next(); + } + test.isTrue(res.content); + var serverTime = parseInt(res.content,10); + test.isTrue(_.isNumber(serverTime)); + test.isTrue(Math.abs(serverTime - Date.now()) < 1000); + next(); + }); +}); \ No newline at end of file From 6e9112cb16e0e67016ba5bd17dfda5f910cd159b Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Sun, 12 Nov 2023 21:43:54 +0100 Subject: [PATCH 4/9] Port over #53 --- server/timesync-server.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/timesync-server.js b/server/timesync-server.js index 179b297..a2e0970 100644 --- a/server/timesync-server.js +++ b/server/timesync-server.js @@ -3,7 +3,9 @@ import { Meteor } from "meteor/meteor"; // Use rawConnectHandlers so we get a response as quickly as possible // https://github.com/meteor/meteor/blob/devel/packages/webapp/webapp_server.js -WebApp.rawConnectHandlers.use('/_timesync', +const url = new URL(Meteor.absoluteUrl("/_timesync")); + +WebApp.rawConnectHandlers.use(url.pathname, function (req, res, next) { // Never ever cache this, otherwise weird times are shown on reload // http://stackoverflow.com/q/18811286/586086 From 484f2cafbbe080a1f02df736d68264e858d00975 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Sun, 12 Nov 2023 22:12:26 +0100 Subject: [PATCH 5/9] Remove IE8 compat function Seriously we don't need that at this date. --- History.md | 3 +-- client/timesync-client.js | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/History.md b/History.md index cb68ac4..46e2649 100644 --- a/History.md +++ b/History.md @@ -3,7 +3,6 @@ ## v0.6.0 - Code Format Refactor -- Bumped minimum package API version to Meteor 1.3 (to support Tracker) - Changed Deps to Tracker (#49) - Only show log output if running in development - Added _timeSync Meteor Method for doing timesync over DDP instead of HTTP @@ -12,7 +11,7 @@ - Shortened resync interval from 1 minute to 30 seconds when using DDP. - Added tests for DDP and HTTP sync - Added option to set the timesync URL using `TimeSync.setSyncUrl` -- ... more to come, v0.6.0 is a work in progress! +- Removed IE8 compat function ## v0.5.4 diff --git a/client/timesync-client.js b/client/timesync-client.js index e651279..9ba520b 100644 --- a/client/timesync-client.js +++ b/client/timesync-client.js @@ -2,11 +2,6 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { HTTP } from 'meteor/http'; -// IE8 doesn't have Date.now() -Date.now = Date.now || function () { - return +new Date; -}; - TimeSync = { loggingEnabled: Meteor.isDevelopment, forceDDP: false From 643a92720b2c66e1ad09a6170270cd9480400232 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Sun, 12 Nov 2023 22:19:10 +0100 Subject: [PATCH 6/9] Modernize tests --- client/timesync-client.js | 2 +- server/timesync-server.js | 2 +- tests/client.js | 40 +++++++++++++++++++-------------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/client/timesync-client.js b/client/timesync-client.js index 9ba520b..56bd804 100644 --- a/client/timesync-client.js +++ b/client/timesync-client.js @@ -191,4 +191,4 @@ Meteor.setInterval(function () { } lastClientTime = currentClientTime; -}, defaultInterval); \ No newline at end of file +}, defaultInterval); diff --git a/server/timesync-server.js b/server/timesync-server.js index a2e0970..d5ae687 100644 --- a/server/timesync-server.js +++ b/server/timesync-server.js @@ -38,4 +38,4 @@ Meteor.methods({ this.unblock(); return Date.now(); } -}); \ No newline at end of file +}); diff --git a/tests/client.js b/tests/client.js index 15265eb..ec84500 100644 --- a/tests/client.js +++ b/tests/client.js @@ -3,17 +3,17 @@ import { Tracker } from 'meteor/tracker'; import { HTTP } from 'meteor/http'; Tinytest.add("timesync - tick check - normal tick", function (test) { - var lastTime = 5000; - var currentTime = 6000; - var interval = 1000; + const lastTime = 5000; + const currentTime = 6000; + const interval = 1000; test.equal(SyncInternals.getDiscrepancy(lastTime, currentTime, interval), 0); }); Tinytest.add("timesync - tick check - slightly off", function (test) { - var lastTime = 5000; - var currentTime = 6500; - var interval = 1000; + const lastTime = 5000; + let currentTime = 6500; + const interval = 1000; test.equal(SyncInternals.getDiscrepancy(lastTime, currentTime, interval), 500); @@ -23,9 +23,9 @@ Tinytest.add("timesync - tick check - slightly off", function (test) { }); Tinytest.add("timesync - tick check - big jump", function (test) { - var lastTime = 5000; - var currentTime = 0; - var interval = 1000; + const lastTime = 5000; + let currentTime = 0; + const interval = 1000; test.equal(SyncInternals.getDiscrepancy(lastTime, currentTime, interval), -6000); @@ -41,7 +41,7 @@ Tinytest.add("timesync - tick check - big jump", function (test) { Tinytest.addAsync("timesync - basic - initial sync", function (test, next) { function success() { - var syncedTime = TimeSync.serverTime(); + const syncedTime = TimeSync.serverTime(); // Make sure the time exists test.isTrue(syncedTime); @@ -80,26 +80,26 @@ Tinytest.addAsync("timesync - basic - serverTime format", function (test, next) Tinytest.addAsync("timesync - basic - different sync intervals", function (test, next) { - var aCount = 0, - bCount = 0, - cCount = 0; + let aCount = 0; + let bCount = 0; + let cCount = 0; - var a = Tracker.autorun(function () { + const a = Tracker.autorun(function () { TimeSync.serverTime(null, 500); aCount++; }); - var b = Tracker.autorun(function () { + const b = Tracker.autorun(function () { TimeSync.serverTime(); bCount++; }); - var c = Tracker.autorun(function () { + const c = Tracker.autorun(function () { TimeSync.serverTime(null, 2000); cCount++; }); - var testInterval = 4990; + const testInterval = 4990; Meteor.setTimeout(function () { @@ -142,7 +142,7 @@ Tinytest.addAsync("timesync - basic - DDP timeSync", function (test, next) { }); Tinytest.addAsync("timesync - basic - HTTP timeSync", function (test, next) { - var syncUrl = TimeSync.getSyncUrl(); + const syncUrl = TimeSync.getSyncUrl(); test.isNotNull(syncUrl); @@ -152,9 +152,9 @@ Tinytest.addAsync("timesync - basic - HTTP timeSync", function (test, next) { next(); } test.isTrue(res.content); - var serverTime = parseInt(res.content,10); + const serverTime = parseInt(res.content,10); test.isTrue(_.isNumber(serverTime)); test.isTrue(Math.abs(serverTime - Date.now()) < 1000); next(); }); -}); \ No newline at end of file +}); From b0c54cb3e3e1b77aafcff74d04155ee3825fe407 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Sun, 12 Nov 2023 22:46:39 +0100 Subject: [PATCH 7/9] Try to move tests to GitHub --- .github/workflows/tests.yml | 34 ++++++++++++++++++++++++++++++++++ .travis.yml | 6 ------ 2 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..c469706 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,34 @@ +name: Test suite +on: + push: + branches: + - master + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + meteorRelease: + - '--release 1.12.1' + - '--release 2.3' + - '--release 2.8.1' + - '--release 2.13.3' + # Latest version + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: '16.x' + + - name: Install Dependencies + run: | + curl https://install.meteor.com | /bin/sh + npm i -g @zodern/mtest + - name: Run Tests + run: | + mtest --package ./ --once ${{ matrix.meteorRelease }} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 787c8c3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: node_js -node_js: - - "12" - - "14" -before_install: - - "curl -L http://git.io/ejPSng | /bin/sh" From 341db208b9674e7a3b9535d45395867827ac79e9 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Mon, 8 Apr 2024 17:48:39 +0200 Subject: [PATCH 8/9] Fix history versions sorting --- History.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index 42ab345..f4db22d 100644 --- a/History.md +++ b/History.md @@ -1,7 +1,4 @@ ## vNEXT -## v0.5.5 - -- Added compatibility for Meteor 3.0-beta.7 ## v0.6.0 @@ -16,6 +13,10 @@ - Added option to set the timesync URL using `TimeSync.setSyncUrl` - Removed IE8 compat function +## v0.5.5 + +- Added compatibility for Meteor 3.0-beta.7 + ## v0.5.4 - Adding CORS compatibility for Ionic capacitorjs From 623804e77b4271b3fa9fe4a0d9ea0cd1f1d7e4ef Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Mon, 19 Aug 2024 16:56:28 +0200 Subject: [PATCH 9/9] Update compatible Meteor versions --- .github/workflows/tests.yml | 2 +- package.js | 2 +- tests/client.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c469706..e1215bc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: - '--release 1.12.1' - '--release 2.3' - '--release 2.8.1' - - '--release 2.13.3' + - '--release 2.16' # Latest version steps: - name: Checkout code diff --git a/package.js b/package.js index c85860b..5ae960e 100644 --- a/package.js +++ b/package.js @@ -6,7 +6,7 @@ Package.describe({ }); Package.onUse(function (api) { - api.versionsFrom(["1.12", "2.3", '3.0-beta.7']); + api.versionsFrom(["1.12", "2.3"]); api.use([ 'check', diff --git a/tests/client.js b/tests/client.js index ec84500..ee21267 100644 --- a/tests/client.js +++ b/tests/client.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { HTTP } from 'meteor/http'; +import { _ } from 'meteor/underscore'; Tinytest.add("timesync - tick check - normal tick", function (test) { const lastTime = 5000;