Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/the-system/src/pi-ext-browser-open.ts

Raw
// Generated by scripts/codegen.mjs. DO NOT EDIT.
// ============================================================================
// THIS FILE IS GENERATED. DO NOT EDIT IT DIRECTLY.
// Instead, edit scripts/codegen/templates/pi-ext-browser-open.ts.
// Then run: mise run //:codegen
// ============================================================================
import { spawn } from "node:child_process";

export type BrowserUrlOpenerOptions = {
	platform?: NodeJS.Platform;
	env?: NodeJS.ProcessEnv;
	timeoutMs?: number;
	candidates?: BrowserOpenerCandidate[];
};

export type BrowserOpenerCandidate = {
	command: string;
	args: string[];
	windowsHide?: boolean;
};

/** Open a URL through platform-native handlers. Returns selected command. */
export async function openUrl(
	url: string,
	options: BrowserUrlOpenerOptions,
): Promise<string | undefined> {
	for (const candidate of options.candidates ??
		openerCandidates(url, options)) {
		if (await trySpawn(candidate, options)) return candidate.command;
	}
	return undefined;
}

export function openerCandidates(
	url: string,
	options: Pick<BrowserUrlOpenerOptions, "platform" | "env"> = {},
): BrowserOpenerCandidate[] {
	const platform = options.platform ?? process.platform;
	const env = options.env ?? process.env;
	if (env.TERMUX_VERSION) return [{ command: "termux-open-url", args: [url] }];
	if (platform === "darwin") return [{ command: "open", args: [url] }];
	if (platform === "win32")
		return [
			{
				command: "rundll32.exe",
				args: ["url.dll,FileProtocolHandler", url],
				windowsHide: true,
			},
		];
	return [
		{ command: "xdg-open", args: [url] },
		{ command: "wslview", args: [url] },
		{ command: "gio", args: ["open", url] },
	];
}

function trySpawn(
	candidate: BrowserOpenerCandidate,
	options: BrowserUrlOpenerOptions,
): Promise<boolean> {
	return new Promise((resolve) => {
		const child = spawn(candidate.command, candidate.args, {
			detached: true,
			stdio: "ignore",
			windowsHide: candidate.windowsHide ?? true,
			env: options.env,
		});
		let settled = false;
		const done = (ok: boolean) => {
			if (settled) return;
			settled = true;
			resolve(ok);
		};
		child.once("error", () => done(false));
		child.once("spawn", () => {
			child.unref();
			done(true);
		});
		if (options.timeoutMs !== undefined)
			setTimeout(() => done(false), options.timeoutMs).unref();
	});
}