fix(code.gs): wrap _doSingle normal-relay fetch in try/catch (#1047, #1049)

Fixes #1047. `_doSingle`'s normal-relay path (cache disabled or cache miss on
non-cachable request) ran `UrlFetchApp.fetch` → `getContent` → `base64Encode`
with no error wrapper. Any throw — most commonly when the response body
approaches Apps Script's ~50 MB ceiling and `base64Encode` blows the V8 heap,
also URL-too-long / payload-too-large / quota exhaustion / 6-minute execution
timeout — propagated unhandled, and Apps Script served its default
`<title>Web App</title>` HTML error page in place of the JSON envelope.

The Rust client (`parse_relay_json` in `domain_fronter.rs`) then failed to
find JSON and surfaced the cryptic `bad response: no json in: <!DOCTYPE html>...`
with no signal as to the actual cause.

The reporter's symptom — a single failing host (`shc-dist.lostsig.co`,
sonichacking.org) serving large ROM-hack binaries — matches this exactly.
Every other download worked because they were all under the body-size
ceiling.

## Fix

Wrap the normal-relay block in `_doSingle` with
`try { ... } catch (err) { return _json({ e: "fetch failed: " + String(err) }); }`.
Mirrors the per-item try/catch already present in `_doBatch`. Turns the
silent HTML crash into a structured `FronterError::Relay("fetch failed: …")`
on the client side that pinpoints the real underlying error.

Cache path intentionally untouched:
- `_fetchAndCache` already wraps its own fetch in try/catch and returns
  `null` on any failure (so `_doSingle` falls through cleanly to the
  normal relay).
- The cached-read path is bounded to ≤ `CACHE_MAX_BODY_BYTES` (35 KB)
  so it cannot trip the size limits that caused this bug.

## Verified locally on top of v1.9.22

- `node --check assets/apps_script/Code.gs`: clean 
- `cargo test --lib --release`: 209/209  (sanity — no Rust change)

Reviewed via Anthropic Claude.

Co-Authored-By: dazzling-no-more <noreply@github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
dazzling-no-more
2026-05-12 02:55:10 +04:00
committed by GitHub
parent 5cfb972deb
commit f4f23c3173
+21 -7
View File
@@ -189,13 +189,27 @@ function _doSingle(req) {
}
// ── Normal relay (cache disabled or unavailable) ────────
var opts = _buildOpts(req);
var resp = UrlFetchApp.fetch(req.u, opts);
return _json({
s: resp.getResponseCode(),
h: _respHeaders(resp),
b: Utilities.base64Encode(resp.getContent()),
});
// Wrap the fetch + body encode in try/catch so any failure surfaces as
// a JSON error envelope the Rust client can parse. Without this, throws
// from UrlFetchApp.fetch (URL too long, payload too large, quota
// exhausted, 6-minute execution timeout) or from base64Encode (response
// body near Apps Script's ~50 MB ceiling can blow the V8 heap during
// encode) propagate unhandled, and Apps Script serves its default
// `<title>Web App</title>` HTML error page — which the client then
// reports as "Relay failed: bad response: no json in: <title>Web App>..."
// and the user has no signal as to the actual cause. Mirrors the
// per-item try/catch in _doBatch below.
try {
var opts = _buildOpts(req);
var resp = UrlFetchApp.fetch(req.u, opts);
return _json({
s: resp.getResponseCode(),
h: _respHeaders(resp),
b: Utilities.base64Encode(resp.getContent()),
});
} catch (err) {
return _json({ e: "fetch failed: " + String(err) });
}
}
// ── Batch Request ──────────────────────────────────────────