|
|
import process from 'node:process';import {Buffer} from 'node:buffer';import path from 'node:path';import {fileURLToPath} from 'node:url';import {promisify} from 'node:util';import childProcess from 'node:child_process';import fs, {constants as fsConstants} from 'node:fs/promises';import {isWsl, powerShellPath} from 'wsl-utils';import defineLazyProperty from 'define-lazy-prop';import defaultBrowser from 'default-browser';import isInsideContainer from 'is-inside-container';
const execFile = promisify(childProcess.execFile);
// Path to included `xdg-open`.
const __dirname = path.dirname(fileURLToPath(import.meta.url));const localXdgOpenPath = path.join(__dirname, 'xdg-open');
const {platform, arch} = process;
/**Get the default browser name in Windows from WSL.
@returns {Promise<string>} Browser name.*/async function getWindowsDefaultBrowserFromWsl() { const powershellPath = await powerShellPath(); const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`; const encodedCommand = Buffer.from(rawCommand, 'utf16le').toString('base64');
const {stdout} = await execFile( powershellPath, [ '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', encodedCommand, ], {encoding: 'utf8'}, );
const progId = stdout.trim();
// Map ProgId to browser IDs
const browserMap = { ChromeHTML: 'com.google.chrome', BraveHTML: 'com.brave.Browser', MSEdgeHTM: 'com.microsoft.edge', FirefoxURL: 'org.mozilla.firefox', };
return browserMap[progId] ? {id: browserMap[progId]} : {};}
const pTryEach = async (array, mapper) => { let latestError;
for (const item of array) { try { return await mapper(item); // eslint-disable-line no-await-in-loop
} catch (error) { latestError = error; } }
throw latestError;};
// eslint-disable-next-line complexity
const baseOpen = async options => { options = { wait: false, background: false, newInstance: false, allowNonzeroExitCode: false, ...options, };
if (Array.isArray(options.app)) { return pTryEach(options.app, singleApp => baseOpen({ ...options, app: singleApp, })); }
let {name: app, arguments: appArguments = []} = options.app ?? {}; appArguments = [...appArguments];
if (Array.isArray(app)) { return pTryEach(app, appName => baseOpen({ ...options, app: { name: appName, arguments: appArguments, }, })); }
if (app === 'browser' || app === 'browserPrivate') { // IDs from default-browser for macOS and windows are the same
const ids = { 'com.google.chrome': 'chrome', 'google-chrome.desktop': 'chrome', 'com.brave.Browser': 'brave', 'org.mozilla.firefox': 'firefox', 'firefox.desktop': 'firefox', 'com.microsoft.msedge': 'edge', 'com.microsoft.edge': 'edge', 'com.microsoft.edgemac': 'edge', 'microsoft-edge.desktop': 'edge', };
// Incognito flags for each browser in `apps`.
const flags = { chrome: '--incognito', brave: '--incognito', firefox: '--private-window', edge: '--inPrivate', };
const browser = isWsl ? await getWindowsDefaultBrowserFromWsl() : await defaultBrowser(); if (browser.id in ids) { const browserName = ids[browser.id];
if (app === 'browserPrivate') { appArguments.push(flags[browserName]); }
return baseOpen({ ...options, app: { name: apps[browserName], arguments: appArguments, }, }); }
throw new Error(`${browser.name} is not supported as a default browser`); }
let command; const cliArguments = []; const childProcessOptions = {};
if (platform === 'darwin') { command = 'open';
if (options.wait) { cliArguments.push('--wait-apps'); }
if (options.background) { cliArguments.push('--background'); }
if (options.newInstance) { cliArguments.push('--new'); }
if (app) { cliArguments.push('-a', app); } } else if (platform === 'win32' || (isWsl && !isInsideContainer() && !app)) { command = await powerShellPath();
cliArguments.push( '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', );
if (!isWsl) { childProcessOptions.windowsVerbatimArguments = true; }
const encodedArguments = ['Start'];
if (options.wait) { encodedArguments.push('-Wait'); }
if (app) { // Double quote with double quotes to ensure the inner quotes are passed through.
// Inner quotes are delimited for PowerShell interpretation with backticks.
encodedArguments.push(`"\`"${app}\`""`); if (options.target) { appArguments.push(options.target); } } else if (options.target) { encodedArguments.push(`"${options.target}"`); }
if (appArguments.length > 0) { appArguments = appArguments.map(argument => `"\`"${argument}\`""`); encodedArguments.push('-ArgumentList', appArguments.join(',')); }
// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
options.target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64'); } else { if (app) { command = app; } else { // When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
const isBundled = !__dirname || __dirname === '/';
// Check if local `xdg-open` exists and is executable.
let exeLocalXdgOpen = false; try { await fs.access(localXdgOpenPath, fsConstants.X_OK); exeLocalXdgOpen = true; } catch {}
const useSystemXdgOpen = process.versions.electron ?? (platform === 'android' || isBundled || !exeLocalXdgOpen); command = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath; }
if (appArguments.length > 0) { cliArguments.push(...appArguments); }
if (!options.wait) { // `xdg-open` will block the process unless stdio is ignored
// and it's detached from the parent even if it's unref'd.
childProcessOptions.stdio = 'ignore'; childProcessOptions.detached = true; } }
if (platform === 'darwin' && appArguments.length > 0) { cliArguments.push('--args', ...appArguments); }
// This has to come after `--args`.
if (options.target) { cliArguments.push(options.target); }
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
if (options.wait) { return new Promise((resolve, reject) => { subprocess.once('error', reject);
subprocess.once('close', exitCode => { if (!options.allowNonzeroExitCode && exitCode > 0) { reject(new Error(`Exited with code ${exitCode}`)); return; }
resolve(subprocess); }); }); }
subprocess.unref();
return subprocess;};
const open = (target, options) => { if (typeof target !== 'string') { throw new TypeError('Expected a `target`'); }
return baseOpen({ ...options, target, });};
export const openApp = (name, options) => { if (typeof name !== 'string' && !Array.isArray(name)) { throw new TypeError('Expected a valid `name`'); }
const {arguments: appArguments = []} = options ?? {}; if (appArguments !== undefined && appArguments !== null && !Array.isArray(appArguments)) { throw new TypeError('Expected `appArguments` as Array type'); }
return baseOpen({ ...options, app: { name, arguments: appArguments, }, });};
function detectArchBinary(binary) { if (typeof binary === 'string' || Array.isArray(binary)) { return binary; }
const {[arch]: archBinary} = binary;
if (!archBinary) { throw new Error(`${arch} is not supported`); }
return archBinary;}
function detectPlatformBinary({[platform]: platformBinary}, {wsl}) { if (wsl && isWsl) { return detectArchBinary(wsl); }
if (!platformBinary) { throw new Error(`${platform} is not supported`); }
return detectArchBinary(platformBinary);}
export const apps = {};
defineLazyProperty(apps, 'chrome', () => detectPlatformBinary({ darwin: 'google chrome', win32: 'chrome', linux: ['google-chrome', 'google-chrome-stable', 'chromium'],}, { wsl: { ia32: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe', x64: ['/mnt/c/Program Files/Google/Chrome/Application/chrome.exe', '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe'], },}));
defineLazyProperty(apps, 'brave', () => detectPlatformBinary({ darwin: 'brave browser', win32: 'brave', linux: ['brave-browser', 'brave'],}, { wsl: { ia32: '/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe', x64: ['/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe', '/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe'], },}));
defineLazyProperty(apps, 'firefox', () => detectPlatformBinary({ darwin: 'firefox', win32: String.raw`C:\Program Files\Mozilla Firefox\firefox.exe`, linux: 'firefox',}, { wsl: '/mnt/c/Program Files/Mozilla Firefox/firefox.exe',}));
defineLazyProperty(apps, 'edge', () => detectPlatformBinary({ darwin: 'microsoft edge', win32: 'msedge', linux: ['microsoft-edge', 'microsoft-edge-dev'],}, { wsl: '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',}));
defineLazyProperty(apps, 'browser', () => 'browser');
defineLazyProperty(apps, 'browserPrivate', () => 'browserPrivate');
export default open;
|