Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 0 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ jobs:
- name: Lint
run: npm run lint

- name: Check renderer Node import baseline
run: npm run check:renderer-node-imports

- name: Type checks
run: npm run type-check

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ rootCA2.*
final.cpp
insomnia.ico
final.rc
.tmp*
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"scripts": {
"dev": "npm start -w insomnia",
"dev:autoRestart": "npm run start:autoRestart -w insomnia",
"check:renderer-node-imports": "npm run check:renderer-node-imports -w insomnia",
"lint": "npm run lint --workspaces --if-present",
"type-check": "npm run type-check --workspaces --if-present",
"test": "npm run test --workspaces --if-present",
Expand Down
3 changes: 0 additions & 3 deletions packages/insomnia/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
"verify-bundle-plugins": "esr --cache ./scripts/verify-bundle-plugins.ts",
"install-x64-native-dependencies": "esr --cache ./scripts/install-x64-native-dependencies.ts",
"build": "react-router build && esr --cache ./scripts/build.ts --noErrorTruncation",
"analyze:renderer-node-imports": "cross-env NODE_OPTIONS=--max-old-space-size=8192 INSOMNIA_NODE_IMPORT_REPORT=1 react-router build",
"check:renderer-node-imports": "npm run analyze:renderer-node-imports && esr --cache ./scripts/check-renderer-node-imports.ts",
"update:renderer-node-import-baseline": "npm run analyze:renderer-node-imports && esr --cache ./scripts/check-renderer-node-imports.ts --write-baseline",
"build:react-router": "react-router build",
"generate:schema": "esr ./src/schema.ts",
"build:electron-entrypoints": "cross-env NODE_ENV=development esr esbuild.entrypoints.ts",
Expand Down
12 changes: 7 additions & 5 deletions packages/insomnia/src/account/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { models, services } from '~/insomnia-data';

import { AI_PLUGIN_NAME, LLM_BACKENDS } from '../common/constants';
import { database } from '../common/database';
import * as crypt from './crypt';
import type { AESMessage } from './crypt';

export interface SessionData {
accountId: string;
Expand All @@ -15,7 +15,7 @@ export interface SessionData {
lastName: string;
symmetricKey: JsonWebKey;
publicKey: JsonWebKey;
encPrivateKey: crypt.AESMessage;
encPrivateKey: AESMessage;
}

/** Creates a session from a sessionId and derived symmetric key. */
Expand All @@ -28,7 +28,8 @@ export async function absorbKey(sessionId: string, key: string) {
]);
const { public_key: publicKey, enc_private_key: encPrivateKey, enc_symmetric_key: encSymmetricKey } = keys;
const { email, id: accountId, first_name: firstName, last_name: lastName } = profile;
const symmetricKeyStr = crypt.decryptAES(key, JSON.parse(encSymmetricKey));
const { decryptAES } = await import('./crypt');
const symmetricKeyStr = decryptAES(key, JSON.parse(encSymmetricKey));

// Store the information for later
await setSessionData(
Expand Down Expand Up @@ -58,7 +59,8 @@ export async function getPrivateKey() {
throw new Error("Can't get private key: session is missing keys.");
}

const privateKeyStr = crypt.decryptAES(symmetricKey, encPrivateKey);
const { decryptAES } = await import('./crypt');
const privateKeyStr = decryptAES(symmetricKey, encPrivateKey);
return JSON.parse(privateKeyStr) as JsonWebKey;
}

Expand Down Expand Up @@ -105,7 +107,7 @@ export async function setSessionData(
email: string,
symmetricKey: JsonWebKey,
publicKey: JsonWebKey,
encPrivateKey: crypt.AESMessage,
encPrivateKey: AESMessage,
) {
const sessionData: SessionData = {
id,
Expand Down
12 changes: 5 additions & 7 deletions packages/insomnia/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import {
import appConfig from '../../config/config.json';
import { version } from '../../package.json';

// Vite is filtering out process.env variables that are not prefixed with VITE_.
const ENV = 'env';

const env = process[ENV];
const env =
typeof window !== 'undefined' && window.app?.env ? window.app.env : typeof process !== 'undefined' ? process.env : {};

export const INSOMNIA_GITLAB_REDIRECT_URI = env.INSOMNIA_GITLAB_REDIRECT_URI;
export const INSOMNIA_GITLAB_CLIENT_ID = env.INSOMNIA_GITLAB_CLIENT_ID;
Expand All @@ -37,7 +35,7 @@ export const getProductName = () => appConfig.productName;
export const getAppSynopsis = () => appConfig.synopsis;
export const getAppId = () => appConfig.appId;
export const getAppBundlePlugins = () => appConfig.bundlePlugins;
export const getAppEnvironment = () => process.env.INSOMNIA_ENV || 'production';
export const getAppEnvironment = () => env.INSOMNIA_ENV || 'production';
export const isDevelopment = () => getAppEnvironment() === 'development';
export const getSegmentWriteKey = () =>
appConfig.segmentWriteKeys[isDevelopment() || env.PLAYWRIGHT_TEST ? 'development' : 'production'];
Expand All @@ -46,7 +44,7 @@ export const getCioWriteKey = () =>
appConfig.cio[isDevelopment() || env.PLAYWRIGHT_TEST ? 'development' : 'production'].writeKey;
export const getCioSiteId = () =>
appConfig.cio[isDevelopment() || env.PLAYWRIGHT_TEST ? 'development' : 'production'].siteId;
export const getAppBuildDate = () => new Date(process.env.BUILD_DATE ?? '').toLocaleDateString();
export const getAppBuildDate = () => new Date(env.BUILD_DATE ?? '').toLocaleDateString();

export const getBrowserUserAgent = () =>
encodeURIComponent(
Expand All @@ -62,7 +60,7 @@ export function updatesSupported() {
}

// Updates are not supported for Windows portable binaries
if (isWindows && process.env['PORTABLE_EXECUTABLE_DIR']) {
if (isWindows && env['PORTABLE_EXECUTABLE_DIR']) {
return false;
}

Expand Down
84 changes: 40 additions & 44 deletions packages/insomnia/src/common/har.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import clone from 'clone';
import type * as Har from 'har-format';
import { Cookie as ToughCookie } from 'tough-cookie';

import type { BaseModel, Environment, Request, RequestGroup, Response, Workspace } from '~/insomnia-data';
import type { BaseModel, Cookie, Environment, Request, RequestGroup, Response, Workspace } from '~/insomnia-data';
import { models, services } from '~/insomnia-data';
import { applyRequestHooks } from '~/network/network-adapter';

import * as plugins from '../plugins';
import * as pluginApp from '../plugins/context/app';
import * as pluginRequest from '../plugins/context/request';
import * as pluginStore from '../plugins/context/store';
import { RenderError } from '../templating/render-error';
import type { RenderedRequest } from '../templating/types';
import { parseGraphQLReqeustBody } from '../utils/graph-ql';
import { smartEncodeUrl } from '../utils/url/querystring';
import { getAppVersion } from './constants';
import { jarFromCookies } from './cookies';
import { database } from './database';
import { filterHeaders, getSetCookieHeaders, hasAuthHeader } from './misc';
import { getRenderedRequestAndContext } from './render';
Expand Down Expand Up @@ -211,7 +205,7 @@ export async function exportHarResponse(response?: Response) {
status: response.statusCode,
statusText: response.statusMessage,
httpVersion: 'HTTP/1.1',
cookies: getResponseCookies(response),
cookies: await getResponseCookies(response),
headers: getResponseHeaders(response),
content: await getResponseContent(response),
redirectURL: '',
Expand Down Expand Up @@ -264,25 +258,7 @@ async function _applyRequestPluginHooks(
renderedRequest: RenderedRequest,
renderedContext: Record<string, any>,
): Promise<RenderedRequest> {
let newRenderedRequest = renderedRequest;

for (const { plugin, hook } of await plugins.getRequestHooks()) {
newRenderedRequest = clone(newRenderedRequest);
const context = {
...(pluginApp.init() as Record<string, any>),
...(pluginRequest.init(newRenderedRequest, renderedContext) as Record<string, any>),
...(pluginStore.init(plugin) as Record<string, any>),
};

try {
await hook(context);
} catch (err) {
err.plugin = plugin;
throw err;
}
}

return newRenderedRequest;
return applyRequestHooks(renderedRequest, renderedContext);
}

export async function exportHarWithRenderedRequest(renderedRequest: RenderedRequest, addContentLength = false) {
Expand Down Expand Up @@ -321,7 +297,7 @@ export async function exportHarWithRenderedRequest(renderedRequest: RenderedRequ
method: renderedRequest.method,
url,
httpVersion: 'HTTP/1.1',
cookies: getRequestCookies(renderedRequest),
cookies: await getRequestCookies(renderedRequest),
headers: getRequestHeaders(renderedRequest),
queryString: getRequestQueryString(renderedRequest),
postData: await getRequestPostData(renderedRequest),
Expand All @@ -331,36 +307,56 @@ export async function exportHarWithRenderedRequest(renderedRequest: RenderedRequ
return harRequest;
}

function getRequestCookies(renderedRequest: RenderedRequest) {
// filter out invalid cookies to avoid getCookiesSync complaining
async function getRequestCookies(renderedRequest: RenderedRequest): Promise<Har.Cookie[]> {
if (!renderedRequest.url) {
return [];
}
if (typeof window !== 'undefined' && window.main?.cookies) {
const domainCookies = await window.main.cookies.getCookiesForUrl({
cookies: renderedRequest.cookieJar.cookies,
url: renderedRequest.url,
});
return domainCookies.map(mapCookieToHar);
}
// Fallback for non-renderer contexts (tests, plugin window)
const { jarFromCookies } = await import('./cookies');
const jar = jarFromCookies(renderedRequest.cookieJar.cookies);
const domainCookies = renderedRequest.url ? jar.getCookiesSync(renderedRequest.url) : [];
const harCookies: Har.Cookie[] = domainCookies.map(mapCookie);
return harCookies;
const domainCookies = jar.getCookiesSync(renderedRequest.url);
return domainCookies.map(c => mapCookieToHar(c.toJSON() as Cookie));
}

export function getResponseCookiesFromHeaders(headers: Har.Cookie[]) {
return getSetCookieHeaders(headers).reduce((accumulator, harCookie) => {
let cookie: null | undefined | ToughCookie = null;

export async function getResponseCookiesFromHeaders(headers: Har.Cookie[]): Promise<Har.Cookie[]> {
const setCookieHeaders = getSetCookieHeaders(headers);
if (typeof window !== 'undefined' && window.main?.cookies) {
const results: Har.Cookie[] = [];
for (const harCookie of setCookieHeaders) {
const cookie = await window.main.cookies.parse(harCookie.value || '');
if (cookie) {
results.push(mapCookieToHar(cookie));
}
}
return results;
}
// Fallback for non-renderer contexts (tests, plugin window)
const { Cookie: ToughCookie } = await import('tough-cookie');
return setCookieHeaders.reduce((accumulator, harCookie) => {
let cookie = null;
try {
cookie = ToughCookie.parse(harCookie.value || '', { loose: true });
} catch {}

if (cookie === null || cookie === undefined) {
if (!cookie) {
return accumulator;
}

return [...accumulator, mapCookie(cookie)];
return [...accumulator, mapCookieToHar(cookie.toJSON() as Cookie)];
}, [] as Har.Cookie[]);
}

function getResponseCookies(response: Response) {
async function getResponseCookies(response: Response): Promise<Har.Cookie[]> {
const headers = response.headers.filter(Boolean);
return getResponseCookiesFromHeaders(headers);
}

function mapCookie(cookie: ToughCookie) {
function mapCookieToHar(cookie: Cookie): Har.Cookie {
const harCookie: Har.Cookie = {
name: cookie.key,
value: cookie.value,
Expand Down
43 changes: 43 additions & 0 deletions packages/insomnia/src/common/mime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const extensionToMimeType: Record<string, string> = {
csv: 'text/csv',
gif: 'image/gif',
html: 'text/html',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
js: 'application/javascript',
json: 'application/json',
pdf: 'application/pdf',
png: 'image/png',
svg: 'image/svg+xml',
txt: 'text/plain',
xml: 'application/xml',
yaml: 'application/yaml',
yml: 'application/yaml',
};

const mimeTypeToExtension: Record<string, string> = Object.fromEntries(
Object.entries(extensionToMimeType).map(([extension, mimeType]) => [mimeType, extension]),
);

export const lookupMimeType = (filePath: string) => {
const match = /\.([^.]+)$/.exec(filePath.trim().toLowerCase());
if (!match) {
return false;
}

return extensionToMimeType[match[1]] || false;
};

export const mimeTypeExtension = (contentType: string) => {
const normalizedType = contentType.split(';', 1)[0]?.trim().toLowerCase();
if (!normalizedType) {
return false;
}

if (mimeTypeToExtension[normalizedType]) {
return mimeTypeToExtension[normalizedType];
}

const subtype = normalizedType.split('/')[1];
return subtype?.split('+').pop() || false;
};
4 changes: 2 additions & 2 deletions packages/insomnia/src/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { HydratedRouter } from 'react-router/dom';

import { insomniaFetch } from '~/common/insomnia-fetch';
import { initDatabase, initServices, services } from '~/insomnia-data';
import { plugins } from '~/plugins/renderer-bridge';
import { database as clientDatabase } from '~/ui/database.client';
import { clearOAuthWindowSessionId } from '~/ui/spawn-oauth-window';

import { migrateFromLocalStorage, type SessionData, setSessionData, setVaultSessionData } from './account/session';
import { getInsomniaSession, getInsomniaVaultKey, getInsomniaVaultSalt, getSkipOnboarding } from './common/constants';
import { init as initPlugins } from './plugins';
import { applyColorScheme } from './plugins/misc';
import { registerSyncMergeConflictListener } from './sync/vcs/insomnia-sync';
import { HtmlElementWrapper } from './ui/components/html-element-wrapper';
Expand All @@ -40,7 +40,7 @@ delete window._dataServices;

configureFetch(options => insomniaFetch({ ...options, onDeepLink: (uri: string) => window.main.openDeepLink(uri) }));

await initPlugins();
await plugins.reloadPlugins();

await migrateFromLocalStorage();
registerSyncMergeConflictListener();
Expand Down
2 changes: 2 additions & 0 deletions packages/insomnia/src/entry.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { registerInsomniaProtocols } from './main/api.protocol';
import { backupIfNewerVersionAvailable } from './main/backup';
import { registerSyncHandlers } from './main/cloud-sync/ipc';
import { registerGitServiceAPI } from './main/git-service';
import { registerCookieHandlers } from './main/ipc/cookies';
import { ipcMainOn, ipcMainOnce, registerElectronHandlers } from './main/ipc/electron';
import { registerElectronStorageHandlers } from './main/ipc/electron-storage';
import { registergRPCHandlers } from './main/ipc/grpc';
Expand Down Expand Up @@ -88,6 +89,7 @@ app.on('ready', async () => {
// @TODO - Maybe move the register stuff in the registerMainHandlers function
registerMainHandlers();
registerPathHandlers();
registerCookieHandlers();
registergRPCHandlers();
registerGitServiceAPI();
registerLLMConfigServiceAPI();
Expand Down
3 changes: 3 additions & 0 deletions packages/insomnia/src/entry.plugin-window-preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { ipcRenderer } from 'electron';
// Provide window.app so plugin-loading code (which checks process.type === 'renderer')
// can resolve the userData path without needing the main renderer's full preload.
window.app = {
env: Object.fromEntries(
Object.entries(process.env).filter(([key, value]) => value !== undefined && key.startsWith('INSOMNIA_')),
),
getPath: (name: string) => ipcRenderer.sendSync('getPath', name) as string,
getAppPath: () => ipcRenderer.sendSync('getAppPath') as string,
process: { platform: process.platform as NodeJS.Platform },
Expand Down
Loading
Loading