c491af5d02
CI / detect-changes (push) Successful in 8s
CI / branch-name (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 13s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 41s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / loc-budget (push) Failing after 16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m47s
storageHelpers.ts: safeSetItem faengt QuotaExceededError, prunet alte doc-check-result-*-Eintraege (oldest first, MAX_KEEP=10) und retried. Bei zweitem Fail aggressiver pruefen. DocCheckTab.tsx nutzt safeSetItem statt setItem fuer doc-check-results, result-Keys und history. Verhindert silent-data-loss + Crash wenn ~5MB localStorage-Limit erreicht. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
72 lines
2.0 KiB
TypeScript
72 lines
2.0 KiB
TypeScript
/**
|
|
* P47 — localStorage-Quota-Management.
|
|
*
|
|
* Wenn alte Compliance-Check-Ergebnisse den Browser-Storage fuellen,
|
|
* versucht das setItem mit QuotaExceededError zu fangen, prunet
|
|
* alte doc-check-result-*-Eintraege (oldest first) und retried.
|
|
*
|
|
* Wird von DocCheckTab/BannerCheckTab/etc beim Persistieren der
|
|
* Result-Bloebs benutzt.
|
|
*/
|
|
|
|
const RESULT_KEY_PREFIX = 'doc-check-result-'
|
|
const MAX_KEEP = 10 // Maximal 10 alte Result-Bloebs behalten.
|
|
|
|
export function safeSetItem(key: string, value: string): boolean {
|
|
try {
|
|
localStorage.setItem(key, value)
|
|
return true
|
|
} catch (err: any) {
|
|
if (err?.name !== 'QuotaExceededError'
|
|
&& err?.code !== 22 && err?.code !== 1014) {
|
|
console.warn('localStorage setItem failed:', err)
|
|
return false
|
|
}
|
|
pruneOldResults()
|
|
try {
|
|
localStorage.setItem(key, value)
|
|
return true
|
|
} catch {
|
|
// Pruning hat nicht gereicht — aggressiver pruefen
|
|
pruneOldResults(0)
|
|
try {
|
|
localStorage.setItem(key, value)
|
|
return true
|
|
} catch {
|
|
console.warn('localStorage immer noch voll, wert wird verworfen')
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function pruneOldResults(keep: number = MAX_KEEP): void {
|
|
try {
|
|
const keys: { key: string; ts: number }[] = []
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const k = localStorage.key(i)
|
|
if (!k || !k.startsWith(RESULT_KEY_PREFIX)) continue
|
|
const ts = Number(k.slice(RESULT_KEY_PREFIX.length)) || 0
|
|
keys.push({ key: k, ts })
|
|
}
|
|
keys.sort((a, b) => a.ts - b.ts) // oldest first
|
|
const toRemove = keys.slice(0, Math.max(0, keys.length - keep))
|
|
for (const k of toRemove) {
|
|
try { localStorage.removeItem(k.key) } catch {}
|
|
}
|
|
} catch {}
|
|
}
|
|
|
|
export function getStorageUsageMB(): number {
|
|
let bytes = 0
|
|
try {
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const k = localStorage.key(i)
|
|
if (!k) continue
|
|
const v = localStorage.getItem(k) || ''
|
|
bytes += k.length + v.length
|
|
}
|
|
} catch {}
|
|
return bytes / (1024 * 1024)
|
|
}
|