feat(browser): opt-in offline queue for spans and logs - #1156
Confidence 3/5 · 1 issue to address
Confidence 3/5 · needs attention
The opt-in queue is well tested against real IndexedDB; the one defect is the origin-wide lock held across an untimed POST in drain.
quality 90/100 · 1 warning · tests covered · risk medium
Adds an opt-in (transport.offline, default off) offline queue: span and log batches the exports gave up on are serialized as OTLP JSON into IndexedDB and re-POSTed on online or the next page load, with consent and age limits. The design is sound and the store/resend/keep/drop paths are exercised by browser tests; one liveness defect in the resend lease is worth fixing before merge.
resolveConfigmapstransport.offlinetoResolvedConfig.offlineQueue, default falseOfflineSpanExporter/OfflineLogExporterhand failed batches to the deferred queuestartOfflineQueuestores OTLP JSON batches in IndexedDB and resends them under anavigator.lockslease- Consent revoke clears the stored queue; batches older than 24h or 100 batches are dropped
Findings
Warning · F3 · The resend lease is held across POSTs that have no timeout
correctness · packages/browser/src/deferred/offline.ts:112-116
run takes the origin-wide navigator.locks lease and holds it for the whole drain, whose fetch (line 93) has no timeout. One tab with a stalled connection (the flaky-network case this feature exists for) keeps the lease, so no other tab of the origin can resend, and every online event in that tab joins the same inflight drain (line 124) instead of starting a new one — the stored batches then wait for the next page load. Abort the POST after a short budget (AbortSignal.timeout), which the existing .catch(() => undefined) already turns into "keep it for next time".
Add `signal: AbortSignal.timeout(10_000)` to the drain `fetch`, or scope the lock to the store read/delete and not to the network call.
What was checked
tracing.ts:174wraps inside the consent and HTTP-status exporters, so the stored batch is the one that would have been sent- Stored records are validated before resend (
isStoredBatch), sobatch.signalcannot escape into the/v1/<signal>URL - Resend drops pre-consent and expired batches and prunes past
MAX_BATCHES(offline.ts:82,92), matching the documented limits
e5c997c · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple to ask about one.
Annotations
Check warning on line 116 in packages/browser/src/deferred/offline.ts
maple-review-bot / Maple / review
correctness: The resend lease is held across POSTs that have no timeout
`run` takes the origin-wide `navigator.locks` lease and holds it for the whole `drain`, whose `fetch` (line 93) has no timeout. One tab with a stalled connection (the flaky-network case this feature exists for) keeps the lease, so no other tab of the origin can resend, and every `online` event in that tab joins the same `inflight` drain (line 124) instead of starting a new one — the stored batches then wait for the next page load. Abort the POST after a short budget (`AbortSignal.timeout`), which the existing `.catch(() => undefined)` already turns into "keep it for next time".