fix: resolve PWA freeze caused by infinite microtask loop in sync.ts and hung fetches without timeout
This commit is contained in:
@@ -10,22 +10,43 @@ export class ApiError extends Error {
|
|||||||
|
|
||||||
export async function apiFetch(
|
export async function apiFetch(
|
||||||
input: string,
|
input: string,
|
||||||
init: RequestInit = {}
|
init: RequestInit = {},
|
||||||
|
timeoutMs = 15000
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const headers = new Headers(init.headers)
|
const headers = new Headers(init.headers)
|
||||||
if (init.body !== undefined && !headers.has('Content-Type')) {
|
if (init.body !== undefined && !headers.has('Content-Type')) {
|
||||||
headers.set('Content-Type', 'application/json')
|
headers.set('Content-Type', 'application/json')
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(input, {
|
const controller = new AbortController()
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
||||||
|
|
||||||
|
if (init.signal) {
|
||||||
|
if (init.signal.aborted) {
|
||||||
|
controller.abort()
|
||||||
|
} else {
|
||||||
|
init.signal.addEventListener('abort', () => controller.abort())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fetch(input, {
|
||||||
...init,
|
...init,
|
||||||
headers,
|
headers,
|
||||||
credentials: 'include'
|
credentials: 'include',
|
||||||
|
signal: controller.signal
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apiJson<T>(input: string, init: RequestInit = {}): Promise<T> {
|
export async function apiJson<T>(
|
||||||
const res = await apiFetch(input, init)
|
input: string,
|
||||||
|
init: RequestInit = {},
|
||||||
|
timeoutMs = 15000
|
||||||
|
): Promise<T> {
|
||||||
|
const res = await apiFetch(input, init, timeoutMs)
|
||||||
const data = await res.json().catch(() => ({}))
|
const data = await res.json().catch(() => ({}))
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const message =
|
const message =
|
||||||
|
|||||||
@@ -131,12 +131,7 @@ async function coalesceSyncQueue(logbookId: string): Promise<SyncQueueItem[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scheduleResync(logbookId: string) {
|
function scheduleResync(logbookId: string) {
|
||||||
if (pendingResync.has(logbookId)) return
|
|
||||||
pendingResync.add(logbookId)
|
pendingResync.add(logbookId)
|
||||||
queueMicrotask(() => {
|
|
||||||
pendingResync.delete(logbookId)
|
|
||||||
syncLogbook(logbookId).catch((err) => console.warn('Deferred sync failed:', err))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogbookPushAccess = 'OWNER' | 'WRITE' | 'READ' | 'UNKNOWN'
|
type LogbookPushAccess = 'OWNER' | 'WRITE' | 'READ' | 'UNKNOWN'
|
||||||
@@ -540,6 +535,12 @@ export async function syncLogbook(logbookId: string): Promise<boolean> {
|
|||||||
} finally {
|
} finally {
|
||||||
syncingLogbooks.delete(logbookId)
|
syncingLogbooks.delete(logbookId)
|
||||||
recomputeSyncingState()
|
recomputeSyncingState()
|
||||||
|
if (pendingResync.has(logbookId)) {
|
||||||
|
pendingResync.delete(logbookId)
|
||||||
|
setTimeout(() => {
|
||||||
|
syncLogbook(logbookId).catch((err) => console.warn('Deferred sync failed:', err))
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user