From 617a2d10eb21b24b7644f9d37afe8565ee036d0d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Apr 2026 13:23:33 +0000 Subject: [PATCH] Wrap fetch interceptor in top-level try/catch to prevent breaking page requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If anything in Silent Send's substitution logic throws (bad mapping, regex error, unexpected body format, etc.), the exception was propagating up to the caller instead of falling through to the original fetch. This broke Claude Code's web interface (claude.ai/code) when a bad mapping caused a substitution error mid-request. The inner try/catch on JSON.parse only covered the JSON path — the outer logic (hasSubstitutions, processBody, notifySubstitutions, etc.) had no protection. Added a top-level try/catch that catches any unhandled error and falls through to originalFetch, logging a warning so the error is still visible in the console. https://claude.ai/code/session_01CwcZK8nqL8pyBH9AxDs9qo --- src/content/content.js | 125 ++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 59 deletions(-) diff --git a/src/content/content.js b/src/content/content.js index c71ec63..0702685 100644 --- a/src/content/content.js +++ b/src/content/content.js @@ -825,67 +825,74 @@ } window.__ssInterceptFetch = async function (url, options) { - if (!settings.enabled || !hasSubstitutions()) { + // Top-level guard: any unhandled error must never break the original request. + try { + if (!settings.enabled || !hasSubstitutions()) { + return originalFetch.call(this, url, options); + } + + const urlStr = typeof url === 'string' ? url : url?.url || ''; + const method = (options?.method || 'GET').toUpperCase(); + + // Only intercept POST/PUT/PATCH + if ( + (method === 'POST' || method === 'PUT' || method === 'PATCH') && + options?.body && !shouldSkipUrl(urlStr) + ) { + // FormData body — scan file uploads via DocumentScanner + if (options.body instanceof FormData && typeof globalThis.DocumentScanner !== 'undefined') { + try { + const scannedForm = await scanFormData(options.body); + if (scannedForm) { + options = { ...options, body: scannedForm.formData }; + if (scannedForm.replacements.length > 0) { + notifySubstitutions(scannedForm.replacements); + console.log( + `[Silent Send] Substituted ${scannedForm.replacements.length} value(s) in file upload to ${urlStr}` + ); + } + } + } catch (e) { + console.warn('[Silent Send] FormData scan failed:', e); + } + } + + // String body — JSON or raw text + if (typeof options.body === 'string') { + try { + // Try JSON + const body = JSON.parse(options.body); + const { modified, replacements } = processBody(body); + + if (modified) { + options = { ...options, body: JSON.stringify(body) }; + notifySubstitutions(replacements); + console.log( + `[Silent Send] Substituted ${replacements.length} value(s) in ${urlStr}` + ); + } + } catch (e) { + // Not JSON — try raw string substitution (form data, etc.) + if (options.body.length > MIN_STRING_LENGTH) { + const result = substituteAll(options.body); + if (result.modified) { + options = { ...options, body: result.text }; + notifySubstitutions(result.replacements); + console.log( + `[Silent Send] Substituted ${result.replacements.length} value(s) in form body` + ); + } + } + } + } + } + + return originalFetch.call(this, url, options); + } catch (e) { + // Something went wrong in Silent Send — never block the original request + console.warn('[Silent Send] Fetch interceptor error, passing through:', e); return originalFetch.call(this, url, options); } - - const urlStr = typeof url === 'string' ? url : url?.url || ''; - const method = (options?.method || 'GET').toUpperCase(); - - // Only intercept POST/PUT/PATCH - if ( - (method === 'POST' || method === 'PUT' || method === 'PATCH') && - options?.body && !shouldSkipUrl(urlStr) - ) { - // FormData body — scan file uploads via DocumentScanner - if (options.body instanceof FormData && typeof globalThis.DocumentScanner !== 'undefined') { - try { - const scannedForm = await scanFormData(options.body); - if (scannedForm) { - options = { ...options, body: scannedForm.formData }; - if (scannedForm.replacements.length > 0) { - notifySubstitutions(scannedForm.replacements); - console.log( - `[Silent Send] Substituted ${scannedForm.replacements.length} value(s) in file upload to ${urlStr}` - ); - } - } - } catch (e) { - console.warn('[Silent Send] FormData scan failed:', e); - } - } - - // String body — JSON or raw text - if (typeof options.body === 'string') { - try { - // Try JSON - const body = JSON.parse(options.body); - const { modified, replacements } = processBody(body); - - if (modified) { - options = { ...options, body: JSON.stringify(body) }; - notifySubstitutions(replacements); - console.log( - `[Silent Send] Substituted ${replacements.length} value(s) in ${urlStr}` - ); - } - } catch (e) { - // Not JSON — try raw string substitution (form data, etc.) - if (options.body.length > MIN_STRING_LENGTH) { - const result = substituteAll(options.body); - if (result.modified) { - options = { ...options, body: result.text }; - notifySubstitutions(result.replacements); - console.log( - `[Silent Send] Substituted ${result.replacements.length} value(s) in form body` - ); - } - } - } - } - } - - return originalFetch.call(this, url, options); }; // Activate the fetch hook. If early-hook.js ran first (world: MAIN, document_start),