Skip to content

Commit 33e80d5

Browse files
authored
Adds a hidden window (#7063)
* init new window * fix tests * preload listener * merge processes and windows * trigger CI * fix types * mock test flake
1 parent 302172d commit 33e80d5

11 files changed

Lines changed: 146 additions & 45 deletions

File tree

packages/insomnia-smoke-test/tests/smoke/git-sync.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ test('Sign in with GitHub', async ({ app, page }) => {
2323
// https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow
2424
const fakeGitHubOAuthWebFlow = app.evaluate(electron => {
2525
return new Promise<{ redirectUrl: string }>(resolve => {
26-
const webContents = electron.BrowserWindow.getAllWindows()[0].webContents;
26+
const webContents = electron.BrowserWindow.getAllWindows()?.find(w => w.title === 'Insomnia')?.webContents;
2727
// Remove all navigation listeners so that only the one we inject will run
28-
webContents.removeAllListeners('will-navigate');
29-
webContents.on('will-navigate', (event: Event, url: string) => {
28+
webContents?.removeAllListeners('will-navigate');
29+
webContents?.on('will-navigate', (event: Event, url: string) => {
3030
event.preventDefault();
3131
const parsedUrl = new URL(url);
3232
// We use the same state parameter that the app created to assert that we prevent CSRF

packages/insomnia-smoke-test/tests/smoke/mock.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ test('can make a mock route', async ({ page }) => {
1616
await page.getByPlaceholder('200').fill('201');
1717

1818
await page.getByRole('button', { name: 'Test' }).click();
19-
await page.getByRole('tab', { name: 'Preview' }).click();
20-
await page.getByLabel('Preview').getByText('123').click();
2119
await page.getByRole('tab', { name: 'Timeline' }).click();
2220
await page.getByText('201').click();
2321
});

packages/insomnia-smoke-test/tests/smoke/oauth-gitlab.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ test('Sign in with Gitlab', async ({ app, page }) => {
99

1010
const fakeGitLabOAuthWebFlow = app.evaluate(electron => {
1111
return new Promise<{ redirectUrl: string }>(resolve => {
12-
const webContents = electron.BrowserWindow.getAllWindows()[0].webContents;
12+
const webContents = electron.BrowserWindow.getAllWindows()?.find(w => w.title === 'Insomnia')?.webContents;
1313
// Remove all navigation listeners so that only the one we inject will run
14-
webContents.removeAllListeners('will-navigate');
15-
webContents.on('will-navigate', (event: Event, url: string) => {
14+
webContents?.removeAllListeners('will-navigate');
15+
webContents?.on('will-navigate', (event: Event, url: string) => {
1616
event.preventDefault();
1717
const parsedUrl = new URL(url);
1818
// We use the same state parameter that the app created to assert that we prevent CSRF

packages/insomnia/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ src/main.min.js
66
src/main.min.js.map
77
src/preload.js
88
src/preload.js.map
9+
src/renderers/hidden-browser-window/index.js
10+
src/renderers/hidden-browser-window/index.js.map

packages/insomnia/esbuild.main.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export default async function build(options: Options) {
2222
'process.env.APP_RENDER_URL': JSON.stringify(
2323
`http://localhost:${PORT}/index.html`
2424
),
25+
'process.env.HIDDEN_BROWSER_WINDOW_URL': JSON.stringify(
26+
`http://localhost:${PORT}/renderers/hidden-browser-window/index.html`
27+
),
2528
'process.env.NODE_ENV': JSON.stringify('development'),
2629
'process.env.INSOMNIA_ENV': JSON.stringify('development'),
2730
'process.env.BUILD_DATE': JSON.stringify(new Date()),
@@ -41,6 +44,18 @@ export default async function build(options: Options) {
4144
format: 'cjs',
4245
external: ['electron'],
4346
});
47+
48+
const hiddenBrowserWindow = esbuild.build({
49+
entryPoints: ['./src/renderers/hidden-browser-window/index.ts'],
50+
// the hidden browser window script is always outputed to 'src' as index.html requires a built bundle
51+
outfile: path.join(__dirname, 'src', 'renderers/hidden-browser-window/index.js'),
52+
target: 'esnext',
53+
bundle: true,
54+
platform: 'browser',
55+
sourcemap: true,
56+
format: 'cjs',
57+
external: [],
58+
});
4459
const main = esbuild.build({
4560
entryPoints: ['./src/main.development.ts'],
4661
outfile: path.join(outdir, 'main.min.js'),
@@ -56,7 +71,8 @@ export default async function build(options: Options) {
5671
...Object.keys(builtinModules),
5772
],
5873
});
59-
return Promise.all([main, preload]);
74+
75+
return Promise.all([main, preload, hiddenBrowserWindow]);
6076
}
6177

6278
// Build if ran as a cli script

packages/insomnia/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"package": "npm run build:app && cross-env USE_HARD_LINKS=false electron-builder build --config electron-builder.config.js",
2626
"start": "concurrently -n dev,app --kill-others \"npm run start:dev-server\" \"npm run start:electron\"",
2727
"start:dev-server": "vite dev",
28-
"start:electron": "cross-env NODE_ENV=development esr esbuild.main.ts && electron .",
28+
"start:electron": "cross-env NODE_ENV=development esr esbuild.main.ts && electron --inspect=5858 .",
2929
"test": "jest",
3030
"electron:dev-build": "electron ./build/main.min.js",
3131
"test:watch": "jest --watch",

packages/insomnia/src/main.development.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ const _launchApp = async () => {
158158
const args = process.argv.slice(1).filter(a => a !== '.');
159159
if (args.length) {
160160
window = windowUtils.getOrCreateWindow();
161+
windowUtils.createHiddenBrowserWindow();
161162
window.webContents.send('shell:open', args.join());
162163
}
163164
});

packages/insomnia/src/main/window-utils.ts

Lines changed: 95 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import electron, { type BrowserWindow as ElectronBrowserWindow, type MenuItemConstructorOptions } from 'electron';
1+
import {
2+
app,
3+
BrowserWindow,
4+
type BrowserWindow as ElectronBrowserWindow,
5+
clipboard,
6+
dialog,
7+
Menu,
8+
type MenuItemConstructorOptions,
9+
screen,
10+
shell,
11+
} from 'electron';
212
import fs from 'fs';
313
import * as os from 'os';
414
import path from 'path';
@@ -16,17 +26,15 @@ import {
1626
} from '../common/constants';
1727
import { docsBase } from '../common/documentation';
1828
import * as log from '../common/log';
29+
import { invariant } from '../utils/invariant';
1930
import LocalStorage from './local-storage';
2031

21-
const { app, Menu, shell, dialog, clipboard, BrowserWindow } = electron;
22-
2332
const DEFAULT_WIDTH = 1280;
2433
const DEFAULT_HEIGHT = 720;
2534
const MINIMUM_WIDTH = 500;
2635
const MINIMUM_HEIGHT = 400;
2736

28-
let newWindow: ElectronBrowserWindow | null = null;
29-
const windows = new Set<ElectronBrowserWindow>();
37+
const browserWindows = new Map<'Insomnia' | 'HiddenBrowserWindow', ElectronBrowserWindow>();
3038
let localStorage: LocalStorage | null = null;
3139

3240
interface Bounds {
@@ -40,14 +48,67 @@ export function init() {
4048
initLocalStorage();
4149
}
4250

43-
export function createWindow() {
51+
export async function createHiddenBrowserWindow(): Promise<ElectronBrowserWindow> {
52+
// if open, close it
53+
if (browserWindows.get('HiddenBrowserWindow')) {
54+
await new Promise<void>(resolve => {
55+
const hiddenBrowserWindow = browserWindows.get('HiddenBrowserWindow');
56+
invariant(hiddenBrowserWindow, 'hiddenBrowserWindow is not defined');
57+
58+
// overwrite the closed handler
59+
hiddenBrowserWindow.on('closed', () => {
60+
if (hiddenBrowserWindow) {
61+
console.log('[main] restarting hidden browser window');
62+
browserWindows.delete('HiddenBrowserWindow');
63+
}
64+
resolve();
65+
});
66+
67+
stopHiddenBrowserWindow();
68+
});
69+
}
70+
const hiddenBrowserWindow = new BrowserWindow({
71+
show: false,
72+
title: 'HiddenBrowserWindow',
73+
webPreferences: {
74+
sandbox: true,
75+
contextIsolation: true,
76+
nodeIntegration: false,
77+
webSecurity: true,
78+
preload: path.join(__dirname, 'renderers/hidden-browser-window/preload.js'),
79+
spellcheck: false,
80+
},
81+
});
82+
browserWindows.set('HiddenBrowserWindow', hiddenBrowserWindow);
83+
84+
const hiddenBrowserWindowPath = path.resolve(__dirname, './renderers/hidden-browser-window/index.html');
85+
const hiddenBrowserWindowUrl = process.env.HIDDEN_BROWSER_WINDOW_URL || pathToFileURL(hiddenBrowserWindowPath).href;
86+
hiddenBrowserWindow.loadURL(hiddenBrowserWindowUrl);
87+
88+
console.log('[main][init hidden win step 1/6]: starting hidden browser window');
89+
90+
hiddenBrowserWindow.on('closed', () => {
91+
if (browserWindows.get('HiddenBrowserWindow')) {
92+
console.log('[main] closing hidden browser window');
93+
browserWindows.delete('HiddenBrowserWindow');
94+
}
95+
});
96+
97+
return hiddenBrowserWindow;
98+
}
99+
100+
export function stopHiddenBrowserWindow() {
101+
browserWindows.get('HiddenBrowserWindow')?.close();
102+
}
103+
104+
export function createWindow(): ElectronBrowserWindow {
44105
const { bounds, fullscreen, maximize } = getBounds();
45106
const { x, y, width, height } = bounds;
46107

47108
const appLogo = 'static/insomnia-core-logo_16x.png';
48109
let isVisibleOnAnyDisplay = true;
49110

50-
for (const d of electron.screen.getAllDisplays()) {
111+
for (const d of screen.getAllDisplays()) {
51112
const isVisibleOnDisplay =
52113
// @ts-expect-error -- TSCONVERSION genuine error
53114
x >= d.bounds.x &&
@@ -63,7 +124,7 @@ export function createWindow() {
63124
}
64125
}
65126

66-
newWindow = new BrowserWindow({
127+
const mainBrowserWindow = new BrowserWindow({
67128
// Make sure we don't initialize the window outside the bounds
68129
x: isVisibleOnAnyDisplay ? x : undefined,
69130
y: isVisibleOnAnyDisplay ? y : undefined,
@@ -88,22 +149,23 @@ export function createWindow() {
88149
disableBlinkFeatures: 'Auxclick',
89150
},
90151
});
152+
browserWindows.set('Insomnia', mainBrowserWindow);
91153

92154
// BrowserWindow doesn't have an option for this, so we have to do it manually :(
93155
if (maximize) {
94-
newWindow?.maximize();
156+
mainBrowserWindow.maximize();
95157
}
96158

97-
newWindow?.on('resize', () => saveBounds());
98-
newWindow?.on('maximize', () => saveBounds());
99-
newWindow?.on('unmaximize', () => saveBounds());
100-
newWindow?.on('move', () => saveBounds());
101-
newWindow?.on('unresponsive', () => {
159+
mainBrowserWindow.on('resize', () => saveBounds());
160+
mainBrowserWindow.on('maximize', () => saveBounds());
161+
mainBrowserWindow.on('unmaximize', () => saveBounds());
162+
mainBrowserWindow.on('move', () => saveBounds());
163+
mainBrowserWindow.on('unresponsive', () => {
102164
showUnresponsiveModal();
103165
});
104166

105167
// Open generic links (<a .../>) in default browser
106-
newWindow?.webContents.on('will-navigate', (event, url) => {
168+
mainBrowserWindow.webContents.on('will-navigate', (event, url) => {
107169
// Prevents local dev full-reload events from opening browser window, see https://github.com/Kong/insomnia/pull/4925
108170
if (url.startsWith(appUrl)) {
109171
return;
@@ -118,7 +180,7 @@ export function createWindow() {
118180
}
119181
});
120182

121-
newWindow?.webContents.setWindowOpenHandler(() => {
183+
mainBrowserWindow.webContents.setWindowOpenHandler(() => {
122184
return { action: 'deny' };
123185
});
124186

@@ -127,12 +189,11 @@ export function createWindow() {
127189
const appUrl = process.env.APP_RENDER_URL || pathToFileURL(appPath).href;
128190

129191
console.log(`[main] Loading ${appUrl}`);
130-
newWindow?.loadURL(appUrl);
192+
mainBrowserWindow.loadURL(appUrl);
131193
// Emitted when the window is closed.
132-
newWindow?.on('closed', () => {
133-
if (newWindow) {
134-
windows.delete(newWindow);
135-
newWindow = windows.values().next().value || null;
194+
mainBrowserWindow.on('closed', () => {
195+
if (browserWindows.get('Insomnia')) {
196+
browserWindows.delete('Insomnia');
136197
}
137198
});
138199

@@ -259,23 +320,23 @@ export function createWindow() {
259320
{
260321
label: `Resize to ${MNEMONIC_SYM}Small (qHD 540)`,
261322
click: () =>
262-
newWindow?.setBounds({
323+
mainBrowserWindow.setBounds({
263324
width: 960,
264325
height: 540,
265326
}),
266327
},
267328
{
268329
label: `Resize to Defaul${MNEMONIC_SYM}t (HD 720)`,
269330
click: () =>
270-
newWindow?.setBounds({
331+
mainBrowserWindow.setBounds({
271332
width: DEFAULT_WIDTH,
272333
height: DEFAULT_HEIGHT,
273334
}),
274335
},
275336
{
276337
label: `Resize to ${MNEMONIC_SYM}Large (FHD 1080)`,
277338
click: () =>
278-
newWindow?.setBounds({
339+
mainBrowserWindow.setBounds({
279340
width: 1920,
280341
height: 1080,
281342
}),
@@ -367,7 +428,7 @@ export function createWindow() {
367428
{
368429
label: `Show App ${MNEMONIC_SYM}Data Folder`,
369430
click: () => {
370-
const directory = process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData');
431+
const directory = process.env['INSOMNIA_DATA_PATH'] || app.getPath('userData');
371432
shell.showItemInFolder(directory);
372433
},
373434
},
@@ -445,10 +506,10 @@ export function createWindow() {
445506
helpMenu.submenu?.push({
446507
type: 'separator',
447508
},
448-
{
449-
label: `${MNEMONIC_SYM}About`,
450-
click: aboutMenuClickHandler,
451-
});
509+
{
510+
label: `${MNEMONIC_SYM}About`,
511+
click: aboutMenuClickHandler,
512+
});
452513
}
453514

454515
const developerMenu: MenuItemConstructorOptions = {
@@ -470,7 +531,7 @@ export function createWindow() {
470531
label: `Take ${MNEMONIC_SYM}Screenshot`,
471532
click: function() {
472533
// @ts-expect-error -- TSCONVERSION not accounted for in the electron types to provide a function
473-
newWindow?.capturePage(image => {
534+
mainBrowserWindow.capturePage(image => {
474535
const buffer = image.toPNG();
475536
const dir = app.getPath('desktop');
476537
fs.writeFileSync(path.join(dir, `Screenshot-${new Date()}.png`), buffer);
@@ -496,7 +557,7 @@ export function createWindow() {
496557
{
497558
label: `Set window for ${MNEMONIC_SYM}FHD Screenshot`,
498559
click: () => {
499-
newWindow?.setBounds({
560+
mainBrowserWindow.setBounds({
500561
width: 1920,
501562
height: 1080,
502563
});
@@ -546,8 +607,7 @@ export function createWindow() {
546607
}
547608

548609
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
549-
windows.add(newWindow);
550-
return newWindow;
610+
return mainBrowserWindow;
551611
}
552612

553613
async function showUnresponsiveModal() {
@@ -646,10 +706,10 @@ export const setZoom = (transformer: (current: number) => number) => () => {
646706
};
647707

648708
function initLocalStorage() {
649-
const localStoragePath = path.join(process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData'), 'localStorage');
709+
const localStoragePath = path.join(process.env['INSOMNIA_DATA_PATH'] || app.getPath('userData'), 'localStorage');
650710
localStorage = new LocalStorage(localStoragePath);
651711
}
652712

653713
export function getOrCreateWindow() {
654-
return newWindow ?? createWindow();
714+
return browserWindows.get('Insomnia') ?? createWindow();
655715
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en-US">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
http-equiv="Content-Security-Policy"
7+
content="font-src 'self' data:; connect-src * data: api: insomnia-event-source:; default-src * insomnia://*; img-src blob: data: * insomnia://*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src blob: data: mediastream: * insomnia://*;"
8+
/>
9+
<title>Hidden Browser Window</title>
10+
</head>
11+
12+
<body>
13+
<h1>>Hidden Browser Window</h1>
14+
<script src="./index.js" type="module"></script>
15+
</body>
16+
</html>

packages/insomnia/src/renderers/hidden-browser-window/index.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)