71 lines
2.7 KiB
JavaScript
71 lines
2.7 KiB
JavaScript
/**
|
|||
|
|
* Connectivity, as the app experiences it — not as the browser reports it.
|
||
|
|
*
|
||
|
|
* This is for short drops: a few seconds of lost Wi-Fi, a tunnel, a base station
|
||
|
|
* handing over. It is deliberately NOT offline support. The app is already loaded
|
||
|
|
* and its data is already in memory, so there is nothing to cache and nothing to
|
||
|
|
* open from cold; what a blip needs is for the save you just made to survive it,
|
||
|
|
* not for the whole app to run without a server.
|
||
|
|
*
|
||
|
|
* The request layer drives this: it retries a failed request for a short window
|
||
|
|
* (see api.js) and tells this store while it is trying, so the interface can say
|
||
|
|
* "reconnecting" rather than reverting the change under the user's hands. If the
|
||
|
|
* window runs out it is a real outage, and then the normal error path takes over.
|
||
|
|
*
|
||
|
|
* `navigator.onLine` is only a hint — it goes false when the machine has no route,
|
||
|
|
* but it stays true for a dead gateway or a server that is down, so it cannot be
|
||
|
|
* the source of truth. It is used here only to wake a waiting retry the moment the
|
||
|
|
* OS sees a link again, which is the one thing it is reliable for.
|
||
|
|
*/
|
||
|
|
import { browser } from '$app/environment';
|
||
|
|
|
||
|
|
class Connectivity {
|
||
|
|
/** 'online' — every recent request reached the server.
|
||
|
|
* 'retrying' — a request failed on the network and is being retried.
|
||
|
|
* 'lost' — a request gave up after the retry window; a real outage. */
|
||
|
|
status = $state('online');
|
||
|
|
|
||
|
|
/** A request reached the server. The network is up, whatever it was doing. */
|
||
|
|
reachable() {
|
||
|
|
this.status = 'online';
|
||
|
|
}
|
||
|
|
|
||
|
|
/** A request failed on the network and is about to be tried again. */
|
||
|
|
retrying() {
|
||
|
|
if (this.status !== 'lost') this.status = 'retrying';
|
||
|
|
}
|
||
|
|
|
||
|
|
/** A request used up its retry window without reaching the server. */
|
||
|
|
lost() {
|
||
|
|
this.status = 'lost';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export const connectivity = new Connectivity();
|
||
|
|
|
||
|
|
if (browser) {
|
||
|
|
// The OS lost its link: say so at once rather than waiting for the next request
|
||
|
|
// to fail. It does not, on its own, mean the server is unreachable — but with no
|
||
|
|
// route there is nothing a save can do but wait, and the honest thing is to show
|
||
|
|
// it waiting.
|
||
|
|
window.addEventListener('offline', () => connectivity.retrying());
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sleeps for `ms`, but wakes early the moment the OS reports a link again — so a
|
||
|
|
* retry that is biding its time does not sit out the last two seconds of a blip
|
||
|
|
* that has already ended.
|
||
|
|
*/
|
||
|
|
export function sleepOrOnline(ms) {
|
||
|
|
return new Promise((resolve) => {
|
||
|
|
let timer;
|
||
|
|
const done = () => {
|
||
|
|
clearTimeout(timer);
|
||
|
|
if (browser) window.removeEventListener('online', done);
|
||
|
|
resolve();
|
||
|
|
};
|
||
|
|
timer = setTimeout(done, ms);
|
||
|
|
if (browser) window.addEventListener('online', done, { once: true });
|
||
|
|
});
|
||
|
|
}
|