From bd7ae838603fb1b573b32a769e880d08bd6deb4b Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 20 Jul 2026 19:58:04 +0200 Subject: [PATCH] fix(#2443): catch fetch() network errors in bindAsyncForms doSubmit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap the fetch() call in a try/catch so network errors (offline, DNS failure, CORS) surface via themedToast instead of becoming unhandled promise rejections. The asyncBtn finally() still restores the button either way — the catch just adds the missing operator feedback. --- frontend/packages/dashboard/src/common.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/packages/dashboard/src/common.js b/frontend/packages/dashboard/src/common.js index 16a37f76..7b6dff74 100644 --- a/frontend/packages/dashboard/src/common.js +++ b/frontend/packages/dashboard/src/common.js @@ -85,12 +85,18 @@ export function bindAsyncForms(onSuccess) { // Errors are surfaced via themedToast; the caller does not re-throw // so asyncBtn's finally always runs (restoring the button). const doSubmit = async () => { - const resp = await fetch(f.action, { - method: f.method || 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams(new FormData(f)), - redirect: 'manual', - }); + let resp; + try { + resp = await fetch(f.action, { + method: f.method || 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams(new FormData(f)), + redirect: 'manual', + }); + } catch (err) { + themedToast('action failed: ' + err, { type: 'error' }); + return; + } const ok = resp.ok || resp.type === 'opaqueredirect' || (resp.status >= 200 && resp.status < 400); if (!ok) {