diff --git a/internal/html/assets/src/app.ts b/internal/html/assets/src/app.ts index 2b1a7811..03677185 100644 --- a/internal/html/assets/src/app.ts +++ b/internal/html/assets/src/app.ts @@ -341,13 +341,27 @@ type UIShare = ParsedShare & { isHolder?: boolean }; // Load embedded manifest if available (included when MANIFEST.age is small enough) if (personalization.manifestB64) { - const binary = atob(personalization.manifestB64); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); + try { + const binary = atob(personalization.manifestB64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + state.manifest = bytes; + showManifestLoaded('MANIFEST.age', state.manifest.length, 'embedded'); + } catch (err) { + // atob throws DOMException on corrupt base64 (bit rot, truncated copy, + // storage degradation). Surface it so the user knows to try another + // copy of recover.html rather than assuming they need MANIFEST.age + // separately. + showError( + t('error_corrupt_embedded_manifest_message'), + { + title: t('error_corrupt_embedded_manifest_title'), + guidance: t('error_corrupt_embedded_manifest_guidance'), + } + ); } - state.manifest = bytes; - showManifestLoaded('MANIFEST.age', state.manifest.length, 'embedded'); } checkRecoverReady(); diff --git a/internal/translations/recover/en.json b/internal/translations/recover/en.json index 87128145..3542ccd1 100644 --- a/internal/translations/recover/en.json +++ b/internal/translations/recover/en.json @@ -77,6 +77,9 @@ "error_extract_status": "Extraction failed. The archive may be corrupted.", "error_recovery_title": "Recovery failed", "error_recovery_guidance": "Check that you have the correct pieces and the right MANIFEST.age file. You can try again with different pieces.", + "error_corrupt_embedded_manifest_title": "Corrupted embedded archive", + "error_corrupt_embedded_manifest_message": "The archive embedded in this recover.html is damaged and couldn't be loaded.", + "error_corrupt_embedded_manifest_guidance": "Your copy of recover.html may be truncated or corrupted. Try another copy of the file, or load MANIFEST.age manually in Step 2.", "action_reload": "Reload page", "action_use_cli": "Use CLI tool", "action_try_different_shares": "Try different pieces",