mirror of
https://github.com/Lissy93/web-check.git
synced 2026-05-13 06:01:02 -04:00
116 lines
3.3 KiB
JavaScript
116 lines
3.3 KiB
JavaScript
// Thin fetch wrapper matching the axios shape used across the api: opts.params,
|
|
// opts.headers, opts.auth, opts.timeout, opts.validateStatus; returns
|
|
// { data, status, statusText, headers }; throws errors with response/code
|
|
|
|
const DEFAULT_TIMEOUT = 60000;
|
|
|
|
const buildAuth = (auth) => {
|
|
if (!auth?.username) return null;
|
|
return 'Basic ' + Buffer.from(`${auth.username}:${auth.password}`).toString('base64');
|
|
};
|
|
|
|
const appendParams = (url, params) => {
|
|
if (!params) return url;
|
|
const u = new URL(url);
|
|
for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);
|
|
return u.href;
|
|
};
|
|
|
|
const headersToObject = (headers) => {
|
|
const out = {};
|
|
for (const [k, v] of headers.entries()) {
|
|
const key = k.toLowerCase();
|
|
if (key === 'set-cookie') {
|
|
out[key] = headers.getSetCookie ? headers.getSetCookie() : v.split(/, (?=[^;]+=)/);
|
|
} else {
|
|
out[key] = v;
|
|
}
|
|
}
|
|
return out;
|
|
};
|
|
|
|
// Auto-parse JSON when the response advertises it, fall back to raw text
|
|
const parseBody = async (response) => {
|
|
const ct = (response.headers.get('content-type') || '').toLowerCase();
|
|
const text = await response.text();
|
|
if (!text) return ct.includes('json') ? null : '';
|
|
if (ct.includes('json')) {
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch {
|
|
return text;
|
|
}
|
|
}
|
|
return text;
|
|
};
|
|
|
|
const isOk = (status, validate) => (validate ? validate(status) : status >= 200 && status < 300);
|
|
|
|
const wrapNetworkError = (error) => {
|
|
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
|
const e = new Error(error.message || 'Request timed out');
|
|
e.code = 'ECONNABORTED';
|
|
return e;
|
|
}
|
|
const code = error.cause?.code;
|
|
if (code) {
|
|
const e = new Error(error.message);
|
|
e.code = code;
|
|
return e;
|
|
}
|
|
return error;
|
|
};
|
|
|
|
export const UA =
|
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ' +
|
|
'Chrome/120.0.0.0 Safari/537.36 (compatible; web-check/1.0; +https://web-check.xyz)';
|
|
|
|
const send = async (method, url, body, opts = {}) => {
|
|
const finalUrl = appendParams(url, opts.params);
|
|
const headers = { 'user-agent': UA, ...opts.headers };
|
|
const authHeader = buildAuth(opts.auth);
|
|
if (authHeader) headers.authorization = authHeader;
|
|
|
|
const init = {
|
|
method,
|
|
headers,
|
|
signal: AbortSignal.timeout(opts.timeout || DEFAULT_TIMEOUT),
|
|
};
|
|
|
|
if (body !== undefined && body !== null) {
|
|
if (typeof body === 'object') {
|
|
init.body = JSON.stringify(body);
|
|
const hasCt = Object.keys(headers).some((k) => k.toLowerCase() === 'content-type');
|
|
if (!hasCt) init.headers['content-type'] = 'application/json';
|
|
} else {
|
|
init.body = body;
|
|
}
|
|
}
|
|
|
|
let response;
|
|
try {
|
|
response = await fetch(finalUrl, init);
|
|
} catch (error) {
|
|
throw wrapNetworkError(error);
|
|
}
|
|
|
|
const data = await parseBody(response);
|
|
const result = {
|
|
data,
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
headers: headersToObject(response.headers),
|
|
};
|
|
|
|
if (!isOk(response.status, opts.validateStatus)) {
|
|
const err = new Error(`Request failed with status code ${response.status}`);
|
|
err.response = result;
|
|
throw err;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
export const httpGet = (url, opts) => send('GET', url, null, opts);
|
|
export const httpPost = (url, body, opts) => send('POST', url, body, opts);
|