chore: move electron out of the renderer (#4406)

* fix lint

* fix types

* remove flush db.change event send

* guard against tests running electron functions

* copy preload to build output

* fix webview context menu

* fix context menu and plugin install

* move installPlugin to main context
This commit is contained in:
Jack Kavanagh 2022-02-16 19:28:23 +01:00 committed by GitHub
parent 837342ddab
commit 3947bdc4aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 209 additions and 157 deletions

View File

@ -247,12 +247,14 @@ export const database = {
for (const fn of changeListeners) { for (const fn of changeListeners) {
await fn(changes); await fn(changes);
} }
// Notify remote listeners // Notify remote listeners
const windows = electron.BrowserWindow.getAllWindows(); const isMainContext = process.type === 'browser';
if (isMainContext){
const windows = electron.BrowserWindow.getAllWindows();
for (const window of windows) { for (const window of windows) {
window.webContents.send('db.changes', changes); window.webContents.send('db.changes', changes);
}
} }
}, },

View File

@ -19,66 +19,18 @@ export function clickLink(href: string) {
export const getPortableExecutableDir = () => process.env.PORTABLE_EXECUTABLE_DIR; export const getPortableExecutableDir = () => process.env.PORTABLE_EXECUTABLE_DIR;
export function getDataDirectory() { export function getDataDirectory() {
const { app } = electron.remote || electron; const { app } = process.type === 'renderer' ? window : electron;
return process.env.INSOMNIA_DATA_PATH || app.getPath('userData'); return process.env.INSOMNIA_DATA_PATH || app.getPath('userData');
} }
export function getViewportSize(): string | null {
const { BrowserWindow } = electron.remote || electron;
const browserWindow = BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0];
if (browserWindow) {
const { width, height } = browserWindow.getContentBounds();
return `${width}x${height}`;
} else {
// No windows open
return null;
}
}
export function getScreenResolution() {
const { screen } = electron.remote || electron;
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
return `${width}x${height}`;
}
export function getUserLanguage() {
const { app } = electron.remote || electron;
return app.getLocale();
}
export function getTempDir() { export function getTempDir() {
// NOTE: Using a fairly unique name here because "insomnia" is a common word // NOTE: Using a fairly unique name here because "insomnia" is a common word
const { app } = electron.remote || electron; const { app } = process.type === 'renderer' ? window : electron;
const dir = join(app.getPath('temp'), `insomnia_${appConfig.version}`); const dir = join(app.getPath('temp'), `insomnia_${appConfig.version}`);
mkdirp.sync(dir); mkdirp.sync(dir);
return dir; return dir;
} }
export function restartApp() {
const { app } = electron.remote || electron;
app.relaunch();
app.exit();
}
export const exitAppFailure = () => {
const { app } = electron.remote || electron;
app.exit(1);
};
export const setMenuBarVisibility = (visible: boolean) => {
const { BrowserWindow } = electron.remote || electron;
BrowserWindow.getAllWindows()
.forEach(window => {
// the `setMenuBarVisibility` signature uses `visible` semantics
window.setMenuBarVisibility(visible);
// the `setAutoHideMenu` signature uses `hide` semantics
const hide = !visible;
window.setAutoHideMenuBar(hide);
});
};
/** /**
* There's no option that prevents Electron from fetching spellcheck dictionaries from Chromium's CDN and passing a non-resolving URL is the only known way to prevent it from fetching. * There's no option that prevents Electron from fetching spellcheck dictionaries from Chromium's CDN and passing a non-resolving URL is the only known way to prevent it from fetching.
* see: https://github.com/electron/electron/issues/22995 * see: https://github.com/electron/electron/issues/22995

View File

@ -1,4 +1,4 @@
import { OpenDialogOptions, remote } from 'electron'; import { OpenDialogOptions } from 'electron';
import { unreachableCase } from 'ts-assert-unreachable'; import { unreachableCase } from 'ts-assert-unreachable';
interface Options { interface Options {
@ -49,7 +49,8 @@ export const selectFileOrFolder = async ({ itemTypes, extensions }: Options) =>
}], }],
}; };
const { canceled, filePaths } = await remote.dialog.showOpenDialog(options); const { canceled, filePaths } = await window.dialog.showOpenDialog(options);
const fileSelection: FileSelection = { const fileSelection: FileSelection = {
filePath: filePaths[0], filePath: filePaths[0],
canceled, canceled,

View File

@ -22,6 +22,23 @@ declare namespace NodeJS {
interface Window { interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: Function; __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: Function;
main: {
restart: () => void;
authorizeUserInWindow: (options: { url: string; urlSuccessRegex?: RegExp; urlFailureRegex?: RegExp; sessionId: string }) => Promise<string>;
setMenuBarVisibility: (visible: boolean) => void;
installPlugin: (url: string) => void;
};
dialog: {
showOpenDialog: (options: Electron.OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>;
showSaveDialog: (options: Electron.SaveDialogOptions) => Promise<Electron.SaveDialogReturnValue>;
};
app: {
getPath: (name: string) => string;
getAppPath: () => string;
};
shell: {
showItemInFolder: (fullPath: string) => void;
};
} }
// needed for @hot-loader/react-dom in order for TypeScript to build // needed for @hot-loader/react-dom in order for TypeScript to build

View File

@ -7,7 +7,7 @@ import appConfig from '../config/config.json';
import { SegmentEvent, trackSegmentEvent } from './common/analytics'; import { SegmentEvent, trackSegmentEvent } from './common/analytics';
import { changelogUrl, getAppVersion, isDevelopment, isMac } from './common/constants'; import { changelogUrl, getAppVersion, isDevelopment, isMac } from './common/constants';
import { database } from './common/database'; import { database } from './common/database';
import { disableSpellcheckerDownload, exitAppFailure } from './common/electron-helpers'; import { disableSpellcheckerDownload } from './common/electron-helpers';
import log, { initializeLogging } from './common/log'; import log, { initializeLogging } from './common/log';
import { validateInsomniaConfig } from './common/validate-insomnia-config'; import { validateInsomniaConfig } from './common/validate-insomnia-config';
import * as errorHandling from './main/error-handling'; import * as errorHandling from './main/error-handling';
@ -17,6 +17,8 @@ import * as updates from './main/updates';
import * as windowUtils from './main/window-utils'; import * as windowUtils from './main/window-utils';
import * as models from './models/index'; import * as models from './models/index';
import type { Stats } from './models/stats'; import type { Stats } from './models/stats';
import { authorizeUserInWindow } from './network/o-auth-2/misc';
import installPlugin from './plugins/install';
import type { ToastNotification } from './ui/components/toast'; import type { ToastNotification } from './ui/components/toast';
// Handle potential auto-update // Handle potential auto-update
@ -41,7 +43,14 @@ if (!isDevelopment()) {
// So if (window) checks don't throw // So if (window) checks don't throw
global.window = global.window || undefined; global.window = global.window || undefined;
contextMenu(); // setup right click menu
app.on('web-contents-created', (_, contents) => {
if (contents.getType() === 'webview') {
contextMenu({ window: contents });
} else {
contextMenu();
}
});
// When the app is first launched // When the app is first launched
app.on('ready', async () => { app.on('ready', async () => {
@ -50,7 +59,7 @@ app.on('ready', async () => {
if (error) { if (error) {
electron.dialog.showErrorBox(error.title, error.message); electron.dialog.showErrorBox(error.title, error.message);
console.log('[config] Insomnia config is invalid, preventing app initialization'); console.log('[config] Insomnia config is invalid, preventing app initialization');
exitAppFailure(); app.exit(1);
return; return;
} }
@ -203,6 +212,53 @@ async function _trackStats() {
trackSegmentEvent(SegmentEvent.appStarted, {}, { queueable: true }); trackSegmentEvent(SegmentEvent.appStarted, {}, { queueable: true });
ipcMain.handle('showOpenDialog', async (_, options: Electron.OpenDialogOptions) => {
const { filePaths, canceled } = await electron.dialog.showOpenDialog(options);
return { filePaths, canceled };
});
ipcMain.handle('showSaveDialog', async (_, options: Electron.SaveDialogOptions) => {
const { filePath, canceled } = await electron.dialog.showSaveDialog(options);
return { filePath, canceled };
});
ipcMain.handle('installPlugin', async (_, options) => {
return installPlugin(options);
});
ipcMain.on('showItemInFolder', (_, name) => {
electron.shell.showItemInFolder(name);
});
ipcMain.on('restart', () => {
app.relaunch();
app.exit();
});
ipcMain.handle('setMenuBarVisibility', (_, visible) => {
electron.BrowserWindow.getAllWindows()
.forEach(window => {
// the `setMenuBarVisibility` signature uses `visible` semantics
window.setMenuBarVisibility(visible);
// the `setAutoHideMenu` signature uses `hide` semantics
const hide = !visible;
window.setAutoHideMenuBar(hide);
});
});
ipcMain.on('getPath', (event, name) => {
event.returnValue = electron.app.getPath(name);
});
ipcMain.on('getAppPath', event => {
event.returnValue = electron.app.getAppPath();
});
ipcMain.handle('authorizeUserInWindow', (_, options) => {
const { url, urlSuccessRegex, urlFailureRegex, sessionId } = options;
return authorizeUserInWindow({ url, urlSuccessRegex, urlFailureRegex, sessionId });
});
ipcMain.once('window-ready', () => { ipcMain.once('window-ready', () => {
const { currentVersion, launches, lastVersion } = stats; const { currentVersion, launches, lastVersion } = stats;

View File

@ -17,7 +17,7 @@ import {
MNEMONIC_SYM, MNEMONIC_SYM,
} from '../common/constants'; } from '../common/constants';
import { docsBase } from '../common/documentation'; import { docsBase } from '../common/documentation';
import { clickLink, getDataDirectory, restartApp } from '../common/electron-helpers'; import { clickLink, getDataDirectory } from '../common/electron-helpers';
import * as log from '../common/log'; import * as log from '../common/log';
import LocalStorage from './local-storage'; import LocalStorage from './local-storage';
@ -85,10 +85,10 @@ export function createWindow() {
acceptFirstMouse: true, acceptFirstMouse: true,
icon: path.resolve(__dirname, appLogo), icon: path.resolve(__dirname, appLogo),
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'preload.js'),
zoomFactor: zoomFactor, zoomFactor: zoomFactor,
nodeIntegration: true, nodeIntegration: true,
webviewTag: true, webviewTag: true,
enableRemoteModule: true,
// TODO: enable context isolation // TODO: enable context isolation
contextIsolation: false, contextIsolation: false,
disableBlinkFeatures: 'Auxclick', disableBlinkFeatures: 'Auxclick',
@ -482,7 +482,7 @@ export function createWindow() {
}, },
{ {
label: `R${MNEMONIC_SYM}estart`, label: `R${MNEMONIC_SYM}estart`,
click: restartApp, click: window?.main.restart,
}, },
], ],
}; };

View File

@ -1,9 +1,10 @@
import electron from 'electron';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { globalBeforeEach } from '../../__jest__/before-each'; import { globalBeforeEach } from '../../__jest__/before-each';
import { buildMultipart, DEFAULT_BOUNDARY } from '../multipart'; import { buildMultipart, DEFAULT_BOUNDARY } from '../multipart';
window.app = electron.app;
describe('buildMultipart()', () => { describe('buildMultipart()', () => {
beforeEach(globalBeforeEach); beforeEach(globalBeforeEach);

View File

@ -1,4 +1,5 @@
import { CurlHttpVersion } from '@getinsomnia/node-libcurl'; import { CurlHttpVersion } from '@getinsomnia/node-libcurl';
import electron from 'electron';
import fs from 'fs'; import fs from 'fs';
import { HttpVersions } from 'insomnia-common'; import { HttpVersions } from 'insomnia-common';
import { join as pathJoin, resolve as pathResolve } from 'path'; import { join as pathJoin, resolve as pathResolve } from 'path';
@ -18,6 +19,7 @@ import { getRenderedRequestAndContext } from '../../common/render';
import * as models from '../../models'; import * as models from '../../models';
import { DEFAULT_BOUNDARY } from '../multipart'; import { DEFAULT_BOUNDARY } from '../multipart';
import * as networkUtils from '../network'; import * as networkUtils from '../network';
window.app = electron.app;
const getRenderedRequest = async (args: Parameters<typeof getRenderedRequestAndContext>[0]) => (await getRenderedRequestAndContext(args)).request; const getRenderedRequest = async (args: Parameters<typeof getRenderedRequestAndContext>[0]) => (await getRenderedRequestAndContext(args)).request;

View File

@ -1,4 +1,3 @@
import * as electron from 'electron';
import fs from 'fs'; import fs from 'fs';
import mimes from 'mime-types'; import mimes from 'mime-types';
import path from 'path'; import path from 'path';
@ -15,7 +14,7 @@ interface Multipart {
export async function buildMultipart(params: RequestBodyParameter[]) { export async function buildMultipart(params: RequestBodyParameter[]) {
return new Promise<Multipart>(async (resolve, reject) => { return new Promise<Multipart>(async (resolve, reject) => {
const filePath = path.join(electron.remote.app.getPath('temp'), Math.random() + '.body'); const filePath = path.join(window.app.getPath('temp'), Math.random() + '.body');
const writeStream = fs.createWriteStream(filePath); const writeStream = fs.createWriteStream(filePath);
const lineBreak = '\r\n'; const lineBreak = '\r\n';
let totalSize = 0; let totalSize = 0;

View File

@ -5,7 +5,6 @@ import { globalBeforeEach } from '../../../__jest__/before-each';
import { getTempDir } from '../../../common/electron-helpers'; import { getTempDir } from '../../../common/electron-helpers';
import * as network from '../../network'; import * as network from '../../network';
import getToken from '../grant-authorization-code'; import getToken from '../grant-authorization-code';
import { createBWRedirectMock } from './helpers';
// Mock some test things // Mock some test things
const AUTHORIZE_URL = 'https://foo.com/authorizeAuthCode'; const AUTHORIZE_URL = 'https://foo.com/authorizeAuthCode';
@ -22,7 +21,7 @@ describe('authorization_code', () => {
beforeEach(globalBeforeEach); beforeEach(globalBeforeEach);
it('gets token with JSON and basic auth', async () => { it('gets token with JSON and basic auth', async () => {
createBWRedirectMock({ redirectTo: `${REDIRECT_URI}?code=code_123&state=${STATE}` }); window.main = { authorizeUserInWindow: () => Promise.resolve(`${REDIRECT_URI}?code=code_123&state=${STATE}`) };
const bodyPath = path.join(getTempDir(), 'foo.response'); const bodyPath = path.join(getTempDir(), 'foo.response');
fs.writeFileSync( fs.writeFileSync(
bodyPath, bodyPath,
@ -130,7 +129,7 @@ describe('authorization_code', () => {
}); });
it('gets token with urlencoded and body auth', async () => { it('gets token with urlencoded and body auth', async () => {
createBWRedirectMock({ redirectTo: `${REDIRECT_URI}?code=code_123&state=${STATE}` }); window.main = { authorizeUserInWindow: () => Promise.resolve(`${REDIRECT_URI}?code=code_123&state=${STATE}`) };
const bodyPath = path.join(getTempDir(), 'foo.response'); const bodyPath = path.join(getTempDir(), 'foo.response');
fs.writeFileSync( fs.writeFileSync(
bodyPath, bodyPath,
@ -242,7 +241,7 @@ describe('authorization_code', () => {
}); });
it('uses PKCE', async () => { it('uses PKCE', async () => {
createBWRedirectMock({ redirectTo: `${REDIRECT_URI}?code=code_123&state=${STATE}` }); window.main = { authorizeUserInWindow: () => Promise.resolve(`${REDIRECT_URI}?code=code_123&state=${STATE}`) };
const bodyPath = path.join(getTempDir(), 'foo.response'); const bodyPath = path.join(getTempDir(), 'foo.response');
fs.writeFileSync( fs.writeFileSync(
bodyPath, bodyPath,

View File

@ -1,6 +1,5 @@
import { globalBeforeEach } from '../../../__jest__/before-each'; import { globalBeforeEach } from '../../../__jest__/before-each';
import getToken from '../grant-implicit'; import getToken from '../grant-implicit';
import { createBWRedirectMock } from './helpers';
// Mock some test things // Mock some test things
const AUTHORIZE_URL = 'https://foo.com/authorizeAuthCode'; const AUTHORIZE_URL = 'https://foo.com/authorizeAuthCode';
const CLIENT_ID = 'client_123'; const CLIENT_ID = 'client_123';
@ -13,7 +12,8 @@ describe('implicit', () => {
beforeEach(globalBeforeEach); beforeEach(globalBeforeEach);
it('works in default case', async () => { it('works in default case', async () => {
createBWRedirectMock({ redirectTo: `${REDIRECT_URI}#access_token=token_123&state=${STATE}&foo=bar` }); window.main = { authorizeUserInWindow: () => Promise.resolve(`${REDIRECT_URI}#access_token=token_123&state=${STATE}&foo=bar`) };
const result = await getToken(AUTHORIZE_URL, CLIENT_ID, REDIRECT_URI, SCOPE, STATE, AUDIENCE); const result = await getToken(AUTHORIZE_URL, CLIENT_ID, REDIRECT_URI, SCOPE, STATE, AUDIENCE);
expect(result).toEqual({ expect(result).toEqual({
access_token: 'token_123', access_token: 'token_123',

View File

@ -10,7 +10,7 @@ export function createBWRedirectMock({
redirectTo, redirectTo,
setCertificateVerifyProc = () => {}, setCertificateVerifyProc = () => {},
}: Options) { }: Options) {
electron.remote.BrowserWindow = jest.fn(function() { electron.BrowserWindow = jest.fn(function() {
this._emitter = new EventEmitter(); this._emitter = new EventEmitter();
this.loadURL = () => this.webContents.emit('did-navigate'); this.loadURL = () => this.webContents.emit('did-navigate');

View File

@ -64,6 +64,8 @@ describe('authorizeUserInWindow()', () => {
const getCertificateVerifyCallbackMock = () => { const getCertificateVerifyCallbackMock = () => {
const mockCallback = mocked<(verificationResult: number) => void>(jest.fn()); const mockCallback = mocked<(verificationResult: number) => void>(jest.fn());
window.main = { authorizeUserInWindow: () => Promise.resolve(MOCK_AUTHORIZATION_URL) };
createBWRedirectMock({ createBWRedirectMock({
redirectTo: MOCK_AUTHORIZATION_URL, redirectTo: MOCK_AUTHORIZATION_URL,
setCertificateVerifyProc: proc => { setCertificateVerifyProc: proc => {
@ -86,7 +88,7 @@ describe('authorizeUserInWindow()', () => {
// Act // Act
// We don't really care about the result here, since we're only testing an event handler. // We don't really care about the result here, since we're only testing an event handler.
await authorizeUserInWindow(MOCK_AUTHORIZATION_URL, /.*/); await authorizeUserInWindow({ url: MOCK_AUTHORIZATION_URL, urlSuccessRegex: /.*/ });
// Assert // Assert
expect(mockCallback).toHaveBeenCalledWith(ChromiumVerificationResult.USE_CHROMIUM_RESULT); expect(mockCallback).toHaveBeenCalledWith(ChromiumVerificationResult.USE_CHROMIUM_RESULT);
@ -103,7 +105,7 @@ describe('authorizeUserInWindow()', () => {
// Act // Act
// We don't really care about the result here, since we're only testing an event handler. // We don't really care about the result here, since we're only testing an event handler.
await authorizeUserInWindow(MOCK_AUTHORIZATION_URL, /.*/); await authorizeUserInWindow({ url: MOCK_AUTHORIZATION_URL, urlSuccessRegex: /.*/ });
// Assert // Assert
expect(mockCallback).toHaveBeenCalledWith(ChromiumVerificationResult.BLIND_TRUST); expect(mockCallback).toHaveBeenCalledWith(ChromiumVerificationResult.BLIND_TRUST);

View File

@ -7,8 +7,7 @@ import * as models from '../../models/index';
import { getBasicAuthHeader } from '../basic-auth/get-header'; import { getBasicAuthHeader } from '../basic-auth/get-header';
import { sendWithSettings } from '../network'; import { sendWithSettings } from '../network';
import * as c from './constants'; import * as c from './constants';
import { authorizeUserInWindow, responseToObject } from './misc'; import { getOAuthSession, responseToObject } from './misc';
export default async function( export default async function(
requestId: string, requestId: string,
authorizeUrl: string, authorizeUrl: string,
@ -147,9 +146,11 @@ async function _authorize(
// Add query params to URL // Add query params to URL
const qs = buildQueryStringFromParams(params); const qs = buildQueryStringFromParams(params);
const finalUrl = joinUrlAndQueryString(url, qs); const finalUrl = joinUrlAndQueryString(url, qs);
const successRegex = new RegExp(`${escapeRegex(redirectUri)}.*(code=)`, 'i'); const urlSuccessRegex = new RegExp(`${escapeRegex(redirectUri)}.*(code=)`, 'i');
const failureRegex = new RegExp(`${escapeRegex(redirectUri)}.*(error=)`, 'i'); const urlFailureRegex = new RegExp(`${escapeRegex(redirectUri)}.*(error=)`, 'i');
const redirectedTo = await authorizeUserInWindow(finalUrl, successRegex, failureRegex); const sessionId = getOAuthSession();
const redirectedTo = await window.main.authorizeUserInWindow({ url: finalUrl, urlSuccessRegex, urlFailureRegex, sessionId });
console.log('[oauth2] Detected redirect ' + redirectedTo); console.log('[oauth2] Detected redirect ' + redirectedTo);
const { query } = urlParse(redirectedTo); const { query } = urlParse(redirectedTo);
return responseToObject(query, [ return responseToObject(query, [

View File

@ -1,7 +1,7 @@
import { buildQueryStringFromParams, joinUrlAndQueryString } from 'insomnia-url'; import { buildQueryStringFromParams, joinUrlAndQueryString } from 'insomnia-url';
import * as c from './constants'; import * as c from './constants';
import { authorizeUserInWindow, responseToObject } from './misc'; import { getOAuthSession, responseToObject } from './misc';
export default async function( export default async function(
_requestId: string, _requestId: string,
@ -60,11 +60,11 @@ export default async function(
// Add query params to URL // Add query params to URL
const qs = buildQueryStringFromParams(params); const qs = buildQueryStringFromParams(params);
const finalUrl = joinUrlAndQueryString(authorizationUrl, qs); const finalUrl = joinUrlAndQueryString(authorizationUrl, qs);
const redirectedTo = await authorizeUserInWindow( const urlSuccessRegex = /(access_token=|id_token=)/;
finalUrl, const urlFailureRegex = /(error=)/;
/(access_token=|id_token=)/, const sessionId = getOAuthSession();
/(error=)/, const redirectedTo = await window.main.authorizeUserInWindow({ url: finalUrl, urlSuccessRegex, urlFailureRegex, sessionId });
);
const fragment = redirectedTo.split('#')[1]; const fragment = redirectedTo.split('#')[1];
if (fragment) { if (fragment) {

View File

@ -1,4 +1,4 @@
import electron from 'electron'; import { BrowserWindow } from 'electron';
import querystring from 'querystring'; import querystring from 'querystring';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
@ -9,20 +9,17 @@ export enum ChromiumVerificationResult {
USE_CHROMIUM_RESULT = -3 USE_CHROMIUM_RESULT = -3
} }
const LOCALSTORAGE_KEY_SESSION_ID = 'insomnia::current-oauth-session-id'; export const LOCALSTORAGE_KEY_SESSION_ID = 'insomnia::current-oauth-session-id';
let authWindowSessionId; export function getOAuthSession(): string {
const token = window.localStorage.getItem(LOCALSTORAGE_KEY_SESSION_ID);
if (window.localStorage.getItem(LOCALSTORAGE_KEY_SESSION_ID)) { return token || initNewOAuthSession();
authWindowSessionId = window.localStorage.getItem(LOCALSTORAGE_KEY_SESSION_ID);
} else {
initNewOAuthSession();
} }
export function initNewOAuthSession() { export function initNewOAuthSession() {
// the value of this variable needs to start with 'persist:' // the value of this variable needs to start with 'persist:'
// otherwise sessions won't be persisted over application-restarts // otherwise sessions won't be persisted over application-restarts
authWindowSessionId = `persist:oauth2_${uuid.v4()}`; const authWindowSessionId = `persist:oauth2_${uuid.v4()}`;
window.localStorage.setItem(LOCALSTORAGE_KEY_SESSION_ID, authWindowSessionId); window.localStorage.setItem(LOCALSTORAGE_KEY_SESSION_ID, authWindowSessionId);
return authWindowSessionId;
} }
export function responseToObject(body, keys, defaults = {}) { export function responseToObject(body, keys, defaults = {}) {
@ -60,11 +57,12 @@ export function responseToObject(body, keys, defaults = {}) {
return results; return results;
} }
export function authorizeUserInWindow( export function authorizeUserInWindow({
url, url,
urlSuccessRegex = /(code=).*/, urlSuccessRegex = /(code=).*/,
urlFailureRegex = /(error=).*/, urlFailureRegex = /(error=).*/,
) { sessionId,
}) {
return new Promise<string>(async (resolve, reject) => { return new Promise<string>(async (resolve, reject) => {
let finalUrl: string | null = null; let finalUrl: string | null = null;
@ -74,10 +72,10 @@ export function authorizeUserInWindow(
} = await models.settings.getOrCreate(); } = await models.settings.getOrCreate();
// Create a child window // Create a child window
const child = new electron.remote.BrowserWindow({ const child = new BrowserWindow({
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
partition: authWindowSessionId, partition: sessionId,
}, },
show: false, show: false,
}); });

View File

@ -146,7 +146,7 @@ export function init(renderPurpose: RenderPurpose = RENDER_PURPOSE_GENERAL): {
getPath(name: string) { getPath(name: string) {
switch (name.toLowerCase()) { switch (name.toLowerCase()) {
case 'desktop': case 'desktop':
return electron.remote.app.getPath('desktop'); return window.app.getPath('desktop');
default: default:
throw new Error(`Unknown path name ${name}`); throw new Error(`Unknown path name ${name}`);
@ -172,7 +172,7 @@ export function init(renderPurpose: RenderPurpose = RENDER_PURPOSE_GENERAL): {
buttonLabel: 'Save', buttonLabel: 'Save',
defaultPath: options.defaultPath, defaultPath: options.defaultPath,
}; };
const { filePath } = await electron.remote.dialog.showSaveDialog( const { filePath } = await window.dialog.showSaveDialog(
saveOptions saveOptions
); );
return filePath || null; return filePath || null;

View File

@ -56,10 +56,11 @@ export default async function(lookupName: string) {
mkdirp.sync(pluginDir); mkdirp.sync(pluginDir);
// Download the module // Download the module
const request = electron.remote.net.request(info.dist.tarball); const request = electron.net.request(info.dist.tarball);
request.on('error', err => { request.on('error', err => {
reject(new Error(`Failed to make plugin request ${info?.dist.tarball}: ${err.message}`)); reject(new Error(`Failed to make plugin request ${info?.dist.tarball}: ${err.message}`));
}); });
const { tmpDir } = await _installPluginToTmpDir(lookupName); const { tmpDir } = await _installPluginToTmpDir(lookupName);
console.log(`[plugins] Moving plugin from ${tmpDir} to ${pluginDir}`); console.log(`[plugins] Moving plugin from ${tmpDir} to ${pluginDir}`);
@ -247,7 +248,7 @@ export function isDeprecatedDependencies(str: string) {
} }
function _getYarnPath() { function _getYarnPath() {
const { app } = electron.remote || electron; const { app } = process.type === 'renderer' ? window : electron;
// TODO: This is brittle. Make finding this more robust. // TODO: This is brittle. Make finding this more robust.
if (isDevelopment()) { if (isDevelopment()) {

View File

@ -0,0 +1,32 @@
const { contextBridge, ipcRenderer } = require('electron');
const main = {
restart: () => ipcRenderer.send('restart'),
authorizeUserInWindow: options => ipcRenderer.invoke('authorizeUserInWindow', options),
setMenuBarVisibility: options => ipcRenderer.send('setMenuBarVisibility', options),
installPlugin: options => ipcRenderer.invoke('installPlugin', options),
};
const dialog = {
showOpenDialog: options => ipcRenderer.invoke('showOpenDialog', options),
showSaveDialog: options => ipcRenderer.invoke('showSaveDialog', options),
};
const app = {
getPath: options => ipcRenderer.sendSync('getPath', options),
getAppPath: options => ipcRenderer.sendSync('getAppPath', options),
};
const shell = {
showItemInFolder: options => ipcRenderer.send('showItemInFolder', options),
};
// if (process.contextIsolated) { TODO: use if rather than try after upgrading to electron 13
try {
contextBridge.exposeInMainWorld('main', main);
contextBridge.exposeInMainWorld('dialog', dialog);
contextBridge.exposeInMainWorld('app', app);
contextBridge.exposeInMainWorld('shell', shell);
} catch {
window.main = main;
window.dialog = dialog;
window.app = app;
window.shell = shell;
}

View File

@ -1,5 +1,4 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator'; import { autoBindMethodsForReact } from 'class-autobind-decorator';
import electron from 'electron';
import fs from 'fs'; import fs from 'fs';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
@ -26,7 +25,7 @@ export class FileEditor extends PureComponent<Props> {
render() { render() {
const { path } = this.props; const { path } = this.props;
// Replace home path with ~/ to make the path shorter // Replace home path with ~/ to make the path shorter
const homeDirectory = electron.remote.app.getPath('home'); const homeDirectory = window.app.getPath('home');
const pathDescription = path.replace(homeDirectory, '~'); const pathDescription = path.replace(homeDirectory, '~');
let sizeDescription = ''; let sizeDescription = '';

View File

@ -3,7 +3,7 @@ import classnames from 'classnames';
import { EditorFromTextArea, LintOptions, ShowHintOptions, TextMarker } from 'codemirror'; import { EditorFromTextArea, LintOptions, ShowHintOptions, TextMarker } from 'codemirror';
import { GraphQLInfoOptions } from 'codemirror-graphql/info'; import { GraphQLInfoOptions } from 'codemirror-graphql/info';
import { ModifiedGraphQLJumpOptions } from 'codemirror-graphql/jump'; import { ModifiedGraphQLJumpOptions } from 'codemirror-graphql/jump';
import electron, { OpenDialogOptions } from 'electron'; import { OpenDialogOptions } from 'electron';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { DefinitionNode, DocumentNode, GraphQLNonNull, GraphQLSchema, NonNullTypeNode, OperationDefinitionNode } from 'graphql'; import { DefinitionNode, DocumentNode, GraphQLNonNull, GraphQLSchema, NonNullTypeNode, OperationDefinitionNode } from 'graphql';
import { parse, typeFromAST } from 'graphql'; import { parse, typeFromAST } from 'graphql';
@ -362,7 +362,7 @@ export class GraphQLEditor extends PureComponent<Props, State> {
], ],
}; };
const { canceled, filePaths } = await electron.remote.dialog.showOpenDialog(options); const { canceled, filePaths } = await window.dialog.showOpenDialog(options);
if (canceled) { if (canceled) {
return; return;

View File

@ -1,6 +1,6 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator'; import { autoBindMethodsForReact } from 'class-autobind-decorator';
import classnames from 'classnames'; import classnames from 'classnames';
import { clipboard, remote } from 'electron'; import { clipboard } from 'electron';
import fs from 'fs'; import fs from 'fs';
import { HotKeyRegistry } from 'insomnia-common'; import { HotKeyRegistry } from 'insomnia-common';
import { json as jsonPrettify } from 'insomnia-prettify'; import { json as jsonPrettify } from 'insomnia-prettify';
@ -85,7 +85,7 @@ export class ResponsePane extends PureComponent<Props> {
const { contentType } = response; const { contentType } = response;
const extension = mime.extension(contentType) || 'unknown'; const extension = mime.extension(contentType) || 'unknown';
const { canceled, filePath: outputPath } = await remote.dialog.showSaveDialog({ const { canceled, filePath: outputPath } = await window.dialog.showSaveDialog({
title: 'Save Response Body', title: 'Save Response Body',
buttonLabel: 'Save', buttonLabel: 'Save',
defaultPath: `${request.name.replace(/ +/g, '_')}-${Date.now()}.${extension}`, defaultPath: `${request.name.replace(/ +/g, '_')}-${Date.now()}.${extension}`,
@ -140,7 +140,7 @@ export class ResponsePane extends PureComponent<Props> {
.map(v => v.value) .map(v => v.value)
.join(''); .join('');
const { canceled, filePath } = await remote.dialog.showSaveDialog({ const { canceled, filePath } = await window.dialog.showSaveDialog({
title: 'Save Full Response', title: 'Save Full Response',
buttonLabel: 'Save', buttonLabel: 'Save',
defaultPath: `${request.name.replace(/ +/g, '_')}-${Date.now()}.txt`, defaultPath: `${request.name.replace(/ +/g, '_')}-${Date.now()}.txt`,
@ -192,7 +192,7 @@ export class ResponsePane extends PureComponent<Props> {
const data = await exportHarCurrentRequest(request, response); const data = await exportHarCurrentRequest(request, response);
const har = JSON.stringify(data, null, '\t'); const har = JSON.stringify(data, null, '\t');
const { filePath } = await remote.dialog.showSaveDialog({ const { filePath } = await window.dialog.showSaveDialog({
title: 'Export As HAR', title: 'Export As HAR',
buttonLabel: 'Save', buttonLabel: 'Save',
defaultPath: `${request.name.replace(/ +/g, '_')}-${Date.now()}.har`, defaultPath: `${request.name.replace(/ +/g, '_')}-${Date.now()}.har`,

View File

@ -1,5 +1,5 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator'; import { autoBindMethodsForReact } from 'class-autobind-decorator';
import { OpenDialogOptions, remote } from 'electron'; import { OpenDialogOptions } from 'electron';
import { HotKeyRegistry } from 'insomnia-common'; import { HotKeyRegistry } from 'insomnia-common';
import React, { PureComponent, ReactNode } from 'react'; import React, { PureComponent, ReactNode } from 'react';
@ -123,7 +123,7 @@ export class RequestUrlBar extends PureComponent<Props, State> {
buttonLabel: 'Select', buttonLabel: 'Select',
properties: ['openDirectory'], properties: ['openDirectory'],
}; };
const { canceled, filePaths } = await remote.dialog.showOpenDialog(options); const { canceled, filePaths } = await window.dialog.showOpenDialog(options);
if (canceled) { if (canceled) {
return; return;

View File

@ -1,5 +1,4 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator'; import { autoBindMethodsForReact } from 'class-autobind-decorator';
import * as electron from 'electron';
import { PluginConfig } from 'insomnia-common'; import { PluginConfig } from 'insomnia-common';
import { Button, ToggleSwitch } from 'insomnia-components'; import { Button, ToggleSwitch } from 'insomnia-components';
import * as path from 'path'; import * as path from 'path';
@ -18,7 +17,6 @@ import type { Settings } from '../../../models/settings';
import { createPlugin } from '../../../plugins/create'; import { createPlugin } from '../../../plugins/create';
import type { Plugin } from '../../../plugins/index'; import type { Plugin } from '../../../plugins/index';
import { getPlugins } from '../../../plugins/index'; import { getPlugins } from '../../../plugins/index';
import installPlugin from '../../../plugins/install';
import { reload } from '../../../templating/index'; import { reload } from '../../../templating/index';
import { CopyButton } from '../base/copy-button'; import { CopyButton } from '../base/copy-button';
import { Link } from '../base/link'; import { Link } from '../base/link';
@ -76,7 +74,7 @@ export class Plugins extends PureComponent<Props, State> {
}; };
try { try {
await installPlugin(this.state.npmPluginValue.trim()); await window.main.installPlugin(this.state.npmPluginValue.trim());
await this._handleRefreshPlugins(); await this._handleRefreshPlugins();
newState.npmPluginValue = ''; // Clear input if successful install newState.npmPluginValue = ''; // Clear input if successful install
} catch (err) { } catch (err) {
@ -88,7 +86,7 @@ export class Plugins extends PureComponent<Props, State> {
} }
static _handleOpenDirectory(directory: string) { static _handleOpenDirectory(directory: string) {
electron.remote.shell.showItemInFolder(directory); window.shell.showItemInFolder(directory);
} }
async _handleRefreshPlugins() { async _handleRefreshPlugins() {
@ -116,7 +114,7 @@ export class Plugins extends PureComponent<Props, State> {
} }
static _handleClickShowPluginsFolder() { static _handleClickShowPluginsFolder() {
electron.remote.shell.showItemInFolder(PLUGIN_PATH); window.shell.showItemInFolder(PLUGIN_PATH);
} }
_handleCreatePlugin() { _handleCreatePlugin() {

View File

@ -1,5 +1,5 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator'; import { autoBindMethodsForReact } from 'class-autobind-decorator';
import electron, { SaveDialogOptions } from 'electron'; import { SaveDialogOptions } from 'electron';
import fs from 'fs'; import fs from 'fs';
import mimes from 'mime-types'; import mimes from 'mime-types';
import moment from 'moment'; import moment from 'moment';
@ -132,7 +132,7 @@ export class ResponseMultipartViewer extends PureComponent<Props, State> {
const contentType = getContentTypeFromHeaders(part.headers, 'text/plain'); const contentType = getContentTypeFromHeaders(part.headers, 'text/plain');
const extension = mimes.extension(contentType) || '.txt'; const extension = mimes.extension(contentType) || '.txt';
const lastDir = window.localStorage.getItem('insomnia.lastExportPath'); const lastDir = window.localStorage.getItem('insomnia.lastExportPath');
const dir = lastDir || electron.remote.app.getPath('desktop'); const dir = lastDir || window.app.getPath('desktop');
const date = moment().format('YYYY-MM-DD'); const date = moment().format('YYYY-MM-DD');
const filename = part.filename || `${part.name}_${date}`; const filename = part.filename || `${part.name}_${date}`;
const options: SaveDialogOptions = { const options: SaveDialogOptions = {
@ -146,7 +146,7 @@ export class ResponseMultipartViewer extends PureComponent<Props, State> {
}, },
], ],
}; };
const { canceled, filePath } = await electron.remote.dialog.showSaveDialog(options); const { canceled, filePath } = await window.dialog.showSaveDialog(options);
if (canceled) { if (canceled) {
return; return;

View File

@ -1,6 +1,4 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator'; import { autoBindMethodsForReact } from 'class-autobind-decorator';
import contextMenu from 'electron-context-menu';
import { EventEmitter } from 'events';
import React, { createRef, PureComponent } from 'react'; import React, { createRef, PureComponent } from 'react';
import { AUTOBIND_CFG } from '../../../common/constants'; import { AUTOBIND_CFG } from '../../../common/constants';
@ -37,11 +35,6 @@ export class ResponseWebView extends PureComponent<Props> {
this.webview.current.removeEventListener('dom-ready', this._handleDOMReady); this.webview.current.removeEventListener('dom-ready', this._handleDOMReady);
contextMenu({
// @ts-expect-error -- TSCONVERSION type mismatch
window: this.webview.current,
});
this._setBody(); this._setBody();
} }
@ -59,17 +52,11 @@ export class ResponseWebView extends PureComponent<Props> {
// //
// https://github.com/electron/electron/issues/20700 // https://github.com/electron/electron/issues/20700
// //
// webview.loadURL(`data:${contentType},${encodeURIComponent(body)}`, { // this.webview.current.loadURL(`data:${contentType},${encodeURIComponent(body)}`, {
// baseURLForDataURL: url, // baseURLForDataURL: url,
// }); // });
// @ts-expect-error -- TSCONVERSION type mismatch // @ts-expect-error -- TSCONVERSION type mismatch
this.webview.current.loadURL(`data:${contentType},${encodeURIComponent(bodyWithBase)}`); this.webview.current.loadURL(`data:${contentType},${encodeURIComponent(bodyWithBase)}`);
// This is kind of hacky but electron-context-menu fails to save images if this isn't here.
// @ts-expect-error -- TSCONVERSION type mismatch
this.webview.current.webContents = this.webview.current;
// @ts-expect-error -- TSCONVERSION type mismatch
this.webview.current.webContents.session = new EventEmitter();
} }
render() { render() {

View File

@ -1,5 +1,5 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator'; import { autoBindMethodsForReact } from 'class-autobind-decorator';
import { clipboard, ipcRenderer, remote, SaveDialogOptions } from 'electron'; import { clipboard, ipcRenderer, SaveDialogOptions } from 'electron';
import fs from 'fs'; import fs from 'fs';
import HTTPSnippet from 'httpsnippet'; import HTTPSnippet from 'httpsnippet';
import * as mime from 'mime-types'; import * as mime from 'mime-types';
@ -681,7 +681,7 @@ class App extends PureComponent<AppProps, State> {
options.defaultPath = defaultPath; options.defaultPath = defaultPath;
} }
const { filePath } = await remote.dialog.showSaveDialog(options); const { filePath } = await window.dialog.showSaveDialog(options);
// @ts-expect-error -- TSCONVERSION don't set item if filePath is undefined // @ts-expect-error -- TSCONVERSION don't set item if filePath is undefined
window.localStorage.setItem('insomnia.sendAndDownloadLocation', filePath); window.localStorage.setItem('insomnia.sendAndDownloadLocation', filePath);
return filePath || null; return filePath || null;

View File

@ -2,7 +2,6 @@ import { useEffect, useLayoutEffect } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { usePrevious } from 'react-use'; import { usePrevious } from 'react-use';
import { restartApp, setMenuBarVisibility } from '../../common/electron-helpers';
import { Settings } from '../../models/settings'; import { Settings } from '../../models/settings';
import { selectSettings } from '../redux/selectors'; import { selectSettings } from '../redux/selectors';
@ -22,7 +21,7 @@ const useRestartSetting = (setting: keyof Settings) => {
return; return;
} }
restartApp(); window.main.restart();
}, [nextValue, previousValue]); }, [nextValue, previousValue]);
}; };
@ -49,7 +48,7 @@ export const useSettingsSideEffects = () => {
}, [settings.fontSize]); }, [settings.fontSize]);
useEffect(() => { useEffect(() => {
setMenuBarVisibility(!settings.autoHideMenuBar); window.main.setMenuBarVisibility(!settings.autoHideMenuBar);
}, [settings.autoHideMenuBar]); }, [settings.autoHideMenuBar]);
useRestartSetting('nunjucksPowerUserMode'); useRestartSetting('nunjucksPowerUserMode');

View File

@ -1,4 +1,3 @@
import electron from 'electron';
import fs, { NoParamCallback } from 'fs'; import fs, { NoParamCallback } from 'fs';
import moment from 'moment'; import moment from 'moment';
import path from 'path'; import path from 'path';
@ -30,7 +29,6 @@ import { Request } from '../../../models/request';
import { isWorkspace } from '../../../models/workspace'; import { isWorkspace } from '../../../models/workspace';
import { reloadPlugins } from '../../../plugins'; import { reloadPlugins } from '../../../plugins';
import { createPlugin } from '../../../plugins/create'; import { createPlugin } from '../../../plugins/create';
import install from '../../../plugins/install';
import { setTheme } from '../../../plugins/misc'; import { setTheme } from '../../../plugins/misc';
import { AskModal } from '../../../ui/components/modals/ask-modal'; import { AskModal } from '../../../ui/components/modals/ask-modal';
import { AlertModal } from '../../components/modals/alert-modal'; import { AlertModal } from '../../components/modals/alert-modal';
@ -240,7 +238,7 @@ export const newCommand = (command: string, args: any) => async (dispatch: Dispa
} }
try { try {
await install(args.name); await window.main.installPlugin(args.name);
showModal(SettingsModal, TAB_INDEX_PLUGINS); showModal(SettingsModal, TAB_INDEX_PLUGINS);
} catch (err) { } catch (err) {
showError({ showError({
@ -416,13 +414,13 @@ const showSaveExportedFileDialog = async ({
const date = moment().format('YYYY-MM-DD'); const date = moment().format('YYYY-MM-DD');
const name = exportedFileNamePrefix.replace(/ /g, '-'); const name = exportedFileNamePrefix.replace(/ /g, '-');
const lastDir = window.localStorage.getItem('insomnia.lastExportPath'); const lastDir = window.localStorage.getItem('insomnia.lastExportPath');
const dir = lastDir || electron.remote.app.getPath('desktop'); const dir = lastDir || window.app.getPath('desktop');
const options = { const options = {
title: 'Export Insomnia Data', title: 'Export Insomnia Data',
buttonLabel: 'Export', buttonLabel: 'Export',
defaultPath: `${path.join(dir, `${name}_${date}`)}.${selectedFormat}`, defaultPath: `${path.join(dir, `${name}_${date}`)}.${selectedFormat}`,
}; };
const { filePath } = await electron.remote.dialog.showSaveDialog(options); const { filePath } = await window.dialog.showSaveDialog(options);
return filePath || null; return filePath || null;
}; };

View File

@ -89,7 +89,7 @@ export const importFile = (
}, },
], ],
}; };
const { canceled, filePaths } = await electron.remote.dialog.showOpenDialog(openDialogOptions); const { canceled, filePaths } = await window.dialog.showOpenDialog(openDialogOptions);
if (canceled) { if (canceled) {
// It was cancelled, so let's bail out // It was cancelled, so let's bail out

View File

@ -11244,13 +11244,13 @@
} }
}, },
"electron-context-menu": { "electron-context-menu": {
"version": "2.5.2", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.5.2.tgz", "resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-3.1.1.tgz",
"integrity": "sha512-1cEQR6fA9ktFsRBc+eXPwvrOgAPytUD7rUV4iBAA5zTrLAPKokJ23xeMjcK2fjrDPrlFRBxcLz0KP+GUhMrSCQ==", "integrity": "sha512-LJhwaKf6XHwk2LQ5SdwoGNODoA8lRwks9bbEeAqqMf4e3hsrT7pZtX6MaHKYNFZKxF14JjI/VR+VRjGvxmaQoA==",
"requires": { "requires": {
"cli-truncate": "^2.1.0", "cli-truncate": "^2.1.0",
"electron-dl": "^3.1.0", "electron-dl": "^3.2.1",
"electron-is-dev": "^1.2.0" "electron-is-dev": "^2.0.0"
} }
}, },
"electron-devtools-installer": { "electron-devtools-installer": {
@ -11293,9 +11293,9 @@
} }
}, },
"electron-is-dev": { "electron-is-dev": {
"version": "1.2.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-1.2.0.tgz", "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-2.0.0.tgz",
"integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" "integrity": "sha512-3X99K852Yoqu9AcW50qz3ibYBWY79/pBhlMCab8ToEWS48R0T9tyxRiQhwylE7zQdXrMnx2JKqUJyMPmt5FBqA=="
}, },
"electron-log": { "electron-log": {
"version": "4.4.3", "version": "4.4.3",

View File

@ -91,7 +91,7 @@
"codemirror": "^5.62.3", "codemirror": "^5.62.3",
"codemirror-graphql": "^1.0.2", "codemirror-graphql": "^1.0.2",
"color": "^3.1.2", "color": "^3.1.2",
"electron-context-menu": "^2.5.2", "electron-context-menu": "^3.1.1",
"electron-log": "^4.4.3", "electron-log": "^4.4.3",
"electron-updater": "^4.6.1", "electron-updater": "^4.6.1",
"framer-motion": "^1.11.1", "framer-motion": "^1.11.1",

View File

@ -30,7 +30,7 @@ if (process.env.NODE_ENV === 'development') {
plugins = productionConfig.plugins; plugins = productionConfig.plugins;
} }
const configuration: Configuration = { const configuration: Configuration[] = [{
...productionConfig, ...productionConfig,
devtool, devtool,
entry: ['./main.development.ts'], entry: ['./main.development.ts'],
@ -40,6 +40,14 @@ const configuration: Configuration = {
}, },
target: 'electron-main', target: 'electron-main',
plugins, plugins,
}; },
{
entry: './app/preload.js',
target: 'electron-preload',
output: {
path: path.join(__dirname, '../build'),
filename: 'preload.js',
},
}];
export default configuration; export default configuration;