Files
return-youtube-dislike/Extensions/UserScript/live/live-cdp-endpoint.js
T
Anarios e500088e65 Prepare 4.0.5 Firefox update and Shorts fixes
Add Firefox data consent, repair contributor sign-in, and introduce optional Hide clutter buttons. Harden video navigation, voting, and action layouts with shared browser regression coverage and reproducible release packaging.
2026-09-06 12:51:12 +02:00

234 lines
8.2 KiB
JavaScript

const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const ACTIVE_PORT_FILE_NAME = "DevToolsActivePort";
const BROWSER_ALIASES = new Set(["brave", "chrome", "chromium", "edge"]);
function nonEmptyEnvironmentValue(environment, name) {
const value = environment[name]?.trim();
return value || null;
}
function platformPath(platform, pathImpl) {
if (pathImpl) return pathImpl;
return platform === "win32" ? path.win32 : path.posix;
}
function browserUserDataDirectories(browser, { environment, homedir, pathImpl, platform }) {
const join = pathImpl.join.bind(pathImpl);
const home = nonEmptyEnvironmentValue(environment, "HOME") || homedir();
if (platform === "win32") {
const localAppData = nonEmptyEnvironmentValue(environment, "LOCALAPPDATA");
if (!localAppData) return [];
const directories = {
brave: [join(localAppData, "BraveSoftware", "Brave-Browser", "User Data")],
chrome: [
join(localAppData, "Google", "Chrome", "User Data"),
join(localAppData, "Google", "Chrome Beta", "User Data"),
join(localAppData, "Google", "Chrome Dev", "User Data"),
join(localAppData, "Google", "Chrome SxS", "User Data"),
],
chromium: [join(localAppData, "Chromium", "User Data")],
edge: [join(localAppData, "Microsoft", "Edge", "User Data")],
};
return directories[browser] || [];
}
if (platform === "darwin") {
const applicationSupport = join(home, "Library", "Application Support");
const directories = {
brave: [join(applicationSupport, "BraveSoftware", "Brave-Browser")],
chrome: [
join(applicationSupport, "Google", "Chrome"),
join(applicationSupport, "Google", "Chrome Beta"),
join(applicationSupport, "Google", "Chrome Dev"),
join(applicationSupport, "Google", "Chrome Canary"),
],
chromium: [join(applicationSupport, "Chromium")],
edge: [join(applicationSupport, "Microsoft Edge")],
};
return directories[browser] || [];
}
const configRoot = nonEmptyEnvironmentValue(environment, "XDG_CONFIG_HOME") || join(home, ".config");
const directories = {
brave: [join(configRoot, "BraveSoftware", "Brave-Browser")],
chrome: [
join(configRoot, "google-chrome"),
join(configRoot, "google-chrome-beta"),
join(configRoot, "google-chrome-unstable"),
],
chromium: [join(configRoot, "chromium")],
edge: [join(configRoot, "microsoft-edge")],
};
return directories[browser] || [];
}
function knownActivePortFiles(browser, settings) {
const browsers = browser ? [browser] : ["chrome", "brave", "edge", "chromium"];
return browsers.flatMap((name) =>
browserUserDataDirectories(name, settings).map((directory) =>
settings.pathImpl.join(directory, ACTIVE_PORT_FILE_NAME),
),
);
}
function parseBareHostAndPort(value) {
let url;
try {
url = new URL(`http://${value}`);
} catch {
return null;
}
if (!url.hostname || !url.port || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
return null;
}
const port = Number(url.port);
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) return null;
return { authority: url.host, port };
}
function parseActivePortFile(contents, filePath) {
const [rawPort, rawBrowserPath] = String(contents).split(/\r?\n/);
const port = Number(rawPort?.trim());
if (!/^\d+$/.test(rawPort?.trim() || "") || !Number.isSafeInteger(port) || port < 1 || port > 65535) {
throw new Error(`The CDP active-port file at ${filePath} has an invalid port.`);
}
let browserPath = rawBrowserPath?.trim();
if (/^wss?:\/\//i.test(browserPath || "")) {
let url;
try {
url = new URL(browserPath);
} catch {
throw new Error(`The CDP active-port file at ${filePath} has an invalid browser WebSocket URL.`);
}
browserPath = `${url.pathname}${url.search}`;
}
if (!/^\/devtools\/browser\/[A-Za-z0-9._-]+$/.test(browserPath || "")) {
throw new Error(`The CDP active-port file at ${filePath} has an invalid browser WebSocket path.`);
}
return { browserPath, port };
}
function unique(values, platform) {
const keys = new Set();
return values.filter((value) => {
const key = platform === "win32" ? value.toLowerCase() : value;
if (keys.has(key)) return false;
keys.add(key);
return true;
});
}
function activePortFileCandidates(browser, settings) {
const activePortFile = nonEmptyEnvironmentValue(settings.environment, "RYD_CDP_ACTIVE_PORT_FILE");
const userDataDirectory = nonEmptyEnvironmentValue(settings.environment, "RYD_CDP_USER_DATA_DIR");
if (activePortFile && userDataDirectory) {
throw new Error("Set only one of RYD_CDP_ACTIVE_PORT_FILE and RYD_CDP_USER_DATA_DIR.");
}
if (activePortFile) return { explicit: true, paths: [settings.pathImpl.resolve(activePortFile)] };
if (userDataDirectory) {
return {
explicit: true,
paths: [settings.pathImpl.join(settings.pathImpl.resolve(userDataDirectory), ACTIVE_PORT_FILE_NAME)],
};
}
return { explicit: false, paths: unique(knownActivePortFiles(browser, settings), settings.platform) };
}
function readCandidate(filePath, settings, explicit) {
try {
return parseActivePortFile(settings.readFileSync(filePath, "utf8"), filePath);
} catch (error) {
if (!explicit && ["ENOENT", "ENOTDIR"].includes(error.code)) return null;
throw error;
}
}
function protocolEndpoint(value) {
if (!/^(?:https?|wss?):\/\//i.test(value)) return null;
let url;
try {
url = new URL(value);
} catch {
throw new Error("RYD_CDP_ENDPOINT must be a valid HTTP(S), WebSocket, host:port, or browser-alias endpoint.");
}
if (!["http:", "https:", "ws:", "wss:"].includes(url.protocol) || !url.hostname) {
throw new Error("RYD_CDP_ENDPOINT must be a valid HTTP(S), WebSocket, host:port, or browser-alias endpoint.");
}
return value;
}
function resolveCdpEndpoint(
configuredEndpoint,
{
environment = process.env,
homedir = os.homedir,
pathImpl,
platform = process.platform,
readFileSync = fs.readFileSync,
} = {},
) {
const value = configuredEndpoint?.trim() || "chrome";
const explicitProtocolEndpoint = protocolEndpoint(value);
if (explicitProtocolEndpoint) return explicitProtocolEndpoint;
const browser = BROWSER_ALIASES.has(value.toLowerCase()) ? value.toLowerCase() : null;
const bareEndpoint = browser ? null : parseBareHostAndPort(value);
if (!browser && !bareEndpoint) {
throw new Error(
"RYD_CDP_ENDPOINT must be an HTTP(S) URL, WebSocket URL, host:port shown by Chromium, or one of chrome, brave, edge, and chromium.",
);
}
const settings = {
environment,
homedir,
pathImpl: platformPath(platform, pathImpl),
platform,
readFileSync,
};
const candidates = activePortFileCandidates(browser, settings);
const matches = [];
for (const filePath of candidates.paths) {
const activePort = readCandidate(filePath, settings, candidates.explicit);
if (!activePort || (bareEndpoint && activePort.port !== bareEndpoint.port)) continue;
const authority = bareEndpoint?.authority || `127.0.0.1:${activePort.port}`;
matches.push({ endpoint: `ws://${authority}${activePort.browserPath}`, filePath });
}
const endpoints = [...new Set(matches.map(({ endpoint }) => endpoint))];
if (endpoints.length === 1) return endpoints[0];
if (endpoints.length > 1) {
throw new Error(
`More than one DevToolsActivePort file matches RYD_CDP_ENDPOINT=${value}. Set RYD_CDP_ACTIVE_PORT_FILE or RYD_CDP_USER_DATA_DIR to select the intended browser profile.`,
);
}
const fileDescription = candidates.explicit
? candidates.paths[0]
: candidates.paths.length
? candidates.paths.join(", ")
: "the platform's standard browser user-data directories";
const portDescription = bareEndpoint ? ` matching port ${bareEndpoint.port}` : "";
throw new Error(
`Could not find a valid DevToolsActivePort file${portDescription} in ${fileDescription}. ` +
"Set RYD_CDP_ACTIVE_PORT_FILE to that file, RYD_CDP_USER_DATA_DIR to its directory, or use an explicit ws:// or legacy http:// endpoint.",
);
}
module.exports = {
ACTIVE_PORT_FILE_NAME,
browserUserDataDirectories,
parseActivePortFile,
parseBareHostAndPort,
resolveCdpEndpoint,
};