Skip to content

Commit a4447dd

Browse files
authored
Merge pull request #255 from Just-Insane/fix/media-and-chunk-errors
fix: Media blob cache and chunk loading error handling
2 parents ea22db6 + f67abc0 commit a4447dd

4 files changed

Lines changed: 64 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: patch
3+
---
4+
5+
Fixed unhandled promise rejections in media blob cache and added automatic retry for chunk loading failures after deployments.

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/index.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,41 @@ const injectIOSMetaTags = () => {
139139

140140
injectIOSMetaTags();
141141

142+
// Handle chunk loading failures with automatic retry
143+
const CHUNK_RETRY_KEY = 'cinny_chunk_retry_count';
144+
const MAX_CHUNK_RETRIES = 2;
145+
146+
window.addEventListener('error', (event) => {
147+
// Check if this is a chunk loading error
148+
const isChunkLoadError =
149+
event.message?.includes('dynamically imported module') ||
150+
event.message?.includes('Failed to fetch') ||
151+
event.error?.name === 'ChunkLoadError';
152+
153+
if (isChunkLoadError) {
154+
const retryCount = parseInt(sessionStorage.getItem(CHUNK_RETRY_KEY) ?? '0', 10);
155+
156+
if (retryCount < MAX_CHUNK_RETRIES) {
157+
// Increment retry count and reload
158+
sessionStorage.setItem(CHUNK_RETRY_KEY, String(retryCount + 1));
159+
log.warn(`Chunk load failed, reloading (attempt ${retryCount + 1}/${MAX_CHUNK_RETRIES})`);
160+
window.location.reload();
161+
162+
// Prevent default error handling since we're reloading
163+
event.preventDefault();
164+
} else {
165+
// Max retries exceeded, clear counter and let error bubble up
166+
sessionStorage.removeItem(CHUNK_RETRY_KEY);
167+
log.error('Chunk load failed after max retries, showing error');
168+
}
169+
}
170+
});
171+
172+
// Clear chunk retry counter on successful page load
173+
window.addEventListener('load', () => {
174+
sessionStorage.removeItem(CHUNK_RETRY_KEY);
175+
});
176+
142177
const mountApp = () => {
143178
const rootContainer = document.getElementById('root');
144179

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)