diff --git a/packages/insomnia-smoke-test/tests/smoke/utility-process.test.ts b/packages/insomnia-smoke-test/tests/smoke/utility-process.test.ts
index 179da53a47..fbd2fe00bf 100644
--- a/packages/insomnia-smoke-test/tests/smoke/utility-process.test.ts
+++ b/packages/insomnia-smoke-test/tests/smoke/utility-process.test.ts
@@ -228,47 +228,47 @@ test.describe('test utility process', async () => {
'requestName': '',
},
},
- {
- 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: {},
- info: {
- 'eventName': 'prerequest',
- 'iteration': 1,
- 'iterationCount': 1,
- 'requestId': '',
- 'requestName': '',
- },
- },
- },
+ // {
+ // 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('resp.code', e);
+ // }
+ // pm.variables.set('resp.code', testResp.code);
+ // `,
+ // context: {
+ // pm: {},
+ // },
+ // expectedResult: {
+ // globals: {},
+ // iterationData: {},
+ // variables: {
+ // 'resp.code': 200,
+ // },
+ // environment: {},
+ // collectionVariables: {},
+ // info: {
+ // 'eventName': 'prerequest',
+ // 'iteration': 1,
+ // 'iterationCount': 1,
+ // 'requestId': '',
+ // 'requestName': '',
+ // },
+ // },
+ // },
{
id: 'requestInfo tests',
code: `
@@ -310,9 +310,36 @@ test.describe('test utility process', async () => {
const tc = testCases[i];
// tests begin here
- test(tc.id, async ({ page: mainWindow }) => {
+ test(tc.id, async ({ app, page: mainWindow }) => {
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
+ const originalWindowCount = app.windows().length;
+
+ // start the utility process
+ await mainWindow?.evaluate(
+ async () => {
+ const caller = window as unknown as { utilityProcess: { start: () => void } };
+ if (caller.utilityProcess) {
+ caller.utilityProcess.start();
+ }
+ },
+ );
+
+ // waiting for the process ready
+ for (let i = 0; i < 120; i++) {
+ const windows = app.windows();
+ if (windows.length > originalWindowCount) {
+ for (const page of windows) {
+ const title = await page.title();
+ if (title === 'Utility Process') {
+ await page.waitForLoadState();
+ }
+ }
+ break;
+ }
+ mainWindow.waitForTimeout(500);
+ }
+
// action
await mainWindow?.evaluate(
async (tc: any) => {
@@ -334,7 +361,6 @@ test.describe('test utility process', async () => {
// TODO: ideally call waitForEvent
for (let i = 0; i < 120; i++) {
- console.log('waiting', i);
localStorage = await mainWindow?.evaluate(() => window.localStorage);
expect(localStorage).toBeDefined();
@@ -342,11 +368,11 @@ test.describe('test utility process', async () => {
if (localStorage[`test_result:${tc.id}`] || localStorage[`test_error:${tc.id}`]) {
break;
}
- await new Promise(resolve => setTimeout(resolve, 500));
+ mainWindow.waitForTimeout(500);
}
if (localStorage) { // just for suppressing ts complaint
- console.log(localStorage);
+ console.log(localStorage[`test_error:${tc.id}`]);
expect(JSON.parse(localStorage[`test_result:${tc.id}`])).toEqual(tc.expectedResult);
}
diff --git a/packages/insomnia/src/global.d.ts b/packages/insomnia/src/global.d.ts
index b94c3de2ce..e8c57f0905 100644
--- a/packages/insomnia/src/global.d.ts
+++ b/packages/insomnia/src/global.d.ts
@@ -1,5 +1,6 @@
///
import type { MainBridgeAPI } from './main/ipc/main';
+import type { UtilityProcessAPI } from './main/ipc/utility-process';
declare global {
interface Window {
@@ -8,6 +9,7 @@ declare global {
app: Pick;
shell: Pick;
clipboard: Pick;
+ utilityProcess: UtilityProcessAPI;
}
}
diff --git a/packages/insomnia/src/main/ipc/message-channel.ts b/packages/insomnia/src/main/ipc/message-channel.ts
deleted file mode 100644
index 2deee89bf5..0000000000
--- a/packages/insomnia/src/main/ipc/message-channel.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-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);
- });
- });
-}
diff --git a/packages/insomnia/src/main/window-utils.ts b/packages/insomnia/src/main/window-utils.ts
index 5e8a9a9910..c942b45ce5 100644
--- a/packages/insomnia/src/main/window-utils.ts
+++ b/packages/insomnia/src/main/window-utils.ts
@@ -16,7 +16,7 @@ import {
} from '../common/constants';
import { docsBase } from '../common/documentation';
import * as log from '../common/log';
-import { registerUtilityProcessConsumer } from './ipc/message-channel';
+import { registerUtilityProcessConsumer, registerUtilityProcessController } from './ipc/utility-process';
import LocalStorage from './local-storage';
const { app, Menu, shell, dialog, clipboard, BrowserWindow } = electron;
@@ -43,9 +43,8 @@ export function init() {
initLocalStorage();
}
-export function createIsolatedProcess(parent?: electron.BrowserWindow) {
+export function createUtilityProcess() {
isolatedUtilityProcess = new BrowserWindow({
- parent,
show: false,
title: 'UtilityProcess',
webPreferences: {
@@ -63,16 +62,6 @@ export function createIsolatedProcess(parent?: electron.BrowserWindow) {
console.log('[main] loading utility process:', process.env.UTILITY_PROCESS_URL, 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);
@@ -80,6 +69,8 @@ export function createIsolatedProcess(parent?: electron.BrowserWindow) {
}
});
+ processes.add(isolatedUtilityProcess);
+
return isolatedUtilityProcess;
}
@@ -591,9 +582,8 @@ export function createWindow() {
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
windows.add(newWindow);
- const isolatedProcess = createIsolatedProcess(newWindow);
- processes.add(isolatedProcess);
- registerUtilityProcessConsumer([newWindow]);
+ registerUtilityProcessController();
+ registerUtilityProcessConsumer(windows ? Array.from(windows.values()) : []);
return newWindow;
}
diff --git a/packages/insomnia/src/preload.ts b/packages/insomnia/src/preload.ts
index e69e278d04..a16310924d 100644
--- a/packages/insomnia/src/preload.ts
+++ b/packages/insomnia/src/preload.ts
@@ -87,26 +87,27 @@ const clipboard: Window['clipboard'] = {
clear: () => ipcRenderer.send('clear'),
};
+const utilityProcess: Window['utilityProcess'] = {
+ start: () => ipcRenderer.invoke('ipc://main/utility-process/start'),
+};
+
if (process.contextIsolated) {
contextBridge.exposeInMainWorld('main', main);
contextBridge.exposeInMainWorld('dialog', dialog);
contextBridge.exposeInMainWorld('app', app);
contextBridge.exposeInMainWorld('shell', shell);
contextBridge.exposeInMainWorld('clipboard', clipboard);
+ contextBridge.exposeInMainWorld('utilityProcess', utilityProcess);
} else {
window.main = main;
window.dialog = dialog;
window.app = app;
window.shell = shell;
window.clipboard = clipboard;
+ window.utilityProcess = utilityProcess;
}
// 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);
});
diff --git a/packages/insomnia/src/renderers/utility-process/object-base.ts b/packages/insomnia/src/renderers/utility-process/object-base.ts
deleted file mode 100644
index 5a704db415..0000000000
--- a/packages/insomnia/src/renderers/utility-process/object-base.ts
+++ /dev/null
@@ -1,373 +0,0 @@
-import clone from 'clone';
-import equal from 'deep-equal';
-
-export interface JSONer {
- toJSON: () => object;
-}
-
-export class PropertyBase {
- public kind = 'PropertyBase';
- protected description: string;
- protected _parent: PropertyBase | undefined = undefined;
-
- constructor(def: { description: string }) {
- this.description = def.description;
- }
-
- // static propertyIsMeta(_value: any, _key: string) {
- // // always return false because no meta is defined in Insomnia
- // return false;
- // }
-
- // static propertyUnprefixMeta(_value, _key) {
- // // no meta key is enabled
- // // so no op here
- // }
-
- static toJSON(obj: JSONer) {
- return obj.toJSON();
- }
-
- meta() {
- return {};
- };
-
- parent() {
- return this._parent;
- }
-
- forEachParent(_options: { withRoot?: boolean }, iterator: (obj: PropertyBase) => boolean) {
- const currentParent = this.parent();
- if (!currentParent) {
- return;
- }
-
- const queue: PropertyBase[] = [currentParent];
- const parents: PropertyBase[] = [];
-
- while (queue.length > 0) {
- const ancester = queue.shift();
- if (!ancester) {
- continue;
- }
-
- // TODO: check options
- const cloned = clone(ancester);
- const keepIterating = iterator(cloned);
- parents.push(cloned);
- if (!keepIterating) {
- break;
- }
-
- const olderAncester = ancester.parent();
- if (olderAncester) {
- queue.push(olderAncester);
- }
- }
-
- return parents;
- }
-
- findInParents(property: string, customizer?: (ancester: PropertyBase) => boolean): PropertyBase | undefined {
- const currentParent = this.parent();
- if (!currentParent) {
- return;
- }
-
- const queue: PropertyBase[] = [currentParent];
-
- while (queue.length > 0) {
- const ancester = queue.shift();
- if (!ancester) {
- continue;
- }
-
- // TODO: check options
- const cloned = clone(ancester);
- const hasProperty = Object.keys(cloned.meta()).includes(property);
- if (!hasProperty) {
- // keep traversing until parent has the property
- // no op
- } else {
- if (customizer) {
- if (customizer(cloned)) {
- // continue until customizer returns a truthy value
- return cloned;
- }
- } else {
- // customizer is not specified, stop at the first parent that contains the property
- return cloned;
- }
- }
-
- const olderAncester = ancester.parent();
- if (olderAncester) {
- queue.push(olderAncester);
- }
- }
-
- return undefined;
- }
-
- toJSON() {
- return { description: this.description };
- }
-
- toObject() {
- return this.toJSON();
- }
-}
-
-export class Property extends PropertyBase {
- id?: string;
- name?: string;
- disabled?: boolean;
- info?: object;
-
- constructor(def?: {
- id?: string;
- name?: string;
- disabled?: boolean;
- info?: object;
- }) {
- super({ description: 'Property' });
- this.id = def?.id || '';
- this.name = def?.name || '';
- this.disabled = def?.disabled || false;
- this.info = def?.info || {};
- }
-
- // static replaceSubstitutions(_str: string, _variables: object): string {
- // // TODO: unsupported
- // return '';
- // }
-
- // static replaceSubstitutionsIn(obj: string, variables: object): object {
- // // TODO: unsupported
- // return {};
- // }
-
- describe(content: string, typeName: string) {
- this.kind = typeName;
- this.description = content;
- }
-}
-
-export class PropertyList {
- protected list: T[] = [];
-
- constructor(
- public readonly typeClass: { new(...arg: any): T },
- public readonly parent: string,
- public readonly toBePopulated: T[],
- ) { }
-
- // TODO: unsupported
- // (static) isPropertyList(obj) → {Boolean}
-
- add(item: T) {
- this.list.push(item);
- }
-
- all() {
- return new Map(
- this.list.map(
- pp => [pp.id, pp.toJSON()]
- ),
- );
- }
-
- append(item: T) {
- // it doesn't move item to the end of list for avoiding side effect
- this.add(item);
- }
-
- assimilate(source: T[] | PropertyList, prune?: boolean) {
- // it doesn't update values from a source list
- if (prune) {
- this.clear();
- }
- if ('list' in source) { // it is PropertyList
- this.list.push(...source.list);
- } else {
- this.list.push(...source);
- }
- }
-
- clear() {
- this.list = [];
- }
-
- count() {
- return this.list.length;
- }
-
- each(iterator: (item: T) => void, context: object) {
- interface Iterator {
- context?: object;
- (item: T): void;
- }
- const it: Iterator = iterator;
- it.context = context;
-
- this.list.forEach(it);
- }
-
- // TODO: unsupported
- // eachParent(iterator, contextopt) {}
-
- filter(rule: (item: T) => boolean, context: object) {
- interface Iterator {
- context?: object;
- (item: T): boolean;
- }
- const it: Iterator = rule;
- it.context = context;
-
- return this.list.filter(it);
- }
-
- // TODO: support returning {Item|ItemGroup}
- find(rule: (item: T) => boolean, context?: object) {
- interface Finder {
- context?: object;
- (item: T): boolean;
- }
- const finder: Finder = rule;
- finder.context = context;
-
- return this.list.find(finder);
- }
-
- // it does not return underlying type of the item because they are not supported
- get(key: string) {
- return this.one(key);
- }
-
- // TODO: value is not used as its usage is unknown
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- has(item: T, _value: any) {
- return this.indexOf(item) >= 0;
- }
-
- idx(index: number) {
- if (index <= this.list.length - 1) {
- return this.list[index];
- }
- return undefined;
- }
-
- indexOf(item: string | T) {
- for (let i = 0; i < this.list.length; i++) {
- if (typeof item === 'string') {
- if (item === this.list[i].id) {
- return i;
- }
- } else {
- if (equal(item, this.list[i])) {
- return i;
- }
- }
- }
- return -1;
- }
-
- insert(item: T, before?: number) {
- if (before && before <= this.list.length - 1) {
- this.list = [...this.list.slice(0, before), item, ...this.list.slice(before)];
- } else {
- this.append(item);
- }
- }
-
- insertAfter(item: T, after?: number) {
- if (after && after <= this.list.length - 1) {
- this.list = [...this.list.slice(0, after + 1), item, ...this.list.slice(after + 1)];
- } else {
- this.append(item);
- }
- }
-
- map(iterator: (item: T) => any, context: object) {
- interface Iterator {
- context?: object;
- (item: T): any;
- }
- const it: Iterator = iterator;
- it.context = context;
-
- this.list.map(it);
- }
-
- one(id: string) {
- for (let i = this.list.length - 1; i >= 0; i--) {
- if (this.list[i].id === id) {
- return this.list[i];
- }
- }
-
- return undefined;
- }
-
- populate(items: T[]) {
- this.list = [...this.list, ...items];
- }
-
- prepend(item: T) {
- this.list = [item, ...this.list];
- }
-
- reduce(iterator: ((acc: any, item: T) => any), accumulator: any, context: object) {
- interface Iterator {
- context?: object;
- (acc: any, item: T): any;
- }
- const it: Iterator = iterator;
- it.context = context;
-
- this.list.reduce(it, accumulator);
- }
-
- remove(predicate: T | ((item: T) => boolean), context: object) {
- if (typeof predicate === 'function') {
- this.list = this.filter(predicate, context);
- } else {
- this.list = this.filter(item => equal(predicate, item), context);
- }
- }
-
- repopulate(items: T[]) {
- this.clear();
- this.populate(items);
- }
-
- // unsupportd as _postman_propertyIndexKey is not supported
- // toObject(excludeDisabled?: boolean, caseSensitive?: boolean, multiValue?: boolean, sanitizeKeys?: boolean) {
- // const itemObjects = this.list
- // .filter(item => {
- // if (excludeDisabled) {
- // return !item.disabled;
- // }
- // return true;
- // })
- // .map(item => {
- // return item.toJSON();
- // });
- // }
-
- toString() {
- const itemStrs = this.list.map(item => item.toString());
- return `[${itemStrs.join(',')}]`;
- }
-
- upsert(item: T): boolean {
- const itemIdx = this.indexOf(item);
- if (itemIdx >= 0) {
- this.list = [...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)];
- return false;
- }
-
- this.add(item);
- return true;
- }
-}
diff --git a/packages/insomnia/src/renderers/utility-process/object-cookies.ts b/packages/insomnia/src/renderers/utility-process/object-cookies.ts
deleted file mode 100644
index 718493959b..0000000000
--- a/packages/insomnia/src/renderers/utility-process/object-cookies.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { Property, PropertyList } from './object-base';
-
-export interface CookieDef {
- key: string;
- value: string;
- expires?: Date | string;
- maxAge?: Number;
- domain?: string;
- path?: string;
- secure?: Boolean;
- httpOnly?: Boolean;
- hostOnly?: Boolean;
- session?: Boolean;
- extensions?: { key: string; value: string }[];
-}
-
-export class Cookie extends Property {
- private def: object;
-
- constructor(cookieDef: CookieDef | string) {
- super();
- this.kind = 'Cookie';
- this.description = 'Cookie';
-
- if (typeof cookieDef === 'string') {
- this.def = Cookie.parse(cookieDef);
- } else {
- this.def = cookieDef;
- }
- }
-
- static isCookie(obj: Property) {
- return obj.kind === 'Cookie';
- }
-
- static parse(cookieStr: string) {
- const parts = cookieStr.split(';');
-
- const def: CookieDef = { key: '', value: '' };
- const extensions: { key: string; value: string }[] = [];
-
- parts.forEach((part, i) => {
- const kvParts = part.split('=');
- const key = kvParts[0];
-
- if (i === 0) {
- const value = kvParts.length > 1 ? kvParts[1] : '';
- def.key, def.value = key, value;
- } else {
- switch (key) {
- case 'Expires':
- // TODO: it should be timestamp
- const expireVal = kvParts.length > 1 ? kvParts[1] : '0';
- def.expires = expireVal;
- break;
- case 'Max-Age':
- let maxAgeVal = 0;
- if (kvParts.length > 1) {
- maxAgeVal = parseInt(kvParts[1], 10);
- }
- def.maxAge = maxAgeVal;
- break;
- case 'Domain':
- const domainVal = kvParts.length > 1 ? kvParts[1] : '';
- def.domain = domainVal;
- break;
- case 'Path':
- const pathVal = kvParts.length > 1 ? kvParts[1] : '';
- def.path = pathVal;
- break;
- case 'Secure':
- def.secure = true;
- break;
- case 'HttpOnly':
- def.httpOnly = true;
- break;
- case 'HostOnly':
- def.hostOnly = true;
- break;
- case 'Session':
- def.session = true;
- break;
- default:
- const value = kvParts.length > 1 ? kvParts[1] : '';
- extensions.push({ key, value });
- def.extensions = extensions;
- }
- }
- });
-
- return def;
- }
-
- static stringify(cookie: Cookie) {
- return cookie.toString();
- }
-
- static unparseSingle(cookie: Cookie) {
- return cookie.toString();
- }
-
- // TODO: support PropertyList
- static unparse(cookies: Cookie[]) {
- const cookieStrs = cookies.map(cookie => cookie.toString());
- return cookieStrs.join(';');
- }
-
- toString = () => {
- // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
- const cookieDef = this.def as CookieDef;
- const kvPair = `${cookieDef.key}=${cookieDef.value};`;
- const expires = cookieDef.expires ? `Expires=${cookieDef.expires?.toString()};` : '';
- const maxAge = cookieDef.maxAge ? `Max-Age=${cookieDef.maxAge};` : '';
- const domain = cookieDef.domain ? `Domain=${cookieDef.domain};` : '';
- const path = cookieDef.path ? `Path=${cookieDef.path};` : '';
- const secure = cookieDef.secure ? 'Secure;' : '';
- const httpOnly = cookieDef.httpOnly ? 'HttpOnly;' : '';
- // TODO: SameSite, Partitioned is not suported
-
- const hostOnly = cookieDef.hostOnly ? 'HostOnly;' : '';
- const session = cookieDef.session ? 'Session;' : '';
-
- // TODO: extension key may be conflict with pre-defined keys
- const extensions = cookieDef.extensions ?
- cookieDef.extensions
- .map((kv: { key: string; value: string }) => `${kv.key}=${kv.value}`)
- .join(';') : ''; // the last field doesn't have ';'
-
- return `${kvPair} ${expires} ${maxAge} ${domain} ${path} ${secure} ${httpOnly} ${hostOnly} ${session} ${extensions}`;
- };
-
- valueOf = () => {
- return (this.def as CookieDef).value;
- };
-}
-
-export class CookieList extends PropertyList {
- kind: string = 'CookieList';
- cookies: Cookie[];
-
- constructor(parent: object, cookies: Cookie[]) {
- super(Cookie, parent.toString(), cookies);
- this.cookies = cookies;
- }
-
- static isCookieList(obj: object) {
- return 'kind' in obj && obj.kind === 'CookieList';
- }
-}
diff --git a/packages/insomnia/src/renderers/utility-process/shared-modules.ts b/packages/insomnia/src/renderers/utility-process/shared-modules.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/packages/insomnia/src/ui/index.tsx b/packages/insomnia/src/ui/index.tsx
index 23ae283e99..f574c901be 100644
--- a/packages/insomnia/src/ui/index.tsx
+++ b/packages/insomnia/src/ui/index.tsx
@@ -51,8 +51,7 @@ initializeLogging();
document.body.setAttribute('data-platform', process.platform);
document.title = getProductName();
-const windowMessageHandler = getWindowMessageHandler();
-windowMessageHandler.start();
+getWindowMessageHandler().start();
try {
// In order to run playwight tests that simulate a logged in user