feat(content): owned-content persistence + Fedimint paid downloads, fmcd caps fix, FIPS warm-path perf
Buyer-side paid downloads now persist: purchases are cached on disk (content_owned.rs) keyed by (seller onion, content_id), the gallery shows an "Owned" badge unblurred, and items view/play in-app from the local cache with no re-payment or reliance on a browser download (which silently failed on the mobile companion). New RPCs content.owned-list / content.owned-get. Validated e2e .116<-.198 (paid 100 sats via Fedimint, 166KB jpeg returns, survives restart). fedimint-clientd manifest: restore the standard container capability set (CHOWN/DAC_OVERRIDE/FOWNER/SETUID/SETGID) so fmcd's startup chown of an existing-federation /data succeeds instead of dying EPERM (#7). Confirmed the orchestrator applies these to the running container. FIPS perf: tighten the supervisor warm-path keepalive 45s -> 25s so peer paths stay inside the ~30-60s NAT cold window. Dials now reliably land on FIPS instead of re-punching and falling back to Tor. Measured to the same peer: cloud browse 18-22s -> 0.4s; full Fedimint paid download 29s -> 11s (residual is the seller-side guardian reissue round-trip). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b0c9bd2a0c
commit
db7d424bff
+31
-2
@@ -110,10 +110,39 @@ function recordError(source: string, err: unknown, info?: string) {
|
||||
|
||||
app.config.errorHandler = (err, _instance, info) => recordError('Vue Error', err, info)
|
||||
|
||||
// After a frontend deploy the browser's cached index.html still points at the
|
||||
// OLD hashed chunks (e.g. AppSession-Cq93o4ao.js), which 404 — Vite then throws
|
||||
// "Failed to fetch dynamically imported module". The fix is to reload once so
|
||||
// the browser pulls the fresh index + chunk map. Guard with sessionStorage so a
|
||||
// genuinely-broken chunk can't trap us in a reload loop.
|
||||
function isStaleChunkError(err: unknown): boolean {
|
||||
const msg = (err as { message?: string })?.message ?? String(err ?? '')
|
||||
return /Failed to fetch dynamically imported module|error loading dynamically imported module|Importing a module script failed|ChunkLoadError|dynamically imported module/i.test(msg)
|
||||
}
|
||||
function reloadOnceForStaleChunk(err: unknown): boolean {
|
||||
if (!isStaleChunkError(err)) return false
|
||||
try {
|
||||
const KEY = 'archy-chunk-reload-at'
|
||||
const last = Number(sessionStorage.getItem(KEY) || '0')
|
||||
// Only reload if we haven't already tried in the last 10s (loop guard).
|
||||
if (Date.now() - last < 10_000) return false
|
||||
sessionStorage.setItem(KEY, String(Date.now()))
|
||||
} catch { /* sessionStorage unavailable — reload anyway, once is better than stuck */ }
|
||||
// Cache-bust the navigation so the stale index isn't served again.
|
||||
location.reload()
|
||||
return true
|
||||
}
|
||||
|
||||
// Vue's errorHandler only catches errors raised synchronously inside Vue's
|
||||
// lifecycle/reactivity. Async rejections and plain runtime errors (e.g. a JS
|
||||
// API missing in an older WebView) slip past it, so catch those too.
|
||||
window.addEventListener('error', (ev) => recordError('window.error', ev.error ?? ev.message))
|
||||
window.addEventListener('unhandledrejection', (ev) => recordError('unhandledrejection', ev.reason))
|
||||
window.addEventListener('error', (ev) => {
|
||||
if (reloadOnceForStaleChunk(ev.error ?? ev.message)) return
|
||||
recordError('window.error', ev.error ?? ev.message)
|
||||
})
|
||||
window.addEventListener('unhandledrejection', (ev) => {
|
||||
if (reloadOnceForStaleChunk(ev.reason)) return
|
||||
recordError('unhandledrejection', ev.reason)
|
||||
})
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
Reference in New Issue
Block a user