Changeset 0.9.1 (#51)

This commit is contained in:
2026-02-23 13:42:23 +00:00
parent 8264aa6016
commit febcbde3d7
21 changed files with 1881 additions and 423 deletions

View File

@@ -54,6 +54,25 @@ const API = {
async health() {
const resp = await fetch(BASE + '/api/v1/health', { signal: AbortSignal.timeout(8000) });
if (!resp.ok) throw new Error(`Health: ${resp.status}`);
return this._parseJSON(resp, '/api/v1/health');
},
// Detect proxy interception: HTTP 200 but HTML instead of JSON
async _parseJSON(resp, context) {
const ct = (resp.headers.get('content-type') || '').toLowerCase();
if (ct.includes('text/html')) {
// Proxy returned an HTML page (block page, auth wall, etc.)
let title = 'unknown';
try {
const html = await resp.clone().text();
const m = html.match(/<title[^>]*>([^<]+)<\/title>/i);
if (m) title = m[1].trim();
} catch (e) { /* best effort */ }
const err = new Error(`Proxy interception detected on ${context}: "${title}"`);
err.proxyBlocked = true;
err.proxyTitle = title;
throw err;
}
return resp.json();
},
@@ -157,9 +176,21 @@ const API = {
}
if (!resp.ok) {
const ct = (resp.headers.get('content-type') || '').toLowerCase();
if (ct.includes('text/html')) {
const err = new Error('Proxy blocked the regeneration request');
err.proxyBlocked = true;
throw err;
}
const err = await resp.json().catch(() => ({}));
throw new Error(err.error || `HTTP ${resp.status}`);
}
const ct = (resp.headers.get('content-type') || '').toLowerCase();
if (ct.includes('text/html')) {
const err = new Error('Proxy intercepted the regeneration stream');
err.proxyBlocked = true;
throw err;
}
return resp;
},
@@ -205,9 +236,22 @@ const API = {
}
if (!resp.ok) {
const ct = (resp.headers.get('content-type') || '').toLowerCase();
if (ct.includes('text/html')) {
const err = new Error('Proxy blocked the completion request');
err.proxyBlocked = true;
throw err;
}
const err = await resp.json().catch(() => ({}));
throw new Error(err.error || `HTTP ${resp.status}`);
}
// Also check 200 OK but HTML (proxy returning block page with 200)
const ct = (resp.headers.get('content-type') || '').toLowerCase();
if (ct.includes('text/html')) {
const err = new Error('Proxy intercepted the completion stream');
err.proxyBlocked = true;
throw err;
}
return resp;
},
@@ -381,6 +425,13 @@ const API = {
// ── HTTP Internals ───────────────────────
_esc(s) {
if (!s) return '';
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
},
_authHeaders() {
return {
'Content-Type': 'application/json',
@@ -413,11 +464,26 @@ const API = {
const resp = await fetch(BASE + path, opts);
if (!resp.ok) {
// Check if the error response is also a proxy page
const ct = (resp.headers.get('content-type') || '').toLowerCase();
if (ct.includes('text/html')) {
let title = 'unknown';
try {
const html = await resp.text();
const m = html.match(/<title[^>]*>([^<]+)<\/title>/i);
if (m) title = m[1].trim();
} catch (e) { /* */ }
const err = new Error(`Proxy blocked ${method} ${path}: "${title}"`);
err.status = resp.status;
err.proxyBlocked = true;
err.proxyTitle = title;
throw err;
}
const data = await resp.json().catch(() => ({}));
const err = new Error(data.error || `HTTP ${resp.status}`);
err.status = resp.status;
throw err;
}
return resp.json();
return this._parseJSON(resp, path);
}
};