mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-21 22:57:59 -04:00
feat: enable islated utility process [INS-3378]
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Read more about creating fixtures https://playwright.dev/docs/test-fixtures
|
||||
import { ElectronApplication, test as baseTest, TraceMode } from '@playwright/test';
|
||||
import { ElectronApplication, expect, Page, test as baseTest, TraceMode } from '@playwright/test';
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
@@ -97,11 +97,31 @@ export const test = baseTest.extend<{
|
||||
await electronApp.close();
|
||||
},
|
||||
page: async ({ app }, use) => {
|
||||
const page = await app.firstWindow();
|
||||
// getMainPage waits main renderer loaded
|
||||
const getMainPage = async () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const wins = app.windows();
|
||||
|
||||
await page.waitForLoadState();
|
||||
for (const page of wins) {
|
||||
const title = await page.title();
|
||||
if (title === 'Insomnia') {
|
||||
return page;
|
||||
}
|
||||
}
|
||||
|
||||
await use(page);
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const page = await getMainPage();
|
||||
if (!page) {
|
||||
expect(page).toBeDefined();
|
||||
} else {
|
||||
await page.waitForLoadState();
|
||||
await use(page);
|
||||
}
|
||||
},
|
||||
dataPath: async ({ }, use) => {
|
||||
const insomniaDataPath = randomDataPath();
|
||||
|
||||
283
packages/insomnia-smoke-test/tests/smoke/utility-process.test.ts
Normal file
283
packages/insomnia-smoke-test/tests/smoke/utility-process.test.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { test } from '../../playwright/test';;
|
||||
|
||||
test.describe('test utility process', async () => {
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
id: 'environment basic operations',
|
||||
code: `
|
||||
const bool2 = pm.environment.has('bool1');
|
||||
const num2 = pm.environment.get('num1');
|
||||
const str2 = pm.environment.get('str1');
|
||||
// add & update
|
||||
pm.environment.set('bool1', false);
|
||||
pm.environment.set('num1', 11);
|
||||
pm.environment.set('str1', 'strr');
|
||||
pm.environment.set('bool2', bool2);
|
||||
pm.environment.set('num2', num2);
|
||||
pm.environment.set('str2', str2);
|
||||
// toObject
|
||||
const newObject = pm.environment.toObject();
|
||||
pm.environment.set('newObject.bool', newObject.bool2);
|
||||
pm.environment.set('newObject.num', newObject.num2);
|
||||
pm.environment.set('newObject.str', newObject.str2);
|
||||
// unset
|
||||
pm.environment.set('willDelete', 11);
|
||||
pm.environment.unset('willDelete');
|
||||
// render
|
||||
const vals = pm.environment.replaceIn('{{bool1}}-{{num1}}-{{str1}}');
|
||||
pm.environment.set('rendered', vals);
|
||||
`,
|
||||
context: {
|
||||
pm: {
|
||||
environment: {
|
||||
bool1: true,
|
||||
num1: 1,
|
||||
str1: 'str',
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: {
|
||||
collectionVariables: {},
|
||||
iterationData: {},
|
||||
globals: {},
|
||||
variables: {
|
||||
bool1: false,
|
||||
num1: 11,
|
||||
str1: 'strr',
|
||||
bool2: true,
|
||||
num2: 1,
|
||||
str2: 'str',
|
||||
'newObject.bool': true,
|
||||
'newObject.num': 1,
|
||||
'newObject.str': 'str',
|
||||
'rendered': 'false-11-strr',
|
||||
},
|
||||
environment: {
|
||||
bool1: false,
|
||||
num1: 11,
|
||||
str1: 'strr',
|
||||
bool2: true,
|
||||
num2: 1,
|
||||
str2: 'str',
|
||||
'newObject.bool': true,
|
||||
'newObject.num': 1,
|
||||
'newObject.str': 'str',
|
||||
'rendered': 'false-11-strr',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'collectionVariables (baseEnvironment) basic operations',
|
||||
code: `
|
||||
const bool2 = pm.collectionVariables.has('bool1');
|
||||
const num2 = pm.collectionVariables.get('num1');
|
||||
const str2 = pm.collectionVariables.get('str1');
|
||||
// add & update
|
||||
pm.collectionVariables.set('bool1', false);
|
||||
pm.collectionVariables.set('num1', 11);
|
||||
pm.collectionVariables.set('str1', 'strr');
|
||||
pm.collectionVariables.set('bool2', bool2);
|
||||
pm.collectionVariables.set('num2', num2);
|
||||
pm.collectionVariables.set('str2', str2);
|
||||
// toObject
|
||||
const newObject = pm.collectionVariables.toObject();
|
||||
pm.collectionVariables.set('newObject.bool', newObject.bool2);
|
||||
pm.collectionVariables.set('newObject.num', newObject.num2);
|
||||
pm.collectionVariables.set('newObject.str', newObject.str2);
|
||||
// unset
|
||||
pm.collectionVariables.set('willDelete', 11);
|
||||
pm.collectionVariables.unset('willDelete');
|
||||
// render
|
||||
const vals = pm.collectionVariables.replaceIn('{{bool1}}-{{num1}}-{{str1}}');
|
||||
pm.collectionVariables.set('rendered', vals);
|
||||
`,
|
||||
context: {
|
||||
pm: {
|
||||
collectionVariables: {
|
||||
bool1: true,
|
||||
num1: 1,
|
||||
str1: 'str',
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: {
|
||||
environment: {},
|
||||
variables: {
|
||||
'bool1': false,
|
||||
'bool2': true,
|
||||
'newObject.bool': true,
|
||||
'newObject.num': 1,
|
||||
'newObject.str': 'str',
|
||||
'num1': 11,
|
||||
'num2': 1,
|
||||
'rendered': 'false-11-strr',
|
||||
'str1': 'strr',
|
||||
'str2': 'str',
|
||||
},
|
||||
globals: {},
|
||||
iterationData: {},
|
||||
collectionVariables: {
|
||||
bool1: false,
|
||||
num1: 11,
|
||||
str1: 'strr',
|
||||
bool2: true,
|
||||
num2: 1,
|
||||
str2: 'str',
|
||||
'newObject.bool': true,
|
||||
'newObject.num': 1,
|
||||
'newObject.str': 'str',
|
||||
'rendered': 'false-11-strr',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'variables basic operations',
|
||||
code: `
|
||||
const unexisting = pm.variables.has('VarNotExist');
|
||||
const strFromGlobal = pm.variables.get('glb');
|
||||
const strFromCollection = pm.variables.get('col');
|
||||
const numFromEnv = pm.variables.get('num');
|
||||
const strFromIter = pm.variables.get('str');
|
||||
const rendered = pm.variables.replaceIn('{{bool}}-{{num}}-{{str}}');
|
||||
// set local
|
||||
pm.variables.set('strFromGlobal', strFromGlobal);
|
||||
pm.variables.set('strFromCollection', strFromCollection);
|
||||
pm.variables.set('numFromEnv', numFromEnv);
|
||||
pm.variables.set('strFromIter', strFromIter);
|
||||
pm.variables.set('rendered', rendered);
|
||||
`,
|
||||
context: {
|
||||
pm: {
|
||||
globals: {
|
||||
bool: false,
|
||||
num: 1,
|
||||
glb: 'glb',
|
||||
},
|
||||
collectionVariables: {
|
||||
num: 2,
|
||||
col: 'col',
|
||||
},
|
||||
environment: {
|
||||
num: 3,
|
||||
str: 'env',
|
||||
},
|
||||
iterationData: {
|
||||
str: 'iter',
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: {
|
||||
globals: {
|
||||
bool: false,
|
||||
num: 1,
|
||||
glb: 'glb',
|
||||
},
|
||||
collectionVariables: {
|
||||
num: 2,
|
||||
col: 'col',
|
||||
},
|
||||
environment: {
|
||||
num: 3,
|
||||
str: 'env',
|
||||
},
|
||||
iterationData: {
|
||||
str: 'iter',
|
||||
},
|
||||
variables: {
|
||||
strFromGlobal: 'glb',
|
||||
strFromCollection: 'col',
|
||||
numFromEnv: 3,
|
||||
strFromIter: 'iter',
|
||||
rendered: 'false-3-iter',
|
||||
bool: false,
|
||||
col: 'col',
|
||||
glb: 'glb',
|
||||
num: 3,
|
||||
'str': 'iter',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'simple test sendRequest and await/async',
|
||||
code: `
|
||||
let testResp;
|
||||
try {
|
||||
await new Promise(
|
||||
resolve => {
|
||||
pm.sendRequest(
|
||||
'http://127.0.0.1:4010/pets/1',
|
||||
(err, resp) => {
|
||||
testResp = resp;
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
pm.variables.set('error', e);
|
||||
}
|
||||
pm.variables.set('resp.code', testResp.code);
|
||||
`,
|
||||
context: {
|
||||
pm: {},
|
||||
},
|
||||
expectedResult: {
|
||||
globals: {},
|
||||
iterationData: {},
|
||||
variables: {
|
||||
'resp.code': 200,
|
||||
},
|
||||
environment: {},
|
||||
collectionVariables: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const tc = testCases[i];
|
||||
|
||||
// tests begin here
|
||||
test(tc.id, async ({ page: mainWindow }) => {
|
||||
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
|
||||
|
||||
// action
|
||||
await mainWindow?.evaluate(
|
||||
async (tc: any) => {
|
||||
window.postMessage(
|
||||
{
|
||||
action: 'message-event://utility.process/debug',
|
||||
id: tc.id,
|
||||
code: tc.code,
|
||||
context: tc.context,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
},
|
||||
tc,
|
||||
);
|
||||
|
||||
// assert
|
||||
let localStorage;
|
||||
|
||||
// TODO: ideally call waitForEvent
|
||||
for (let i = 0; i < 120; i++) {
|
||||
localStorage = await mainWindow?.evaluate(() => window.localStorage);
|
||||
expect(localStorage).toBeDefined();
|
||||
|
||||
if (localStorage[`test_result:${tc.id}`] || localStorage[`test_error:${tc.id}`]) {
|
||||
break;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
if (localStorage) { // just for suppressing ts complaint
|
||||
expect(JSON.parse(localStorage[`test_result:${tc.id}`])).toEqual(tc.expectedResult);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
2
packages/insomnia/.gitignore
vendored
2
packages/insomnia/.gitignore
vendored
@@ -6,3 +6,5 @@ src/main.min.js
|
||||
src/main.min.js.map
|
||||
src/preload.js
|
||||
src/preload.js.map
|
||||
src/renderers/utility-process/**/*.js
|
||||
src/renderers/utility-process/**/*.js.map
|
||||
|
||||
@@ -41,6 +41,26 @@ export default async function build(options: Options) {
|
||||
format: 'cjs',
|
||||
external: ['electron'],
|
||||
});
|
||||
const preloadUtilityProcess = esbuild.build({
|
||||
entryPoints: ['./src/renderers/utility-process/preload.ts'],
|
||||
outfile: path.join(outdir, 'renderers/utility-process/preload-utility-process.js'),
|
||||
target: 'esnext',
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
sourcemap: true,
|
||||
format: 'cjs',
|
||||
external: ['electron'],
|
||||
});
|
||||
const utilityProcess = esbuild.build({
|
||||
entryPoints: ['./src/renderers/utility-process/index.ts'],
|
||||
outfile: path.join(outdir, 'renderers/utility-process/utility-process.min.js'),
|
||||
target: 'esnext',
|
||||
bundle: true,
|
||||
platform: 'browser',
|
||||
sourcemap: true,
|
||||
format: 'cjs',
|
||||
external: [],
|
||||
});
|
||||
const main = esbuild.build({
|
||||
entryPoints: ['./src/main.development.ts'],
|
||||
outfile: path.join(outdir, 'main.min.js'),
|
||||
@@ -56,7 +76,7 @@ export default async function build(options: Options) {
|
||||
...Object.keys(builtinModules),
|
||||
],
|
||||
});
|
||||
return Promise.all([main, preload]);
|
||||
return Promise.all([main, preload, preloadUtilityProcess, utilityProcess]);
|
||||
}
|
||||
|
||||
// Build if ran as a cli script
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"package": "npm run build:app && cross-env USE_HARD_LINKS=false electron-builder build --config electron-builder.config.js",
|
||||
"start": "concurrently -n dev,app --kill-others \"npm run start:dev-server\" \"npm run start:electron\"",
|
||||
"start:dev-server": "vite dev",
|
||||
"start:electron": "cross-env NODE_ENV=development esr esbuild.main.ts && electron .",
|
||||
"start:electron": "cross-env NODE_ENV=development esr esbuild.main.ts && electron --inspect=5858 .",
|
||||
"test": "jest",
|
||||
"electron:dev-build": "electron ./build/main.min.js",
|
||||
"test:watch": "jest --watch",
|
||||
|
||||
10
packages/insomnia/src/main/ipc/message-channel.ts
Normal file
10
packages/insomnia/src/main/ipc/message-channel.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
|
||||
// registerUtilityProcessPort broadcasts message ports to observer windows
|
||||
export function registerUtilityProcessConsumer(consumerWindows: BrowserWindow[]) {
|
||||
ipcMain.on('ipc://main/publish-port', ev => {
|
||||
consumerWindows.forEach(win => {
|
||||
win.webContents.postMessage('ipc://renderers/publish-port', null, ev.ports);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '../common/constants';
|
||||
import { docsBase } from '../common/documentation';
|
||||
import * as log from '../common/log';
|
||||
import { registerUtilityProcessConsumer } from './ipc/message-channel';
|
||||
import LocalStorage from './local-storage';
|
||||
|
||||
const { app, Menu, shell, dialog, clipboard, BrowserWindow } = electron;
|
||||
@@ -26,7 +27,9 @@ const MINIMUM_WIDTH = 500;
|
||||
const MINIMUM_HEIGHT = 400;
|
||||
|
||||
let newWindow: ElectronBrowserWindow | null = null;
|
||||
let isolatedUtilityProcess: ElectronBrowserWindow | null = null;
|
||||
const windows = new Set<ElectronBrowserWindow>();
|
||||
const processes = new Set<ElectronBrowserWindow>();
|
||||
let localStorage: LocalStorage | null = null;
|
||||
|
||||
interface Bounds {
|
||||
@@ -40,6 +43,45 @@ export function init() {
|
||||
initLocalStorage();
|
||||
}
|
||||
|
||||
export function createIsolatedProcess(parent?: electron.BrowserWindow) {
|
||||
isolatedUtilityProcess = new BrowserWindow({
|
||||
parent,
|
||||
show: false,
|
||||
title: 'UtilityProcess',
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
webSecurity: true,
|
||||
preload: path.join(__dirname, 'renderers/utility-process/preload-utility-process.js'),
|
||||
spellcheck: false,
|
||||
},
|
||||
});
|
||||
|
||||
const utilityProcessPath = path.resolve(__dirname, './renderers/utility-process/index.html');
|
||||
const utilityProcessUrl = pathToFileURL(utilityProcessPath).href;
|
||||
isolatedUtilityProcess.loadURL(utilityProcessUrl);
|
||||
|
||||
isolatedUtilityProcess.once('ready-to-show', () => {
|
||||
if (isolatedUtilityProcess) {
|
||||
isolatedUtilityProcess.show();
|
||||
if (parent) {
|
||||
parent.webContents.openDevTools();
|
||||
}
|
||||
isolatedUtilityProcess.webContents.openDevTools();
|
||||
}
|
||||
});
|
||||
|
||||
isolatedUtilityProcess?.on('closed', () => {
|
||||
if (isolatedUtilityProcess) {
|
||||
processes.delete(isolatedUtilityProcess);
|
||||
isolatedUtilityProcess = processes.values().next().value || null;
|
||||
}
|
||||
});
|
||||
|
||||
return isolatedUtilityProcess;
|
||||
}
|
||||
|
||||
export function createWindow() {
|
||||
const { bounds, fullscreen, maximize } = getBounds();
|
||||
const { x, y, width, height } = bounds;
|
||||
@@ -445,10 +487,10 @@ export function createWindow() {
|
||||
helpMenu.submenu?.push({
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: `${MNEMONIC_SYM}About`,
|
||||
click: aboutMenuClickHandler,
|
||||
});
|
||||
{
|
||||
label: `${MNEMONIC_SYM}About`,
|
||||
click: aboutMenuClickHandler,
|
||||
});
|
||||
}
|
||||
|
||||
const developerMenu: MenuItemConstructorOptions = {
|
||||
@@ -547,6 +589,11 @@ export function createWindow() {
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
||||
windows.add(newWindow);
|
||||
|
||||
const isolatedProcess = createIsolatedProcess(newWindow);
|
||||
processes.add(isolatedProcess);
|
||||
registerUtilityProcessConsumer([newWindow]);
|
||||
|
||||
return newWindow;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
|
||||
import { gRPCBridgeAPI } from './main/ipc/grpc';
|
||||
import { CurlBridgeAPI } from './main/network/curl';
|
||||
@@ -95,3 +95,13 @@ if (process.contextIsolated) {
|
||||
window.shell = shell;
|
||||
window.clipboard = clipboard;
|
||||
}
|
||||
|
||||
// it is different from window.main.on, it requires events to pass ports
|
||||
ipcRenderer.on('ipc://renderers/publish-port', async (ev: IpcRendererEvent) => {
|
||||
const windowLoaded = new Promise(resolve => {
|
||||
window.onload = resolve;
|
||||
});
|
||||
await windowLoaded;
|
||||
|
||||
window.postMessage({ action: 'message-event://renderers/publish-port' }, '*', ev.ports);
|
||||
});
|
||||
|
||||
16
packages/insomnia/src/renderers/utility-process/index.html
Normal file
16
packages/insomnia/src/renderers/utility-process/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="font-src 'self' data:; connect-src * data: api: insomnia-event-source:; default-src * insomnia://*; img-src blob: data: * insomnia://*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src blob: data: mediastream: * insomnia://*;"
|
||||
/>
|
||||
<title>Utility Process</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Utility Process</h1>
|
||||
<script src="./utility-process.min.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
49
packages/insomnia/src/renderers/utility-process/index.ts
Normal file
49
packages/insomnia/src/renderers/utility-process/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { initPm } from './inso-object';
|
||||
|
||||
const executeAction = 'message-port://utility.process/execute';
|
||||
|
||||
async function init() {
|
||||
const channel = new MessageChannel();
|
||||
|
||||
channel.port1.onmessage = async (ev: MessageEvent) => {
|
||||
const action = ev.data.action;
|
||||
|
||||
if (action === executeAction || action === 'message-port://utility.process/debug') {
|
||||
try {
|
||||
const getPm = new Function('pm', 'return pm;');
|
||||
const rawPm = getPm(ev.data.options.context.pm);
|
||||
const pm = initPm(rawPm);
|
||||
const AsyncFunction = (async () => { }).constructor;
|
||||
const func = AsyncFunction(
|
||||
'pm',
|
||||
// TODO: support require function
|
||||
// TODO: support async/await
|
||||
`
|
||||
${ev.data.options.code};
|
||||
return pm.toObject();
|
||||
`
|
||||
);
|
||||
|
||||
const result = await func(pm);
|
||||
channel.port1.postMessage({
|
||||
action: action === executeAction ? 'message-port://caller/respond' : 'message-port://caller/debug/respond',
|
||||
id: action === executeAction ? undefined : ev.data.options.id,
|
||||
result,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(JSON.stringify(e));
|
||||
channel.port1.postMessage({
|
||||
action: action === executeAction ? 'message-port://caller/respond' : 'message-port://caller/debug/respond',
|
||||
id: action === executeAction ? undefined : ev.data.options.id,
|
||||
error: JSON.stringify(e),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.error(`unknown action ${ev.data}`);
|
||||
}
|
||||
};
|
||||
|
||||
window.postMessage('message-event://preload/publish-port', '*', [channel.port2]);
|
||||
}
|
||||
|
||||
init();
|
||||
190
packages/insomnia/src/renderers/utility-process/inso-object.ts
Normal file
190
packages/insomnia/src/renderers/utility-process/inso-object.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { getHttpRequestSender, getIntepolator, HttpRequestSender, PmHttpRequest, PmHttpResponse } from './static-modules';
|
||||
|
||||
class BaseKV {
|
||||
private kvs = new Map<string, boolean | number | string>();
|
||||
|
||||
constructor(jsonObject: object | undefined) {
|
||||
// TODO: currently it doesn't support getting nested field directly
|
||||
this.kvs = new Map(Object.entries(jsonObject || {}));
|
||||
}
|
||||
|
||||
has = (variableName: string) => {
|
||||
return this.kvs.has(variableName);
|
||||
};
|
||||
|
||||
get = (variableName: string) => {
|
||||
return this.kvs.get(variableName);
|
||||
};
|
||||
|
||||
set = (variableName: string, variableValue: boolean | number | string) => {
|
||||
this.kvs.set(variableName, variableValue);
|
||||
};
|
||||
|
||||
unset = (variableName: string) => {
|
||||
this.kvs.delete(variableName);
|
||||
};
|
||||
|
||||
clear = () => {
|
||||
this.kvs.clear();
|
||||
};
|
||||
|
||||
replaceIn = (template: string) => {
|
||||
return getIntepolator().render(template, this.toObject());
|
||||
};
|
||||
|
||||
toObject = () => {
|
||||
return Object.fromEntries(this.kvs.entries());
|
||||
};
|
||||
}
|
||||
|
||||
class Environment extends BaseKV {
|
||||
constructor(jsonObject: object | undefined) {
|
||||
super(jsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
class Variables {
|
||||
// TODO: support vars for all levels
|
||||
private globals: BaseKV;
|
||||
private collection: BaseKV;
|
||||
private environment: BaseKV;
|
||||
private data: BaseKV;
|
||||
private local: BaseKV;
|
||||
|
||||
constructor(
|
||||
args: {
|
||||
globals: BaseKV;
|
||||
collection: BaseKV;
|
||||
environment: BaseKV;
|
||||
data: BaseKV;
|
||||
local: BaseKV;
|
||||
},
|
||||
) {
|
||||
this.globals = args.globals;
|
||||
this.collection = args.collection;
|
||||
this.environment = args.environment;
|
||||
this.data = args.data;
|
||||
this.local = args.local;
|
||||
}
|
||||
|
||||
has = (variableName: string) => {
|
||||
const globalsHas = this.globals.has(variableName);
|
||||
const collectionHas = this.collection.has(variableName);
|
||||
const environmentHas = this.environment.has(variableName);
|
||||
const dataHas = this.data.has(variableName);
|
||||
const localHas = this.local.has(variableName);
|
||||
|
||||
return globalsHas || collectionHas || environmentHas || dataHas || localHas;
|
||||
};
|
||||
|
||||
get = (variableName: string) => {
|
||||
let finalVal: boolean | number | string | object | undefined = undefined;
|
||||
[
|
||||
this.local,
|
||||
this.data,
|
||||
this.environment,
|
||||
this.collection,
|
||||
this.globals,
|
||||
].forEach(vars => {
|
||||
const value = vars.get(variableName);
|
||||
if (!finalVal && value) {
|
||||
finalVal = value;
|
||||
}
|
||||
});
|
||||
|
||||
return finalVal;
|
||||
};
|
||||
|
||||
set = (variableName: string, variableValue: boolean | number | string) => {
|
||||
this.local.set(variableName, variableValue);
|
||||
};
|
||||
|
||||
replaceIn = (template: string) => {
|
||||
const context = this.toObject();
|
||||
return getIntepolator().render(template, context);
|
||||
};
|
||||
|
||||
toObject = () => {
|
||||
return [
|
||||
this.globals,
|
||||
this.collection,
|
||||
this.environment,
|
||||
this.data,
|
||||
this.local,
|
||||
].map(
|
||||
vars => vars.toObject()
|
||||
).reduce(
|
||||
(ctx, obj) => ({ ...ctx, ...obj }),
|
||||
{},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
class InsomniaObject {
|
||||
public globals: Environment;
|
||||
public collectionVariables: Environment;
|
||||
public environment: Environment;
|
||||
public iterationData: Environment;
|
||||
public variables: Variables;
|
||||
|
||||
private httpRequestSender: HttpRequestSender = getHttpRequestSender();
|
||||
|
||||
constructor(input: {
|
||||
globals: Environment;
|
||||
collectionVariables: Environment;
|
||||
environment: Environment;
|
||||
iterationData: Environment;
|
||||
variables: Variables;
|
||||
}) {
|
||||
this.globals = input.globals;
|
||||
this.collectionVariables = input.collectionVariables;
|
||||
this.environment = input.environment;
|
||||
this.iterationData = input.iterationData;
|
||||
this.variables = input.variables;
|
||||
}
|
||||
|
||||
toObject = () => {
|
||||
return {
|
||||
globals: this.globals.toObject(),
|
||||
variables: this.variables.toObject(),
|
||||
environment: this.environment.toObject(),
|
||||
collectionVariables: this.collectionVariables.toObject(),
|
||||
iterationData: this.iterationData.toObject(),
|
||||
};
|
||||
};
|
||||
|
||||
sendRequest = async (req: string | PmHttpRequest, callback: (e?: Error, resp?: PmHttpResponse) => void) => {
|
||||
return await this.httpRequestSender.sendRequest(req, callback);
|
||||
};
|
||||
}
|
||||
|
||||
interface RawObject {
|
||||
globals?: object;
|
||||
environment?: object;
|
||||
collectionVariables?: object;
|
||||
iterationData?: object;
|
||||
}
|
||||
|
||||
export function initPm(rawObj: RawObject) {
|
||||
const globals = new Environment(rawObj.globals);
|
||||
const environment = new Environment(rawObj.environment);
|
||||
const collectionVariables = new Environment(rawObj.collectionVariables);
|
||||
const iterationData = new Environment(rawObj.iterationData);
|
||||
const local = new Environment({});
|
||||
|
||||
const variables = new Variables({
|
||||
globals,
|
||||
environment,
|
||||
collection: collectionVariables,
|
||||
data: iterationData,
|
||||
local,
|
||||
});
|
||||
|
||||
return new InsomniaObject({
|
||||
globals,
|
||||
environment,
|
||||
collectionVariables,
|
||||
iterationData,
|
||||
variables,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
window.onmessage = (ev: MessageEvent) => {
|
||||
if (ev.data === 'message-event://preload/publish-port') {
|
||||
ipcRenderer.postMessage('ipc://main/publish-port', null, [ev.ports[0]]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
import { configure, type ConfigureOptions, type Environment as NunjuncksEnv } from 'nunjucks';
|
||||
|
||||
export class PmHttpRequest {
|
||||
public name: string;
|
||||
constructor(_: {}, name?: string) {
|
||||
this.name = name || '';
|
||||
}
|
||||
}
|
||||
|
||||
export interface PmHttpResponse {
|
||||
code: number;
|
||||
}
|
||||
|
||||
// common modules
|
||||
class PmHttpRequestSender {
|
||||
constructor() { }
|
||||
|
||||
sendRequest = async (req: string | PmHttpRequest, callback: (e?: Error, resp?: PmHttpResponse) => void): Promise<void> => {
|
||||
if (typeof (req) === 'string') {
|
||||
// simple request
|
||||
return fetch(req)
|
||||
.then(rawResp => {
|
||||
// TODO: init all response fields
|
||||
const resp = {
|
||||
code: rawResp.status,
|
||||
};
|
||||
|
||||
callback(
|
||||
undefined,
|
||||
resp,
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
// TODO:
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HttpRequestSender {
|
||||
sendRequest: (req: string | PmHttpRequest, callback: (e?: Error, resp?: PmHttpResponse) => void) => Promise<void>;
|
||||
}
|
||||
|
||||
const httpRequestSender = new PmHttpRequestSender();
|
||||
export function getHttpRequestSender() {
|
||||
return httpRequestSender;
|
||||
}
|
||||
|
||||
class Intepolator {
|
||||
private engine: NunjuncksEnv;
|
||||
|
||||
constructor(config: ConfigureOptions) {
|
||||
this.engine = configure(config);
|
||||
}
|
||||
|
||||
render = (template: string, context: object) => {
|
||||
// TODO: handle timeout
|
||||
// TODO: support plugin?
|
||||
return this.engine.renderString(template, context);
|
||||
};
|
||||
}
|
||||
|
||||
const intepolator = new Intepolator({
|
||||
autoescape: false,
|
||||
// Don't escape HTML
|
||||
throwOnUndefined: true,
|
||||
// Strict mode
|
||||
tags: {
|
||||
blockStart: '{%',
|
||||
blockEnd: '%}',
|
||||
variableStart: '{{',
|
||||
variableEnd: '}}',
|
||||
commentStart: '{#',
|
||||
commentEnd: '#}',
|
||||
},
|
||||
});
|
||||
|
||||
export function getIntepolator() {
|
||||
return intepolator;
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import { Migrate } from './routes/onboarding.migrate';
|
||||
import { shouldOrganizationsRevalidate } from './routes/organization';
|
||||
import Root from './routes/root';
|
||||
import { initializeSentry } from './sentry';
|
||||
import { getWindowMessageHandler } from './window-message-handlers';
|
||||
|
||||
const Organization = lazy(() => import('./routes/organization'));
|
||||
const Project = lazy(() => import('./routes/project'));
|
||||
@@ -49,6 +50,9 @@ initializeLogging();
|
||||
document.body.setAttribute('data-platform', process.platform);
|
||||
document.title = getProductName();
|
||||
|
||||
const windowMessageHandler = getWindowMessageHandler();
|
||||
windowMessageHandler.start();
|
||||
|
||||
try {
|
||||
// In order to run playwight tests that simulate a logged in user
|
||||
// we need to inject state into localStorage
|
||||
|
||||
89
packages/insomnia/src/ui/window-message-handlers.ts
Normal file
89
packages/insomnia/src/ui/window-message-handlers.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
|
||||
type MessageHandler = (ev: MessageEvent) => Promise<void>;
|
||||
|
||||
class WindowMessageHandler {
|
||||
private utilityProcessPort: MessagePort | undefined;
|
||||
private actionHandlers: Map<string, MessageHandler> = new Map();
|
||||
|
||||
constructor() { }
|
||||
|
||||
publishPortHandler = async (ev: MessageEvent) => {
|
||||
if (ev.ports.length === 0) {
|
||||
console.error('no port is found in the publishing port event');
|
||||
return;
|
||||
}
|
||||
|
||||
this.utilityProcessPort = ev.ports[0];
|
||||
|
||||
this.utilityProcessPort.onmessage = ev => {
|
||||
if (ev.data.action === 'message-port://caller/respond') {
|
||||
// TODO: hook to UI and display result
|
||||
console.log('[main] result from utility process:', ev.data.result);
|
||||
} else if (ev.data.action === 'message-port://caller/debug/respond') {
|
||||
if (ev.data.result) {
|
||||
window.localStorage.setItem(`test_result:${ev.data.id}`, JSON.stringify(ev.data.result));
|
||||
} else {
|
||||
window.localStorage.setItem(`test_error:${ev.data.id}`, JSON.stringify(ev.data.error));
|
||||
}
|
||||
} else {
|
||||
console.error(`unknown action ${ev}`);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
debugEventHandler = async (ev: MessageEvent) => {
|
||||
if (!this.utilityProcessPort) {
|
||||
console.error('utility process port is not inited');
|
||||
return;
|
||||
}
|
||||
|
||||
this.utilityProcessPort.postMessage({
|
||||
action: 'message-port://utility.process/debug',
|
||||
options: {
|
||||
id: ev.data.id,
|
||||
code: ev.data.code,
|
||||
context: ev.data.context,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// startUtilityProcessHandler = async (ev: MessageEvent) => {
|
||||
|
||||
// };
|
||||
|
||||
// TODO: registerMessagePortEventHandler
|
||||
|
||||
register = (actionName: string, handler: MessageHandler) => {
|
||||
this.actionHandlers.set(actionName, handler);
|
||||
};
|
||||
|
||||
start = () => {
|
||||
this.register('message-event://renderers/publish-port', this.publishPortHandler);
|
||||
this.register('message-event://utility.process/debug', this.debugEventHandler);
|
||||
|
||||
window.onmessage = (ev: MessageEvent) => {
|
||||
const action = ev.data.action;
|
||||
if (!action) {
|
||||
// could be react events
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = this.actionHandlers.get(action);
|
||||
if (!handler) {
|
||||
console.error(`no handler is found for action ${action}`);
|
||||
return;
|
||||
}
|
||||
|
||||
handler(ev);
|
||||
};
|
||||
};
|
||||
|
||||
stop = () => {
|
||||
this.actionHandlers.clear();
|
||||
};
|
||||
}
|
||||
|
||||
const windowMessageHandler = new WindowMessageHandler();
|
||||
export function getWindowMessageHandler() {
|
||||
return windowMessageHandler;
|
||||
}
|
||||
Reference in New Issue
Block a user