mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
app: catch error when checking for updates
ServiceWorkerRegistration.update() can raise exceptions, so we need to catch and handle them, otherwise it will crash redux sagas and the app will stop responding. Fixes: https://github.com/pybricks/pybricks-code/issues/1299
This commit is contained in:
@@ -4,6 +4,11 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- Fixed app freezing when checking for updates and update server is unreachable ([pybricks-code#1299]).
|
||||
|
||||
[pybricks-code#1299]: https://github.com/pybricks/pybricks-code/issues/1299
|
||||
|
||||
## [2.0.0-beta.10] - 2022-11-11
|
||||
|
||||
### Added
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { ToastProps } from '@blueprintjs/core';
|
||||
import alerts from './alerts/alerts';
|
||||
import app from './app/alerts';
|
||||
import ble from './ble/alerts';
|
||||
import explorer from './explorer/alerts';
|
||||
import firmware from './firmware/alerts';
|
||||
@@ -12,6 +13,7 @@ import type { CreateToast } from './toasterTypes';
|
||||
/** This collects alerts from all of the subsystems of the app */
|
||||
const alertDomains = {
|
||||
alerts,
|
||||
app,
|
||||
ble,
|
||||
explorer,
|
||||
firmware,
|
||||
|
||||
@@ -21,6 +21,11 @@ export const appDidCheckForUpdate = createAction((updateFound: boolean) => ({
|
||||
updateFound,
|
||||
}));
|
||||
|
||||
/** Action that indicates that checking for an update failed. */
|
||||
export const appDidFailToCheckForUpdate = createAction(() => ({
|
||||
type: 'app.action.didFailToCheckForUpdate',
|
||||
}));
|
||||
|
||||
/* Action that indicates the browser wants to prompt the use to install the app. */
|
||||
export const appDidReceiveBeforeInstallPrompt = createAction(() => ({
|
||||
type: 'app.action.didBeforeInstallPrompt',
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import type { CreateToast } from '../../toasterTypes';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const UpdateServerFailure: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return <p>{i18n.translate('updateServerFailure.message')}</p>;
|
||||
};
|
||||
|
||||
export const updateServerFailure: CreateToast = (onAction) => {
|
||||
return {
|
||||
message: <UpdateServerFailure />,
|
||||
icon: 'error',
|
||||
intent: Intent.DANGER,
|
||||
onDismiss: () => onAction('dismiss'),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
|
||||
import type { TypedI18n } from '../../i18n';
|
||||
import type translations from './translations/en.json';
|
||||
|
||||
export function useI18n(): TypedI18n<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { updateServerFailure } from './UpdateServerFailure';
|
||||
|
||||
// gathers all of the alert creation functions for passing up to the top level
|
||||
export default {
|
||||
updateServerFailure,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"updateServerFailure": {
|
||||
"message": "Failed to connect to update server. The Internet connection or the server may be down. Try again later."
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import {
|
||||
appCheckForUpdate,
|
||||
appDidCheckForUpdate,
|
||||
appDidFailToCheckForUpdate,
|
||||
appDidReceiveBeforeInstallPrompt,
|
||||
appDidResolveInstallPrompt,
|
||||
appShowInstallPrompt,
|
||||
@@ -59,6 +60,10 @@ test('checkingForUpdate', () => {
|
||||
reducers({ checkingForUpdate: true } as State, appDidCheckForUpdate(false))
|
||||
.checkingForUpdate,
|
||||
).toBe(false);
|
||||
expect(
|
||||
reducers({ checkingForUpdate: true } as State, appDidFailToCheckForUpdate())
|
||||
.checkingForUpdate,
|
||||
).toBe(false);
|
||||
expect(
|
||||
reducers({ checkingForUpdate: true } as State, serviceWorkerDidUpdate())
|
||||
.checkingForUpdate,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import {
|
||||
appCheckForUpdate,
|
||||
appDidCheckForUpdate,
|
||||
appDidFailToCheckForUpdate,
|
||||
appDidReceiveBeforeInstallPrompt,
|
||||
appDidResolveInstallPrompt,
|
||||
appShowInstallPrompt,
|
||||
@@ -39,6 +40,10 @@ const checkingForUpdate: Reducer<boolean> = (state = false, action) => {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (appDidFailToCheckForUpdate.matches(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (serviceWorkerDidUpdate.matches(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+20
-3
@@ -3,15 +3,18 @@
|
||||
|
||||
import { eventChannel } from 'redux-saga';
|
||||
import { call, delay, fork, put, take, takeEvery } from 'typed-redux-saga/macro';
|
||||
import { alertsShowAlert } from '../alerts/actions';
|
||||
import {
|
||||
serviceWorkerDidSucceed,
|
||||
serviceWorkerDidUpdate,
|
||||
} from '../service-worker/actions';
|
||||
import * as serviceWorkerRegistration from '../serviceWorkerRegistration';
|
||||
import { ensureError } from '../utils';
|
||||
import { BeforeInstallPromptEvent } from '../utils/dom';
|
||||
import {
|
||||
appCheckForUpdate,
|
||||
appDidCheckForUpdate,
|
||||
appDidFailToCheckForUpdate,
|
||||
appDidReceiveBeforeInstallPrompt,
|
||||
appDidResolveInstallPrompt,
|
||||
appReload,
|
||||
@@ -36,9 +39,23 @@ function* handleAppReload(registration: ServiceWorkerRegistration): Generator {
|
||||
* Must be called (forked) with serviceWorkerRegistration context set.
|
||||
*/
|
||||
function* handleAppCheckForUpdate(registration: ServiceWorkerRegistration): Generator {
|
||||
yield* call(() => registration.update());
|
||||
const updateFound = registration.installing !== null;
|
||||
yield* put(appDidCheckForUpdate(updateFound));
|
||||
try {
|
||||
yield* call(() => registration.update());
|
||||
const updateFound = registration.installing !== null;
|
||||
yield* put(appDidCheckForUpdate(updateFound));
|
||||
} catch (err) {
|
||||
if (err instanceof TypeError) {
|
||||
yield* put(alertsShowAlert('app', 'updateServerFailure'));
|
||||
} else {
|
||||
yield* put(
|
||||
alertsShowAlert('alerts', 'unexpectedError', {
|
||||
error: ensureError(err),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
yield* put(appDidFailToCheckForUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user