fix(matrix): upload retry/backoff, robust MatrixError, typed m.direct, reliable presence on unload

- uploadContent: bounded retry (max 3) reusing rateLimitedActions' capped
  exponential backoff; retries only transient failures (network/429/5xx), never 4xx.
- Robust MatrixError construction from UploadResponse / unknown error shapes.
- addRoomIdToMDirect/removeRoomIdFromMDirect: drop `as any`, use typed
  EventType.Direct + MDirectContent.
- usePresenceUpdater: keep fetch({keepalive}) for the unload offline-presence
  update (sendBeacon can't set the auth header) and log redacted warnings instead
  of silently swallowing presence errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 08:22:00 -04:00
parent b7e1f89c1d
commit d2946c00ce
2 changed files with 110 additions and 29 deletions
+18 -4
View File
@@ -23,6 +23,13 @@ export function usePresenceUpdater() {
const readStatus = () =>
userId ? (localStorage.getItem(`lotus-status-msg-${userId}`) ?? '') : '';
// Log presence failures without leaking PII (user id, token, status message).
const warnPresenceFailure = (presence: string, err: unknown) => {
const reason =
err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
console.warn(`Failed to set presence to "${presence}":`, reason);
};
const setOnline = () => {
const status = readStatus();
return mx
@@ -30,7 +37,7 @@ export function usePresenceUpdater() {
presence: 'online',
...(status ? { status_msg: status } : {}),
})
.catch(() => undefined);
.catch((err) => warnPresenceFailure('online', err));
};
const setUnavailable = (statusMsg?: string) => {
const status = readStatus();
@@ -39,10 +46,12 @@ export function usePresenceUpdater() {
presence: 'unavailable',
...(statusMsg ? { status_msg: statusMsg } : status ? { status_msg: status } : {}),
})
.catch(() => undefined);
.catch((err) => warnPresenceFailure('unavailable', err));
};
const setOffline = () =>
mx.setPresence({ presence: 'offline', status_msg: '' }).catch(() => undefined);
mx
.setPresence({ presence: 'offline', status_msg: '' })
.catch((err) => warnPresenceFailure('offline', err));
// Manual presence overrides — no activity tracking needed.
if (hidePresence || presenceStatus === 'invisible') {
@@ -100,6 +109,11 @@ export function usePresenceUpdater() {
const baseUrl = mx.getHomeserverUrl();
if (!userId || !token || !baseUrl) return;
// Reliable delivery during page teardown: navigator.sendBeacon cannot set the
// Authorization header required by the authenticated Matrix presence endpoint, so
// it isn't usable here. fetch(..., { keepalive: true }) lets the request outlive the
// page and is the correct mechanism for an authed endpoint. (keepalive bodies are
// capped at 64KB, which this tiny payload is well under.)
fetch(`${baseUrl}/_matrix/client/v3/presence/${encodeURIComponent(userId)}/status`, {
method: 'PUT',
headers: {
@@ -108,7 +122,7 @@ export function usePresenceUpdater() {
},
body: JSON.stringify({ presence: 'offline' }),
keepalive: true,
}).catch(() => undefined);
}).catch((err) => warnPresenceFailure('offline (pagehide)', err));
};
setOnline();