SyncFatalError: show nag instead of hard-crashing. (#331)
* SyncFatalError: show nag instead of hard-crashing. ## Issue When sync fails, we crash the app. ## Ticket Maybe closes 39 "Better handle both internal and web backend interruptions / downtime" ## Approach I'm tackling this from the standpoint that (1) sync errors are not that fatal -- we'll just lost a few recent changes (2) network disconnection is the common cause. ## Changes - If we are offline: - Inform user through a nag. All other status is meaningless if we are offline. - If we are online: - If api is STATUS_DOWN, show the existing crash page. - If there is a sync error, show a nag saying settings are now potentially unsynchronized, and add a button to retry sync. - If there is a chunk error, nag to reload. * Attempt to detect `status=DOWN` Previous code resolves the status to either "ok" or "not", which makes the app unable to differentiate between the "degraded" (nag) and "down" (crash) states.
This commit is contained in:
parent
13cbbc8342
commit
87c3dcc057
3 changed files with 63 additions and 14 deletions
|
@ -2221,5 +2221,8 @@
|
||||||
"Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8": "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8",
|
"Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8": "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8",
|
||||||
"Choose %asset%": "Choose %asset%",
|
"Choose %asset%": "Choose %asset%",
|
||||||
"Showing %filtered% results of %total%": "Showing %filtered% results of %total%",
|
"Showing %filtered% results of %total%": "Showing %filtered% results of %total%",
|
||||||
|
"Failed to synchronize settings. Wait a while before retrying.": "Failed to synchronize settings. Wait a while before retrying.",
|
||||||
|
"You are offline. Check your internet connection.": "You are offline. Check your internet connection.",
|
||||||
|
"A new version of Odysee is available.": "A new version of Odysee is available.",
|
||||||
"--end--": "--end--"
|
"--end--": "--end--"
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,7 @@ function App(props: Props) {
|
||||||
const { pathname, hash, search } = props.location;
|
const { pathname, hash, search } = props.location;
|
||||||
const [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
|
const [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
|
||||||
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
|
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
|
||||||
|
const [retryingSync, setRetryingSync] = useState(false);
|
||||||
const [sidebarOpen] = usePersistedState('sidebar', true);
|
const [sidebarOpen] = usePersistedState('sidebar', true);
|
||||||
const [seenSunsestMessage, setSeenSunsetMessage] = usePersistedState('lbrytv-sunset', false);
|
const [seenSunsestMessage, setSeenSunsetMessage] = usePersistedState('lbrytv-sunset', false);
|
||||||
const showUpgradeButton =
|
const showUpgradeButton =
|
||||||
|
@ -153,6 +154,7 @@ function App(props: Props) {
|
||||||
const hasActiveChannelClaim = activeChannelClaim !== undefined;
|
const hasActiveChannelClaim = activeChannelClaim !== undefined;
|
||||||
const isPersonalized = !IS_WEB || hasVerifiedEmail;
|
const isPersonalized = !IS_WEB || hasVerifiedEmail;
|
||||||
const renderFiledrop = !IS_WEB || isAuthenticated;
|
const renderFiledrop = !IS_WEB || isAuthenticated;
|
||||||
|
const isOnline = navigator.onLine;
|
||||||
|
|
||||||
let uri;
|
let uri;
|
||||||
try {
|
try {
|
||||||
|
@ -164,6 +166,50 @@ function App(props: Props) {
|
||||||
setShowAnalyticsNag(false);
|
setShowAnalyticsNag(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStatusNag() {
|
||||||
|
// Handle "offline" first. Everything else is meaningless if it's offline.
|
||||||
|
if (!isOnline) {
|
||||||
|
return <Nag type="helpful" message={__('You are offline. Check your internet connection.')} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only 1 nag is possible, so show the most important:
|
||||||
|
|
||||||
|
if (user === null) {
|
||||||
|
return <NagNoUser />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lbryTvApiStatus === STATUS_DEGRADED || lbryTvApiStatus === STATUS_FAILING) {
|
||||||
|
if (!shouldHideNag) {
|
||||||
|
return <NagDegradedPerformance onClose={() => setLbryTvApiStatus(STATUS_OK)} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syncFatalError) {
|
||||||
|
if (!retryingSync) {
|
||||||
|
return (
|
||||||
|
<Nag
|
||||||
|
type="error"
|
||||||
|
message={__('Failed to synchronize settings. Wait a while before retrying.')}
|
||||||
|
actionText={__('Retry')}
|
||||||
|
onClick={() => {
|
||||||
|
syncLoop(true);
|
||||||
|
setRetryingSync(true);
|
||||||
|
setTimeout(() => setRetryingSync(false), 4000);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (isReloadRequired) {
|
||||||
|
return (
|
||||||
|
<Nag
|
||||||
|
message={__('A new version of Odysee is available.')}
|
||||||
|
actionText={__('Refresh')}
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
analytics.setUser(userId);
|
analytics.setUser(userId);
|
||||||
|
@ -333,7 +379,8 @@ function App(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncFatalError) {
|
if (isOnline && lbryTvApiStatus === STATUS_DOWN) {
|
||||||
|
// TODO: Rename `SyncFatalError` since it has nothing to do with syncing.
|
||||||
return (
|
return (
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback={null}>
|
||||||
<SyncFatalError lbryTvApiStatus={lbryTvApiStatus} />
|
<SyncFatalError lbryTvApiStatus={lbryTvApiStatus} />
|
||||||
|
@ -384,22 +431,11 @@ function App(props: Props) {
|
||||||
{fromLbrytvParam && !seenSunsestMessage && !shouldHideNag && (
|
{fromLbrytvParam && !seenSunsestMessage && !shouldHideNag && (
|
||||||
<NagSunset email={hasVerifiedEmail} onClose={() => setSeenSunsetMessage(true)} />
|
<NagSunset email={hasVerifiedEmail} onClose={() => setSeenSunsetMessage(true)} />
|
||||||
)}
|
)}
|
||||||
{(lbryTvApiStatus === STATUS_DEGRADED || lbryTvApiStatus === STATUS_FAILING) && !shouldHideNag && (
|
|
||||||
<NagDegradedPerformance onClose={() => setLbryTvApiStatus(STATUS_OK)} />
|
|
||||||
)}
|
|
||||||
{!SIMPLE_SITE && lbryTvApiStatus === STATUS_OK && showAnalyticsNag && !shouldHideNag && (
|
{!SIMPLE_SITE && lbryTvApiStatus === STATUS_OK && showAnalyticsNag && !shouldHideNag && (
|
||||||
<NagDataCollection onClose={handleAnalyticsDismiss} />
|
<NagDataCollection onClose={handleAnalyticsDismiss} />
|
||||||
)}
|
)}
|
||||||
{user === null && <NagNoUser />}
|
{getStatusNag()}
|
||||||
</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>
|
||||||
|
|
|
@ -5,6 +5,14 @@ import { X_LBRY_AUTH_TOKEN } from 'constants/token';
|
||||||
|
|
||||||
import fetchWithTimeout from 'util/fetch';
|
import fetchWithTimeout from 'util/fetch';
|
||||||
|
|
||||||
|
const STATUS_GENERAL_STATE = {
|
||||||
|
// internal/status/status.go#L44
|
||||||
|
OK: 'ok',
|
||||||
|
NOT_READY: 'not_ready',
|
||||||
|
OFFLINE: 'offline',
|
||||||
|
FAILING: 'failing',
|
||||||
|
};
|
||||||
|
|
||||||
const STATUS_TIMEOUT_LIMIT = 10000;
|
const STATUS_TIMEOUT_LIMIT = 10000;
|
||||||
export const STATUS_OK = 'ok';
|
export const STATUS_OK = 'ok';
|
||||||
export const STATUS_DEGRADED = 'degraded';
|
export const STATUS_DEGRADED = 'degraded';
|
||||||
|
@ -33,7 +41,9 @@ export function useDegradedPerformance(onDegradedPerformanceCallback, user) {
|
||||||
fetchWithTimeout(STATUS_TIMEOUT_LIMIT, fetch(STATUS_ENDPOINT, getParams(user)))
|
fetchWithTimeout(STATUS_TIMEOUT_LIMIT, fetch(STATUS_ENDPOINT, getParams(user)))
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((status) => {
|
.then((status) => {
|
||||||
if (status.general_state !== STATUS_OK) {
|
if (status.general_state === STATUS_GENERAL_STATE.OFFLINE) {
|
||||||
|
onDegradedPerformanceCallback(STATUS_DOWN);
|
||||||
|
} else if (status.general_state !== STATUS_GENERAL_STATE.OK) {
|
||||||
onDegradedPerformanceCallback(STATUS_FAILING);
|
onDegradedPerformanceCallback(STATUS_FAILING);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue