MiniCPM5-2B-WebGPU-Pi-HTTP / app /src /browser-http.mjs
Mike0021's picture
Allow curl requests to any HTTPS host; remove experimental domain allowlist
9c380c2 verified
Raw
History Blame Contribute Delete
3.54 kB
// just-bash's curl uses this browser transport. There is no proxy/server.
export const HTTP_METHODS = Object.freeze(['GET', 'HEAD', 'POST']);
export const HTTP_LIMIT = 1024 * 1024;
export const HTTP_TIMEOUT = 8_000;
export function createBrowserFetch({ transport = globalThis.fetch,
timeoutMs = HTTP_TIMEOUT, maxBytes = HTTP_LIMIT } = {}) {
return async (input, options = {}) => {
const url = new URL(input);
if (url.protocol !== 'https:') throw Error('Only HTTPS URLs are supported. Use https:// instead.');
if (url.username || url.password) throw Error('Credentials in URLs are unsupported. Use an explicit Authorization header if needed.');
const method = (options.method ?? 'GET').toUpperCase();
if (!HTTP_METHODS.includes(method)) throw Error(`HTTP method ${method} is not enabled. Use GET, HEAD, or POST.`);
const requestedTimeout = options.timeoutMs > 0 ? options.timeoutMs : timeoutMs;
const controller = new AbortController();
const deadline = Math.min(requestedTimeout, timeoutMs);
const timer = setTimeout(() => controller.abort(new DOMException(`HTTP request timed out after ${deadline} ms.`, 'TimeoutError')), deadline);
const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
let reader;
try {
signal.throwIfAborted();
const response = await transport(url.href, { method, headers: options.headers,
body: ['GET', 'HEAD'].includes(method) ? undefined : options.body,
mode: 'cors', credentials: 'omit', redirect: 'manual', referrerPolicy: 'no-referrer', signal });
// Browsers hide both the Location header and status on manual redirects.
// Following automatically would skip the HTTPS check at intermediate hops.
if (response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400 && response.status !== 304)) {
throw Error('HTTP redirect cannot be followed safely in this browser shell. Use the final HTTPS URL directly (including exact capitalization).');
}
if (response.status === 0 || response.type === 'opaque') throw Error('HTTP response is unreadable. The destination must allow browser access with CORS.');
if (method !== 'HEAD' && Number(response.headers.get('content-length')) > maxBytes) {
await response.body?.cancel();
throw Error(`HTTP response exceeds the ${maxBytes}-byte limit.`);
}
const chunks = []; let size = 0;
if (response.body) {
reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
signal.throwIfAborted();
if (done) break;
size += value.byteLength;
if (size > maxBytes) { await reader.cancel(); throw Error(`HTTP response exceeds the ${maxBytes}-byte limit.`); }
chunks.push(value);
}
}
const body = new Uint8Array(size); let offset = 0;
for (const chunk of chunks) { body.set(chunk, offset); offset += chunk.byteLength; }
return { status: response.status, statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()), body, url: response.url || url.href };
} catch (error) {
if (signal.aborted) throw signal.reason;
if (error instanceof TypeError) throw Error('HTTP request failed: the server may block browser requests (CORS), or the connection failed. No proxy is used.');
throw error;
} finally { clearTimeout(timer); reader?.releaseLock(); }
};
}