Skip to content

Commit 77046ab

Browse files
author
Evie Gauthier
committed
fix: unhandled promise rejection in media blob cache
Fixes unhandled promise rejection when multiple components fetch the same media URL and the initial request fails with 401. ### Root Cause When a blob fetch request was in-flight and failed, subsequent callers awaiting the same promise would receive an unhandled rejection because the await was outside the try-catch block. ### Changes 1. useBlobCache: Wrap inflightRequests.get() await in try-catch 2. useBlobCache: Improve error message to include HTTP status 3. Service Worker: Add logging for session retrieval failures to diagnose authentication issues with media requests Addresses Sentry issue with 401 errors on authenticated media endpoints.
1 parent cd0c134 commit 77046ab

2 files changed

Lines changed: 24 additions & 5 deletions

File tree

src/app/hooks/useBlobCache.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,21 @@ export function useBlobCache(url?: string): string | undefined {
2323

2424
const fetchBlob = async () => {
2525
if (inflightRequests.has(url)) {
26-
const existingBlobUrl = await inflightRequests.get(url);
27-
if (isMounted) setCacheState({ sourceUrl: url, blobUrl: existingBlobUrl });
26+
try {
27+
const existingBlobUrl = await inflightRequests.get(url);
28+
if (isMounted) setCacheState({ sourceUrl: url, blobUrl: existingBlobUrl });
29+
} catch {
30+
// Inflight request failed, silently ignore (consistent with fetchBlob behavior)
31+
}
2832
return;
2933
}
3034

3135
const requestPromise = (async () => {
3236
try {
3337
const res = await fetch(url, { mode: 'cors' });
34-
if (!res.ok) throw new Error();
38+
if (!res.ok) {
39+
throw new Error(`Failed to fetch blob: ${res.status} ${res.statusText}`);
40+
}
3541
const blob = await res.blob();
3642
const objectUrl = URL.createObjectURL(blob);
3743

src/sw.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,11 @@ async function cleanupDeadClients() {
9191
function setSession(clientId: string, accessToken: unknown, baseUrl: unknown) {
9292
if (typeof accessToken === 'string' && typeof baseUrl === 'string') {
9393
sessions.set(clientId, { accessToken, baseUrl });
94+
console.debug('[SW] setSession: stored', clientId, baseUrl);
9495
} else {
9596
// Logout or invalid session
9697
sessions.delete(clientId);
98+
console.debug('[SW] setSession: removed', clientId);
9799
}
98100

99101
const resolveSession = clientToResolve.get(clientId);
@@ -124,12 +126,18 @@ async function requestSessionWithTimeout(
124126
timeoutMs = 3000
125127
): Promise<SessionInfo | undefined> {
126128
const client = await self.clients.get(clientId);
127-
if (!client) return undefined;
129+
if (!client) {
130+
console.warn('[SW] requestSessionWithTimeout: client not found', clientId);
131+
return undefined;
132+
}
128133

129134
const sessionPromise = requestSession(client);
130135

131136
const timeout = new Promise<undefined>((resolve) => {
132-
setTimeout(() => resolve(undefined), timeoutMs);
137+
setTimeout(() => {
138+
console.warn('[SW] requestSessionWithTimeout: timed out after', timeoutMs, 'ms', clientId);
139+
resolve(undefined);
140+
}, timeoutMs);
133141
});
134142

135143
return Promise.race([sessionPromise, timeout]);
@@ -274,6 +282,11 @@ self.addEventListener('fetch', (event: FetchEvent) => {
274282
if (s && validMediaRequest(url, s.baseUrl)) {
275283
return fetch(url, { ...fetchConfig(s.accessToken), redirect });
276284
}
285+
console.warn(
286+
'[SW fetch] No valid session for media request',
287+
{ url, clientId, hasSession: !!s },
288+
'falling back to unauthenticated fetch'
289+
);
277290
return fetch(event.request);
278291
})
279292
);

0 commit comments

Comments
 (0)