mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-14 10:35:11 +00:00
- 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
129 lines
4.2 KiB
TypeScript
129 lines
4.2 KiB
TypeScript
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) 2021-2022 The Pybricks Authors
|
|
|
|
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,
|
|
didInstall,
|
|
} 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) => {
|
|
emit(e);
|
|
};
|
|
// chromium-only event
|
|
window.addEventListener('appinstalled', listener);
|
|
// istanbul ignore next: currently we don't ever stop monitoring
|
|
return () => window.removeEventListener('appinstalled', listener);
|
|
});
|
|
|
|
while (true) {
|
|
yield* take(chan);
|
|
yield* put(didInstall());
|
|
}
|
|
}
|
|
|
|
function* handleBeforeInstallPromptEvent(event: BeforeInstallPromptEvent): Generator {
|
|
// wait for user to request to install the app - may never happen
|
|
yield* take(appShowInstallPrompt);
|
|
|
|
yield* call(() => event.prompt());
|
|
const choice = yield* call(() => event.userChoice);
|
|
yield* put(appDidResolveInstallPrompt(choice));
|
|
}
|
|
|
|
function* monitorBeforeInstallPrompt(): Generator {
|
|
const chan = eventChannel<BeforeInstallPromptEvent>((emit) => {
|
|
const listener = (e: BeforeInstallPromptEvent) => {
|
|
emit(e);
|
|
};
|
|
// @ts-expect-error: chromium-only event
|
|
window.addEventListener('beforeinstallprompt', listener);
|
|
// istanbul ignore next: currently we don't ever stop monitoring
|
|
// @ts-expect-error: chromium-only event
|
|
return () => window.removeEventListener('beforeinstallprompt', listener);
|
|
});
|
|
|
|
// in theory, the before install prompt event should only happen once, so
|
|
// we don't bother canceling the forked task when another event is received
|
|
while (true) {
|
|
const event = yield* take(chan);
|
|
yield* fork(handleBeforeInstallPromptEvent, event);
|
|
yield* put(appDidReceiveBeforeInstallPrompt());
|
|
}
|
|
}
|
|
|
|
export default function* app(): Generator {
|
|
yield* fork(monitorServiceWorkerRegistration);
|
|
yield* fork(monitorAppInstalled);
|
|
yield* fork(monitorBeforeInstallPrompt);
|
|
}
|