Chore/node-integration-continued (#6106)

* don't import electron

* add clipboard to preload

* remove datadir helper

* clean up helpers

* remove electron-helpers

* revert db path

* delete test

* more helpers

* fix tests

* fix tests

* lint

* fix inso
This commit is contained in:
Jack Kavanagh 2023-07-07 21:30:12 +02:00 committed by GitHub
parent e8c9afb0c8
commit abfabd41cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 106 additions and 241 deletions

View File

@ -16,7 +16,7 @@ module.exports = {
'react/no-find-dom-node': OFF(UNKNOWN),
'no-restricted-properties': [ERROR, {
property: 'openExternal',
message: 'use the `clickLink` function in `electron-helpers.ts` instead. see https://security.stackexchange.com/questions/225799/dangers-of-electrons-shell-openexternal-on-untrusted-content for more information.',
message: 'use the `window.main.openInBrowser` function instead. see https://security.stackexchange.com/questions/225799/dangers-of-electrons-shell-openexternal-on-untrusted-content for more information.',
}],
},
};

View File

@ -1,34 +1,4 @@
const localStorageMock: Storage = (() => {
let store: Record<string, string> = {};
return {
get length() {
return Object.keys(store).length;
},
clear() {
store = {};
},
getItem(key: string) {
return store[key];
},
key() {
return null;
},
removeItem(key: string) {
delete store[key];
},
setItem(key: string, value: string) {
store[key] = value.toString();
},
};
})();
globalThis.__DEV__ = false;
globalThis.localStorage = localStorageMock;
globalThis.requestAnimationFrame = (callback: FrameRequestCallback) => {
process.nextTick(callback);

View File

@ -1,26 +0,0 @@
import { describe, expect, it } from '@jest/globals';
import electron from 'electron';
import { clickLink } from '../electron-helpers';
/* eslint-disable no-restricted-properties */
describe('clickLink', () => {
it('should allow http links', () => {
const url = 'http://mockbin.org';
clickLink(url);
expect(electron.shell.openExternal).toHaveBeenCalledWith(url);
});
it('should allow https links', () => {
const url = 'https://mockbin.org';
clickLink(url);
expect(electron.shell.openExternal).toHaveBeenCalledWith(url);
});
it('should not allow smb links', () => {
const url = 'file:///C:/windows/system32/calc.exe';
clickLink(url);
expect(electron.shell.openExternal).not.toHaveBeenCalledWith(url);
});
});
/* eslint-enable no-restricted-properties */

View File

@ -1,6 +1,5 @@
import appConfig from '../../config/config.json';
import { version } from '../../package.json';
import { getPortableExecutableDir } from './electron-helpers';
import { KeyCombination } from './settings';
const env = process['env'];
@ -50,7 +49,7 @@ export function updatesSupported() {
}
// Updates are not supported for Windows portable binaries
if (isWindows() && getPortableExecutableDir()) {
if (isWindows() && process.env['PORTABLE_EXECUTABLE_DIR']) {
return false;
}

View File

@ -14,7 +14,6 @@ import * as models from '../models/index';
import { isSettings } from '../models/settings';
import type { Workspace } from '../models/workspace';
import { DB_PERSIST_INTERVAL } from './constants';
import { getDataDirectory } from './electron-helpers';
import { generateId } from './misc';
export interface Query {
@ -669,7 +668,7 @@ const allTypes = () => Object.keys(db);
function getDBFilePath(modelType: string) {
// NOTE: Do not EVER change this. EVER!
return fsPath.join(getDataDirectory(), `insomnia.${modelType}.db`);
return fsPath.join(process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData'), `insomnia.${modelType}.db`);
}
// ~~~~~~~~~~~~~~~~ //

View File

@ -1,44 +0,0 @@
import * as electron from 'electron';
import mkdirp from 'mkdirp';
import { join } from 'path';
import { version } from '../../package.json';
export function clickLink(href: string) {
const { protocol } = new URL(href);
if (protocol === 'http:' || protocol === 'https:') {
// eslint-disable-next-line no-restricted-properties -- this is, other than tests, what _should be_ the one and only place in this project where this is called.
electron.shell.openExternal(href);
}
}
/**
* This environment variable is added by electron-builder.
* see: https://www.electron.build/configuration/nsis.html#portable\
*/
export const getPortableExecutableDir = () => process.env['PORTABLE_EXECUTABLE_DIR'];
export function getDataDirectory() {
const { app } = process.type === 'renderer' ? window : electron;
return process.env['INSOMNIA_DATA_PATH'] || app.getPath('userData');
}
export function getTempDir() {
// NOTE: Using a fairly unique name here because "insomnia" is a common word
const { app } = process.type === 'renderer' ? window : electron;
const dir = join(app.getPath('temp'), `insomnia_${version}`);
mkdirp.sync(dir);
return dir;
}
/**
* 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
* On macOS the OS spellchecker is used and therefore we do not download any dictionary files.
* This API is a no-op on macOS.
*/
export const disableSpellcheckerDownload = () => {
electron.session.defaultSession.setSpellCheckerDictionaryDownloadURL(
'https://00.00/'
);
};

View File

@ -8,6 +8,7 @@ declare global {
dialog: Pick<Electron.Dialog, 'showOpenDialog' | 'showSaveDialog'>;
app: Pick<Electron.App, 'getPath' | 'getAppPath'>;
shell: Pick<Electron.Shell, 'showItemInFolder'>;
clipboard: Pick<Electron.Clipboard, 'readText' | 'writeText' | 'clear'>;
}
}

View File

@ -7,7 +7,6 @@ import path from 'path';
import appConfig from '../config/config.json';
import { changelogUrl, getAppVersion, isDevelopment, isMac } from './common/constants';
import { database } from './common/database';
import { disableSpellcheckerDownload } from './common/electron-helpers';
import log, { initializeLogging } from './common/log';
import { validateInsomniaConfig } from './common/validate-insomnia-config';
import { registerElectronHandlers } from './main/ipc/electron';
@ -72,6 +71,17 @@ app.on('ready', async () => {
registergRPCHandlers();
registerWebSocketHandlers();
/**
* 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
* On macOS the OS spellchecker is used and therefore we do not download any dictionary files.
* This API is a no-op on macOS.
*/
const disableSpellcheckerDownload = () => {
electron.session.defaultSession.setSpellCheckerDictionaryDownloadURL(
'https://00.00/'
);
};
disableSpellcheckerDownload();
if (isDevelopment()) {

View File

@ -7,7 +7,6 @@ import mkdirp from 'mkdirp';
import path from 'path';
import { isDevelopment, isWindows } from '../common/constants';
import { getDataDirectory, getTempDir } from '../common/electron-helpers';
const YARN_DEPRECATED_WARN = /(?<keyword>warning)(?<dependencies>[^>:].+[>:])(?<issue>.+)/;
@ -51,7 +50,7 @@ export default async function(lookupName: string) {
info = await _isInsomniaPlugin(lookupName);
// Get actual module name without version suffixes and things
const moduleName = info.name;
const pluginDir = path.join(getDataDirectory(), 'plugins', moduleName);
const pluginDir = path.join(process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData'), 'plugins', moduleName);
// Make plugin directory
mkdirp.sync(pluginDir);
@ -166,7 +165,7 @@ async function _isInsomniaPlugin(lookupName: string) {
async function _installPluginToTmpDir(lookupName: string) {
return new Promise<{ tmpDir: string }>((resolve, reject) => {
const tmpDir = path.join(getTempDir(), `${lookupName}-${Date.now()}`);
const tmpDir = path.join(electron.app.getPath('temp'), `${lookupName}-${Date.now()}`);
mkdirp.sync(tmpDir);
console.log(`[plugins] Installing plugin to ${tmpDir}`);
childProcess.execFile(

View File

@ -1,5 +1,5 @@
import type { OpenDialogOptions, SaveDialogOptions } from 'electron';
import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron';
import { app, BrowserWindow, clipboard, dialog, ipcMain, shell } from 'electron';
export function registerElectronHandlers() {
ipcMain.on('setMenuBarVisibility', (_, visible: boolean) => {
@ -26,6 +26,18 @@ export function registerElectronHandlers() {
shell.showItemInFolder(name);
});
ipcMain.on('readText', event => {
event.returnValue = clipboard.readText();
});
ipcMain.on('writeText', (_, text: string) => {
clipboard.writeText(text);
});
ipcMain.on('clear', () => {
clipboard.clear();
});
ipcMain.on('getPath', (event, name: Parameters<typeof Electron.app['getPath']>[0]) => {
event.returnValue = app.getPath(name);
});

View File

@ -4,7 +4,7 @@ import { Spectral } from '@stoplight/spectral-core';
// @ts-expect-error - This is a bundled file not sure why it's not found
import { bundleAndLoadRuleset } from '@stoplight/spectral-ruleset-bundler/with-loader';
import { oas } from '@stoplight/spectral-rulesets';
import { app, BrowserWindow, ipcMain, IpcRendererEvent } from 'electron';
import { app, BrowserWindow, ipcMain, IpcRendererEvent, shell } from 'electron';
import fs from 'fs';
import { axiosRequest } from '../../network/axios-request';
@ -18,6 +18,7 @@ import { gRPCBridgeAPI } from './grpc';
export interface MainBridgeAPI {
loginStateChange: () => void;
openInBrowser: (url: string) => void;
restart: () => void;
halfSecondAfterAppStart: () => void;
manualUpdateCheck: () => void;
@ -78,11 +79,20 @@ export function registerMainHandlers() {
ipcMain.handle('installPlugin', (_, lookupName: string) => {
return installPlugin(lookupName);
});
ipcMain.on('restart', () => {
app.relaunch();
app.exit();
});
ipcMain.on('openInBrowser', (_, href: string) => {
const { protocol } = new URL(href);
if (protocol === 'http:' || protocol === 'https:') {
// eslint-disable-next-line no-restricted-properties
shell.openExternal(href);
}
});
ipcMain.handle('spectralRun', async (_, { contents, rulesetPath }: {
contents: string;
rulesetPath?: string;

View File

@ -15,7 +15,6 @@ import {
MNEMONIC_SYM,
} from '../common/constants';
import { docsBase } from '../common/documentation';
import { clickLink, getDataDirectory } from '../common/electron-helpers';
import * as log from '../common/log';
import LocalStorage from './local-storage';
@ -112,7 +111,11 @@ export function createWindow() {
console.log('[app] Navigate to ' + url);
event.preventDefault();
clickLink(url);
const { protocol } = new URL(url);
if (protocol === 'http:' || protocol === 'https:') {
// eslint-disable-next-line no-restricted-properties
shell.openExternal(url);
}
});
newWindow?.webContents.setWindowOpenHandler(() => {
@ -153,7 +156,12 @@ export function createWindow() {
return;
}
clickLink(changelogUrl());
const href = changelogUrl();
const { protocol } = new URL(href);
if (protocol === 'http:' || protocol === 'https:') {
// eslint-disable-next-line no-restricted-properties
shell.openExternal(href);
}
},
},
{
@ -335,7 +343,11 @@ export function createWindow() {
label: `${MNEMONIC_SYM}Help and Support`,
...(isMac() ? {} : { accelerator: 'F1' }),
click: () => {
clickLink(docsBase);
const { protocol } = new URL(docsBase);
if (protocol === 'http:' || protocol === 'https:') {
// eslint-disable-next-line no-restricted-properties
shell.openExternal(docsBase);
}
},
},
{
@ -355,7 +367,7 @@ export function createWindow() {
{
label: `Show App ${MNEMONIC_SYM}Data Folder`,
click: () => {
const directory = getDataDirectory();
const directory = process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData');
shell.showItemInFolder(directory);
},
},
@ -379,7 +391,8 @@ export function createWindow() {
{
label: 'Show Software License',
click: () => {
clickLink('https://insomnia.rest/license');
// eslint-disable-next-line no-restricted-properties
shell.openExternal('https://insomnia.rest/license');
},
},
],
@ -633,7 +646,7 @@ export const setZoom = (transformer: (current: number) => number) => () => {
};
function initLocalStorage() {
const localStoragePath = path.join(getDataDirectory(), 'localStorage');
const localStoragePath = path.join(process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData'), 'localStorage');
localStorage = new LocalStorage(localStoragePath);
}

View File

@ -1,17 +1,17 @@
import { beforeEach, describe, expect, it } from '@jest/globals';
import electron from 'electron';
import fs from 'fs';
import path from 'path';
import zlib from 'zlib';
import { globalBeforeEach } from '../../__jest__/before-each';
import { getDataDirectory } from '../../common/electron-helpers';
import * as models from '../../models';
describe('migrate()', () => {
beforeEach(globalBeforeEach);
it('does it', async () => {
const bodyPath = path.join(getDataDirectory(), 'foo.zip');
const bodyPath = path.join(electron.app.getPath('userData'), 'foo.zip');
fs.writeFileSync(bodyPath, zlib.gzipSync('Hello World!'));
const response = await models.initModel(models.response.type, {
bodyPath,

View File

@ -2,7 +2,6 @@ import { afterAll, beforeEach, describe, expect, it, jest } from '@jest/globals'
import { mocked } from 'jest-mock';
import * as _constants from '../../../common/constants';
import * as electronHelpers from '../../../common/electron-helpers';
import { Settings } from '../../../common/settings';
import * as models from '../../../models';
import * as settingsHelpers from '../settings';
@ -42,51 +41,29 @@ describe('getConfigFile', () => {
});
afterAll(jest.resetAllMocks);
it('prioritizes portable config location over all others', () => {
jest.spyOn(electronHelpers, 'getPortableExecutableDir').mockReturnValue('portableExecutable');
jest.spyOn(electronHelpers, 'getDataDirectory').mockReturnValue('insomniaDataDirectory');
jest.spyOn(settingsHelpers, 'getLocalDevConfigFilePath').mockReturnValue('localDev');
const result = getConfigFile();
expect(result.configPath).toContain('portableExecutable');
expect(result.configPath).toContain('insomnia.config.json');
});
it('prioritizes insomnia data directory over local dev when portable config is not found', () => {
jest.spyOn(electronHelpers, 'getPortableExecutableDir').mockReturnValue(undefined);
jest.spyOn(electronHelpers, 'getDataDirectory').mockReturnValue('insomniaDataDirectory');
jest.spyOn(settingsHelpers, 'getLocalDevConfigFilePath').mockReturnValue('localDev');
const result = getConfigFile();
expect(result.configPath).toContain('insomniaDataDirectory');
expect(result.configPath).toContain('insomnia.config.json');
});
it('returns the local dev config file if no others are found', () => {
jest.spyOn(electronHelpers, 'getPortableExecutableDir').mockReturnValue(undefined);
// @ts-expect-error intentionally invalid to simulate the file not being found
jest.spyOn(electronHelpers, 'getDataDirectory').mockReturnValue(undefined);
jest.spyOn(settingsHelpers, 'getLocalDevConfigFilePath').mockReturnValue('localDev');
const result = getConfigFile();
expect(result.configPath).toContain('localDev');
expect(result.configPath).toContain('insomnia.config.json');
});
it('returns an internal fallback if no configs are found (in production mode)', () => {
isDevelopment.mockReturnValue(false);
jest.spyOn(electronHelpers, 'getPortableExecutableDir').mockReturnValue(undefined);
// @ts-expect-error intentionally invalid to simulate the file not being found
jest.spyOn(electronHelpers, 'getDataDirectory').mockReturnValue(undefined);
const result = getConfigFile();
expect(result).toMatchObject({ configPath: '<internal fallback insomnia config>' });
});
});
describe('getControlledStatus', () => {

View File

@ -1,9 +1,9 @@
import electron from 'electron';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import type { ValueOf } from 'type-fest';
import { isDevelopment } from '../../common/constants';
import { getDataDirectory, getPortableExecutableDir } from '../../common/electron-helpers';
import { Settings } from '../../common/settings';
import { INSOMNIA_CONFIG_FILENAME, InsomniaConfig } from '../../utils/config/entities';
import { ErrorResult, isErrorResult, validate } from '../../utils/config/validate';
@ -68,8 +68,8 @@ const addConfigFileToPath = (path: string | undefined) => (
);
export const getConfigFile = () => {
const portableExecutable = getPortableExecutableDir();
const insomniaDataDirectory = getDataDirectory();
const portableExecutable = process.env['PORTABLE_EXECUTABLE_DIR'];
const insomniaDataDirectory = process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData');
const localDev = getLocalDevConfigFilePath();
const configPaths = [
portableExecutable,

View File

@ -1,4 +1,5 @@
import clone from 'clone';
import electron from 'electron';
import fs from 'fs';
import mkdirp from 'mkdirp';
import { join as pathJoin } from 'path';
@ -6,7 +7,6 @@ import { v4 as uuidv4 } from 'uuid';
import { cookiesFromJar, jarFromCookies } from '../common/cookies';
import { database as db } from '../common/database';
import { getDataDirectory } from '../common/electron-helpers';
import {
getContentTypeHeader,
getLocationHeader,
@ -387,7 +387,7 @@ async function _applyResponsePluginHooks(
export function storeTimeline(timeline: ResponseTimelineEntry[]): Promise<string> {
const timelineStr = JSON.stringify(timeline, null, '\t');
const timelineHash = uuidv4();
const responsesDir = pathJoin(getDataDirectory(), 'responses');
const responsesDir = pathJoin(process.env['INSOMNIA_DATA_PATH'] || (process.type === 'renderer' ? window : electron).app.getPath('userData'), 'responses');
mkdirp.sync(responsesDir);
const timelinePath = pathJoin(responsesDir, timelineHash + '.timeline');
if (process.type === 'renderer') {

View File

@ -1,6 +1,4 @@
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
import electron from 'electron';
import { mocked } from 'jest-mock';
import appPackageJson from '../../../../package.json';
import { globalBeforeEach } from '../../../__jest__/before-each';
@ -118,45 +116,3 @@ describe('app.getInfo()', () => {
});
});
describe('app.clipboard', () => {
it('writes to clipboard', () => {
// Arrange
const mockedClipboard = mocked(electron.clipboard);
const context = plugin.init();
const text = 'abc';
// Act
context.app.clipboard.writeText(text);
// Assert
expect(mockedClipboard.writeText).toHaveBeenCalledWith(text);
});
it('reads from clipboard', () => {
// Arrange
const text = 'abc';
const mockedClipboard = mocked(electron.clipboard);
mockedClipboard.readText.mockReturnValue(text);
const context = plugin.init();
// Act
const outputText = context.app.clipboard.readText();
// Assert
expect(outputText).toBe(text);
expect(mockedClipboard.readText).toHaveBeenCalled();
});
it('clears clipboard', () => {
// Arrange
const mockedClipboard = mocked(electron.clipboard);
const context = plugin.init();
// Act
context.app.clipboard.clear();
// Assert
expect(mockedClipboard.clear).toHaveBeenCalled();
});
});

View File

@ -1,9 +1,9 @@
import { beforeEach, describe, expect, it } from '@jest/globals';
import electron from 'electron';
import fs from 'fs';
import path from 'path';
import { globalBeforeEach } from '../../../__jest__/before-each';
import { getTempDir } from '../../../common/electron-helpers';
import * as models from '../../../models/index';
import * as plugin from '../response';
@ -37,7 +37,7 @@ describe('response.*', () => {
beforeEach(globalBeforeEach);
it('works for basic and full response', async () => {
const bodyPath = path.join(getTempDir(), 'response.zip');
const bodyPath = path.join(electron.app.getPath('temp'), 'response.zip');
fs.writeFileSync(bodyPath, Buffer.from('Hello World!'));
const response = await models.initModel(models.response.type, {
bodyPath,

View File

@ -1,4 +1,3 @@
import * as electron from 'electron';
import React from 'react';
import type ReactDOM from 'react-dom';
@ -180,15 +179,15 @@ export function init(renderPurpose: RenderPurpose = RENDER_PURPOSE_GENERAL): {
clipboard: {
readText() {
return electron.clipboard.readText();
return window.clipboard.readText();
},
writeText(text) {
electron.clipboard.writeText(text);
window.clipboard.writeText(text);
},
clear() {
electron.clipboard.clear();
window.clipboard.clear();
},
},

View File

@ -1,15 +1,14 @@
import electron from 'electron';
import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
import { getDataDirectory } from '../common/electron-helpers';
export async function createPlugin(
moduleName: string,
version: string,
mainJs: string,
) {
const pluginDir = path.join(getDataDirectory(), 'plugins', moduleName);
const pluginDir = path.join(process.env['INSOMNIA_DATA_PATH'] || (process.type === 'renderer' ? window : electron).app.getPath('userData'), 'plugins', moduleName);
if (fs.existsSync(pluginDir)) {
throw new Error(`Plugin already exists at "${pluginDir}"`);

View File

@ -1,10 +1,10 @@
import electron from 'electron';
import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
import appConfig from '../../config/config.json';
import { ParsedApiSpec } from '../common/api-specs';
import { getDataDirectory } from '../common/electron-helpers';
import { resolveHomePath } from '../common/misc';
import type { PluginConfig, PluginConfigMap } from '../common/settings';
import * as models from '../models';
@ -209,7 +209,7 @@ export async function getPlugins(force = false): Promise<Plugin[]> {
.filter(p => p)
.map(resolveHomePath);
// Make sure the default directories exist
const pluginPath = path.join(getDataDirectory(), 'plugins');
const pluginPath = path.join(process.env['INSOMNIA_DATA_PATH'] || (process.type === 'renderer' ? window : electron).app.getPath('userData'), 'plugins');
mkdirp.sync(pluginPath);
// Also look in node_modules folder in each directory
const basePaths = [pluginPath, ...extraPaths];

View File

@ -27,6 +27,7 @@ const grpc: gRPCBridgeAPI = {
const main: Window['main'] = {
loginStateChange: () => ipcRenderer.send('loginStateChange'),
restart: () => ipcRenderer.send('restart'),
openInBrowser: options => ipcRenderer.send('openInBrowser', options),
halfSecondAfterAppStart: () => ipcRenderer.send('halfSecondAfterAppStart'),
manualUpdateCheck: () => ipcRenderer.send('manualUpdateCheck'),
exportAllWorkspaces: () => ipcRenderer.invoke('exportAllWorkspaces'),
@ -57,15 +58,22 @@ const app: Window['app'] = {
const shell: Window['shell'] = {
showItemInFolder: options => ipcRenderer.send('showItemInFolder', options),
};
const clipboard: Window['clipboard'] = {
readText: () => ipcRenderer.sendSync('readText'),
writeText: options => ipcRenderer.send('writeText', options),
clear: () => ipcRenderer.send('clear'),
};
if (process.contextIsolated) {
contextBridge.exposeInMainWorld('main', main);
contextBridge.exposeInMainWorld('dialog', dialog);
contextBridge.exposeInMainWorld('app', app);
contextBridge.exposeInMainWorld('shell', shell);
contextBridge.exposeInMainWorld('clipboard', clipboard);
} else {
window.main = main;
window.dialog = dialog;
window.app = app;
window.shell = shell;
window.clipboard = clipboard;
}

View File

@ -2,7 +2,6 @@ import React, { Fragment } from 'react';
import styled from 'styled-components';
import * as session from '../../account/session';
import { clickLink } from '../../common/electron-helpers';
import { Dropdown, DropdownButton, DropdownItem, ItemContent } from './base/dropdown';
import { Link as ExternalLink } from './base/link';
import { showLoginModal } from './modals/login-modal';
@ -53,7 +52,7 @@ export const AccountToolbar = () => {
icon="gear"
label='Account Settings'
stayOpenAfterClick
onClick={() => clickLink('https://app.insomnia.rest/app/account/')}
onClick={() => window.main.openInBrowser('https://app.insomnia.rest/app/account/')}
/>
</DropdownItem>
<DropdownItem

View File

@ -1,4 +1,3 @@
import { clipboard } from 'electron';
import React, { FC, useCallback, useState } from 'react';
import { useInterval } from 'react-use';
@ -24,7 +23,7 @@ export const CopyButton: FC<Props> = ({
const toCopy = typeof content === 'string' ? content : await content();
if (toCopy) {
clipboard.writeText(toCopy);
window.clipboard.writeText(toCopy);
}
setshowConfirmation(true);
}, [content]);

View File

@ -1,8 +1,6 @@
import classnames from 'classnames';
import React, { FC, ReactNode, useCallback } from 'react';
import { clickLink } from '../../../common/electron-helpers';
interface Props {
href: string;
title?: string;
@ -25,7 +23,7 @@ export const Link: FC<Props> = ({
const handleClick = useCallback((event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
event?.preventDefault();
onClick?.(event); // Also call onClick that was passed to us if there was one
clickLink(href);
window.main.openInBrowser(href);
}, [onClick, href]);
if (button) {

View File

@ -1,4 +1,3 @@
import { clipboard } from 'electron';
import HTTPSnippet from 'httpsnippet';
import React, { forwardRef, useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
@ -97,7 +96,7 @@ export const RequestActionsDropdown = forwardRef<DropdownHandle, Props>(({
const cmd = snippet.convert('shell', 'curl');
if (cmd) {
clipboard.writeText(cmd);
window.clipboard.writeText(cmd);
}
} catch (err) {
showModal(AlertModal, {

View File

@ -1,7 +1,7 @@
import { LintOptions, ShowHintOptions, TextMarker } from 'codemirror';
import { GraphQLInfoOptions } from 'codemirror-graphql/info';
import { ModifiedGraphQLJumpOptions } from 'codemirror-graphql/jump';
import { OpenDialogOptions } from 'electron';
import type { OpenDialogOptions } from 'electron';
import { readFileSync } from 'fs';
import { DefinitionNode, DocumentNode, GraphQLNonNull, GraphQLSchema, Kind, NonNullTypeNode, OperationDefinitionNode, parse, typeFromAST } from 'graphql';
import { buildClientSchema, getIntrospectionQuery } from 'graphql/utilities';

View File

@ -2,7 +2,6 @@ import classnames from 'classnames';
import highlight from 'highlight.js/lib/common';
import React, { FC, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { clickLink } from '../../common/electron-helpers';
import { markdownToHTML } from '../../common/markdown-to-html';
import { HandleRender } from '../../common/render';
import { useGatedNunjucks } from '../context/nunjucks/use-gated-nunjucks';
@ -55,7 +54,7 @@ export const MarkdownPreview: FC<Props> = ({ markdown, className, heading }) =>
}, [compiled]);
const _handleClickLink = (event: any) => {
event.preventDefault();
clickLink(event.target.getAttribute('href'));
window.main.openInBrowser(event.target.getAttribute('href'));
};
return (

View File

@ -1,8 +1,6 @@
import { clipboard } from 'electron';
import React, { FormEvent, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import * as session from '../../../account/session';
import { clickLink } from '../../../common/electron-helpers';
import { getLoginUrl, submitAuthCode } from '../../auth-session-provider';
import { type ModalHandle, Modal } from '../base/modal';
import { ModalBody } from '../base/modal-body';
@ -68,7 +66,7 @@ export const LoginModal = forwardRef<LoginModalHandle, {}>(function LoginModal({
modalRef.current?.show();
if (!reauth) {
clickLink(url);
window.main.openInBrowser(url);
}
},
}), []);
@ -91,11 +89,11 @@ export const LoginModal = forwardRef<LoginModalHandle, {}>(function LoginModal({
}, []);
const copyUrl = useCallback(() => {
clipboard.writeText(state.url);
window.clipboard.writeText(state.url);
}, [state.url]);
const openUrl = useCallback(() => {
clickLink(state.url);
window.main.openInBrowser(state.url);
}, [state.url]);
const renderBody = () => {

View File

@ -1,4 +1,3 @@
import { clipboard } from 'electron';
import fs from 'fs';
import { extension as mimeExtension } from 'mime-types';
import React, { FC, useCallback } from 'react';
@ -70,7 +69,7 @@ export const ResponsePane: FC<Props> = ({
const handleCopyResponseToClipboard = useCallback(async () => {
const bodyBuffer = handleGetResponseBody();
if (bodyBuffer) {
clipboard.writeText(bodyBuffer.toString('utf8'));
window.clipboard.writeText(bodyBuffer.toString('utf8'));
}
}, [handleGetResponseBody]);
const handleDownloadResponseBody = useCallback(async (prettify: boolean) => {

View File

@ -7,7 +7,6 @@ import {
PLUGIN_HUB_BASE,
} from '../../../common/constants';
import { docsPlugins } from '../../../common/documentation';
import { clickLink, getDataDirectory } from '../../../common/electron-helpers';
import * as models from '../../../models';
import { createPlugin } from '../../../plugins/create';
import type { Plugin } from '../../../plugins/index';
@ -206,7 +205,7 @@ export const Plugins: FC = () => {
<hr />
<div className="text-right">
<Button
onClick={() => clickLink(PLUGIN_HUB_BASE)}
onClick={() => window.main.openInBrowser(PLUGIN_HUB_BASE)}
>
Browse Plugin Hub
</Button>
@ -252,7 +251,7 @@ export const Plugins: FC = () => {
style={{
marginLeft: '0.3em',
}}
onClick={() => window.shell.showItemInFolder(path.join(getDataDirectory(), 'plugins'))}
onClick={() => window.shell.showItemInFolder(path.join(process.env['INSOMNIA_DATA_PATH'] || window.app.getPath('userData'), 'plugins'))}
>
Reveal Plugins Folder
</Button>

View File

@ -1,5 +1,5 @@
import { format } from 'date-fns';
import { SaveDialogOptions } from 'electron';
import type { SaveDialogOptions } from 'electron';
import fs from 'fs';
import { extension as mimeExtension } from 'mime-types';
import multiparty from 'multiparty';

View File

@ -1,6 +1,5 @@
import React, { FC, useEffect, useRef } from 'react';
import { clickLink } from '../../../common/electron-helpers';
import type { ResponseTimelineEntry } from '../../../main/network/libcurl-promise';
import { CodeEditor, CodeEditorHandle } from '../codemirror/code-editor';
@ -46,7 +45,7 @@ export const ResponseTimelineViewer: FC<Props> = ({ timeline }) => {
ref={editorRef}
hideLineNumbers
readOnly
onClickLink={clickLink}
onClickLink={window.main.openInBrowser}
defaultValue={rows}
className="pad-left"
mode="curl"

View File

@ -11,7 +11,6 @@ import {
PREVIEW_MODE_FRIENDLY,
PREVIEW_MODE_RAW,
} from '../../../common/constants';
import { clickLink } from '../../../common/electron-helpers';
import { xmlDecode } from '../../../common/misc';
import { CodeEditor, CodeEditorHandle } from '../codemirror/code-editor';
import { useDocBodyKeyboardShortcuts } from '../keydown-binder';
@ -347,7 +346,7 @@ export const ResponseViewer = ({
filterHistory={filterHistory}
mode={getBodyAsString()?.match(/^\s*<\?xml [^?]*\?>/) ? 'application/xml' : _getContentType()}
noMatchBrackets
onClickLink={url => !disablePreviewLinks && clickLink(getBodyAsString()?.match(/^\s*<\?xml [^?]*\?>/) ? xmlDecode(url) : url)}
onClickLink={url => !disablePreviewLinks && window.main.openInBrowser(getBodyAsString()?.match(/^\s*<\?xml [^?]*\?>/) ? xmlDecode(url) : url)}
placeholder="..."
readOnly
uniquenessKey={responseId}

View File

@ -1,4 +1,3 @@
import { clipboard } from 'electron';
import fs from 'fs';
import React, { FC, useCallback } from 'react';
import { useSelector } from 'react-redux';
@ -78,7 +77,7 @@ export const MessageEventView: FC<Props<WebSocketMessageEvent>> = ({ event, requ
}, [raw]);
const handleCopyResponseToClipboard = useCallback(() => {
clipboard.writeText(raw);
window.clipboard.writeText(raw);
}, [raw]);
const previewMode = useSelector(selectResponsePreviewMode);

View File

@ -1,13 +1,12 @@
import { useAsync } from 'react-use';
import { onLoginLogout } from '../../account/session';
import { getDataDirectory } from '../../common/electron-helpers';
import FileSystemDriver from '../../sync/store/drivers/file-system-driver';
import { migrateCollectionsIntoRemoteProject } from '../../sync/vcs/migrate-collections';
import { VCS } from '../../sync/vcs/vcs';
const check = async () => {
const driver = FileSystemDriver.create(getDataDirectory());
const driver = FileSystemDriver.create(process.env['INSOMNIA_DATA_PATH'] || window.app.getPath('userData'));
await migrateCollectionsIntoRemoteProject(new VCS(driver));
};

View File

@ -1,6 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import { getDataDirectory } from '../../common/electron-helpers';
import { generateId } from '../../common/misc';
import FileSystemDriver from '../../sync/store/drivers/file-system-driver';
import { type MergeConflict } from '../../sync/types';
@ -27,7 +26,7 @@ export function useVCS({
async function updateVCS() {
let vcsInstance = getVCS();
if (!vcsInstance) {
const driver = FileSystemDriver.create(getDataDirectory());
const driver = FileSystemDriver.create(process.env['INSOMNIA_DATA_PATH'] || window.app.getPath('userData'));
vcsInstance = await initVCS(driver, async conflicts => {
return new Promise(resolve => {

View File

@ -1,5 +1,4 @@
// Import
import { clipboard } from 'electron';
import { ActionFunction, redirect } from 'react-router-dom';
import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../common/constants';
@ -42,7 +41,7 @@ export const scanForResourcesAction: ActionFunction = async ({ request }): Promi
uri,
});
} else {
content = clipboard.readText();
content = window.clipboard.readText();
}
if (!content) {

View File

@ -30,7 +30,6 @@ import {
ACTIVITY_SPEC,
DashboardSortOrder,
} from '../../common/constants';
import { clickLink } from '../../common/electron-helpers';
import { fuzzyMatchAll, isNotNullOrUndefined } from '../../common/misc';
import { descendingNumberSort, sortMethodMap } from '../../common/sorting';
import { strings } from '../../common/strings';
@ -502,7 +501,7 @@ const OrganizationProjectsSidebar: FC<{
<List
onAction={key => {
clickLink(key.toString());
window.main.openInBrowser(key.toString());
}}
>
<Item

View File

@ -1,4 +1,4 @@
import { IpcRendererEvent } from 'electron';
import type { IpcRendererEvent } from 'electron';
import React, { useEffect, useState } from 'react';
import {
LoaderFunction,