camoufox-mcp-server
Version:
MCP server for browser automation using Camoufox - a privacy-focused Firefox fork with advanced anti-detection features
135 lines (134 loc) • 3.89 kB
JavaScript
import { MAX_DIAGNOSTIC_TEXT_CHARS, SUPPORTED_OSES } from "./config.js";
export function describeError(error) {
return error instanceof Error ? error.message : String(error);
}
export async function withTimeout(promise, ms, label) {
let timer;
const timeout = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(new Error(`${label} timed out.`));
}, ms);
});
try {
return await Promise.race([promise, timeout]);
}
finally {
if (timer) {
clearTimeout(timer);
}
}
}
export function redactUrl(raw) {
try {
const url = new URL(raw);
url.username = "";
url.password = "";
url.search = url.search ? "?..." : "";
url.hash = "";
return url.toString();
}
catch {
return "<invalid-url>";
}
}
export function getProxyServer(proxy) {
if (!proxy) {
return undefined;
}
return typeof proxy === "string" ? proxy : proxy.server;
}
export function getProxySecrets(proxy) {
if (!proxy || typeof proxy === "string") {
return [];
}
return [proxy.username, proxy.password].filter((secret) => Boolean(secret));
}
export function sanitizeErrorMessage(message, rawUrls, secrets = []) {
let sanitized = message;
for (const secret of secrets) {
sanitized = sanitized.replaceAll(secret, "<redacted>");
}
for (const rawUrl of rawUrls) {
sanitized = sanitized.replaceAll(rawUrl, redactUrl(rawUrl));
}
return sanitized.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, (matchedUrl) => {
let suffix = "";
let candidate = matchedUrl;
while (candidate.length > 0 && /[),.;\]]$/.test(candidate)) {
suffix = `${candidate[candidate.length - 1]}${suffix}`;
candidate = candidate.slice(0, -1);
}
return `${redactUrl(candidate)}${suffix}`;
});
}
export function truncateString(value, maxChars) {
return {
value: value.slice(0, maxChars),
truncated: value.length > maxChars,
};
}
export function sanitizeDiagnosticText(value, rawUrls, secrets) {
return truncateString(sanitizeErrorMessage(value, rawUrls, secrets), MAX_DIAGNOSTIC_TEXT_CHARS).value;
}
export function serializeBounded(value, maxChars, rawUrls, secrets) {
let serialized;
try {
const json = JSON.stringify(value);
serialized = json === undefined ? "undefined" : json;
}
catch {
serialized = String(value);
}
return truncateString(sanitizeErrorMessage(serialized, rawUrls, secrets), maxChars);
}
export function selectOperatingSystem(os) {
if (os) {
return os;
}
return SUPPORTED_OSES[Math.floor(Math.random() * SUPPORTED_OSES.length)];
}
export function defaultHeadlessMode(headless) {
if (headless !== undefined) {
return headless;
}
return process.platform === "linux" ? "virtual" : true;
}
export function applyStealthProfile(input) {
const profile = input.stealthProfile ?? "normal";
const defaults = {
humanize: true,
geoip: true,
block_webrtc: true,
block_images: false,
block_webgl: false,
disable_coop: false,
enable_cache: false,
includeConsole: false,
includeNetwork: false,
};
const profileDefaults = {
normal: {},
privacy: {
block_webgl: true,
},
human_assisted: {
headless: false,
enable_cache: true,
captchaPolicy: "pause",
},
fast: {
block_images: true,
humanize: false,
},
debug: {
includeConsole: true,
includeNetwork: true,
},
};
return {
...defaults,
...profileDefaults[profile],
...input,
stealthProfile: profile,
};
}