diff --git a/packages/autoupdate/autoupdate_client.js b/packages/autoupdate/autoupdate_client.js
index 0465e3bd1e..5fb75aba3d 100644
--- a/packages/autoupdate/autoupdate_client.js
+++ b/packages/autoupdate/autoupdate_client.js
@@ -24,28 +24,46 @@
// The client version of the client code currently running in the
// browser.
-var autoupdateVersion = __meteor_runtime_config__.autoupdateVersion || "unknown";
-var autoupdateVersionRefreshable =
- __meteor_runtime_config__.autoupdateVersionRefreshable || "unknown";
-// The collection of acceptable client versions.
-ClientVersions = new Mongo.Collection("meteor_autoupdate_clientVersions");
+const clientArch = Meteor.isCordova ? "web.cordova" :
+ Meteor.isModern ? "web.browser" : "web.browser.legacy";
+
+const autoupdateVersions =
+ __meteor_runtime_config__.autoupdate.versions[clientArch] || {
+ version: "unknown",
+ versionRefreshable: "unknown",
+ versionNonRefreshable: "unknown",
+ assets: [],
+ };
Autoupdate = {};
+// The collection of acceptable client versions.
+ClientVersions =
+ Autoupdate._ClientVersions = // Used by a self-test.
+ new Mongo.Collection("meteor_autoupdate_clientVersions");
+
Autoupdate.newClientAvailable = function () {
- return !! ClientVersions.findOne({
- _id: "version",
- version: {$ne: autoupdateVersion} }) ||
- !! ClientVersions.findOne({
- _id: "version-refreshable",
- version: {$ne: autoupdateVersionRefreshable} });
+ return !! (
+ ClientVersions.findOne({
+ _id: clientArch,
+ versionNonRefreshable: {
+ $ne: autoupdateVersions.versionNonRefreshable,
+ }
+ }) ||
+ ClientVersions.findOne({
+ _id: clientArch,
+ versionRefreshable: {
+ $ne: autoupdateVersions.versionRefreshable,
+ }
+ })
+ );
};
-Autoupdate._ClientVersions = ClientVersions; // Used by a self-test
-var knownToSupportCssOnLoad = false;
+// Set to true if the link.onload callback ever fires for any node.
+let knownToSupportCssOnLoad = false;
-var retry = new Retry({
+const retry = new Retry({
// Unlike the stream reconnect use of Retry, which we want to be instant
// in normal operation, this is a wacky failure. We don't want to retry
// right away, we can start slowly.
@@ -57,19 +75,12 @@ var retry = new Retry({
minCount: 0, // don't do any immediate retries
baseTimeout: 30*1000 // start with 30s
});
-var failures = 0;
-function after(times, func) {
- return function() {
- if (--times < 1) {
- return func.apply(this, arguments);
- }
- };
-};
+let failures = 0;
-Autoupdate._retrySubscription = function () {
+Autoupdate._retrySubscription = () => {
Meteor.subscribe("meteor_autoupdate_clientVersions", {
- onError: function (error) {
+ onError(error) {
Meteor._debug("autoupdate subscription failed", error);
failures++;
retry.retryLater(failures, function () {
@@ -83,94 +94,104 @@ Autoupdate._retrySubscription = function () {
Autoupdate._retrySubscription();
});
},
- onReady: function () {
- if (Package.reload) {
- var checkNewVersionDocument = function (doc) {
- var self = this;
- if (doc._id === 'version-refreshable' &&
- doc.version !== autoupdateVersionRefreshable) {
- autoupdateVersionRefreshable = doc.version;
- // Switch out old css links for the new css links. Inspired by:
- // https://github.com/guard/guard-livereload/blob/master/js/livereload.js#L710
- var newCss = (doc.assets && doc.assets.allCss) || [];
- var oldLinks = [];
- Array.prototype.forEach.call(
- document.getElementsByTagName('link'),
- function (link) {
- if (link.className === '__meteor-css__') {
- oldLinks.push(link);
- }
- }
- );
+ onReady() {
+ const handle = ClientVersions.find().observe({
+ added: checkNewVersionDocument,
+ changed: checkNewVersionDocument
+ });
- function waitUntilCssLoads(link, callback) {
- var called;
- function executeCallback(...args) {
- if (! called) {
- called = true;
- return callback(...args);
- }
- }
+ function checkNewVersionDocument(doc) {
+ if (doc._id !== clientArch) {
+ return;
+ }
- link.onload = function () {
- knownToSupportCssOnLoad = true;
- executeCallback();
- };
+ if (doc.versionNonRefreshable !==
+ autoupdateVersions.versionNonRefreshable) {
+ // Non-refreshable assets have changed, so we have to reload the
+ // whole page rather than just replacing tags.
+ if (handle) handle.stop();
+ if (Package.reload) {
+ // The reload package should be provided by ddp-client, which
+ // is provided by the ddp package that autoupdate depends on.
+ Package.reload.Reload._reload();
+ }
+ return;
+ }
- if (! knownToSupportCssOnLoad) {
- var id = Meteor.setInterval(function () {
- if (link.sheet) {
- executeCallback();
- Meteor.clearInterval(id);
- }
- }, 50);
+ if (doc.versionRefreshable !== autoupdateVersions.versionRefreshable) {
+ autoupdateVersions.versionRefreshable = doc.versionRefreshable;
+
+ // Switch out old css links for the new css links. Inspired by:
+ // https://github.com/guard/guard-livereload/blob/master/js/livereload.js#L710
+ var newCss = doc.assets || [];
+ var oldLinks = [];
+
+ Array.prototype.forEach.call(
+ document.getElementsByTagName('link'),
+ function (link) {
+ if (link.className === '__meteor-css__') {
+ oldLinks.push(link);
}
}
+ );
- var removeOldLinks = after(newCss.length, function () {
- oldLinks.forEach(function (link) {
+ function waitUntilCssLoads(link, callback) {
+ var called;
+
+ link.onload = function () {
+ knownToSupportCssOnLoad = true;
+ if (! called) {
+ called = true;
+ callback();
+ }
+ };
+
+ if (! knownToSupportCssOnLoad) {
+ var id = Meteor.setInterval(function () {
+ if (link.sheet) {
+ if (! called) {
+ called = true;
+ callback();
+ }
+ Meteor.clearInterval(id);
+ }
+ }, 50);
+ }
+ }
+
+ let newLinksLeftToLoad = newCss.length;
+ function removeOldLinks() {
+ if (oldLinks.length > 0 &&
+ --newLinksLeftToLoad < 1) {
+ oldLinks.splice(0).forEach(link => {
link.parentNode.removeChild(link);
});
- });
+ }
+ }
- var attachStylesheetLink = function (newLink) {
- document.getElementsByTagName("head").item(0).appendChild(newLink);
+ if (newCss.length > 0) {
+ newCss.forEach(css => {
+ const newLink = document.createElement("link");
+ newLink.setAttribute("rel", "stylesheet");
+ newLink.setAttribute("type", "text/css");
+ newLink.setAttribute("class", "__meteor-css__");
+ newLink.setAttribute("href", css.url);
waitUntilCssLoads(newLink, function () {
Meteor.setTimeout(removeOldLinks, 200);
});
- };
-
- if (newCss.length !== 0) {
- newCss.forEach(function (css) {
- var newLink = document.createElement("link");
- newLink.setAttribute("rel", "stylesheet");
- newLink.setAttribute("type", "text/css");
- newLink.setAttribute("class", "__meteor-css__");
- newLink.setAttribute("href", css.url);
- attachStylesheetLink(newLink);
- });
- } else {
- removeOldLinks();
- }
+ const head = document.getElementsByTagName("head").item(0);
+ head.appendChild(newLink);
+ });
+ } else {
+ removeOldLinks();
}
- else if (doc._id === 'version' && doc.version !== autoupdateVersion) {
- handle && handle.stop();
-
- if (Package.reload) {
- Package.reload.Reload._reload();
- }
- }
- };
-
- var handle = ClientVersions.find().observe({
- added: checkNewVersionDocument,
- changed: checkNewVersionDocument
- });
+ }
}
}
});
};
+
Autoupdate._retrySubscription();
diff --git a/packages/autoupdate/autoupdate_cordova.js b/packages/autoupdate/autoupdate_cordova.js
index fd047b26e5..af62a537f1 100644
--- a/packages/autoupdate/autoupdate_cordova.js
+++ b/packages/autoupdate/autoupdate_cordova.js
@@ -1,16 +1,20 @@
-var autoupdateVersionCordova = __meteor_runtime_config__.autoupdateVersionCordova || "unknown";
+var autoupdateVersionsCordova =
+ __meteor_runtime_config__.autoupdate.versions["web.cordova"] || {
+ version: "unknown"
+ };
// The collection of acceptable client versions.
ClientVersions = new Mongo.Collection("meteor_autoupdate_clientVersions");
Autoupdate = {};
-Autoupdate.newClientAvailable = function() {
- return !! ClientVersions.findOne({
- _id: 'version-cordova',
- version: {$ne: autoupdateVersionCordova}
+Autoupdate.newClientAvailable =
+ () => !! ClientVersions.findOne({
+ _id: "web.cordova",
+ version: {
+ $ne: autoupdateVersionsCordova.version
+ }
});
-};
var retry = new Retry({
// Unlike the stream reconnect use of Retry, which we want to be instant
@@ -24,12 +28,14 @@ var retry = new Retry({
minCount: 0, // don't do any immediate retries
baseTimeout: 30*1000 // start with 30s
});
-var failures = 0;
-Autoupdate._retrySubscription = function() {
- var appId = __meteor_runtime_config__.appId;
+let failures = 0;
+
+Autoupdate._retrySubscription = () => {
+ const { appId } = __meteor_runtime_config__;
+
Meteor.subscribe("meteor_autoupdate_clientVersions", appId, {
- onError: function(error) {
+ onError(error) {
console.log("autoupdate subscription failed:", error);
failures++;
retry.retryLater(failures, function() {
@@ -43,16 +49,18 @@ Autoupdate._retrySubscription = function() {
Autoupdate._retrySubscription();
});
},
- onReady: function() {
+
+ onReady() {
if (Package.reload) {
- var checkNewVersionDocument = function(doc) {
- var self = this;
- if (doc.version !== autoupdateVersionCordova) {
+ function checkNewVersionDocument(doc) {
+ if (doc.version !== autoupdateVersionsCordova.version) {
newVersionAvailable();
}
- };
+ }
- var handle = ClientVersions.find({_id: 'version-cordova'}).observe({
+ ClientVersions.find({
+ _id: "web.cordova"
+ }).observe({
added: checkNewVersionDocument,
changed: checkNewVersionDocument
});
@@ -61,8 +69,8 @@ Autoupdate._retrySubscription = function() {
});
};
-Meteor.startup(function() {
- WebAppLocalServer.onNewVersionReady(function() {
+Meteor.startup(() => {
+ WebAppLocalServer.onNewVersionReady(() => {
if (Package.reload) {
Package.reload.Reload._reload();
}
@@ -71,6 +79,6 @@ Meteor.startup(function() {
Autoupdate._retrySubscription();
});
-var newVersionAvailable = function() {
+function newVersionAvailable() {
WebAppLocalServer.checkForUpdates();
}
diff --git a/packages/autoupdate/autoupdate_server.js b/packages/autoupdate/autoupdate_server.js
index e8cc178579..8083824c1c 100644
--- a/packages/autoupdate/autoupdate_server.js
+++ b/packages/autoupdate/autoupdate_server.js
@@ -1,39 +1,38 @@
-// Publish the current client versions to the client. When a client
-// sees the subscription change and that there is a new version of the
-// client available on the server, it can reload.
+// Publish the current client versions for each client architecture
+// (web.browser, web.browser.legacy, web.cordova). When a client observes
+// a change in the versions associated with its client architecture,
+// it will refresh itself, either by swapping out CSS assets or by
+// reloading the page.
//
-// By default there are two current client versions. The refreshable client
-// version is identified by a hash of the client resources seen by the browser
-// that are refreshable, such as CSS, while the non refreshable client version
-// is identified by a hash of the rest of the client assets
-// (the HTML, code, and static files in the `public` directory).
+// There are three versions for any given client architecture: `version`,
+// `versionRefreshable`, and `versionNonRefreshable`. The refreshable
+// version is a hash of just the client resources that are refreshable,
+// such as CSS, while the non-refreshable version is a hash of the rest of
+// the client assets, excluding the refreshable ones: HTML, JS, and static
+// files in the `public` directory. The `version` version is a combined
+// hash of everything.
//
-// If the environment variable `AUTOUPDATE_VERSION` is set it will be
-// used as the client id instead. You can use this to control when
-// the client reloads. For example, if you want to only force a
-// reload on major changes, you can use a custom AUTOUPDATE_VERSION
-// which you only change when something worth pushing to clients
-// immediately happens.
+// If the environment variable `AUTOUPDATE_VERSION` is set, it will be
+// used in place of all client versions. You can use this variable to
+// control when the client reloads. For example, if you want to force a
+// reload only after major changes, use a custom AUTOUPDATE_VERSION and
+// change it only when something worth pushing to clients happens.
//
-// The server publishes a `meteor_autoupdate_clientVersions`
-// collection. There are two documents in this collection, a document
-// with _id 'version' which represents the non refreshable client assets,
-// and a document with _id 'version-refreshable' which represents the
-// refreshable client assets. Each document has a 'version' field
-// which is equivalent to the hash of the relevant assets. The refreshable
-// document also contains a list of the refreshable assets, so that the client
-// can swap in the new assets without forcing a page refresh. Clients can
-// observe changes on these documents to detect when there is a new
-// version available.
-//
-// In this implementation only two documents are present in the collection
-// the current refreshable client version and the current nonRefreshable client
-// version. Developers can easily experiment with different versioning and
-// updating models by forking this package.
+// The server publishes a `meteor_autoupdate_clientVersions` collection.
+// The ID of each document is the client architecture, and the fields of
+// the document are the versions described above.
var Future = Npm.require("fibers/future");
-Autoupdate = {};
+Autoupdate = __meteor_runtime_config__.autoupdate = {
+ // Map from client architectures (web.browser, web.browser.legacy,
+ // web.cordova) to version fields { version, versionRefreshable,
+ // versionNonRefreshable, refreshable } that will be stored in
+ // ClientVersions documents (whose IDs are client architectures). This
+ // data gets serialized into the boilerplate because it's stored in
+ // __meteor_runtime_config__.autoupdate.versions.
+ versions: {}
+};
// The collection of acceptable client versions.
ClientVersions = new Mongo.Collection("meteor_autoupdate_clientVersions",
@@ -53,91 +52,47 @@ Autoupdate.appId = __meteor_runtime_config__.appId = process.env.APP_ID;
var syncQueue = new Meteor._SynchronousQueue();
-// updateVersions can only be called after the server has fully loaded.
-var updateVersions = function (shouldReloadClientProgram) {
- // Step 1: load the current client program on the server and update the
- // hash values in __meteor_runtime_config__.
+function updateVersions(shouldReloadClientProgram) {
+ // Step 1: load the current client program on the server
if (shouldReloadClientProgram) {
WebAppInternals.reloadClientPrograms();
}
- // If we just re-read the client program, or if we don't have an autoupdate
- // version, calculate it.
- if (shouldReloadClientProgram || Autoupdate.autoupdateVersion === null) {
- Autoupdate.autoupdateVersion =
- process.env.AUTOUPDATE_VERSION ||
- WebApp.calculateClientHashNonRefreshable();
- }
- // If we just recalculated it OR if it was set by (eg) test-in-browser,
- // ensure it ends up in __meteor_runtime_config__.
- __meteor_runtime_config__.autoupdateVersion =
- Autoupdate.autoupdateVersion;
+ // Step 2: update __meteor_runtime_config__.autoupdate.versions.
+ const clientArchs = Object.keys(WebApp.clientPrograms);
+ clientArchs.forEach(arch => {
+ Autoupdate.versions[arch] = {
+ version: WebApp.calculateClientHashNonRefreshable(arch),
+ versionRefreshable: WebApp.calculateClientHashRefreshable(arch),
+ versionNonRefreshable:
+ WebApp.calculateClientHashNonRefreshable(arch),
+ };
+ });
- Autoupdate.autoupdateVersionRefreshable =
- __meteor_runtime_config__.autoupdateVersionRefreshable =
- process.env.AUTOUPDATE_VERSION ||
- WebApp.calculateClientHashRefreshable();
-
- Autoupdate.autoupdateVersionCordova =
- __meteor_runtime_config__.autoupdateVersionCordova =
- process.env.AUTOUPDATE_VERSION ||
- WebApp.calculateClientHashCordova();
-
- // Step 2: form the new client boilerplate which contains the updated
+ // Step 3: form the new client boilerplate which contains the updated
// assets and __meteor_runtime_config__.
if (shouldReloadClientProgram) {
WebAppInternals.generateBoilerplate();
}
- // XXX COMPAT WITH 0.8.3
- if (! ClientVersions.findOne({current: true})) {
- // To ensure apps with version of Meteor prior to 0.9.0 (in
- // which the structure of documents in `ClientVersions` was
- // different) also reload.
- ClientVersions.insert({current: true});
- }
-
- if (! ClientVersions.findOne({_id: "version"})) {
- ClientVersions.insert({
- _id: "version",
- version: Autoupdate.autoupdateVersion
- });
- } else {
- ClientVersions.update("version", { $set: {
- version: Autoupdate.autoupdateVersion
- }});
- }
-
- if (! ClientVersions.findOne({_id: "version-cordova"})) {
- ClientVersions.insert({
- _id: "version-cordova",
- version: Autoupdate.autoupdateVersionCordova,
- refreshable: false
- });
- } else {
- ClientVersions.update("version-cordova", { $set: {
- version: Autoupdate.autoupdateVersionCordova
- }});
- }
-
- // Use `onListening` here because we need to use
- // `WebAppInternals.refreshableAssets`, which is only set after
+ // Step 4: update the ClientVersions collection.
+ // We use `onListening` here because we need to use
+ // `WebApp.getRefreshableAssets`, which is only set after
// `WebApp.generateBoilerplate` is called by `main` in webapp.
- WebApp.onListening(function () {
- if (! ClientVersions.findOne({_id: "version-refreshable"})) {
- ClientVersions.insert({
- _id: "version-refreshable",
- version: Autoupdate.autoupdateVersionRefreshable,
- assets: WebAppInternals.refreshableAssets
- });
- } else {
- ClientVersions.update("version-refreshable", { $set: {
- version: Autoupdate.autoupdateVersionRefreshable,
- assets: WebAppInternals.refreshableAssets
- }});
- }
+ WebApp.onListening(() => {
+ clientArchs.forEach(arch => {
+ const payload = {
+ ...Autoupdate.versions[arch],
+ assets: WebApp.getRefreshableAssets(arch),
+ };
+ if (! ClientVersions.findOne({ _id: arch })) {
+ ClientVersions.insert({ _id: arch, ...payload });
+ } else {
+ ClientVersions.update(arch, { $set: payload });
+ }
+ });
});
-};
+}
Meteor.publish(
"meteor_autoupdate_clientVersions",
diff --git a/packages/autoupdate/package.js b/packages/autoupdate/package.js
index 576cb295e6..536eeae5f7 100644
--- a/packages/autoupdate/package.js
+++ b/packages/autoupdate/package.js
@@ -1,6 +1,6 @@
Package.describe({
summary: "Update the client when new client code is available",
- version: '1.4.1'
+ version: '1.5.0'
});
Package.onUse(function (api) {
diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js
index 14827483fd..63c99ed84d 100644
--- a/packages/webapp/webapp_server.js
+++ b/packages/webapp/webapp_server.js
@@ -206,12 +206,6 @@ Meteor.startup(function () {
].versionNonRefreshable;
};
- WebApp.calculateClientHashCordova = function () {
- return (WebApp.clientPrograms["web.cordova"] || {
- version: "none"
- }).version;
- };
-
WebApp.getRefreshableAssets = function (arch) {
return WebApp.clientPrograms[
arch || WebApp.defaultArch