Skip to content

Commit b8d8107

Browse files
committed
Refactor
1 parent ce31b94 commit b8d8107

File tree

2 files changed

+32
-95
lines changed

2 files changed

+32
-95
lines changed

index.js

Lines changed: 29 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import process from 'node:process';
2-
import {Buffer} from 'node:buffer';
32
import path from 'node:path';
43
import {fileURLToPath} from 'node:url';
5-
import {promisify} from 'node:util';
64
import childProcess from 'node:child_process';
75
import fs, {constants as fsConstants} from 'node:fs/promises';
8-
import {isWsl, powerShellPath, convertWslPathToWindows} from 'wsl-utils';
6+
import {
7+
isWsl,
8+
powerShellPath,
9+
convertWslPathToWindows,
10+
canAccessPowerShell,
11+
wslDefaultBrowser,
12+
} from 'wsl-utils';
13+
import {executePowerShell} from 'powershell-utils';
914
import defineLazyProperty from 'define-lazy-prop';
10-
import defaultBrowser from 'default-browser';
15+
import defaultBrowser, {_windowsBrowserProgIdMap} from 'default-browser';
1116
import isInsideContainer from 'is-inside-container';
1217
import isInSsh from 'is-in-ssh';
1318

14-
const execFile = promisify(childProcess.execFile);
1519
const fallbackAttemptSymbol = Symbol('fallbackAttempt');
1620

1721
// Path to included `xdg-open`.
@@ -20,77 +24,6 @@ const localXdgOpenPath = path.join(__dirname, 'xdg-open');
2024

2125
const {platform, arch} = process;
2226

23-
/**
24-
Escape value for PowerShell. Single-quoted strings are literal (no variable expansion or escape sequences), unlike double-quoted strings. Escapes single quotes by doubling (handles already-doubled quotes correctly).
25-
*/
26-
const escapeForPowerShell = value => `'${String(value).replaceAll('\'', '\'\'')}'`;
27-
28-
/**
29-
Cached promise for PowerShell accessibility check.
30-
We only need to check once per process since PowerShell availability doesn't change at runtime.
31-
Caching the promise (rather than the result) ensures concurrent calls don't trigger duplicate checks.
32-
*/
33-
let powerShellAccessiblePromise;
34-
35-
/**
36-
Check if PowerShell is accessible in WSL.
37-
This is used to determine whether to use Windows integration (PowerShell) or fall back to Linux behavior (xdg-open).
38-
In sandboxed WSL environments where Windows access is restricted, PowerShell may not be accessible,
39-
and we should automatically use native Linux tools instead.
40-
41-
@returns {Promise<boolean>} True if PowerShell is accessible, false otherwise.
42-
*/
43-
async function isPowerShellAccessible() {
44-
powerShellAccessiblePromise ??= (async () => {
45-
try {
46-
const psPath = await powerShellPath();
47-
await fs.access(psPath, fsConstants.X_OK);
48-
return true;
49-
} catch {
50-
// PowerShell is not accessible (either doesn't exist, no execute permission, or other error)
51-
return false;
52-
}
53-
})();
54-
55-
return powerShellAccessiblePromise;
56-
}
57-
58-
/**
59-
Get the default browser name in Windows from WSL.
60-
61-
@returns {Promise<string>} Browser name.
62-
*/
63-
async function getWindowsDefaultBrowserFromWsl() {
64-
const powershellPath = await powerShellPath();
65-
const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
66-
const encodedCommand = Buffer.from(rawCommand, 'utf16le').toString('base64');
67-
68-
const {stdout} = await execFile(
69-
powershellPath,
70-
[
71-
'-NoProfile',
72-
'-NonInteractive',
73-
'-ExecutionPolicy',
74-
'Bypass',
75-
'-EncodedCommand',
76-
encodedCommand,
77-
],
78-
{encoding: 'utf8'},
79-
);
80-
81-
const progId = stdout.trim();
82-
83-
// Map ProgId to browser IDs
84-
const browserMap = {
85-
ChromeHTML: 'com.google.chrome',
86-
BraveHTML: 'com.brave.Browser',
87-
MSEdgeHTM: 'com.microsoft.edge',
88-
FirefoxURL: 'org.mozilla.firefox',
89-
};
90-
91-
return browserMap[progId] ? {id: browserMap[progId]} : {};
92-
}
93-
9427
const tryEachApp = async (apps, opener) => {
9528
if (apps.length === 0) {
9629
// No app was provided
@@ -146,18 +79,19 @@ const baseOpen = async options => {
14679
}
14780

14881
if (app === 'browser' || app === 'browserPrivate') {
149-
// IDs from default-browser for macOS and windows are the same
82+
// IDs from default-browser for macOS and windows are the same.
83+
// IDs are lowercased to increase chances of a match.
15084
const ids = {
15185
'com.google.chrome': 'chrome',
15286
'google-chrome.desktop': 'chrome',
153-
'com.brave.Browser': 'brave',
87+
'com.brave.browser': 'brave',
15488
'org.mozilla.firefox': 'firefox',
15589
'firefox.desktop': 'firefox',
15690
'com.microsoft.msedge': 'edge',
15791
'com.microsoft.edge': 'edge',
15892
'com.microsoft.edgemac': 'edge',
15993
'microsoft-edge.desktop': 'edge',
160-
'com.apple.Safari': 'safari',
94+
'com.apple.safari': 'safari',
16195
};
16296

16397
// Incognito flags for each browser in `apps`.
@@ -169,9 +103,17 @@ const baseOpen = async options => {
169103
// Safari doesn't support private mode via command line
170104
};
171105

172-
const browser = isWsl ? await getWindowsDefaultBrowserFromWsl() : await defaultBrowser();
106+
let browser;
107+
if (isWsl) {
108+
const progId = await wslDefaultBrowser();
109+
const browserInfo = _windowsBrowserProgIdMap.get(progId);
110+
browser = browserInfo ?? {};
111+
} else {
112+
browser = await defaultBrowser();
113+
}
114+
173115
if (browser.id in ids) {
174-
const browserName = ids[browser.id];
116+
const browserName = ids[browser.id.toLowerCase()];
175117

176118
if (app === 'browserPrivate') {
177119
// Safari doesn't support private mode via command line
@@ -203,7 +145,7 @@ const baseOpen = async options => {
203145
// This allows the package to work in sandboxed WSL environments where Windows access is restricted.
204146
let shouldUseWindowsInWsl = false;
205147
if (isWsl && !isInsideContainer() && !isInSsh && !app) {
206-
shouldUseWindowsInWsl = await isPowerShellAccessible();
148+
shouldUseWindowsInWsl = await canAccessPowerShell();
207149
}
208150

209151
if (platform === 'darwin') {
@@ -227,13 +169,7 @@ const baseOpen = async options => {
227169
} else if (platform === 'win32' || shouldUseWindowsInWsl) {
228170
command = await powerShellPath();
229171

230-
cliArguments.push(
231-
'-NoProfile',
232-
'-NonInteractive',
233-
'-ExecutionPolicy',
234-
'Bypass',
235-
'-EncodedCommand',
236-
);
172+
cliArguments.push(...executePowerShell.argumentsPrefix);
237173

238174
if (!isWsl) {
239175
childProcessOptions.windowsVerbatimArguments = true;
@@ -252,21 +188,21 @@ const baseOpen = async options => {
252188
}
253189

254190
if (app) {
255-
encodedArguments.push(escapeForPowerShell(app));
191+
encodedArguments.push(executePowerShell.escapeArgument(app));
256192
if (options.target) {
257193
appArguments.push(options.target);
258194
}
259195
} else if (options.target) {
260-
encodedArguments.push(escapeForPowerShell(options.target));
196+
encodedArguments.push(executePowerShell.escapeArgument(options.target));
261197
}
262198

263199
if (appArguments.length > 0) {
264-
appArguments = appArguments.map(argument => escapeForPowerShell(argument));
200+
appArguments = appArguments.map(argument => executePowerShell.escapeArgument(argument));
265201
encodedArguments.push('-ArgumentList', appArguments.join(','));
266202
}
267203

268204
// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
269-
options.target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
205+
options.target = executePowerShell.encodeCommand(encodedArguments.join(' '));
270206

271207
if (!options.wait) {
272208
// PowerShell will keep the parent process alive unless stdio is ignored.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@
5454
"file"
5555
],
5656
"dependencies": {
57-
"default-browser": "^5.3.0",
57+
"default-browser": "^5.4.0",
5858
"define-lazy-prop": "^3.0.0",
5959
"is-in-ssh": "^1.0.0",
6060
"is-inside-container": "^1.0.0",
61-
"wsl-utils": "^0.2.0"
61+
"powershell-utils": "^0.1.0",
62+
"wsl-utils": "^0.3.0"
6263
},
6364
"devDependencies": {
6465
"@types/node": "^24.10.1",

0 commit comments

Comments
 (0)