mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
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:
parent
e8c9afb0c8
commit
abfabd41cf
@ -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.',
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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`);
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~ //
|
||||
|
@ -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/'
|
||||
);
|
||||
};
|
1
packages/insomnia/src/global.d.ts
vendored
1
packages/insomnia/src/global.d.ts
vendored
@ -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'>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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', () => {
|
||||
|
@ -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,
|
||||
|
@ -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') {
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -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}"`);
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
|
@ -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) {
|
||||
|
@ -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, {
|
||||
|
@ -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';
|
||||
|
@ -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 (
|
||||
|
@ -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 = () => {
|
||||
|
@ -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) => {
|
||||
|
@ -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>
|
||||
|
@ -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';
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
};
|
||||
|
||||
|
@ -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 => {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { IpcRendererEvent } from 'electron';
|
||||
import type { IpcRendererEvent } from 'electron';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
LoaderFunction,
|
||||
|
Loading…
Reference in New Issue
Block a user