Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-media-chunk-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: patch
---

Fixed unhandled promise rejections in media blob cache and added automatic retry for chunk loading failures after deployments.
12 changes: 9 additions & 3 deletions src/app/hooks/useBlobCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ export function useBlobCache(url?: string): string | undefined {

const fetchBlob = async () => {
if (inflightRequests.has(url)) {
const existingBlobUrl = await inflightRequests.get(url);
if (isMounted) setCacheState({ sourceUrl: url, blobUrl: existingBlobUrl });
try {
const existingBlobUrl = await inflightRequests.get(url);
if (isMounted) setCacheState({ sourceUrl: url, blobUrl: existingBlobUrl });
} catch {
// Inflight request failed, silently ignore (consistent with fetchBlob behavior)
}
return;
}

const requestPromise = (async () => {
try {
const res = await fetch(url, { mode: 'cors' });
if (!res.ok) throw new Error();
if (!res.ok) {
throw new Error(`Failed to fetch blob: ${res.status} ${res.statusText}`);
}
const blob = await res.blob();
const objectUrl = URL.createObjectURL(blob);

Expand Down
35 changes: 35 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
return;
}

if (window.confirm('A new version of the app is available. Refresh to update?')) {

Check warning on line 53 in src/index.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected confirm
if (registration.waiting) {
registration.waiting.postMessage({ type: 'SKIP_WAITING_AND_CLAIM' });
} else {
Expand Down Expand Up @@ -139,6 +139,41 @@

injectIOSMetaTags();

// Handle chunk loading failures with automatic retry
const CHUNK_RETRY_KEY = 'cinny_chunk_retry_count';
const MAX_CHUNK_RETRIES = 2;

window.addEventListener('error', (event) => {
// Check if this is a chunk loading error
const isChunkLoadError =
event.message?.includes('dynamically imported module') ||
event.message?.includes('Failed to fetch') ||
event.error?.name === 'ChunkLoadError';

if (isChunkLoadError) {
const retryCount = parseInt(sessionStorage.getItem(CHUNK_RETRY_KEY) ?? '0', 10);

if (retryCount < MAX_CHUNK_RETRIES) {
// Increment retry count and reload
sessionStorage.setItem(CHUNK_RETRY_KEY, String(retryCount + 1));
log.warn(`Chunk load failed, reloading (attempt ${retryCount + 1}/${MAX_CHUNK_RETRIES})`);
window.location.reload();

// Prevent default error handling since we're reloading
event.preventDefault();
} else {
// Max retries exceeded, clear counter and let error bubble up
sessionStorage.removeItem(CHUNK_RETRY_KEY);
log.error('Chunk load failed after max retries, showing error');
}
}
});

// Clear chunk retry counter on successful page load
window.addEventListener('load', () => {
sessionStorage.removeItem(CHUNK_RETRY_KEY);
});

const mountApp = () => {
const rootContainer = document.getElementById('root');

Expand Down
17 changes: 15 additions & 2 deletions src/sw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@
function setSession(clientId: string, accessToken: unknown, baseUrl: unknown) {
if (typeof accessToken === 'string' && typeof baseUrl === 'string') {
sessions.set(clientId, { accessToken, baseUrl });
console.debug('[SW] setSession: stored', clientId, baseUrl);

Check warning on line 94 in src/sw.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
} else {
// Logout or invalid session
sessions.delete(clientId);
console.debug('[SW] setSession: removed', clientId);

Check warning on line 98 in src/sw.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
}

const resolveSession = clientToResolve.get(clientId);
Expand Down Expand Up @@ -124,12 +126,18 @@
timeoutMs = 3000
): Promise<SessionInfo | undefined> {
const client = await self.clients.get(clientId);
if (!client) return undefined;
if (!client) {
console.warn('[SW] requestSessionWithTimeout: client not found', clientId);

Check warning on line 130 in src/sw.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
return undefined;
}

const sessionPromise = requestSession(client);

const timeout = new Promise<undefined>((resolve) => {
setTimeout(() => resolve(undefined), timeoutMs);
setTimeout(() => {
console.warn('[SW] requestSessionWithTimeout: timed out after', timeoutMs, 'ms', clientId);

Check warning on line 138 in src/sw.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
resolve(undefined);
}, timeoutMs);
});

return Promise.race([sessionPromise, timeout]);
Expand Down Expand Up @@ -274,6 +282,11 @@
if (s && validMediaRequest(url, s.baseUrl)) {
return fetch(url, { ...fetchConfig(s.accessToken), redirect });
}
console.warn(

Check warning on line 285 in src/sw.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
'[SW fetch] No valid session for media request',
{ url, clientId, hasSession: !!s },
'falling back to unauthenticated fetch'
);
return fetch(event.request);
})
);
Expand All @@ -296,13 +309,13 @@
// because iOS Safari PWA often returns empty or stale results from matchAll().
const hasVisibleClient =
appIsVisible || clients.some((client) => client.visibilityState === 'visible');
console.debug(

Check warning on line 312 in src/sw.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
'[SW push] appIsVisible:',
appIsVisible,
'| clients:',
clients.map((c) => ({ url: c.url, visibility: c.visibilityState }))
);
console.debug('[SW push] hasVisibleClient:', hasVisibleClient);

Check warning on line 318 in src/sw.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
if (hasVisibleClient) {
console.debug('[SW push] suppressing OS notification — app is visible');
return;
Expand Down
Loading