New version nudge (#1793)

## Ticket
1329

The existing nudge to refresh the webpage only happens when the app cannot find the specific javascript file in the server. As we don't purge the files on each build, the browser typically uses the cached version of the app, which could be weeks behind (based on the error logs).

## Approach
Poll the current version periodically (set to 1 hour for now) and invoke the nudge when a newer version is detected.

We typically don't need to bump `MINIMUM_VERSION` unless there is an urgent need to make users move away from older versions (e.g. API changes, mistakes, etc.)

## Trade-offs
Wanted to put the value in a separate file called `.min.version` so that the env's history won't be polluted with version bumps, but not sure how to implement with minimal code without having to read from the file. Getting from the env is the easiest to implement (per my limited knowledge).
This commit is contained in:
infinite-persistence 2022-07-05 21:14:32 +08:00 committed by GitHub
parent 769b1cdabb
commit 5638f64831
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 4 deletions

View file

@ -2,6 +2,10 @@
# Copy this file to .env to make modifications # Copy this file to .env to make modifications
# --- Minimum Version (Format: YYYYMMDDNNN) ---
# Bump this when we want to nudge the user to refresh.
MINIMUM_VERSION=20220705001
# --- Base config --- # --- Base config ---
WEBPACK_WEB_PORT=9090 WEBPACK_WEB_PORT=9090
WEBPACK_ELECTRON_PORT=9091 WEBPACK_ELECTRON_PORT=9091

View file

@ -3,6 +3,7 @@
require('dotenv-defaults').config({ silent: false }); require('dotenv-defaults').config({ silent: false });
const config = { const config = {
MINIMUM_VERSION: process.env.MINIMUM_VERSION,
WEBPACK_WEB_PORT: process.env.WEBPACK_WEB_PORT, WEBPACK_WEB_PORT: process.env.WEBPACK_WEB_PORT,
WEBPACK_ELECTRON_PORT: process.env.WEBPACK_ELECTRON_PORT, WEBPACK_ELECTRON_PORT: process.env.WEBPACK_ELECTRON_PORT,
WEB_SERVER_PORT: process.env.WEB_SERVER_PORT, WEB_SERVER_PORT: process.env.WEB_SERVER_PORT,

View file

@ -13,7 +13,14 @@ import * as MODALS from 'constants/modal_types';
import React, { Fragment, useState, useEffect } from 'react'; import React, { Fragment, useState, useEffect } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { doDaemonReady, doAutoUpdate, doOpenModal, doHideModal, doToggle3PAnalytics } from 'redux/actions/app'; import {
doDaemonReady,
doAutoUpdate,
doOpenModal,
doHideModal,
doToggle3PAnalytics,
doMinVersionSubscribe,
} from 'redux/actions/app';
import Lbry, { apiCall } from 'lbry'; import Lbry, { apiCall } from 'lbry';
import { isURIValid } from 'util/lbryURI'; import { isURIValid } from 'util/lbryURI';
import { setSearchApi } from 'redux/actions/search'; import { setSearchApi } from 'redux/actions/search';
@ -252,9 +259,16 @@ function AppWrapper() {
app.store.dispatch(doFetchUserLocale()); app.store.dispatch(doFetchUserLocale());
}, 25); }, 25);
const nonCriticalTimer = setTimeout(() => {
app.store.dispatch(doMinVersionSubscribe());
}, 5000);
analytics.startupEvent(Date.now()); analytics.startupEvent(Date.now());
return () => clearTimeout(timer); return () => {
clearTimeout(timer);
clearTimeout(nonCriticalTimer);
};
} }
}, [readyToLaunch, persistDone]); }, [readyToLaunch, persistDone]);

View file

@ -4,6 +4,7 @@ import isDev from 'electron-is-dev';
import { ipcRenderer, remote } from 'electron'; import { ipcRenderer, remote } from 'electron';
// @endif // @endif
import path from 'path'; import path from 'path';
import { MINIMUM_VERSION, URL } from 'config';
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
@ -269,6 +270,33 @@ export function doCheckUpgradeSubscribe() {
}; };
} }
export function doMinVersionCheck() {
return (dispatch) => {
fetch(`${URL}/$/minVersion/v1/get`)
.then((response) => response.json())
.then((json) => {
if (json?.status === 'success' && json?.data && MINIMUM_VERSION) {
const liveMinimumVersion = Number(json.data);
if (liveMinimumVersion > MINIMUM_VERSION) {
dispatch({ type: ACTIONS.RELOAD_REQUIRED });
}
}
})
.catch((err) => {
console.error(err);
});
};
}
export function doMinVersionSubscribe() {
return (dispatch) => {
dispatch(doMinVersionCheck());
const CHECK_UPGRADE_INTERVAL_MS = 60 * 60 * 1000;
setInterval(() => dispatch(doMinVersionCheck()), CHECK_UPGRADE_INTERVAL_MS);
};
}
export function doCheckDaemonVersion() { export function doCheckDaemonVersion() {
return (dispatch) => { return (dispatch) => {
// @if TARGET='app' // @if TARGET='app'

25
web/src/minVersion.js Normal file
View file

@ -0,0 +1,25 @@
const { MINIMUM_VERSION } = require('../../config');
async function getMinVersion(ctx, version) {
if (!MINIMUM_VERSION) {
ctx.status = 404;
ctx.body = { message: 'Not Found' };
return;
}
try {
ctx.set('Content-Type', 'application/json');
ctx.set('Access-Control-Allow-Origin', '*');
ctx.body = {
status: 'success',
data: MINIMUM_VERSION,
};
} catch (err) {
ctx.status = err.statusCode || err.status || 500;
ctx.body = {
message: err.message,
};
}
}
module.exports = { getMinVersion };

View file

@ -1,12 +1,13 @@
const { fetchStreamUrl } = require('./fetchStreamUrl'); const { fetchStreamUrl } = require('./fetchStreamUrl');
const { getHomepage } = require('./homepageApi');
const { getHtml } = require('./html'); const { getHtml } = require('./html');
const { getMinVersion } = require('./minVersion');
const { getOEmbed } = require('./oEmbed'); const { getOEmbed } = require('./oEmbed');
const { getRss } = require('./rss'); const { getRss } = require('./rss');
const { getTempFile } = require('./tempfile'); const { getTempFile } = require('./tempfile');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const Router = require('@koa/router'); const Router = require('@koa/router');
const { getHomepage } = require('./homepageApi');
// So any code from 'lbry-redux'/'lbryinc' that uses `fetch` can be run on the server // So any code from 'lbry-redux'/'lbryinc' that uses `fetch` can be run on the server
global.fetch = fetch; global.fetch = fetch;
@ -36,8 +37,9 @@ const tempfileMiddleware = async (ctx) => {
ctx.body = temp; ctx.body = temp;
}; };
router.get(`/$/api/content/v1/get`, async (ctx) => getHomepage(ctx, 1)); router.get(`/$/minVersion/v1/get`, async (ctx) => getMinVersion(ctx));
router.get(`/$/api/content/v1/get`, async (ctx) => getHomepage(ctx, 1));
router.get(`/$/api/content/v2/get`, async (ctx) => getHomepage(ctx, 2)); router.get(`/$/api/content/v2/get`, async (ctx) => getHomepage(ctx, 2));
router.get(`/$/download/:claimName/:claimId`, async (ctx) => { router.get(`/$/download/:claimName/:claimId`, async (ctx) => {