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) {
await fn(changes);
}
// 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) {
window.webContents.send('db.changes', changes);
for (const window of windows) {
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 function getDataDirectory() {
const { app } = electron.remote || electron;
const { app } = process.type === 'renderer' ? window : electron;
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() {
// 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}`);
mkdirp.sync(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.
* 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';
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 = {
filePath: filePaths[0],
canceled,

View File

@ -22,6 +22,23 @@ declare namespace NodeJS {
interface Window {
__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

View File

@ -7,7 +7,7 @@ import appConfig from '../config/config.json';
import { SegmentEvent, trackSegmentEvent } from './common/analytics';
import { changelogUrl, getAppVersion, isDevelopment, isMac } from './common/constants';
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 { validateInsomniaConfig } from './common/validate-insomnia-config';
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 models from './models/index';
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';
// Handle potential auto-update
@ -41,7 +43,14 @@ if (!isDevelopment()) {
// So if (window) checks don't throw
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
app.on('ready', async () => {
@ -50,7 +59,7 @@ app.on('ready', async () => {
if (error) {
electron.dialog.showErrorBox(error.title, error.message);
console.log('[config] Insomnia config is invalid, preventing app initialization');
exitAppFailure();
app.exit(1);
return;
}
@ -203,6 +212,53 @@ async function _trackStats() {
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', () => {
const { currentVersion, launches, lastVersion } = stats;

View File

@ -17,7 +17,7 @@ import {
MNEMONIC_SYM,
} from '../common/constants';
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 LocalStorage from './local-storage';
@ -85,10 +85,10 @@ export function createWindow() {
acceptFirstMouse: true,
icon: path.resolve(__dirname, appLogo),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
zoomFactor: zoomFactor,
nodeIntegration: true,
webviewTag: true,
enableRemoteModule: true,
// TODO: enable context isolation
contextIsolation: false,
disableBlinkFeatures: 'Auxclick',
@ -482,7 +482,7 @@ export function createWindow() {
},
{
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 path from 'path';
import { globalBeforeEach } from '../../__jest__/before-each';
import { buildMultipart, DEFAULT_BOUNDARY } from '../multipart';
window.app = electron.app;
describe('buildMultipart()', () => {
beforeEach(globalBeforeEach);

View File

@ -1,4 +1,5 @@
import { CurlHttpVersion } from '@getinsomnia/node-libcurl';
import electron from 'electron';
import fs from 'fs';
import { HttpVersions } from 'insomnia-common';
import { join as pathJoin, resolve as pathResolve } from 'path';
@ -18,6 +19,7 @@ import { getRenderedRequestAndContext } from '../../common/render';
import * as models from '../../models';
import { DEFAULT_BOUNDARY } from '../multipart';
import * as networkUtils from '../network';
window.app = electron.app;
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 mimes from 'mime-types';
import path from 'path';
@ -15,7 +14,7 @@ interface Multipart {
export async function buildMultipart(params: RequestBodyParameter[]) {
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 lineBreak = '\r\n';
let totalSize = 0;

View File

@ -5,7 +5,6 @@ import { globalBeforeEach } from '../../../__jest__/before-each';
import { getTempDir } from '../../../common/electron-helpers';
import * as network from '../../network';
import getToken from '../grant-authorization-code';
import { createBWRedirectMock } from './helpers';
// Mock some test things
const AUTHORIZE_URL = 'https://foo.com/authorizeAuthCode';
@ -22,7 +21,7 @@ describe('authorization_code', () => {
beforeEach(globalBeforeEach);
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');
fs.writeFileSync(
bodyPath,
@ -130,7 +129,7 @@ describe('authorization_code', () => {
});
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');
fs.writeFileSync(
bodyPath,
@ -242,7 +241,7 @@ describe('authorization_code', () => {
});
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');
fs.writeFileSync(
bodyPath,

View File

@ -1,6 +1,5 @@
import { globalBeforeEach } from '../../../__jest__/before-each';
import getToken from '../grant-implicit';
import { createBWRedirectMock } from './helpers';
// Mock some test things
const AUTHORIZE_URL = 'https://foo.com/authorizeAuthCode';
const CLIENT_ID = 'client_123';
@ -13,7 +12,8 @@ describe('implicit', () => {
beforeEach(globalBeforeEach);
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);
expect(result).toEqual({
access_token: 'token_123',

View File

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

View File

@ -64,6 +64,8 @@ describe('authorizeUserInWindow()', () => {
const getCertificateVerifyCallbackMock = () => {
const mockCallback = mocked<(verificationResult: number) => void>(jest.fn());
window.main = { authorizeUserInWindow: () => Promise.resolve(MOCK_AUTHORIZATION_URL) };
createBWRedirectMock({
redirectTo: MOCK_AUTHORIZATION_URL,
setCertificateVerifyProc: proc => {
@ -86,7 +88,7 @@ describe('authorizeUserInWindow()', () => {
// Act
// 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
expect(mockCallback).toHaveBeenCalledWith(ChromiumVerificationResult.USE_CHROMIUM_RESULT);
@ -103,7 +105,7 @@ describe('authorizeUserInWindow()', () => {
// Act
// 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
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 { sendWithSettings } from '../network';
import * as c from './constants';
import { authorizeUserInWindow, responseToObject } from './misc';
import { getOAuthSession, responseToObject } from './misc';
export default async function(
requestId: string,
authorizeUrl: string,
@ -147,9 +146,11 @@ async function _authorize(
// Add query params to URL
const qs = buildQueryStringFromParams(params);
const finalUrl = joinUrlAndQueryString(url, qs);
const successRegex = new RegExp(`${escapeRegex(redirectUri)}.*(code=)`, 'i');
const failureRegex = new RegExp(`${escapeRegex(redirectUri)}.*(error=)`, 'i');
const redirectedTo = await authorizeUserInWindow(finalUrl, successRegex, failureRegex);
const urlSuccessRegex = new RegExp(`${escapeRegex(redirectUri)}.*(code=)`, 'i');
const urlFailureRegex = new RegExp(`${escapeRegex(redirectUri)}.*(error=)`, 'i');
const sessionId = getOAuthSession();
const redirectedTo = await window.main.authorizeUserInWindow({ url: finalUrl, urlSuccessRegex, urlFailureRegex, sessionId });
console.log('[oauth2] Detected redirect ' + redirectedTo);
const { query } = urlParse(redirectedTo);
return responseToObject(query, [

View File

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

View File

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

View File

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

View File

@ -56,10 +56,11 @@ export default async function(lookupName: string) {
mkdirp.sync(pluginDir);
// Download the module
const request = electron.remote.net.request(info.dist.tarball);
const request = electron.net.request(info.dist.tarball);
request.on('error', err => {
reject(new Error(`Failed to make plugin request ${info?.dist.tarball}: ${err.message}`));
});
const { tmpDir } = await _installPluginToTmpDir(lookupName);
console.log(`[plugins] Moving plugin from ${tmpDir} to ${pluginDir}`);
@ -247,7 +248,7 @@ export function isDeprecatedDependencies(str: string) {
}
function _getYarnPath() {
const { app } = electron.remote || electron;
const { app } = process.type === 'renderer' ? window : electron;
// TODO: This is brittle. Make finding this more robust.
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 electron from 'electron';
import fs from 'fs';
import React, { PureComponent } from 'react';
@ -26,7 +25,7 @@ export class FileEditor extends PureComponent<Props> {
render() {
const { path } = this.props;
// 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, '~');
let sizeDescription = '';

View File

@ -3,7 +3,7 @@ import classnames from 'classnames';
import { EditorFromTextArea, LintOptions, ShowHintOptions, TextMarker } from 'codemirror';
import { GraphQLInfoOptions } from 'codemirror-graphql/info';
import { ModifiedGraphQLJumpOptions } from 'codemirror-graphql/jump';
import electron, { OpenDialogOptions } from 'electron';
import { OpenDialogOptions } from 'electron';
import { readFileSync } from 'fs';
import { DefinitionNode, DocumentNode, GraphQLNonNull, GraphQLSchema, NonNullTypeNode, OperationDefinitionNode } 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) {
return;

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import electron, { SaveDialogOptions } from 'electron';
import { SaveDialogOptions } from 'electron';
import fs from 'fs';
import mimes from 'mime-types';
import moment from 'moment';
@ -132,7 +132,7 @@ export class ResponseMultipartViewer extends PureComponent<Props, State> {
const contentType = getContentTypeFromHeaders(part.headers, 'text/plain');
const extension = mimes.extension(contentType) || '.txt';
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 filename = part.filename || `${part.name}_${date}`;
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) {
return;

View File

@ -1,6 +1,4 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import contextMenu from 'electron-context-menu';
import { EventEmitter } from 'events';
import React, { createRef, PureComponent } from 'react';
import { AUTOBIND_CFG } from '../../../common/constants';
@ -37,11 +35,6 @@ export class ResponseWebView extends PureComponent<Props> {
this.webview.current.removeEventListener('dom-ready', this._handleDOMReady);
contextMenu({
// @ts-expect-error -- TSCONVERSION type mismatch
window: this.webview.current,
});
this._setBody();
}
@ -59,17 +52,11 @@ export class ResponseWebView extends PureComponent<Props> {
//
// https://github.com/electron/electron/issues/20700
//
// webview.loadURL(`data:${contentType},${encodeURIComponent(body)}`, {
// this.webview.current.loadURL(`data:${contentType},${encodeURIComponent(body)}`, {
// baseURLForDataURL: url,
// });
// @ts-expect-error -- TSCONVERSION type mismatch
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() {

View File

@ -1,5 +1,5 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import { clipboard, ipcRenderer, remote, SaveDialogOptions } from 'electron';
import { clipboard, ipcRenderer, SaveDialogOptions } from 'electron';
import fs from 'fs';
import HTTPSnippet from 'httpsnippet';
import * as mime from 'mime-types';
@ -681,7 +681,7 @@ class App extends PureComponent<AppProps, State> {
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
window.localStorage.setItem('insomnia.sendAndDownloadLocation', filePath);
return filePath || null;

View File

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

View File

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

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) {
// It was cancelled, so let's bail out

View File

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

View File

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

View File

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