mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
app: rework service worker registration
- registration is moved from top-level index to app/sagas. - mock implementation is provided for tests - actions are renamed to include "app" prefix - actions are changed to not use non-serializable arguments - reducers are changed to not use non-serializable state
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
// mock implementation of serviceWorkerRegistration for testing
|
||||
|
||||
type Config = {
|
||||
onSuccess?: (r: ServiceWorkerRegistration) => void;
|
||||
onUpdate?: (r: ServiceWorkerRegistration) => void;
|
||||
};
|
||||
|
||||
/** The config that was registered, if any. */
|
||||
let globalConfig: Config | undefined;
|
||||
|
||||
/**
|
||||
* Mocks the register() function.
|
||||
*
|
||||
* This just saves the config for later use.
|
||||
*
|
||||
* @param config The config.
|
||||
*/
|
||||
export function register(config?: Config): void {
|
||||
globalConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires the onSuccess callback that was registered, if any.
|
||||
*
|
||||
* @param registration The registration to pass to the callback.
|
||||
*/
|
||||
export function _fireOnSuccess(registration: ServiceWorkerRegistration): void {
|
||||
if (globalConfig && globalConfig.onSuccess) {
|
||||
globalConfig.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires the onUpdate callback that was registered, if any.
|
||||
*
|
||||
* @param registration The registration to pass to the callback.
|
||||
*/
|
||||
export function _fireOnUpdate(registration: ServiceWorkerRegistration): void {
|
||||
if (globalConfig && globalConfig.onUpdate) {
|
||||
globalConfig.onUpdate(registration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks the unregister() function.
|
||||
*/
|
||||
export function unregister(): void {
|
||||
// nothing to do for now
|
||||
}
|
||||
+7
-11
@@ -5,22 +5,18 @@
|
||||
|
||||
import { createAction } from '../actions';
|
||||
|
||||
/** Creates an action that requests the app to reload. */
|
||||
export const reload = createAction((registration: ServiceWorkerRegistration) => ({
|
||||
/** Action that requests the app to reload. */
|
||||
export const appReload = createAction(() => ({
|
||||
type: 'app.action.reload',
|
||||
registration,
|
||||
}));
|
||||
|
||||
/** Action that requests to check for updates. */
|
||||
export const checkForUpdate = createAction(
|
||||
(registration: ServiceWorkerRegistration) => ({
|
||||
type: 'app.action.checkForUpdate',
|
||||
registration,
|
||||
}),
|
||||
);
|
||||
/* Action that requests to check for updates. */
|
||||
export const appCheckForUpdate = createAction(() => ({
|
||||
type: 'app.action.checkForUpdate',
|
||||
}));
|
||||
|
||||
/** Action that indicates that checking for an update has completed. */
|
||||
export const didCheckForUpdate = createAction((updateFound: boolean) => ({
|
||||
export const appDidCheckForUpdate = createAction((updateFound: boolean) => ({
|
||||
type: 'app.action.didCheckForUpdate',
|
||||
updateFound,
|
||||
}));
|
||||
|
||||
+28
-35
@@ -7,11 +7,11 @@ import {
|
||||
serviceWorkerDidUpdate,
|
||||
} from '../service-worker/actions';
|
||||
import {
|
||||
appCheckForUpdate,
|
||||
appDidCheckForUpdate,
|
||||
appDidReceiveBeforeInstallPrompt,
|
||||
appDidResolveInstallPrompt,
|
||||
appShowInstallPrompt,
|
||||
checkForUpdate,
|
||||
didCheckForUpdate,
|
||||
didInstall,
|
||||
} from './actions';
|
||||
import reducers from './reducers';
|
||||
@@ -23,57 +23,52 @@ test('initial state', () => {
|
||||
Object {
|
||||
"checkingForUpdate": false,
|
||||
"hasUnresolvedInstallPrompt": false,
|
||||
"isServiceWorkerRegistered": false,
|
||||
"promptingInstall": false,
|
||||
"readyForOfflineUse": false,
|
||||
"serviceWorker": null,
|
||||
"updateAvailable": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('serviceWorker', () => {
|
||||
const registration = {} as ServiceWorkerRegistration;
|
||||
expect(
|
||||
reducers(
|
||||
{ serviceWorker: null } as State,
|
||||
serviceWorkerDidSucceed(registration),
|
||||
).serviceWorker,
|
||||
).toBe(registration);
|
||||
describe('isServiceWorkerRegistered', () => {
|
||||
it('should be true when service worker registration succeeds', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ isServiceWorkerRegistered: false } as State,
|
||||
serviceWorkerDidSucceed(),
|
||||
).isServiceWorkerRegistered,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('checkingForUpdate', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ checkingForUpdate: false } as State,
|
||||
checkForUpdate({} as ServiceWorkerRegistration),
|
||||
).checkingForUpdate,
|
||||
).toBe(true);
|
||||
expect(
|
||||
reducers({ checkingForUpdate: false } as State, didCheckForUpdate(true))
|
||||
.checkingForUpdate,
|
||||
).toBe(false);
|
||||
expect(
|
||||
reducers({ checkingForUpdate: true } as State, didCheckForUpdate(true))
|
||||
reducers({ checkingForUpdate: false } as State, appCheckForUpdate())
|
||||
.checkingForUpdate,
|
||||
).toBe(true);
|
||||
expect(
|
||||
reducers({ checkingForUpdate: true } as State, didCheckForUpdate(false))
|
||||
reducers({ checkingForUpdate: false } as State, appDidCheckForUpdate(true))
|
||||
.checkingForUpdate,
|
||||
).toBe(false);
|
||||
expect(
|
||||
reducers(
|
||||
{ checkingForUpdate: true } as State,
|
||||
serviceWorkerDidUpdate({} as ServiceWorkerRegistration),
|
||||
).checkingForUpdate,
|
||||
reducers({ checkingForUpdate: true } as State, appDidCheckForUpdate(true))
|
||||
.checkingForUpdate,
|
||||
).toBe(true);
|
||||
expect(
|
||||
reducers({ checkingForUpdate: true } as State, appDidCheckForUpdate(false))
|
||||
.checkingForUpdate,
|
||||
).toBe(false);
|
||||
expect(
|
||||
reducers({ checkingForUpdate: true } as State, serviceWorkerDidUpdate())
|
||||
.checkingForUpdate,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('updateAvailable', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ updateAvailable: false } as State,
|
||||
serviceWorkerDidUpdate({} as ServiceWorkerRegistration),
|
||||
).updateAvailable,
|
||||
reducers({ updateAvailable: false } as State, serviceWorkerDidUpdate())
|
||||
.updateAvailable,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -124,9 +119,7 @@ describe('promptingInstall', () => {
|
||||
|
||||
test('readyForOfflineUse', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ readyForOfflineUse: false } as State,
|
||||
serviceWorkerDidSucceed({} as ServiceWorkerRegistration),
|
||||
).readyForOfflineUse,
|
||||
reducers({ readyForOfflineUse: false } as State, serviceWorkerDidSucceed())
|
||||
.readyForOfflineUse,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
+8
-10
@@ -9,31 +9,29 @@ import {
|
||||
serviceWorkerDidUpdate,
|
||||
} from '../service-worker/actions';
|
||||
import {
|
||||
appCheckForUpdate,
|
||||
appDidCheckForUpdate,
|
||||
appDidReceiveBeforeInstallPrompt,
|
||||
appDidResolveInstallPrompt,
|
||||
appShowInstallPrompt,
|
||||
checkForUpdate,
|
||||
didCheckForUpdate,
|
||||
didInstall,
|
||||
} from './actions';
|
||||
|
||||
const serviceWorker: Reducer<ServiceWorkerRegistration | null> = (
|
||||
state = null,
|
||||
action,
|
||||
) => {
|
||||
/** Indicates that the service worker was successfully registered. */
|
||||
const isServiceWorkerRegistered: Reducer<boolean> = (state = false, action) => {
|
||||
if (serviceWorkerDidSucceed.matches(action)) {
|
||||
return action.registration;
|
||||
return true;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const checkingForUpdate: Reducer<boolean> = (state = false, action) => {
|
||||
if (checkForUpdate.matches(action)) {
|
||||
if (appCheckForUpdate.matches(action)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (didCheckForUpdate.matches(action)) {
|
||||
if (appDidCheckForUpdate.matches(action)) {
|
||||
if (!action.updateFound) {
|
||||
return false;
|
||||
}
|
||||
@@ -94,7 +92,7 @@ const readyForOfflineUse: Reducer<boolean> = (state = false, action) => {
|
||||
};
|
||||
|
||||
export default combineReducers({
|
||||
serviceWorker,
|
||||
isServiceWorkerRegistered,
|
||||
checkingForUpdate,
|
||||
updateAvailable,
|
||||
hasUnresolvedInstallPrompt,
|
||||
|
||||
+79
-24
@@ -2,19 +2,45 @@
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
|
||||
import { createEvent, fireEvent } from '@testing-library/dom';
|
||||
import { AsyncSaga, delay } from '../../test';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { AsyncSaga } from '../../test';
|
||||
import {
|
||||
serviceWorkerDidSucceed,
|
||||
serviceWorkerDidUpdate,
|
||||
} from '../service-worker/actions';
|
||||
import { BeforeInstallPromptEvent } from '../utils/dom';
|
||||
import {
|
||||
appCheckForUpdate,
|
||||
appDidCheckForUpdate,
|
||||
appDidReceiveBeforeInstallPrompt,
|
||||
appDidResolveInstallPrompt,
|
||||
appReload,
|
||||
appShowInstallPrompt,
|
||||
checkForUpdate,
|
||||
didCheckForUpdate,
|
||||
didInstall,
|
||||
reload,
|
||||
} from './actions';
|
||||
import app from './sagas';
|
||||
|
||||
jest.mock('../serviceWorkerRegistration');
|
||||
|
||||
/**
|
||||
* Creates an AsyncSaga initialize with a service worker.
|
||||
* @param registration The service worker registration.
|
||||
* @returns The saga.
|
||||
*/
|
||||
async function createSagaWithRegistration(
|
||||
registration: ServiceWorkerRegistration,
|
||||
): Promise<AsyncSaga> {
|
||||
const saga = new AsyncSaga(app);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require('../serviceWorkerRegistration')._fireOnSuccess(registration);
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action).toEqual(serviceWorkerDidSucceed());
|
||||
|
||||
return saga;
|
||||
}
|
||||
|
||||
test('monitorAppInstalled', async () => {
|
||||
const saga = new AsyncSaga(app);
|
||||
|
||||
@@ -37,13 +63,13 @@ test('monitorBeforeInstallPrompt', async () => {
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
test('reload', async () => {
|
||||
const saga = new AsyncSaga(app);
|
||||
|
||||
test('handleAppReload', async () => {
|
||||
// mock registration as if service worker was register on app startup
|
||||
const registration: Partial<ServiceWorkerRegistration> = {
|
||||
const registration = mock<ServiceWorkerRegistration>({
|
||||
unregister: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const saga = await createSagaWithRegistration(registration);
|
||||
|
||||
// @ts-expect-error: JSDOM implementation of location.reload() causes error
|
||||
delete window.location;
|
||||
@@ -52,7 +78,7 @@ test('reload', async () => {
|
||||
reload: jest.fn(),
|
||||
};
|
||||
|
||||
saga.put(reload(registration as ServiceWorkerRegistration));
|
||||
saga.put(appReload());
|
||||
|
||||
expect(registration.unregister).toHaveBeenCalled();
|
||||
expect(location.reload).toHaveBeenCalled();
|
||||
@@ -60,26 +86,55 @@ test('reload', async () => {
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
test('checkForUpdates', async () => {
|
||||
const saga = new AsyncSaga(app);
|
||||
describe('handleAppCheckForUpdate', () => {
|
||||
it('should return true if updates are available', async () => {
|
||||
// mock registration as if service worker was registered on app startup
|
||||
const registration = mock<ServiceWorkerRegistration>({
|
||||
update: jest.fn(),
|
||||
installing: mock<ServiceWorker>(),
|
||||
});
|
||||
|
||||
// mock registration as if service worker was register on app startup
|
||||
const registration: Partial<ServiceWorkerRegistration> = {
|
||||
update: jest.fn(),
|
||||
installing: null,
|
||||
};
|
||||
const saga = await createSagaWithRegistration(registration);
|
||||
|
||||
saga.put(checkForUpdate(registration as ServiceWorkerRegistration));
|
||||
saga.put(appCheckForUpdate());
|
||||
|
||||
// yield to allow generators to complete
|
||||
await delay(0);
|
||||
const action = await saga.take();
|
||||
expect(action).toStrictEqual(appDidCheckForUpdate(true));
|
||||
expect(registration.update).toHaveBeenCalled();
|
||||
|
||||
expect(registration.update).toHaveBeenCalled();
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action).toStrictEqual(didCheckForUpdate(false));
|
||||
it('should return false if no updates are available', async () => {
|
||||
// mock registration as if service worker was registered on app startup
|
||||
const registration = mock<ServiceWorkerRegistration>({
|
||||
update: jest.fn(),
|
||||
installing: null,
|
||||
});
|
||||
|
||||
await saga.end();
|
||||
const saga = await createSagaWithRegistration(registration);
|
||||
|
||||
saga.put(appCheckForUpdate());
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action).toStrictEqual(appDidCheckForUpdate(false));
|
||||
expect(registration.update).toHaveBeenCalled();
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
});
|
||||
|
||||
describe('monitorServiceWorkerRegistration', () => {
|
||||
it('should dispatch serviceWorkerDidUpdate', async () => {
|
||||
const registration = mock<ServiceWorkerRegistration>();
|
||||
const saga = await createSagaWithRegistration(registration);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require('../serviceWorkerRegistration')._fireOnUpdate(registration);
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action).toEqual(serviceWorkerDidUpdate());
|
||||
});
|
||||
});
|
||||
|
||||
test('appShowInstallPrompt', async () => {
|
||||
|
||||
+64
-16
@@ -3,17 +3,77 @@
|
||||
|
||||
import { eventChannel } from 'redux-saga';
|
||||
import { call, fork, put, take, takeEvery } from 'typed-redux-saga/macro';
|
||||
import {
|
||||
serviceWorkerDidSucceed,
|
||||
serviceWorkerDidUpdate,
|
||||
} from '../service-worker/actions';
|
||||
import * as serviceWorkerRegistration from '../serviceWorkerRegistration';
|
||||
import { BeforeInstallPromptEvent } from '../utils/dom';
|
||||
import {
|
||||
appCheckForUpdate,
|
||||
appDidCheckForUpdate,
|
||||
appDidReceiveBeforeInstallPrompt,
|
||||
appDidResolveInstallPrompt,
|
||||
appReload,
|
||||
appShowInstallPrompt,
|
||||
checkForUpdate,
|
||||
didCheckForUpdate,
|
||||
didInstall,
|
||||
reload,
|
||||
} from './actions';
|
||||
|
||||
/**
|
||||
* Handles appReload actions.
|
||||
*
|
||||
* Must be called (forked) with serviceWorkerRegistration context set.
|
||||
*/
|
||||
function* handleAppReload(registration: ServiceWorkerRegistration): Generator {
|
||||
yield* call(() => registration.unregister());
|
||||
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles appCheckForUpdate actions.
|
||||
*
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Marshals CRA serviceWorkerRegistration to saga.
|
||||
*/
|
||||
function* monitorServiceWorkerRegistration(): Generator {
|
||||
const chan = eventChannel<{
|
||||
isUpdate: boolean;
|
||||
registration: ServiceWorkerRegistration;
|
||||
}>((emit) => {
|
||||
serviceWorkerRegistration.register({
|
||||
onSuccess: (r) => emit({ isUpdate: false, registration: r }),
|
||||
onUpdate: (r) => emit({ isUpdate: true, registration: r }),
|
||||
});
|
||||
|
||||
// istanbul ignore next: never unregistered
|
||||
return () => serviceWorkerRegistration.unregister();
|
||||
});
|
||||
|
||||
// HACK: is assumed that this will only be called at most two times, once
|
||||
// with isUpdate === false and after that, once with isUpdate === true.
|
||||
while (true) {
|
||||
const { isUpdate, registration } = yield* take(chan);
|
||||
|
||||
if (isUpdate) {
|
||||
yield* put(serviceWorkerDidUpdate());
|
||||
} else {
|
||||
yield* takeEvery(appReload, handleAppReload, registration);
|
||||
yield* takeEvery(appCheckForUpdate, handleAppCheckForUpdate, registration);
|
||||
|
||||
yield* put(serviceWorkerDidSucceed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function* monitorAppInstalled(): Generator {
|
||||
const chan = eventChannel<Event>((emit) => {
|
||||
const listener = (e: Event) => {
|
||||
@@ -61,20 +121,8 @@ function* monitorBeforeInstallPrompt(): Generator {
|
||||
}
|
||||
}
|
||||
|
||||
function* handleReload(action: ReturnType<typeof reload>): Generator {
|
||||
yield* call(() => action.registration.unregister());
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function* handleCheckForUpdate(action: ReturnType<typeof checkForUpdate>): Generator {
|
||||
yield* call(() => action.registration.update());
|
||||
const updateFound = action.registration.installing !== null;
|
||||
yield* put(didCheckForUpdate(updateFound));
|
||||
}
|
||||
|
||||
export default function* app(): Generator {
|
||||
yield* fork(monitorServiceWorkerRegistration);
|
||||
yield* fork(monitorAppInstalled);
|
||||
yield* fork(monitorBeforeInstallPrompt);
|
||||
yield* takeEvery(reload, handleReload);
|
||||
yield* takeEvery(checkForUpdate, handleCheckForUpdate);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,6 @@ import * as I18nToaster from './notifications/I18nToaster';
|
||||
import { rootReducer } from './reducers';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import rootSaga, { RootSagaContext } from './sagas';
|
||||
import {
|
||||
serviceWorkerDidSucceed,
|
||||
serviceWorkerDidUpdate,
|
||||
} from './service-worker/actions';
|
||||
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
|
||||
import { defaultTerminalContext } from './terminal/TerminalContext';
|
||||
import ViewHeightSensor from './utils/ViewHeightSensor';
|
||||
import { createCountFunc } from './utils/iter';
|
||||
@@ -65,14 +60,6 @@ ReactDOM.render(
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://cra.link/PWA
|
||||
serviceWorkerRegistration.register({
|
||||
onUpdate: (r) => store.dispatch(serviceWorkerDidUpdate(r)),
|
||||
onSuccess: (r) => store.dispatch(serviceWorkerDidSucceed(r)),
|
||||
});
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '@pybricks/firmware';
|
||||
import { AnyAction } from 'redux';
|
||||
import { AsyncSaga } from '../../test';
|
||||
import { didCheckForUpdate } from '../app/actions';
|
||||
import { appDidCheckForUpdate } from '../app/actions';
|
||||
import { bleDIServiceDidReceiveFirmwareRevision } from '../ble-device-info-service/actions';
|
||||
import {
|
||||
BleDeviceFailToConnectReasonType,
|
||||
@@ -63,7 +63,7 @@ test.each([
|
||||
didFailToCompile(['reason']),
|
||||
add('warning', 'message'),
|
||||
add('error', 'message', 'url'),
|
||||
serviceWorkerDidUpdate({} as ServiceWorkerRegistration),
|
||||
serviceWorkerDidUpdate(),
|
||||
didFailToFinish(FailToFinishReasonType.TimedOut),
|
||||
didFailToFinish(
|
||||
FailToFinishReasonType.BleError,
|
||||
@@ -89,7 +89,7 @@ test.each([
|
||||
didFailToFinish(FailToFinishReasonType.FailedToCompile),
|
||||
didFailToFinish(FailToFinishReasonType.FirmwareSize),
|
||||
didFailToFinish(FailToFinishReasonType.Unknown, new Error('test error')),
|
||||
didCheckForUpdate(false),
|
||||
appDidCheckForUpdate(false),
|
||||
bleDIServiceDidReceiveFirmwareRevision('3.0.0'),
|
||||
didFailToSaveAs(new DOMException('test message', 'NotAllowedError')),
|
||||
fileStorageDidFailToInitialize(new Error('test error')),
|
||||
@@ -125,8 +125,8 @@ test.each([
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.Canceled }),
|
||||
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Canceled),
|
||||
didFailToFinish(FailToFinishReasonType.FailedToConnect),
|
||||
serviceWorkerDidSucceed({} as ServiceWorkerRegistration),
|
||||
didCheckForUpdate(true),
|
||||
serviceWorkerDidSucceed(),
|
||||
appDidCheckForUpdate(true),
|
||||
bleDIServiceDidReceiveFirmwareRevision(firmwareVersion),
|
||||
didFailToSaveAs(new DOMException('test message', 'AbortError')),
|
||||
fileStorageDidFailToExportFile(
|
||||
|
||||
@@ -10,7 +10,7 @@ import React from 'react';
|
||||
import { channel } from 'redux-saga';
|
||||
import * as semver from 'semver';
|
||||
import { delay, getContext, put, take, takeEvery } from 'typed-redux-saga/macro';
|
||||
import { didCheckForUpdate, reload } from '../app/actions';
|
||||
import { appDidCheckForUpdate, appReload } from '../app/actions';
|
||||
import { appName } from '../app/constants';
|
||||
import { bleDIServiceDidReceiveFirmwareRevision } from '../ble-device-info-service/actions';
|
||||
import {
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
didFailToConnect as bootloaderDidFailToConnect,
|
||||
} from '../lwp3-bootloader/actions';
|
||||
import { didCompile, didFailToCompile } from '../mpy/actions';
|
||||
import { serviceWorkerDidUpdate as serviceWorkerDidUpdate } from '../service-worker/actions';
|
||||
import { serviceWorkerDidUpdate } from '../service-worker/actions';
|
||||
import { pythonVersionToSemver } from '../utils/version';
|
||||
import NotificationAction from './NotificationAction';
|
||||
import NotificationMessage from './NotificationMessage';
|
||||
@@ -336,9 +336,7 @@ function* handleAddNotification(action: ReturnType<typeof addNotification>): Gen
|
||||
});
|
||||
}
|
||||
|
||||
function* showServiceWorkerUpdate(
|
||||
action: ReturnType<typeof serviceWorkerDidUpdate>,
|
||||
): Generator {
|
||||
function* showServiceWorkerUpdate(): Generator {
|
||||
const ch = channel<React.MouseEvent<HTMLElement>>();
|
||||
const userAction = dispatchAction(
|
||||
MessageId.ServiceWorkerUpdateAction,
|
||||
@@ -358,10 +356,10 @@ function* showServiceWorkerUpdate(
|
||||
|
||||
yield* take(ch);
|
||||
|
||||
yield* put(reload(action.registration));
|
||||
yield* put(appReload());
|
||||
}
|
||||
|
||||
function* showNoUpdateInfo(action: ReturnType<typeof didCheckForUpdate>): Generator {
|
||||
function* showNoUpdateInfo(action: ReturnType<typeof appDidCheckForUpdate>): Generator {
|
||||
if (action.updateFound) {
|
||||
// this will be handled by serviceWorkerDidUpdate action
|
||||
return;
|
||||
@@ -442,7 +440,7 @@ export default function* (): Generator {
|
||||
yield* takeEvery(didFailToCompile, showCompilerError);
|
||||
yield* takeEvery(addNotification, handleAddNotification);
|
||||
yield* takeEvery(serviceWorkerDidUpdate, showServiceWorkerUpdate);
|
||||
yield* takeEvery(didCheckForUpdate, showNoUpdateInfo);
|
||||
yield* takeEvery(appDidCheckForUpdate, showNoUpdateInfo);
|
||||
yield* takeEvery(bleDIServiceDidReceiveFirmwareRevision, checkVersion);
|
||||
yield* takeEvery(fileStorageDidFailToInitialize, showFileStorageFailToInitialize);
|
||||
yield* takeEvery(fileStorageDidFailToReadFile, showFileStorageFailToRead);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// istanbul ignore file
|
||||
|
||||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler): void => {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// You can also remove this file if you'd prefer not to use a
|
||||
// service worker, and the Workbox build step will be skipped.
|
||||
|
||||
// istanbul ignore file
|
||||
|
||||
import { clientsClaim } from 'workbox-core';
|
||||
import { ExpirationPlugin } from 'workbox-expiration';
|
||||
import { createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching';
|
||||
|
||||
@@ -3,16 +3,10 @@
|
||||
|
||||
import { createAction } from '../actions';
|
||||
|
||||
export const serviceWorkerDidUpdate = createAction(
|
||||
(registration: ServiceWorkerRegistration) => ({
|
||||
type: 'serviceWorker.action.didUpdate',
|
||||
registration,
|
||||
}),
|
||||
);
|
||||
export const serviceWorkerDidUpdate = createAction(() => ({
|
||||
type: 'serviceWorker.action.didUpdate',
|
||||
}));
|
||||
|
||||
export const serviceWorkerDidSucceed = createAction(
|
||||
(registration: ServiceWorkerRegistration) => ({
|
||||
type: 'serviceWorker.action.didSucceed',
|
||||
registration,
|
||||
}),
|
||||
);
|
||||
export const serviceWorkerDidSucceed = createAction(() => ({
|
||||
type: 'serviceWorker.action.didSucceed',
|
||||
}));
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://cra.link/PWA
|
||||
|
||||
// istanbul ignore file
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useI18n } from '@shopify/react-i18n';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import AboutDialog from '../about/AboutDialog';
|
||||
import { appShowInstallPrompt, checkForUpdate, reload } from '../app/actions';
|
||||
import { appCheckForUpdate, appReload, appShowInstallPrompt } from '../app/actions';
|
||||
import {
|
||||
pybricksBugReportsUrl,
|
||||
pybricksGitterUrl,
|
||||
@@ -50,7 +50,9 @@ const SettingsDrawer: React.FunctionComponent<SettingsProps> = (props) => {
|
||||
const showDocs = useSelector((s) => s.settings.showDocs);
|
||||
const darkMode = useSelector((s) => s.settings.darkMode);
|
||||
const flashCurrentProgram = useSelector((s) => s.settings.flashCurrentProgram);
|
||||
const serviceWorker = useSelector((s) => s.app.serviceWorker);
|
||||
const isServiceWorkerRegistered = useSelector(
|
||||
(s) => s.app.isServiceWorkerRegistered,
|
||||
);
|
||||
const checkingForUpdate = useSelector((s) => s.app.checkingForUpdate);
|
||||
const updateAvailable = useSelector((s) => s.app.updateAvailable);
|
||||
const hasUnresolvedInstallPrompt = useSelector(
|
||||
@@ -290,12 +292,10 @@ const SettingsDrawer: React.FunctionComponent<SettingsProps> = (props) => {
|
||||
{i18n.translate(SettingsStringId.AppInstallLabel)}
|
||||
</Button>
|
||||
)}
|
||||
{serviceWorker && !updateAvailable && (
|
||||
{isServiceWorkerRegistered && !updateAvailable && (
|
||||
<Button
|
||||
icon="refresh"
|
||||
onClick={() =>
|
||||
dispatch(checkForUpdate(serviceWorker))
|
||||
}
|
||||
onClick={() => dispatch(appCheckForUpdate())}
|
||||
loading={checkingForUpdate}
|
||||
>
|
||||
{i18n.translate(
|
||||
@@ -303,10 +303,10 @@ const SettingsDrawer: React.FunctionComponent<SettingsProps> = (props) => {
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{serviceWorker && updateAvailable && (
|
||||
{isServiceWorkerRegistered && updateAvailable && (
|
||||
<Button
|
||||
icon="refresh"
|
||||
onClick={() => dispatch(reload(serviceWorker))}
|
||||
onClick={() => dispatch(appReload())}
|
||||
>
|
||||
{i18n.translate(SettingsStringId.AppRestartLabel)}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user