feat(offline): ride out short connection drops instead of losing the save
A save made while the connection blips for a few seconds is retried rather than reverted: the request layer keeps trying a network failure for a fifteen-second window, waking the instant the OS reports a link again, and only falls back to the existing error path if the drop outlasts it. A quiet "reconnecting" pill appears if the wait passes about a second, and leaves the moment it heals — so a two-second blip is felt as nothing at all. This is deliberately not offline mode: the app is a live view of a shared list, so there is no cache to work in with no server and no write log to reconcile. It is only the blip, ridden out. A server error (4xx/5xx) is the server answering and is not retried; an aborted request (a superseded search) is left to propagate. Kristian: an offline function for when the net drops for short periods, seconds. Shared items stay server-authoritative — nothing is edited against a stale copy for longer than the blip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8dc601d09a
commit
b2fcc0b104
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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 });
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user