Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
198 lines
6.5 KiB
JavaScript
198 lines
6.5 KiB
JavaScript
// Subscribe to the `meteor_autoupdate_clientVersions` collection,
|
|
// which contains the set of acceptable client versions.
|
|
//
|
|
// A "hard code push" occurs when the running client version is not in
|
|
// the set of acceptable client versions (or the server updates the
|
|
// collection, there is a published client version marked `current` and
|
|
// the running client version is no longer in the set).
|
|
//
|
|
// When the `reload` package is loaded, a hard code push causes
|
|
// the browser to reload, so that it will load the latest client
|
|
// version from the server.
|
|
//
|
|
// A "soft code push" represents the situation when the running client
|
|
// version is in the set of acceptable versions, but there is a newer
|
|
// version available on the server.
|
|
//
|
|
// `Autoupdate.newClientAvailable` is a reactive data source which
|
|
// becomes `true` if a new version of the client is available on
|
|
// the server.
|
|
//
|
|
// This package doesn't implement a soft code reload process itself,
|
|
// but `newClientAvailable` could be used for example to display a
|
|
// "click to reload" link to the user.
|
|
|
|
// The client version of the client code currently running in the
|
|
// browser.
|
|
|
|
import { Meteor } from 'meteor/meteor';
|
|
import { ClientVersions } from './client_versions.js';
|
|
|
|
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: [],
|
|
};
|
|
|
|
export const Autoupdate = {};
|
|
|
|
// Stores acceptable client versions.
|
|
const clientVersions = (Autoupdate._clientVersions = // Used by a self-test and hot-module-replacement
|
|
new ClientVersions());
|
|
|
|
Meteor.connection.registerStoreClient('meteor_autoupdate_clientVersions', clientVersions.createStore());
|
|
|
|
Autoupdate.newClientAvailable = function () {
|
|
return clientVersions.newClientAvailable(clientArch, ['versionRefreshable', 'versionNonRefreshable'], autoupdateVersions);
|
|
};
|
|
|
|
// Set to true if the link.onload callback ever fires for any <link> node.
|
|
let knownToSupportCssOnLoad = false;
|
|
|
|
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.
|
|
//
|
|
// A better way than timeconstants here might be to use the knowledge
|
|
// of when we reconnect to help trigger these retries. Typically, the
|
|
// server fixing code will result in a restart and reconnect, but
|
|
// potentially the subscription could have a transient error.
|
|
minCount: 0, // don't do any immediate retries
|
|
baseTimeout: 30 * 1000, // start with 30s
|
|
});
|
|
|
|
let failures = 0;
|
|
|
|
Autoupdate._retrySubscription = () => {
|
|
Meteor.subscribe('meteor_autoupdate_clientVersions', {
|
|
onError(error) {
|
|
Meteor._debug('autoupdate subscription failed', error);
|
|
failures++;
|
|
retry.retryLater(failures, function () {
|
|
// Just retry making the subscription, don't reload the whole
|
|
// page. While reloading would catch more cases (for example,
|
|
// the server went back a version and is now doing old-style hot
|
|
// code push), it would also be more prone to reload loops,
|
|
// which look really bad to the user. Just retrying the
|
|
// subscription over DDP means it is at least possible to fix by
|
|
// updating the server.
|
|
Autoupdate._retrySubscription();
|
|
});
|
|
},
|
|
|
|
onReady() {
|
|
// Call checkNewVersionDocument with a slight delay, so that the
|
|
// const handle declaration is guaranteed to be initialized, even if
|
|
// the added or changed callbacks are called synchronously.
|
|
const resolved = Promise.resolve();
|
|
function check(doc) {
|
|
resolved.then(() => checkNewVersionDocument(doc));
|
|
}
|
|
|
|
const stop = clientVersions.watch(check);
|
|
|
|
const reloadDelayInSeconds = Meteor.isProduction ? 60 : 0;
|
|
|
|
function checkNewVersionDocument(doc) {
|
|
if (doc._id !== clientArch) {
|
|
return;
|
|
}
|
|
|
|
if (doc.versionNonRefreshable !== autoupdateVersions.versionNonRefreshable) {
|
|
// Non-refreshable assets have changed, so we have to reload the
|
|
// whole page rather than just replacing <link> tags.
|
|
if (stop) stop();
|
|
if (Package.reload) {
|
|
// The reload package should be provided by ddp-client, which
|
|
// is provided by the ddp package that autoupdate depends on.
|
|
|
|
// Delay reload in 60 seconds
|
|
console.warn(
|
|
'Client version changed from',
|
|
autoupdateVersions.versionNonRefreshable,
|
|
'to',
|
|
doc.versionNonRefreshable,
|
|
`Page will reload in ${reloadDelayInSeconds} seconds`,
|
|
);
|
|
document.dispatchEvent(new Event('client_changed'));
|
|
}
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|
|
|
|
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);
|
|
});
|
|
}
|
|
}
|
|
|
|
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);
|
|
});
|
|
|
|
const head = document.getElementsByTagName('head').item(0);
|
|
head.appendChild(newLink);
|
|
});
|
|
} else {
|
|
removeOldLinks();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
Autoupdate._retrySubscription();
|