fix: intermittent media 401 errors after token refresh or SW restart#548
fix: intermittent media 401 errors after token refresh or SW restart#548Just-Insane wants to merge 4 commits intoSableClient:devfrom
Conversation
8ecb7cc to
715e9e4
Compare
ae8c97d to
c177218
Compare
There was a problem hiding this comment.
Pull request overview
This PR targets media reliability by addressing token refresh propagation and service-worker handling of authenticated media fetches, plus reducing noisy “Uncaught (in promise)” console errors in media-loading UI flows.
Changes:
- Adjust
useAsyncCallbackerror handling behavior and update its tests. - Persist OIDC
refresh_token/expires_in_msinto session state and wire atokenRefreshFunctioninto the Matrix client, propagating refreshed access tokens to the service worker. - Add a SW media fetch wrapper that retries once on HTTP 401 using the latest in-memory/live session token.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/sw.ts |
Adds fetchMediaWithRetry() and routes media fetch paths through it to mitigate 401s during token-refresh propagation races. |
src/client/initMatrix.ts |
Passes refresh-token options into createClient and plumbs an optional onTokenRefresh callback to propagate new tokens outward. |
src/app/pages/client/ClientRoot.tsx |
Supplies the onTokenRefresh callback to update sessions state and push updated tokens to the SW. |
src/app/pages/auth/login/loginUtil.ts |
Stores refresh_token and expires_in_ms into the session record on login. |
src/app/hooks/useAsyncCallback.ts |
Changes error handling in the async wrapper (no longer rejects on error) and documents rationale. |
src/app/hooks/useAsyncCallback.test.tsx |
Updates error test to no longer expect a rejected promise. |
.changeset/fix-media-error-handling.md |
Adds a patch changeset describing the combined fixes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- useAsyncCallback: re-throw in catch to preserve rejection semantics; add no-op .catch wrapper in useAsyncCallback to suppress unhandled rejection warnings for fire-and-forget callers - useAsyncCallback.test: expect rejects.toBe(boom) to assert rejection - loginUtil: use != null guards for refresh_token / expires_in_ms to correctly handle zero/falsy-but-present values - initMatrix: use typeof === 'number' guard for expires_in_ms expiry calc - ClientRoot: pass activeSession.userId to both pushSessionToSW calls so the SW session record keeps a complete userId
- useAsyncCallback: re-throw in catch to preserve rejection semantics; add no-op .catch wrapper in useAsyncCallback to suppress unhandled rejection warnings for fire-and-forget callers - useAsyncCallback.test: expect rejects.toBe(boom) to assert rejection - loginUtil: use != null guards for refresh_token / expires_in_ms to correctly handle zero/falsy-but-present values - initMatrix: use typeof === 'number' guard for expires_in_ms expiry calc - ClientRoot: pass activeSession.userId to both pushSessionToSW calls so the SW session record keeps a complete userId
- useAsyncCallback: re-throw in catch to preserve rejection semantics; add no-op .catch wrapper in useAsyncCallback to suppress unhandled rejection warnings for fire-and-forget callers - useAsyncCallback.test: expect rejects.toBe(boom) to assert rejection - loginUtil: use != null guards for refresh_token / expires_in_ms to correctly handle zero/falsy-but-present values - initMatrix: use typeof === 'number' guard for expires_in_ms expiry calc - ClientRoot: pass activeSession.userId to both pushSessionToSW calls so the SW session record keeps a complete userId
72110d9 to
e41e126
Compare
- useAsyncCallback: re-throw in catch to preserve rejection semantics; add no-op .catch wrapper in useAsyncCallback to suppress unhandled rejection warnings for fire-and-forget callers - useAsyncCallback.test: expect rejects.toBe(boom) to assert rejection - loginUtil: use != null guards for refresh_token / expires_in_ms to correctly handle zero/falsy-but-present values - initMatrix: use typeof === 'number' guard for expires_in_ms expiry calc - ClientRoot: pass activeSession.userId to both pushSessionToSW calls so the SW session record keeps a complete userId
e41e126 to
d7f4e8c
Compare
3106aaa to
3c413f0
Compare
…te-sessions devtool
3c413f0 to
abc22c5
Compare
|
I think there might be some extraneous features in here? The whole new dev tools section, the UrlPreviewCard.tsx normalization changes and the sliding sync probe caching? I think this should either be split into multiple prs or the description should be updated and there should be more changesets. In general I think the code looks fine. |
7w1
left a comment
There was a problem hiding this comment.
update changesets or split into multiple prs
Description
Fixes a chain of service worker session bugs that caused intermittent 401/404 errors on authenticated media:
Fixes #
Type of change
Checklist:
AI disclosure:
sw.ts: ThesetSession/clearSessioncache writes are now wrapped inevent.waitUntilso the browser keeps the SW alive until thecaches.putresolves — preventing stale tokens surviving a mid-write SW kill on iOS. Added arequestSessionWithTimeoutfallback infetchMediaWithRetryfor the window between a token refresh completing and the resultingsetSessionmessage arriving.index.tsx: CallspushSessionToSWsynchronously before React renders so the SW has a valid token before the first fetch event.ClientRoot.tsx: WirespushSessionToSWinto the SDK'sonTokenRefreshcallback so OIDC token refreshes propagate to the SW immediately. Decrypt failure and media error async paths now have proper rejection handling to avoid unhandled promise rejections.