ChunkLoadError: ask user to reload instead of automatically reloading (#139)

## Issue
We previously automatically reload when there is a chunk error. This works fine if it's the case of new code was pushed recently while the user was active. But if the failure was caused by other things like network problems or the file IS actually missing, we end up in an infinite loop of refreshes.

## New approach
Tell the user to reload instead of automatically doing it.
This commit is contained in:
infinite-persistence 2021-10-27 23:07:06 +08:00 committed by GitHub
parent 03f69eff86
commit 247ee757d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 47 additions and 31 deletions

View file

@ -21,6 +21,7 @@ import {
selectAutoUpdateDownloaded, selectAutoUpdateDownloaded,
selectModal, selectModal,
selectActiveChannelClaim, selectActiveChannelClaim,
selectIsReloadRequired,
} from 'redux/selectors/app'; } from 'redux/selectors/app';
import { doGetWalletSyncPreference, doSetLanguage, doSetHomepage } from 'redux/actions/settings'; import { doGetWalletSyncPreference, doSetLanguage, doSetHomepage } from 'redux/actions/settings';
import { doSyncLoop } from 'redux/actions/sync'; import { doSyncLoop } from 'redux/actions/sync';
@ -44,6 +45,7 @@ const select = (state) => ({
languages: selectLoadedLanguages(state), languages: selectLoadedLanguages(state),
autoUpdateDownloaded: selectAutoUpdateDownloaded(state), autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state), isUpgradeAvailable: selectIsUpgradeAvailable(state),
isReloadRequired: selectIsReloadRequired(state),
syncError: selectGetSyncErrorMessage(state), syncError: selectGetSyncErrorMessage(state),
uploadCount: selectUploadCount(state), uploadCount: selectUploadCount(state),
rewards: selectUnclaimedRewards(state), rewards: selectUnclaimedRewards(state),

View file

@ -6,6 +6,7 @@ import classnames from 'classnames';
import analytics from 'analytics'; import analytics from 'analytics';
import { buildURI, parseURI } from 'util/lbryURI'; import { buildURI, parseURI } from 'util/lbryURI';
import { SIMPLE_SITE } from 'config'; import { SIMPLE_SITE } from 'config';
import Nag from 'component/common/nag';
import Router from 'component/router/index'; import Router from 'component/router/index';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { openContextMenu } from 'util/context-menu'; import { openContextMenu } from 'util/context-menu';
@ -17,10 +18,12 @@ import REWARDS from 'rewards';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
import LANGUAGES from 'constants/languages'; import LANGUAGES from 'constants/languages';
// @if TARGET='app' // @if TARGET='app'
import useZoom from 'effects/use-zoom'; import useZoom from 'effects/use-zoom';
import useHistoryNav from 'effects/use-history-nav'; import useHistoryNav from 'effects/use-history-nav';
// @endif // @endif
// @if TARGET='web' // @if TARGET='web'
import { import {
useDegradedPerformance, useDegradedPerformance,
@ -30,11 +33,11 @@ import {
STATUS_DOWN, STATUS_DOWN,
} from 'web/effects/use-degraded-performance'; } from 'web/effects/use-degraded-performance';
// @endif // @endif
import LANGUAGE_MIGRATIONS from 'constants/language-migrations'; import LANGUAGE_MIGRATIONS from 'constants/language-migrations';
const FileDrop = lazyImport(() => import('component/fileDrop' /* webpackChunkName: "secondary" */)); const FileDrop = lazyImport(() => import('component/fileDrop' /* webpackChunkName: "secondary" */));
const ModalRouter = lazyImport(() => import('modal/modalRouter' /* webpackChunkName: "secondary" */)); const ModalRouter = lazyImport(() => import('modal/modalRouter' /* webpackChunkName: "secondary" */));
const Nag = lazyImport(() => import('component/common/nag' /* webpackChunkName: "secondary" */));
const NagContinueFirstRun = lazyImport(() => const NagContinueFirstRun = lazyImport(() =>
import('component/nagContinueFirstRun' /* webpackChunkName: "secondary" */) import('component/nagContinueFirstRun' /* webpackChunkName: "secondary" */)
); );
@ -92,6 +95,7 @@ type Props = {
setLanguage: (string) => void, setLanguage: (string) => void,
doSetHomepage: (string) => void, doSetHomepage: (string) => void,
isUpgradeAvailable: boolean, isUpgradeAvailable: boolean,
isReloadRequired: boolean,
autoUpdateDownloaded: boolean, autoUpdateDownloaded: boolean,
updatePreferences: () => Promise<any>, updatePreferences: () => Promise<any>,
getWalletSyncPref: () => Promise<any>, getWalletSyncPref: () => Promise<any>,
@ -126,6 +130,7 @@ function App(props: Props) {
signIn, signIn,
autoUpdateDownloaded, autoUpdateDownloaded,
isUpgradeAvailable, isUpgradeAvailable,
isReloadRequired,
requestDownloadUpgrade, requestDownloadUpgrade,
uploadCount, uploadCount,
history, history,
@ -502,6 +507,14 @@ function App(props: Props) {
{user === null && <NagNoUser />} {user === null && <NagNoUser />}
{/* @endif */} {/* @endif */}
</React.Suspense> </React.Suspense>
{isReloadRequired && (
<Nag
message={__('A new version of Odysee is available.')}
actionText={__('Refresh')}
onClick={() => window.location.reload()}
/>
)}
</React.Fragment> </React.Fragment>
)} )}
</div> </div>

View file

@ -32,6 +32,7 @@ export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION'; export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL'; export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL';
export const SET_INCOGNITO = 'SET_INCOGNITO'; export const SET_INCOGNITO = 'SET_INCOGNITO';
export const RELOAD_REQUIRED = 'RELOAD_REQUIRED';
// Navigation // Navigation
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH'; export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';

View file

@ -34,6 +34,7 @@ export type AppState = {
checkUpgradeTimer: ?number, checkUpgradeTimer: ?number,
isUpgradeAvailable: ?boolean, isUpgradeAvailable: ?boolean,
isUpgradeSkipped: ?boolean, isUpgradeSkipped: ?boolean,
isReloadRequired: ?boolean,
hasClickedComment: boolean, hasClickedComment: boolean,
enhancedLayout: boolean, enhancedLayout: boolean,
splashAnimationEnabled: boolean, splashAnimationEnabled: boolean,
@ -71,6 +72,7 @@ const defaultState: AppState = {
checkUpgradeTimer: undefined, checkUpgradeTimer: undefined,
isUpgradeAvailable: undefined, isUpgradeAvailable: undefined,
isUpgradeSkipped: undefined, isUpgradeSkipped: undefined,
isReloadRequired: undefined,
enhancedLayout: false, enhancedLayout: false,
splashAnimationEnabled: true, splashAnimationEnabled: true,
searchOptionsExpanded: false, searchOptionsExpanded: false,
@ -213,6 +215,11 @@ reducers[ACTIONS.UPGRADE_DOWNLOAD_PROGRESSED] = (state, action) =>
downloadProgress: action.data.percent, downloadProgress: action.data.percent,
}); });
reducers[ACTIONS.RELOAD_REQUIRED] = (state, action) =>
Object.assign({}, state, {
isReloadRequired: true,
});
reducers[ACTIONS.DOWNLOADING_COMPLETED] = (state) => { reducers[ACTIONS.DOWNLOADING_COMPLETED] = (state) => {
const { badgeNumber } = state; const { badgeNumber } = state;

View file

@ -21,6 +21,7 @@ export const selectUpdateUrl = createSelector(selectPlatform, (platform) => {
export const selectHasClickedComment = (state) => selectState(state).hasClickedComment; export const selectHasClickedComment = (state) => selectState(state).hasClickedComment;
export const selectRemoteVersion = (state) => selectState(state).remoteVersion; export const selectRemoteVersion = (state) => selectState(state).remoteVersion;
export const selectIsUpgradeAvailable = (state) => selectState(state).isUpgradeAvailable; export const selectIsUpgradeAvailable = (state) => selectState(state).isUpgradeAvailable;
export const selectIsReloadRequired = (state) => selectState(state).isReloadRequired;
export const selectUpgradeFilename = createSelector(selectPlatform, selectRemoteVersion, (platform, version) => { export const selectUpgradeFilename = createSelector(selectPlatform, selectRemoteVersion, (platform, version) => {
switch (platform) { switch (platform) {

View file

@ -1,34 +1,26 @@
import React from 'react'; import React from 'react';
import * as ACTIONS from 'constants/action_types';
let localStorageAvailable; const RETRY_DELAY_MS = 2000;
try { const RETRY_ATTEMPTS = 2;
localStorageAvailable = Boolean(window.localStorage);
} catch (e) { function componentLoader(lazyComponent, attemptsLeft) {
localStorageAvailable = false; return new Promise((resolve, reject) => {
lazyComponent()
.then(resolve)
.catch((error) => {
setTimeout(() => {
if (attemptsLeft === 1) {
window.store.dispatch({ type: ACTIONS.RELOAD_REQUIRED });
console.error(error.message); // Spew the error so users can report to us if reloading doesn't help.
} else {
componentLoader(lazyComponent, attemptsLeft - 1).then(resolve, reject);
}
}, RETRY_DELAY_MS);
});
});
} }
export const lazyImport = (componentImport) => export function lazyImport(componentImport) {
React.lazy(async () => { return React.lazy(() => componentLoader(componentImport, RETRY_ATTEMPTS));
const pageHasAlreadyBeenForceRefreshed = localStorageAvailable }
? JSON.parse(window.localStorage.getItem('page-has-been-force-refreshed') || 'false')
: false;
try {
const component = await componentImport();
if (localStorageAvailable) {
window.localStorage.setItem('page-has-been-force-refreshed', 'false');
}
return component;
} catch (error) {
if (!pageHasAlreadyBeenForceRefreshed) {
// It's highly likely that the user's session is old. Try reloading once.
if (localStorageAvailable) {
window.localStorage.setItem('page-has-been-force-refreshed', 'true');
}
return window.location.reload();
}
// If it still didn't work, then relay the error.
throw error;
}
});