smodusermc/offline-games / site /js /libcurlbareclient.mjs
smodusermc's picture
download
raw
6.7 kB
import { libcurl } from "/libcurljs/libcurl_full.mjs";
var libcurlClient = Object.freeze({
wasm: "/libcurljs/libcurl.wasm",
defaultConnections: [30, 24, 3],
transientCurlError: /error code (?:18|35|52|55|56|92)\b|Failure when receiving data|Server returned nothing|SSL connect error|Stream error|Recv failure/i,
blockedRequestHeaders: new Set([
"connection",
"content-length",
"host",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"proxy-connection",
"te",
"trailer",
"transfer-encoding",
"upgrade",
]),
defaultRequestHeaders: [
["accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"],
["accept-language", navigator.languages?.length ? navigator.languages.join(",") : "en-US,en;q=0.9"],
],
});
export default class LibcurlBareClient {
ready = false;
session = null;
constructor(options) {
this.wisp = options.wisp ?? options.websocket;
this.transport = options.transport;
this.proxy = options.proxy;
this.connections = options.connections || libcurlClient.defaultConnections;
if (!this.wisp.endsWith("/")) {
throw new TypeError("The Websocket URL must end with a trailing forward slash.");
}
if (!this.wisp.startsWith("ws://") && !this.wisp.startsWith("wss://")) {
throw new TypeError("The Websocket URL must use the ws:// or wss:// protocols.");
}
if (typeof options.proxy === "string" || options.proxy instanceof String) {
const protocol = new URL(options.proxy).protocol;
if (!["socks5h:", "socks5:", "socks4a:", "http:"].includes(protocol)) {
throw new TypeError("Only socks5h, socks5, socks4a, and http proxies are supported.");
}
}
}
async init() {
if (this.ready && this.session) {
return;
}
if (this.transport) {
libcurl.transport = this.transport;
}
await libcurl.load_wasm(libcurlClient.wasm);
libcurl.set_websocket(this.wisp);
this.session = this.createSession();
this.ready = true;
}
createSession() {
const session = new libcurl.HTTPSession({
proxy: this.proxy,
});
if (this.connections) {
session.set_connections(...this.connections);
}
return session;
}
resetSession() {
try {
this.session?.close?.();
} catch {
}
this.session = this.createSession();
}
async meta() {}
normalizeHeaders(headers, remote, method) {
const pairs = Array.isArray(headers)
? headers
: headers instanceof Headers
? [...headers.entries()]
: Object.entries(headers || {});
const cleanPairs = [];
const seenHeaders = new Set();
for (const entry of pairs) {
if (!Array.isArray(entry) || entry.length < 2) continue;
const name = String(entry[0] || "").trim().toLowerCase();
if (!name || libcurlClient.blockedRequestHeaders.has(name)) continue;
if (name.startsWith(":") || name.startsWith("sec-") || name.startsWith("proxy-")) continue;
const value = Array.isArray(entry[1]) ? entry[1].join(", ") : entry[1];
const cleanValue = String(value ?? "").replace(/[^\x09\x20-\x7e\x80-\xff]/g, "");
if (!cleanValue) continue;
cleanPairs.push([name, cleanValue]);
seenHeaders.add(name);
}
for (const [name, value] of libcurlClient.defaultRequestHeaders) {
if (!seenHeaders.has(name)) cleanPairs.push([name, value]);
}
return cleanPairs;
}
getFallbackResponse(remote) {
return null;
}
shouldRetryRequest(error, method) {
const methodName = String(method || "GET").toUpperCase();
return ["GET", "HEAD", "OPTIONS"].includes(methodName) &&
libcurlClient.transientCurlError.test(String(error?.message || error || ""));
}
normalizeResponseHeaderPairs(headers) {
if (!headers) {
return [];
}
const parseHeaderLine = (line) => {
const header = String(line || "").trim();
if (!header || /^HTTP\/\d(?:\.\d)?\s+/i.test(header)) return null;
const colonIndex = header.indexOf(":");
const commaIndex = header.indexOf(",");
const separatorIndex = colonIndex === -1
? commaIndex
: commaIndex === -1
? colonIndex
: Math.min(colonIndex, commaIndex);
if (separatorIndex <= 0) return null;
const name = header.slice(0, separatorIndex).trim();
const value = header.slice(separatorIndex + 1).trim();
return name && value ? [name, value] : null;
};
if (Array.isArray(headers)) {
return headers
.map((entry) => {
if (Array.isArray(entry)) {
if (entry.length >= 2 && !String(entry[0]).includes(":") && !String(entry[0]).includes(",")) {
const name = String(entry[0]).trim();
const value = String(entry.slice(1).join(",")).trim();
return name && value ? [name, value] : null;
}
return parseHeaderLine(entry.join(","));
}
return parseHeaderLine(entry);
})
.filter((entry) => entry?.[0]);
}
return Object.entries(headers).map(([key, value]) => [key, String(value)]);
}
async request(remote, method, body, headers, signal) {
const fallbackResponse = this.getFallbackResponse(remote);
if (fallbackResponse) {
return fallbackResponse;
}
const requestOptions = {
method,
headers: this.normalizeHeaders(headers, remote, method),
body,
redirect: "manual",
signal,
};
let payload;
try {
payload = await this.session.fetch(remote.href, requestOptions);
} catch (error) {
if (!this.shouldRetryRequest(error, method)) throw error;
this.resetSession();
payload = await this.session.fetch(remote.href, requestOptions);
}
const responseHeaders = this.normalizeResponseHeaderPairs(payload.raw_headers ?? payload.headers);
return {
body: payload.body,
headers: responseHeaders,
status: payload.status,
statusText: payload.statusText,
};
}
connect(url, protocols, requestHeaders, onopen, onmessage, onclose, onerror) {
const socket = new libcurl.WebSocket(url.toString(), protocols, {
headers: requestHeaders,
});
socket.binaryType = "arraybuffer";
socket.onopen = () => {
onopen("");
};
socket.onclose = (event) => {
onclose(event.code, event.reason);
};
socket.onerror = () => {
onerror("");
};
socket.onmessage = (event) => {
onmessage(event.data);
};
return [
(data) => {
socket.send(data);
},
(code, reason) => {
socket.close(code, reason);
},
];
}
}

Xet Storage Details

Size:
6.7 kB
·
Xet hash:
7aa1a2049f0c8cda542ff31d39c867b6df6e9882b243f4722251115e3fa825d3

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.