Skip to content

Commit 3b79981

Browse files
leslieyip02sindresorhus
andauthoredMar 20, 2023
Add the ability to open default browser and default browser in private mode (#294)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
1 parent cbc008b commit 3b79981

File tree

6 files changed

+144
-43
lines changed

6 files changed

+144
-43
lines changed
 

‎index.d.ts

+40-22
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,32 @@ declare namespace open {
6666
type AppName =
6767
| 'chrome'
6868
| 'firefox'
69-
| 'edge';
69+
| 'edge'
70+
| 'browser'
71+
| 'browserPrivate';
7072

7173
type App = {
7274
name: string | readonly string[];
7375
arguments?: readonly string[];
7476
};
7577
}
7678

79+
/**
80+
An object containing auto-detected binary names for common apps. Useful to work around cross-platform differences.
81+
82+
@example
83+
```
84+
import open from 'open';
85+
86+
await open('https://google.com', {
87+
app: {
88+
name: open.apps.chrome
89+
}
90+
});
91+
```
92+
*/
93+
declare const apps: Record<open.AppName, string | readonly string[]>;
94+
7795
// eslint-disable-next-line no-redeclare
7896
declare const open: {
7997
/**
@@ -88,20 +106,23 @@ declare const open: {
88106
89107
@example
90108
```
91-
import open = require('open');
109+
import open from 'open';
92110
93-
// Opens the image in the default image viewer
111+
// Opens the image in the default image viewer.
94112
await open('unicorn.png', {wait: true});
95-
console.log('The image viewer app closed');
113+
console.log('The image viewer app quit');
96114
97-
// Opens the url in the default browser
115+
// Opens the URL in the default browser.
98116
await open('https://sindresorhus.com');
99117
100118
// Opens the URL in a specified browser.
101119
await open('https://sindresorhus.com', {app: {name: 'firefox'}});
102120
103121
// Specify app arguments.
104122
await open('https://sindresorhus.com', {app: {name: 'google chrome', arguments: ['--incognito']}});
123+
124+
// Opens the URL in the default browser in incognito mode.
125+
await open('https://sindresorhus.com', {app: {name: open.apps.browserPrivate}});
105126
```
106127
*/
107128
(
@@ -111,19 +132,8 @@ declare const open: {
111132

112133
/**
113134
An object containing auto-detected binary names for common apps. Useful to work around cross-platform differences.
114-
115-
@example
116-
```
117-
import open = require('open');
118-
119-
await open('https://google.com', {
120-
app: {
121-
name: open.apps.chrome
122-
}
123-
});
124-
```
125135
*/
126-
apps: Record<open.AppName, string | readonly string[]>;
136+
apps: typeof apps;
127137

128138
/**
129139
Open an app. Cross-platform.
@@ -135,19 +145,27 @@ declare const open: {
135145
136146
@example
137147
```
138-
const {apps, openApp} = require('open');
148+
import open from 'open';
149+
const {apps, openApp} = open;
139150
140-
// Open Firefox
151+
// Open Firefox.
141152
await openApp(apps.firefox);
142153
143-
// Open Chrome incognito mode
154+
// Open Chrome in incognito mode.
144155
await openApp(apps.chrome, {arguments: ['--incognito']});
145156
146-
// Open Xcode
157+
// Open default browser.
158+
await openApp(apps.browser);
159+
160+
// Open default browser in incognito mode.
161+
await openApp(apps.browserPrivate);
162+
163+
// Open Xcode.
147164
await openApp('xcode');
148165
```
149166
*/
150167
openApp: (name: open.App['name'], options?: open.OpenAppOptions) => Promise<ChildProcess>;
151168
};
152169

153-
export = open;
170+
export {apps};
171+
export default open;

‎index.js

+58-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
const path = require('path');
2-
const childProcess = require('child_process');
3-
const {promises: fs, constants: fsConstants} = require('fs');
4-
const isWsl = require('is-wsl');
5-
const isDocker = require('is-docker');
6-
const defineLazyProperty = require('define-lazy-prop');
1+
import path from 'path';
2+
import {fileURLToPath} from 'url';
3+
import childProcess from 'child_process';
4+
import {promises as fs, constants as fsConstants} from 'fs';
5+
import isWsl from 'is-wsl';
6+
import isDocker from 'is-docker';
7+
import defineLazyProperty from 'define-lazy-prop';
8+
import defaultBrowser from 'default-browser';
79

810
// Path to included `xdg-open`.
11+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
912
const localXdgOpenPath = path.join(__dirname, 'xdg-open');
1013

1114
const {platform, arch} = process;
@@ -117,6 +120,45 @@ const baseOpen = async options => {
117120
}));
118121
}
119122

123+
if (app === 'browser' || app === 'browserPrivate') {
124+
// IDs from default-browser for macOS and windows are the same
125+
const ids = {
126+
'com.google.chrome': 'chrome',
127+
'google-chrome.desktop': 'chrome',
128+
'org.mozilla.firefox': 'firefox',
129+
'firefox.desktop': 'firefox',
130+
'com.microsoft.msedge': 'edge',
131+
'com.microsoft.edge': 'edge',
132+
'microsoft-edge.desktop': 'edge'
133+
};
134+
135+
// Incognito flags for each browser in open.apps
136+
const flags = {
137+
chrome: '--incognito',
138+
firefox: '--private-window',
139+
edge: '--inPrivate'
140+
};
141+
142+
const browser = await defaultBrowser();
143+
if (browser.id in ids) {
144+
const browserName = ids[browser.id];
145+
146+
if (app === 'browserPrivate') {
147+
appArguments.push(flags[browserName]);
148+
}
149+
150+
return baseOpen({
151+
...options,
152+
app: {
153+
name: open.apps[browserName],
154+
arguments: appArguments
155+
}
156+
});
157+
}
158+
159+
throw new Error(`${browser.name} is not supported as a default browser`);
160+
}
161+
120162
let command;
121163
const cliArguments = [];
122164
const childProcessOptions = {};
@@ -149,7 +191,7 @@ const baseOpen = async options => {
149191
cliArguments.push(
150192
'-NoProfile',
151193
'-NonInteractive',
152-
'ExecutionPolicy',
194+
'-ExecutionPolicy',
153195
'Bypass',
154196
'-EncodedCommand'
155197
);
@@ -167,17 +209,17 @@ const baseOpen = async options => {
167209
if (app) {
168210
// Double quote with double quotes to ensure the inner quotes are passed through.
169211
// Inner quotes are delimited for PowerShell interpretation with backticks.
170-
encodedArguments.push(`"\`"${app}\`""`, '-ArgumentList');
212+
encodedArguments.push(`"\`"${app}\`""`);
171213
if (options.target) {
172-
appArguments.unshift(options.target);
214+
appArguments.push(options.target);
173215
}
174216
} else if (options.target) {
175217
encodedArguments.push(`"${options.target}"`);
176218
}
177219

178220
if (appArguments.length > 0) {
179221
appArguments = appArguments.map(arg => `"\`"${arg}\`""`);
180-
encodedArguments.push(appArguments.join(','));
222+
encodedArguments.push('-ArgumentList', appArguments.join(','));
181223
}
182224

183225
// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
@@ -328,7 +370,12 @@ defineLazyProperty(apps, 'edge', () => detectPlatformBinary({
328370
wsl: '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
329371
}));
330372

373+
defineLazyProperty(apps, 'browser', () => 'browser');
374+
375+
defineLazyProperty(apps, 'browserPrivate', () => 'browserPrivate');
376+
331377
open.apps = apps;
332378
open.openApp = openApp;
333379

334-
module.exports = open;
380+
export {apps};
381+
export default open;

‎index.test-d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {expectType} from 'tsd';
22
import {ChildProcess} from 'child_process';
3-
import open = require('.');
3+
import open from '.';
44

55
// eslint-disable-next-line @typescript-eslint/no-unused-vars
66
const options: open.Options = {};

‎package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"name": "open",
3-
"version": "8.4.2",
3+
"version": "8.4.0",
4+
"type": "module",
45
"description": "Open stuff like URLs, files, executables. Cross-platform.",
56
"license": "MIT",
67
"repository": "sindresorhus/open",
@@ -50,7 +51,8 @@
5051
"dependencies": {
5152
"define-lazy-prop": "^2.0.0",
5253
"is-docker": "^2.1.1",
53-
"is-wsl": "^2.2.0"
54+
"is-wsl": "^2.2.0",
55+
"default-browser": "^3.1.0"
5456
},
5557
"devDependencies": {
5658
"@types/node": "^15.0.0",

‎readme.md

+23-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ npm install open
2626
## Usage
2727

2828
```js
29-
const open = require('open');
29+
import open from 'open';
3030

3131
// Opens the image in the default image viewer and waits for the opened app to quit.
3232
await open('unicorn.png', {wait: true});
@@ -41,10 +41,13 @@ await open('https://sindresorhus.com', {app: {name: 'firefox'}});
4141
// Specify app arguments.
4242
await open('https://sindresorhus.com', {app: {name: 'google chrome', arguments: ['--incognito']}});
4343

44-
// Open an app
44+
// Opens the URL in the default browser in incognito mode.
45+
await open('https://sindresorhus.com', {app: {name: open.apps.browserPrivate}});
46+
47+
// Open an app.
4548
await open.openApp('xcode');
4649

47-
// Open an app with arguments
50+
// Open an app with arguments.
4851
await open.openApp(open.apps.chrome, {arguments: ['--incognito']});
4952
```
5053

@@ -116,25 +119,40 @@ Allow the opened app to exit with nonzero exit code when the `wait` option is `t
116119

117120
We do not recommend setting this option. The convention for success is exit code zero.
118121

119-
### open.apps
122+
### open.apps / apps
120123

121124
An object containing auto-detected binary names for common apps. Useful to work around [cross-platform differences](#app).
122125

123126
```js
124-
const open = require('open');
127+
// Using default export.
128+
import open from 'open';
125129

126130
await open('https://google.com', {
127131
app: {
128132
name: open.apps.chrome
129133
}
130134
});
135+
136+
// Using named export.
137+
import open, {apps} from 'open';
138+
139+
await open('https://firefox.com', {
140+
app: {
141+
name: apps.browserPrivate
142+
}
143+
});
131144
```
145+
`browser` and `browserPrivate` can also be used to access the user's default browser through [`default-browser`](https://github.com/sindresorhus/default-browser).
132146

133147
#### Supported apps
134148

135149
- [`chrome`](https://www.google.com/chrome) - Web browser
136150
- [`firefox`](https://www.mozilla.org/firefox) - Web browser
137151
- [`edge`](https://www.microsoft.com/edge) - Web browser
152+
- `browser` - Default web browser
153+
- `browserPrivate` - Default web browser in incognito mode
154+
155+
`browser` and `browserPrivate` only supports `chrome`, `firefox` and `edge`.
138156

139157
### open.openApp(name, options?)
140158

‎test.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
const test = require('ava');
2-
const open = require('.');
1+
import test from 'ava';
2+
import open from './index.js';
33
const {openApp} = open;
44

55
// Tests only checks that opening doesn't return an error
@@ -78,3 +78,19 @@ test('open Firefox without arguments', async t => {
7878
test('open Chrome in incognito mode', async t => {
7979
await t.notThrowsAsync(openApp(open.apps.chrome, {arguments: ['--incognito'], newInstance: true}));
8080
});
81+
82+
test('open URL with default browser argument', async t => {
83+
await t.notThrowsAsync(open('https://sindresorhus.com', {app: {name: open.apps.browser}}));
84+
});
85+
86+
test('open URL with default browser in incognito mode', async t => {
87+
await t.notThrowsAsync(open('https://sindresorhus.com', {app: {name: open.apps.browserPrivate}}));
88+
});
89+
90+
test('open default browser', async t => {
91+
await t.notThrowsAsync(openApp(open.apps.browser, {newInstance: true}));
92+
});
93+
94+
test('open default browser in incognito mode', async t => {
95+
await t.notThrowsAsync(openApp(open.apps.browserPrivate, {newInstance: true}));
96+
});

0 commit comments

Comments
 (0)
Please sign in to comment.