mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-30 00:22:10 -04:00
Calendar: cross-session delete sync — 404 = success, refetch on tab focus
A stale event deleted on one device stayed undeletable on every other session: the cached row showed up, the DELETE call returned 404 (server already removed it), the optimistic catch-block restored the row, and the user could never clear it. - Treat HTTP 404 on DELETE as success — the event is already gone, which is the state we wanted. Skip the optimistic restore. - Re-fetch the visible range on document `visibilitychange` (mobile app returns to foreground) and on window `focus` (desktop alt-tab), throttled to once per 10s so rapid tab-flipping doesn't hammer the API. Without a focus refresh, mobile only got fresh server state at page-load and lived on stale data until a full reload.
This commit is contained in:
+43
-1
@@ -328,7 +328,11 @@ async function _deleteEvent(uid) {
|
|||||||
fetch(`${API_BASE}/api/calendar/events/${encodeURIComponent(uid)}`, {
|
fetch(`${API_BASE}/api/calendar/events/${encodeURIComponent(uid)}`, {
|
||||||
method: 'DELETE', credentials: 'same-origin',
|
method: 'DELETE', credentials: 'same-origin',
|
||||||
}).then(r => {
|
}).then(r => {
|
||||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
// 404 = the event was already deleted by another session/device. That's
|
||||||
|
// exactly the state we want, so treat it as success — don't restore the
|
||||||
|
// row, otherwise the user can never clear stale cached events that were
|
||||||
|
// deleted from desktop while mobile was open (and vice versa).
|
||||||
|
if (!r.ok && r.status !== 404) throw new Error('HTTP ' + r.status);
|
||||||
if (isRecurring) {
|
if (isRecurring) {
|
||||||
_fetchedRanges = [];
|
_fetchedRanges = [];
|
||||||
localStorage.removeItem(LS_KEY);
|
localStorage.removeItem(LS_KEY);
|
||||||
@@ -3435,6 +3439,44 @@ window.addEventListener('calendar-refresh', () => {
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cross-session catch-up: when the tab/app becomes visible again (you alt-tab
|
||||||
|
// back, the mobile app comes to the foreground, or you switch back from
|
||||||
|
// another browser session), drop the range cache and re-fetch. Without this,
|
||||||
|
// a delete or add on desktop never propagates to the still-open mobile tab
|
||||||
|
// until the user does a full reload — so stale events sit there undeletable
|
||||||
|
// (they 404 on the server). Triggers on every visibility change but the
|
||||||
|
// fetch is cheap and already de-duped by _fetchPromise on line ~120.
|
||||||
|
let _lastVisRefetchAt = 0;
|
||||||
|
const _VIS_REFETCH_MIN_MS = 10 * 1000; // throttle if user is rapidly tab-flipping
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState !== 'visible') return;
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - _lastVisRefetchAt < _VIS_REFETCH_MIN_MS) return;
|
||||||
|
_lastVisRefetchAt = now;
|
||||||
|
_fetchedRanges = [];
|
||||||
|
const range = (_view === 'year')
|
||||||
|
? [`${_currentDate.getFullYear()}-01-01`, `${_currentDate.getFullYear() + 1}-01-01`]
|
||||||
|
: (_view === 'week') ? _weekRange(_currentDate) : _monthRange(_currentDate);
|
||||||
|
_fetchEvents(range[0], range[1], /*force*/ true)
|
||||||
|
.then(() => { if (_open) _render(); _updateBadge(); })
|
||||||
|
.catch(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Same idea for window-level focus — covers desktop alt-tabbing back to a
|
||||||
|
// browser that already had the tab visible (visibilitychange won't fire).
|
||||||
|
window.addEventListener('focus', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - _lastVisRefetchAt < _VIS_REFETCH_MIN_MS) return;
|
||||||
|
_lastVisRefetchAt = now;
|
||||||
|
_fetchedRanges = [];
|
||||||
|
const range = (_view === 'year')
|
||||||
|
? [`${_currentDate.getFullYear()}-01-01`, `${_currentDate.getFullYear() + 1}-01-01`]
|
||||||
|
: (_view === 'week') ? _weekRange(_currentDate) : _monthRange(_currentDate);
|
||||||
|
_fetchEvents(range[0], range[1], /*force*/ true)
|
||||||
|
.then(() => { if (_open) _render(); _updateBadge(); })
|
||||||
|
.catch(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
// Calendar reminders are stored as Notes. The Notes reminder loop owns
|
// Calendar reminders are stored as Notes. The Notes reminder loop owns
|
||||||
// notification dispatch so calendar reminders do not fire twice.
|
// notification dispatch so calendar reminders do not fire twice.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user