Ctrl K
ComunidadGuías…

src/browser.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { spawn } from "node:child_process";
import { basename, dirname, win32 } from "node:path";
import { fileURLToPath } from "node:url";

const HANDOFF_DIRECTORY_PATTERN = /^relmio-browser-[A-Za-z0-9_-]{6,64}$/u;
const HANDOFF_FILE_PATTERN = /^launch-[a-f0-9]{24}\.html$/u;

function windowsFilePath(url) {
  const parsed = new URL(url);
  let pathname;
  try {
    pathname = decodeURIComponent(parsed.pathname);
  } catch {
    throw new TypeError("Relmio browser handoff URL is invalid.");
  }
  if (/^\/[A-Za-z]:\//u.test(pathname)) pathname = pathname.slice(1);
  return pathname.replaceAll("/", "\\");
}

function windowsSystemRoot(systemRoot) {
  if (
    typeof systemRoot !== "string" || !win32.isAbsolute(systemRoot) ||
    /[\u0000-\u001F\u007F"<>|*?]/u.test(systemRoot)
  ) {
    throw new TypeError("Relmio Windows system root is invalid.");
  }
  return win32.normalize(systemRoot);
}

function windowsExplorer(systemRoot) {
  return win32.join(windowsSystemRoot(systemRoot), "explorer.exe");
}

// explorer.exe opens the handoff file. It does not open an https URL with ? and &.
// cmd.exe expands %NAME% in its command text, so %3A%2F in a URL would pick up a variable named 3A.
// The targets travel in variables read by delayed expansion (!NAME!), which runs after cmd has
// parsed the line; their %, & and ! are never interpreted.
export function windowsUrlLaunch(targets) {
  if (!Array.isArray(targets) || targets.length < 1 || targets.length > 4) {
    throw new TypeError("Relmio Windows browser target is invalid.");
  }
  const env = {};
  const quoted = targets.map((target, index) => {
    if (
      typeof target !== "string" || target.length === 0 || target.length > 4096 ||
      /[\u0000-\u001F\u007F"]/u.test(target)
    ) {
      throw new TypeError("Relmio Windows browser target is invalid.");
    }
    env[`RELMIO_BROWSER_TARGET_${index}`] = target;
    return `"!RELMIO_BROWSER_TARGET_${index}!"`;
  });
  return { args: ["/d", "/v:on", "/s", "/c", `start "" ${quoted.join(" ")}`], env };
}

export function isPrivateBrowserLaunchUrl(value) {
  if (typeof value !== "string" || /[\u0000-\u001F\u007F]/u.test(value)) return false;
  try {
    const parsed = new URL(value);
    if (
      parsed.protocol !== "file:" || parsed.hostname !== "" || parsed.username ||
      parsed.password || parsed.search || parsed.hash
    ) return false;
    const path = fileURLToPath(parsed);
    return HANDOFF_FILE_PATTERN.test(basename(path)) &&
      HANDOFF_DIRECTORY_PATTERN.test(basename(dirname(path)));
  } catch {
    return false;
  }
}

export function isOpenAiAuthorizationUrl(value) {
  if (typeof value !== "string" || value.length > 4096 || /[\u0000-\u0020\u007F]/u.test(value)) return false;
  try {
    const url = new URL(value);
    if (url.origin !== "https://auth.openai.com" ||
        url.pathname !== "/api/accounts/authorize" || url.hash ||
        url.username || url.password || url.href !== value) return false;
    const allowed = new Set(["client_id", "agent_name_hint", "ext_agent_host_id",
      "response_type", "redirect_uri", "scope", "resource", "state", "nonce",
      "code_challenge_method", "code_challenge", "prompt"]);
    const keys = [...url.searchParams.keys()];
    const required = ["client_id", "ext_agent_host_id", "response_type", "redirect_uri",
      "scope", "resource", "state", "nonce", "code_challenge_method", "code_challenge"];
    const clientId = url.searchParams.get("client_id");
    if (keys.some(key => !allowed.has(key)) || new Set(keys).size !== keys.length ||
        required.some(key => !url.searchParams.has(key)) ||
        !/^[!-~]{1,256}$/u.test(clientId ?? "") ||
        !/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(url.searchParams.get("ext_agent_host_id") ?? "") ||
        (clientId === "dynamic_agent_client" ? url.searchParams.get("agent_name_hint") !== "Relmio" : url.searchParams.has("agent_name_hint")) ||
        (url.searchParams.has("prompt") && url.searchParams.get("prompt") !== "consent") ||
        url.searchParams.get("response_type") !== "code" ||
        url.searchParams.get("code_challenge_method") !== "S256" ||
        url.searchParams.get("resource") !== "https://api.openai.com/v1" ||
        url.searchParams.get("scope") !== "openid profile email offline_access resource.invoke chatgpt.tokens.use.direct" ||
        !/^[A-Za-z0-9_-]{32,128}$/u.test(url.searchParams.get("state") ?? "") ||
        !/^[A-Za-z0-9_-]{32,128}$/u.test(url.searchParams.get("nonce") ?? "") ||
        !/^[A-Za-z0-9_-]{43}$/u.test(url.searchParams.get("code_challenge") ?? "")) return false;
    const redirectUri = url.searchParams.get("redirect_uri");
    const redirect = new URL(redirectUri);
    return redirect.protocol === "http:" && redirect.hostname === "127.0.0.1" &&
      Number(redirect.port) >= 1 && Number(redirect.port) <= 65535 &&
      redirect.pathname === "/auth/callback" && redirect.href === redirectUri &&
      !redirect.search && !redirect.hash;
  } catch {
    return false;
  }
}

export async function openOpenAiAuthorization(url, options = {}) {
  if (!isOpenAiAuthorizationUrl(url)) return false;
  return launchCommand(url, options);
}

export function browserCommand(
  launchUrl,
  platform = process.platform,
  { systemRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT ?? process.env.WINDIR } = {},
) {
  if (!isPrivateBrowserLaunchUrl(launchUrl)) {
    throw new TypeError("Relmio browser handoff URL is invalid.");
  }
  if (platform === "darwin") return { file: "open", args: [launchUrl] };
  if (platform === "win32") {
    return {
      file: windowsExplorer(systemRoot),
      args: [windowsFilePath(launchUrl)],
    };
  }
  return { file: "xdg-open", args: [launchUrl] };
}

export async function openBrowser(launchUrl, options = {}) {
  if (!isPrivateBrowserLaunchUrl(launchUrl)) return false;
  return launchCommand(launchUrl, options);
}

async function launchCommand(launchUrl, {
  platform = process.platform,
  spawnProcess = spawn,
  launchTimeoutMs = 5_000,
  systemRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT ?? process.env.WINDIR,
} = {}) {
  if (!Number.isSafeInteger(launchTimeoutMs) || launchTimeoutMs < 1 || launchTimeoutMs > 30_000) return false;
  let command;
  try {
    command = isPrivateBrowserLaunchUrl(launchUrl)
      ? browserCommand(launchUrl, platform, { systemRoot })
      : isOpenAiAuthorizationUrl(launchUrl)
        ? platform === "darwin" ? { file: "open", args: [launchUrl] }
          : platform === "win32" ? {
              file: win32.join(windowsSystemRoot(systemRoot), "System32", "cmd.exe"),
              ...windowsUrlLaunch([launchUrl]),
            }
            : { file: "xdg-open", args: [launchUrl] }
        : null;
  } catch {
    return false;
  }
  if (!command) return false;
  let child;
  const spawnOptions = {
    detached: true,
    stdio: "ignore",
    shell: false,
  };
  if (command.env) {
    spawnOptions.env = { ...process.env, ...command.env };
    spawnOptions.windowsVerbatimArguments = true;
    spawnOptions.windowsHide = true;
  }
  try {
    child = spawnProcess(command.file, command.args, spawnOptions);
  } catch {
    return false;
  }
  if (
    !child || typeof child.once !== "function" ||
    typeof child.removeListener !== "function" || typeof child.unref !== "function"
  ) return false;
  child.unref();

  return await new Promise((resolveLaunch) => {
    let settled = false;
    let spawned = false;
    let timer;
    const settle = (result) => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      child.removeListener("spawn", onSpawn);
      child.removeListener("error", onError);
      child.removeListener("exit", onExit);
      resolveLaunch(result);
    };
    const explorerDispatch = platform === "win32" && /(?:^|\\)explorer\.exe$/iu.test(command.file);
    const onSpawn = () => {
      spawned = true;
      // Explorer dispatches the handoff file; its exit code does not report loading.
      if (explorerDispatch) settle(true);
    };
    const onError = () => settle(false);
    const onExit = (code, signal) => settle(signal === null && code === 0);
    child.once("spawn", onSpawn);
    child.once("error", onError);
    child.once("exit", onExit);
    timer = setTimeout(
      () => settle(platform === "win32" && !explorerDispatch ? false : spawned),
      launchTimeoutMs,
    );
  });
}

export function attachBrowserReopenOnEnter({
  input = process.stdin,
  prepareLaunch,
  open = openBrowser,
  write = console.log,
}) {
  if (!input.isTTY) return () => {};
  if (typeof prepareLaunch !== "function" || typeof open !== "function") {
    throw new TypeError("Relmio browser reopen adapter is invalid.");
  }

  const onData = (data) => {
    if (/\r|\n/u.test(String(data))) {
      void Promise.resolve()
        .then(() => prepareLaunch())
        .then((url) => open(url))
        .catch(() => {});
    }
  };

  input.setEncoding?.("utf8");
  input.resume?.();
  input.on("data", onData);
  write("If the wizard did not open automatically, press Enter to open it again.");

  return () => {
    input.off("data", onData);
    input.pause?.();
  };
}