Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c1d1f30
feat: enable manipulation on insomnia.request
ihexxa Mar 5, 2024
e6d7e71
chore: revert changes on test
ihexxa Mar 5, 2024
f7ecf5e
chore: fix and re-org pre-request scripts
ihexxa Mar 6, 2024
e391349
fix: bring back request test in smoke spec
ihexxa Mar 6, 2024
22d8286
feat: enable insomnia.sendRequest
ihexxa Mar 6, 2024
3f2fe7f
feat: enable manipulation on insomnia.request
ihexxa Mar 5, 2024
53f1468
chore: clean up dependencies
ihexxa Mar 5, 2024
5ab2a74
chore: fix and re-org pre-request scripts
ihexxa Mar 6, 2024
a8beb1e
fix: add static _index for simulating the original behavior
ihexxa Mar 6, 2024
4a4b801
feat: add simple interpolator for enabling replaceIn methods
ihexxa Mar 6, 2024
d87e457
fix: allow user to update active request certs and proxy through inso…
ihexxa Mar 6, 2024
3757c40
feat: hook settings to insomnia.request and cleanups
ihexxa Mar 6, 2024
ee344fc
test: add a test for proxy and certificate manipulation
ihexxa Mar 7, 2024
350e78d
fix: lint error
ihexxa Mar 7, 2024
41fce6e
feat: enable eval() in script
ihexxa Mar 8, 2024
b896943
fix: incorrect auth param transforming
ihexxa Mar 8, 2024
b324b44
feat: enable requiring node.js modules
ihexxa Mar 8, 2024
f7cb5ef
fix: request auth type is not correctly updated
ihexxa Mar 8, 2024
9cfab2b
test: add tests for unphappy paths
ihexxa Mar 8, 2024
4ba6658
test: fixed a flaky test
ihexxa Mar 8, 2024
5379051
chore: make console.log outputs more friendly
ihexxa Mar 8, 2024
3a91858
chore: add more helper snippets
ihexxa Mar 8, 2024
a1aa8a9
chore: add more helper snippets for pre-req scripting
ihexxa Mar 8, 2024
2b17ae4
fix: use correct icons
ihexxa Mar 8, 2024
b672db0
fix: restart the hidden window if it is down
ihexxa Mar 13, 2024
b311c05
fix: lint error
ihexxa Mar 13, 2024
0881b71
fix: test failure after rebasing
ihexxa Mar 13, 2024
108e3c8
fix: smoke test error because of restarting hidden window
ihexxa Mar 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
1 change: 1 addition & 0 deletions packages/insomnia-smoke-test/fixtures/files/rawfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
raw file content

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,13 @@ test('handle hidden browser window getting closed', async ({ app, page }) => {
await page.getByRole('button', { name: 'Scan' }).click();
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();

await page.getByText('Pre-request Scripts').click();

await page.getByLabel('Request Collection').getByTestId('Long running task').press('Enter');
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();

await page.getByTestId('settings-button').click();
await page.getByLabel('Request timeout (ms)').fill('1');
await page.getByRole('button', { name: '' }).click();
await page.getByRole('button', { name: 'Send' }).click();

await page.getByText('Pre-request Scripts').click();
await page.getByLabel('Request Collection').getByTestId('Long running task').press('Enter');
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();

await page.getByText('Timeout: Pre-request script took too long').click();
await page.getByRole('tab', { name: 'Timeline' }).click();
Expand All @@ -57,5 +55,6 @@ test('handle hidden browser window getting closed', async ({ app, page }) => {
const hiddenWindow = windows[1];
hiddenWindow.close();
await page.getByRole('button', { name: 'Send' }).click();
await page.getByText('Timeout: Hidden browser window is not responding').click();
// as the hidden window is restarted, it should not show "Timeout: Hidden browser window is not responding"
await page.getByText('Timeout: Pre-request script took too long').click();
});
3 changes: 3 additions & 0 deletions packages/insomnia/src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ declare global {
bridge: {
requireInterceptor: (module: string) => any;
onmessage: (listener: (data: any, callback: (result: any) => void) => void) => void;
cancelCurlRequest: (id: string) => void;
curlRequest: (options: any) => Promise<any>;
readCurlResponse: (options: { bodyPath: string; bodyCompression: Compression }) => Promise<{ body: string; error: string }>;
};
}
}
Expand Down
39 changes: 38 additions & 1 deletion packages/insomnia/src/hidden-window-preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,57 @@ const bridge: Window['bridge'] = {
console.log('[preload] opened port to insomnia renderer');
const callback = (result: RequestContext) => port.postMessage(result);
port.onmessage = event => listener(event.data, callback);
ipcRenderer.invoke('hidden-window-received-port');
};

ipcRenderer.on('renderer-listener', rendererListener);
ipcRenderer.invoke('renderer-listener-ready');
return () => ipcRenderer.removeListener('renderer-listener', rendererListener);
},

requireInterceptor: (moduleName: string) => {
if (['uuid', 'fs'].includes(moduleName)) {
if (
[
// node.js modules
'path',
'assert',
'buffer',
'util',
'url',
'punycode',
'querystring',
'string_decoder',
'stream',
'timers',
'events',
'fs',
// npm modules
'uuid',
// 'ajv',
// 'atob',
// 'btoa',
// 'chai',
// 'cheerio',
// 'crypto-js',
// 'csv-parse/lib/sync',
// 'lodash',
// 'moment',
// 'tv4',
// 'uuid',
// 'xml2js',
].includes(moduleName)
) {
return require(moduleName);
} else if (moduleName === 'insomnia-collection' || moduleName === 'postman-collection') {
return CollectionModule;
}

throw Error(`no module is found for "${moduleName}"`);
},

curlRequest: options => ipcRenderer.invoke('curlRequest', options),
cancelCurlRequest: options => ipcRenderer.send('cancelCurlRequest', options),
readCurlResponse: options => ipcRenderer.invoke('readCurlResponse', options),
};

if (process.contextIsolated) {
Expand Down
30 changes: 26 additions & 4 deletions packages/insomnia/src/hidden-window.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { initInsomniaObject } from './sdk/objects/insomnia';
import { initInsomniaObject, InsomniaObject } from './sdk/objects/insomnia';
import { RequestContext } from './sdk/objects/interfaces';
import { mergeRequests } from './sdk/objects/request';
import { mergeClientCertificates, mergeRequests, mergeSettings } from './sdk/objects/request';
import { invariant } from './utils/invariant';

export interface HiddenBrowserWindowBridgeAPI {
runPreRequestScript: (options: { script: string; context: RequestContext }) => Promise<RequestContext>;
Expand Down Expand Up @@ -32,14 +33,27 @@ const runPreRequestScript = async (
const log: string[] = [];
// TODO: we should at least support info, debug, warn, error
const consoleInterceptor = {
log: (...args: any[]) => log.push(JSON.stringify({ value: args.map(a => JSON.stringify(a)).join('\n'), name: 'Text', timestamp: Date.now() }) + '\n'),
log: (...args: any[]) => log.push(
JSON.stringify({
value: args.map(a => JSON.stringify(a, null, 2)).join('\n'),
name: 'Text',
timestamp: Date.now(),
}) + '\n',
),
};

const evalInterceptor = (script: string) => {
invariant(script && typeof script === 'string', 'eval is called with invalid or empty value');
const result = eval(script);

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: javascript.browser.security.eval-detected.eval-detected

Detected the use of eval(). eval() can be dangerous if used to evaluate dynamic content. If this content can be input from outside the program, this may be a code injection vulnerability. Ensure evaluated content is not definable by external sources.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is as expected as it is used to provide compatibility with existing scripts.

return result;
};

const AsyncFunction = (async () => { }).constructor;
const executeScript = AsyncFunction(
'insomnia',
'require',
'console',
'eval',
`
const $ = insomnia, pm = insomnia;
${script};
Expand All @@ -49,10 +63,16 @@ const runPreRequestScript = async (
const mutatedInsomniaObject = await executeScript(
executionContext,
window.bridge.requireInterceptor,
consoleInterceptor
consoleInterceptor,
evalInterceptor,
);
if (mutatedInsomniaObject == null || !(mutatedInsomniaObject instanceof InsomniaObject)) {
throw Error('insomnia object is invalid or script returns earlier than expected.');
}
const mutatedContextObject = mutatedInsomniaObject.toObject();
const updatedRequest = mergeRequests(context.request, mutatedContextObject.request);
const updatedSettings = mergeSettings(context.settings, mutatedContextObject.request);
const updatedCertificates = mergeClientCertificates(context.clientCertificates, mutatedContextObject.request);

await window.bridge.requireInterceptor('fs').promises.writeFile(context.timelinePath, log.join('\n'));

Expand All @@ -64,5 +84,7 @@ const runPreRequestScript = async (
environment: mutatedContextObject.environment,
baseEnvironment: mutatedContextObject.baseEnvironment,
request: updatedRequest,
settings: updatedSettings,
clientCertificates: updatedCertificates,
};
};
18 changes: 18 additions & 0 deletions packages/insomnia/src/main/network/curl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CookieJar } from '../../models/cookie-jar';
import { Environment } from '../../models/environment';
import { RequestAuthentication, RequestHeader } from '../../models/request';
import { Response } from '../../models/response';
import { Compression, getBodyBuffer } from '../../models/response';
import { addSetCookiesToToughCookieJar } from '../../network/set-cookie-util';
import { urlMatchesCertHost } from '../../network/url-matches-cert-host';
import { invariant } from '../../utils/invariant';
Expand Down Expand Up @@ -353,6 +354,7 @@ export interface CurlBridgeAPI {
findMany: typeof findMany;
};
}

export const registerCurlHandlers = () => {
ipcMain.handle('curl.open', openCurlConnection);
ipcMain.on('curl.close', closeCurlConnection);
Expand All @@ -361,4 +363,20 @@ export const registerCurlHandlers = () => {
ipcMain.handle('curl.event.findMany', (_, options: Parameters<typeof findMany>[0]) => findMany(options));
};

ipcMain.handle('readCurlResponse', async (_, options: { bodyPath?: string; bodyCompression?: Compression }) => {
const readFailureMsg = '[main/curlBridgeAPI] failed to read response body message';
const bodyBufferOrErrMsg = getBodyBuffer(options, readFailureMsg);

if (!bodyBufferOrErrMsg) {
return { body: '', error: readFailureMsg };
} else if (typeof bodyBufferOrErrMsg === 'string') {
if (bodyBufferOrErrMsg === readFailureMsg) {
return { body: '', error: readFailureMsg };
}
return { body: '', error: `unknown error in loading response body: ${bodyBufferOrErrMsg}` };
}

return { body: bodyBufferOrErrMsg.toString('utf8'), error: '' };
});

electron.app.on('window-all-closed', closeAllCurlConnections);
114 changes: 65 additions & 49 deletions packages/insomnia/src/main/window-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
type BrowserWindow as ElectronBrowserWindow,
clipboard,
dialog,
ipcMain,
Menu,
type MenuItemConstructorOptions,
MessageChannelMain,
Expand Down Expand Up @@ -49,64 +50,79 @@ export function init() {
initLocalStorage();
}

export async function createHiddenBrowserWindow(): Promise<ElectronBrowserWindow> {
// if open, close it
if (browserWindows.get('HiddenBrowserWindow')) {
await new Promise<void>(resolve => {
const hiddenBrowserWindow = browserWindows.get('HiddenBrowserWindow');
invariant(hiddenBrowserWindow, 'hiddenBrowserWindow is not defined');

// overwrite the closed handler
hiddenBrowserWindow.on('closed', () => {
if (hiddenBrowserWindow) {
console.log('[main] restarting hidden browser window');
browserWindows.delete('HiddenBrowserWindow');
}
resolve();
});
export async function createHiddenBrowserWindow() {
const mainWindow = browserWindows.get('Insomnia');
invariant(mainWindow, 'MainWindow is not defined, please restart the app.');

stopHiddenBrowserWindow();
// when the main window runs a script
// if the hidden window is down, start it
ipcMain.handle('open-channel-to-hidden-browser-window', async event => {
if (browserWindows.get('HiddenBrowserWindow')) {
return;
}

const hiddenBrowserWindow = new BrowserWindow({
show: false,
title: 'HiddenBrowserWindow',
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
minHeight: MINIMUM_HEIGHT,
minWidth: MINIMUM_WIDTH,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'hidden-window-preload.js'),
spellcheck: false,
devTools: process.env.NODE_ENV === 'development',
},
});
}
const hiddenBrowserWindow = new BrowserWindow({
show: false,
title: 'HiddenBrowserWindow',
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
minHeight: MINIMUM_HEIGHT,
minWidth: MINIMUM_WIDTH,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'hidden-window-preload.js'),
spellcheck: false,
devTools: process.env.NODE_ENV === 'development',
},
});
browserWindows.set('HiddenBrowserWindow', hiddenBrowserWindow);

const hiddenBrowserWindowPath = path.resolve(__dirname, 'hidden-window.html');
const hiddenBrowserWindowUrl = process.env.HIDDEN_BROWSER_WINDOW_URL || pathToFileURL(hiddenBrowserWindowPath).href;
hiddenBrowserWindow.loadURL(hiddenBrowserWindowUrl);
console.log(`[main] Loading ${hiddenBrowserWindowUrl}`);
hiddenBrowserWindow.on('closed', () => {
if (browserWindows.get('HiddenBrowserWindow')) {
console.log('[main] closing hidden browser window');
browserWindows.delete('HiddenBrowserWindow');
}
});

hiddenBrowserWindow.on('closed', () => {
if (browserWindows.get('HiddenBrowserWindow')) {
console.log('[main] closing hidden browser window');
browserWindows.delete('HiddenBrowserWindow');
}
});
const mainWindow = browserWindows.get('Insomnia');
const hiddenBrowserWindowPath = path.resolve(__dirname, 'hidden-window.html');
const hiddenBrowserWindowUrl = process.env.HIDDEN_BROWSER_WINDOW_URL || pathToFileURL(hiddenBrowserWindowPath).href;
hiddenBrowserWindow.loadURL(hiddenBrowserWindowUrl);
console.log(`[main] Loading ${hiddenBrowserWindowUrl}`);

ipcMain.removeHandler('renderer-listener-ready');
const hiddenWinListenerReady = new Promise<void>(resolve => {
ipcMain.handleOnce('renderer-listener-ready', () => {
console.log('[main] hidden window listener is ready');
resolve();
});
});
await hiddenWinListenerReady;

ipcMain.removeHandler('hidden-window-received-port');
const hiddenWinPortReady = new Promise<void>(resolve => {
ipcMain.handleOnce('hidden-window-received-port', () => {
console.log('[main] hidden window has received port');
resolve();
});
});

invariant(mainWindow, 'mainWindow is not defined');
mainWindow.webContents.mainFrame.ipc.on('open-channel-to-hidden-browser-window', event => {
const { port1, port2 } = new MessageChannelMain();
hiddenBrowserWindow.webContents.postMessage('renderer-listener', null, [port1]);
await hiddenWinPortReady;

ipcMain.removeHandler('main-window-script-port-ready');
const mainWinPortReady = new Promise<void>(resolve => {
ipcMain.handleOnce('main-window-script-port-ready', () => {
console.log('[main] main window has received hidden window port');
resolve();
});
});

event.senderFrame.postMessage('hidden-browser-window-response-listener', null, [port2]);
port1.close();
port2.close();
await mainWinPortReady;

browserWindows.set('HiddenBrowserWindow', hiddenBrowserWindow);
});
return hiddenBrowserWindow;
}

export function stopHiddenBrowserWindow() {
Expand Down
2 changes: 1 addition & 1 deletion packages/insomnia/src/models/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface ResponseHeader {
value: string;
}

type Compression = 'zip' | null | '__NEEDS_MIGRATION__' | undefined;
export type Compression = 'zip' | null | '__NEEDS_MIGRATION__' | undefined;

export interface BaseResponse {
environmentId: string | null;
Expand Down
11 changes: 10 additions & 1 deletion packages/insomnia/src/network/cancellation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { Request } from '../models/request';
import { RequestContext } from '../sdk/objects/interfaces';

const cancelRequestFunctionMap = new Map<string, () => void>();

export function setCancelRequestFunctionMap(id: string, cb: () => void) {
return cancelRequestFunctionMap.set(id, cb);
}

export function deleteCancelRequestFunctionMap(id: string) {
return cancelRequestFunctionMap.delete(id);
}

export async function cancelRequestById(requestId: string) {
const cancel = cancelRequestFunctionMap.get(requestId);
if (cancel) {
Expand Down Expand Up @@ -65,7 +74,7 @@ export const cancellableCurlRequest = async (requestOptions: CurlRequestOptions)
}
};

const cancellablePromise = ({ signal, fn }: { signal: AbortSignal; fn: Promise<any> }) => {
export const cancellablePromise = ({ signal, fn }: { signal: AbortSignal; fn: Promise<any> }) => {
if (signal?.aborted) {
return Promise.reject(new DOMException('Aborted', 'AbortError'));
}
Expand Down
Loading