A05: Add sessionStorage error handling for unavailable storage

Bug: sessionStorage operations (getItem, setItem, removeItem) could fail
if storage is unavailable (private mode, quota exceeded, etc.), causing
form draft recovery to break.

Fix: Wrap all sessionStorage operations in try/catch via safeStorage helper.
- safeStorage.getItem() - returns null on error
- safeStorage.setItem() - silently ignores errors
- safeStorage.removeItem() - silently ignores errors

This ensures:
- Draft recovery works even if storage is partially unavailable
- Form submission doesn't crash if storage is full
- Cleanup on success doesn't crash
- File input values are not saved (already handled by FormData filter)

Verification:
- Astro build: 0 errors
- All existing A05 behavior preserved
- Error handling added for read, write, and cleanup
This commit is contained in:
ik
2026-09-10 18:10:57 +07:00
parent 6183fb3417
commit 1d403883a0
+16 -4
View File
@@ -22,11 +22,17 @@ const reportId = Astro.url.searchParams.get("report_id");
</form>} </form>}
<script is:inline define:vars={{ state }}> <script is:inline define:vars={{ state }}>
const form = document.querySelector(".report-form"); const key = "rf4-report-draft"; const form = document.querySelector(".report-form"); const key = "rf4-report-draft";
// A05: Safe sessionStorage access with error handling
const safeStorage = {
getItem: (k) => { try { return sessionStorage.getItem(k); } catch { return null; } },
setItem: (k, v) => { try { sessionStorage.setItem(k, v); } catch {} },
removeItem: (k) => { try { sessionStorage.removeItem(k); } catch {} },
};
// A05: Restore draft on all error states that don't destroy the submission // A05: Restore draft on all error states that don't destroy the submission
const recoverableStates = ["create_error", "rate_limited", "server_error", "timeout"]; const recoverableStates = ["create_error", "rate_limited", "server_error", "timeout"];
if (form && recoverableStates.includes(state)) { if (form && recoverableStates.includes(state)) {
try { try {
const draft = JSON.parse(sessionStorage.getItem(key) || "{}"); const draft = JSON.parse(safeStorage.getItem(key) || "{}");
for (const [name, value] of Object.entries(draft)) { for (const [name, value] of Object.entries(draft)) {
const field = form.elements.namedItem(name); const field = form.elements.namedItem(name);
if (field && "value" in field) field.value = value; if (field && "value" in field) field.value = value;
@@ -34,12 +40,18 @@ const reportId = Astro.url.searchParams.get("report_id");
} catch {} } catch {}
document.querySelector("#form-error")?.focus(); document.querySelector("#form-error")?.focus();
} }
if (state === "sent" || state === "screenshot_sent") sessionStorage.removeItem(key); if (state === "sent" || state === "screenshot_sent") safeStorage.removeItem(key);
form?.addEventListener("submit", () => { form?.addEventListener("submit", () => {
const btn = form.querySelector("button[type=submit]"); const btn = form.querySelector("button[type=submit]");
if (btn) { btn.disabled = true; btn.textContent = "Отправка..."; } if (btn) { btn.disabled = true; btn.textContent = "Отправка..."; }
const draft = {}; for (const [name, value] of new FormData(form)) if (typeof value === "string" && name !== "website") draft[name] = value; // A05: Save draft before submit, handle sessionStorage unavailable
sessionStorage.setItem(key, JSON.stringify(draft)); const draft = {};
try {
for (const [name, value] of new FormData(form)) {
if (typeof value === "string" && name !== "website") draft[name] = value;
}
safeStorage.setItem(key, JSON.stringify(draft));
} catch {}
}); });
</script> </script>
</Layout> </Layout>