fix(status): auto-clear survives a rate-limited presence write (#227)
The quick-status auto-clear removed its localStorage record BEFORE the presence write and swallowed any error, so a single failure dropped the timer permanently and the status stayed set forever. Synapse rate-limits presence to ~1 write / 10 s per user (#226) and the heartbeat spends that budget, so 429s here are routine — especially right after startup, when the monitor's first check runs. The clear now goes through setPresenceWithRetry (honours retry_after_ms) and only forgets the status once the server has taken it; a re-entry guard stops overlapping attempts and the poll is 15 s so a retry lands promptly. Reproduced and verified with three injected 429s: before, the status stayed on the server forever with the local record gone; now it clears. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -60,6 +60,7 @@ import {
|
|||||||
unmuteRoom,
|
unmuteRoom,
|
||||||
} from '../../features/room-nav/muteTimers';
|
} from '../../features/room-nav/muteTimers';
|
||||||
import { STATUS_EXPIRY_KEY, STATUS_MSG_KEY } from '../../features/settings/account/Profile';
|
import { STATUS_EXPIRY_KEY, STATUS_MSG_KEY } from '../../features/settings/account/Profile';
|
||||||
|
import { setPresenceWithRetry } from '../../utils/presenceWrite';
|
||||||
import { useDeepLinkNavigate } from '../../hooks/useDeepLinkNavigate';
|
import { useDeepLinkNavigate } from '../../hooks/useDeepLinkNavigate';
|
||||||
import { toastQueueAtom } from '../../state/toast';
|
import { toastQueueAtom } from '../../state/toast';
|
||||||
import { useReminders } from '../../hooks/useReminders';
|
import { useReminders } from '../../hooks/useReminders';
|
||||||
@@ -389,21 +390,38 @@ function StatusExpiryMonitor() {
|
|||||||
const expiryKey = STATUS_EXPIRY_KEY(userId);
|
const expiryKey = STATUS_EXPIRY_KEY(userId);
|
||||||
const msgKey = STATUS_MSG_KEY(userId);
|
const msgKey = STATUS_MSG_KEY(userId);
|
||||||
|
|
||||||
|
// [Gitea #227] The clear must survive a failed write. Synapse rate-limits
|
||||||
|
// presence to ~1 write / 10 s per user (#226) and the heartbeat spends that
|
||||||
|
// budget, so a bare setPresence here 429s often — and because the keys used
|
||||||
|
// to be removed BEFORE the request, a single failure dropped the auto-clear
|
||||||
|
// permanently and the status stayed set forever. Retry the write (honouring
|
||||||
|
// Retry-After), and only forget the status once the server has taken it.
|
||||||
|
let clearing = false;
|
||||||
const check = () => {
|
const check = () => {
|
||||||
|
if (clearing) return;
|
||||||
const stored = localStorage.getItem(expiryKey);
|
const stored = localStorage.getItem(expiryKey);
|
||||||
if (!stored) return;
|
if (!stored) return;
|
||||||
const ts = parseInt(stored, 10);
|
const ts = parseInt(stored, 10);
|
||||||
if (!ts || Date.now() < ts) return;
|
if (!ts || Date.now() < ts) return;
|
||||||
localStorage.removeItem(msgKey);
|
clearing = true;
|
||||||
localStorage.removeItem(expiryKey);
|
setPresenceWithRetry(mx, {
|
||||||
mx.setPresence({
|
|
||||||
presence: presenceStateFromSetting(presenceStatusRef.current, hidePresenceRef.current),
|
presence: presenceStateFromSetting(presenceStatusRef.current, hidePresenceRef.current),
|
||||||
status_msg: '',
|
status_msg: '',
|
||||||
}).catch(() => undefined);
|
})
|
||||||
|
.then(() => {
|
||||||
|
localStorage.removeItem(msgKey);
|
||||||
|
localStorage.removeItem(expiryKey);
|
||||||
|
})
|
||||||
|
.catch(() => undefined)
|
||||||
|
.finally(() => {
|
||||||
|
clearing = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
check();
|
check();
|
||||||
const interval = setInterval(check, 30_000);
|
// Poll often enough that a rate-limited retry lands promptly, but not so
|
||||||
|
// often that a persistent failure hammers the server.
|
||||||
|
const interval = setInterval(check, 15_000);
|
||||||
const onVisible = () => {
|
const onVisible = () => {
|
||||||
if (document.visibilityState === 'visible') check();
|
if (document.visibilityState === 'visible') check();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user