Typed preload (#4886)

* convert preload to typescript

* preload types second pass

* improve types

* fix import

* simplify types

* remove migration map

* preload alongside main

* naming improvements

* fix global typing by adding vite import types

* fix test

* isolate main only network code

* fix mapping mistake

* clarify timeline logic
This commit is contained in:
Jack Kavanagh authored and GitHub committed 2022-06-26 13:36:30 +00:00
1 parent 4f5ef73f7e
commit bbceb6097c
30 files changed
+248 -338

No files matched your search

-1
View File
@@ -31,4 +31,3 @@ packages/insomnia-smoke-test/screenshots
*.tsbuildinfo
dist
.history
packages/insomnia/src/main.min.js.map
+3
View File
@@ -3,3 +3,6 @@ build
# Generated
src/main.min.js
src/main.min.js.map
src/preload.js
src/preload.js.map
+11 -2
View File
@@ -31,8 +31,16 @@ export default async function build(options: Options) {
'process.env.INSOMNIA_ENV': JSON.stringify('production'),
'process.env.BUILD_DATE': JSON.stringify(new Date()),
};
return esbuild.build({
const preload = esbuild.build({
entryPoints: ['./src/preload.ts'],
outfile: path.join(outdir, 'preload.js'),
target: 'esnext',
bundle: true,
platform: 'node',
format: 'cjs',
external: ['electron'],
});
const main = esbuild.build({
entryPoints: ['./src/main.development.ts'],
outfile: path.join(outdir, 'main.min.js'),
bundle: true,
@@ -47,6 +55,7 @@ export default async function build(options: Options) {
...Object.keys(builtinModules),
],
});
return Promise.all([main, preload]);
}
// Build if ran as a cli script
-14
View File
@@ -1,14 +0,0 @@
import { build } from 'esbuild';
import path from 'path';
const outdir = path.join(__dirname, 'build');
build({
entryPoints: ['./src/preload.ts'],
outfile: path.join(outdir, 'preload.js'),
bundle: true,
platform: 'node',
sourcemap: true,
format: 'esm',
external: ['@getinsomnia/node-libcurl', 'electron'],
});
+3 -20
View File
@@ -1,5 +1,4 @@
import childProcess from 'child_process';
import { build } from 'esbuild';
import { readFileSync, writeFileSync } from 'fs';
import licenseChecker from 'license-checker';
import mkdirp from 'mkdirp';
@@ -8,7 +7,7 @@ import path from 'path';
import rimraf from 'rimraf';
import * as vite from 'vite';
import buildMain from '../esbuild.main';
import buildMainAndPreload from '../esbuild.main';
// Start build if ran from CLI
if (require.main === module) {
@@ -138,27 +137,11 @@ export const start = async () => {
path.join(buildFolder, 'opensource-licenses.txt')
);
console.log('[build] Building main.min.js');
await buildMain({
console.log('[build] Building main.min.js and preload');
await buildMainAndPreload({
mode: 'production',
});
console.log('[build] Building preload');
await build({
entryPoints: [path.join(__dirname, '../src/preload.js')],
outfile: path.join(__dirname, '../build/preload.js'),
platform: 'node',
bundle: true,
target: 'esnext',
sourcemap: false,
format: 'esm',
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
minify: true,
external: ['electron'],
});
console.log('[build] Building renderer');
await vite.build({
@@ -1,6 +1,6 @@
import { describe, expect, it, jest } from '@jest/globals';
import { containsOnlyDeprecationWarnings, isDeprecatedDependencies } from '../plugins/install';
import { containsOnlyDeprecationWarnings, isDeprecatedDependencies } from '../main/install-plugin';
describe('install.js', () => {
describe('containsOnlyDeprecationWarning', () => {
-11
View File
@@ -450,14 +450,3 @@ export const toTitleCase = (value: string) => (
.map(capitalize)
.join(' ')
);
// Because node-libcurl changed some names that we used in the timeline
export const LIBCURL_DEBUG_MIGRATION_MAP = {
HeaderIn: 'HEADER_IN',
DataIn: 'DATA_IN',
SslDataIn: 'SSL_DATA_IN',
HeaderOut: 'HEADER_OUT',
DataOut: 'DATA_OUT',
SslDataOut: 'SSL_DATA_OUT',
Text: 'TEXT',
} as const;
+9 -42
View File
@@ -1,11 +1,13 @@
declare module '*.svg' {
const content: any;
export default content;
}
import type { MainBridgeAPI } from './main/ipc/main';
declare module '*.png' {
const content: any;
export default content;
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: Function;
main: MainBridgeAPI;
dialog: Pick<Electron.Dialog, 'showOpenDialog' | 'showSaveDialog'>;
app: Pick<Electron.App, 'getPath' | 'getAppPath'>;
shell: Pick<Electron.Shell, 'showItemInFolder'>;
}
}
declare const __DEV__: boolean;
@@ -19,38 +21,3 @@ declare namespace NodeJS {
jsyaml: any;
}
}
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: Function;
main: {
restart: () => void;
authorizeUserInWindow: (options: { url: string; urlSuccessRegex?: RegExp; urlFailureRegex?: RegExp; sessionId: string }) => Promise<string>;
setMenuBarVisibility: (visible: boolean) => void;
installPlugin: (url: string) => void;
writeFile: (options: {path: string; content: string}) => Promise<string>;
cancelCurlRequest: (requestId: string) => void;
curlRequest: (options: {
curlOptions: CurlOpt[];
responseBodyPath: string;
maxTimelineDataSizeKB: number;
requestId: string;
requestBodyPath?: string;
isMultipart: boolean;
}) => Promise<{
patch: ResponsePatch;
debugTimeline: ResponseTimelineEntry[];
headerResults: HeaderResult[];
}>;
};
dialog: {
showOpenDialog: (options: Electron.OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>;
showSaveDialog: (options: Electron.SaveDialogOptions) => Promise<Electron.SaveDialogReturnValue>;
};
app: {
getPath: (name: string) => string;
getAppPath: () => string;
};
shell: {
showItemInFolder: (fullPath: string) => void;
};
}
+8 -72
View File
@@ -1,7 +1,6 @@
import * as electron from 'electron';
import electron, { app, ipcMain, session } from 'electron';
import contextMenu from 'electron-context-menu';
import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer';
import { writeFile } from 'fs/promises';
import path from 'path';
import appConfig from '../config/config.json';
@@ -11,16 +10,15 @@ 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 * as grpcIpcMain from './main/grpc-ipc-main';
import { registerElectronHandlers } from './main/ipc/electron';
import { registergRPCHandlers } from './main/ipc/grpc';
import { registerMainHandlers } from './main/ipc/main';
import { initializeSentry, sentryWatchAnalyticsEnabled } from './main/sentry';
import { checkIfRestartNeeded } from './main/squirrel-startup';
import * as updates from './main/updates';
import * as windowUtils from './main/window-utils';
import * as models from './models/index';
import type { Stats } from './models/stats';
import { cancelCurlRequest, curlRequest } from './network/libcurl-promise';
import { authorizeUserInWindow } from './network/o-auth-2/misc';
import installPlugin from './plugins/install';
import type { ToastNotification } from './ui/components/toast';
initializeSentry();
@@ -31,7 +29,6 @@ if (checkIfRestartNeeded()) {
}
initializeLogging();
const { app, ipcMain, session } = electron;
const commandLineArgs = process.argv.slice(1);
log.info(`Running version ${getAppVersion()}`);
@@ -61,6 +58,7 @@ app.on('web-contents-created', (_, contents) => {
// When the app is first launched
app.on('ready', async () => {
const { error } = validateInsomniaConfig();
if (error) {
@@ -69,6 +67,9 @@ app.on('ready', async () => {
app.exit(1);
return;
}
registerElectronHandlers();
registerMainHandlers();
registergRPCHandlers();
disableSpellcheckerDownload();
@@ -92,7 +93,6 @@ app.on('ready', async () => {
// Init the rest
await updates.init();
grpcIpcMain.init();
});
// Set as default protocol
@@ -218,70 +218,6 @@ async function _trackStats() {
trackSegmentEvent(SegmentEvent.appStarted, {}, { queueable: true });
ipcMain.handle('showOpenDialog', async (_, options: Electron.OpenDialogOptions) => {
const { filePaths, canceled } = await electron.dialog.showOpenDialog(options);
return { filePaths, canceled };
});
ipcMain.handle('showSaveDialog', async (_, options: Electron.SaveDialogOptions) => {
const { filePath, canceled } = await electron.dialog.showSaveDialog(options);
return { filePath, canceled };
});
ipcMain.handle('installPlugin', (_, options) => {
return installPlugin(options);
});
ipcMain.on('showItemInFolder', (_, name) => {
electron.shell.showItemInFolder(name);
});
ipcMain.on('restart', () => {
app.relaunch();
app.exit();
});
ipcMain.on('setMenuBarVisibility', (_, visible) => {
electron.BrowserWindow.getAllWindows()
.forEach(window => {
// the `setMenuBarVisibility` signature uses `visible` semantics
window.setMenuBarVisibility(visible);
// the `setAutoHideMenu` signature uses `hide` semantics
const hide = !visible;
window.setAutoHideMenuBar(hide);
});
});
ipcMain.on('getPath', (event, name) => {
event.returnValue = electron.app.getPath(name);
});
ipcMain.on('getAppPath', event => {
event.returnValue = electron.app.getAppPath();
});
ipcMain.handle('authorizeUserInWindow', (_, options) => {
const { url, urlSuccessRegex, urlFailureRegex, sessionId } = options;
return authorizeUserInWindow({ url, urlSuccessRegex, urlFailureRegex, sessionId });
});
ipcMain.handle('writeFile', async (_, options) => {
try {
await writeFile(options.path, options.content);
return options.path;
} catch (err) {
throw new Error(err);
}
});
ipcMain.handle('curlRequest', (_, options) => {
return curlRequest(options);
});
ipcMain.on('cancelCurlRequest', (_, requestId: string): void => {
cancelCurlRequest(requestId);
});
ipcMain.once('window-ready', () => {
const { currentVersion, launches, lastVersion } = stats;
@@ -4,7 +4,7 @@ import { ipcMain } from 'electron';
import { GrpcRequestEventEnum } from '../../common/grpc-events';
import * as grpc from '../../network/grpc';
import { ResponseCallbacks } from '../../network/grpc/response-callbacks';
import * as grpcIpcMain from '../grpc-ipc-main';
import * as grpcIpcMain from '../ipc/grpc';
jest.mock('../../network/grpc');
@@ -14,7 +14,7 @@ describe('grpcIpcMain', () => {
};
const id = 'abc';
beforeEach(() => {
grpcIpcMain.init(); // ipcMain is mocked
grpcIpcMain.registergRPCHandlers(); // ipcMain is mocked
});
it.each(Object.values(GrpcRequestEventEnum))('should add listener for channel: %s', channel => {
@@ -1,5 +1,6 @@
import childProcess from 'child_process';
import * as electron from 'electron';
import { app } from 'electron';
import fs from 'fs';
import fsx from 'fs-extra';
import mkdirp from 'mkdirp';
@@ -248,8 +249,6 @@ export function isDeprecatedDependencies(str: string) {
}
function _getYarnPath() {
const { app } = process.type === 'renderer' ? window : electron;
// TODO: This is brittle. Make finding this more robust.
if (isDevelopment()) {
return path.resolve(app.getAppPath(), './bin/yarn-standalone.js');
@@ -0,0 +1,36 @@
import type { OpenDialogOptions, SaveDialogOptions } from 'electron';
import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron';
export function registerElectronHandlers() {
ipcMain.on('setMenuBarVisibility', (_, visible: boolean) => {
BrowserWindow.getAllWindows()
.forEach(window => {
// the `setMenuBarVisibility` signature uses `visible` semantics
window.setMenuBarVisibility(visible);
// the `setAutoHideMenu` signature uses `hide` semantics
const hide = !visible;
window.setAutoHideMenuBar(hide);
});
});
ipcMain.handle('showOpenDialog', async (_, options: OpenDialogOptions) => {
const { filePaths, canceled } = await dialog.showOpenDialog(options);
return { filePaths, canceled };
});
ipcMain.handle('showSaveDialog', async (_, options: SaveDialogOptions) => {
const { filePath, canceled } = await dialog.showSaveDialog(options);
return { filePath, canceled };
});
ipcMain.on('showItemInFolder', (_, name: string) => {
shell.showItemInFolder(name);
});
ipcMain.on('getPath', (event, name: Parameters<typeof Electron.app['getPath']>[0]) => {
event.returnValue = app.getPath(name);
});
ipcMain.on('getAppPath', event => {
event.returnValue = app.getAppPath();
});
}
@@ -1,11 +1,11 @@
import { ipcMain } from 'electron';
import { GrpcRequestEventEnum } from '../common/grpc-events';
import * as grpc from '../network/grpc';
import { GrpcIpcRequestParams } from '../network/grpc/prepare';
import { ResponseCallbacks } from '../network/grpc/response-callbacks';
import { GrpcRequestEventEnum } from '../../common/grpc-events';
import * as grpc from '../../network/grpc';
import { GrpcIpcRequestParams } from '../../network/grpc/prepare';
import { ResponseCallbacks } from '../../network/grpc/response-callbacks';
export function init() {
export function registergRPCHandlers() {
ipcMain.on(GrpcRequestEventEnum.start, (event, params: GrpcIpcRequestParams) =>
grpc.start(params, new ResponseCallbacks(event)),
);
+47
View File
@@ -0,0 +1,47 @@
import { app, ipcMain } from 'electron';
import { writeFile } from 'fs/promises';
import { authorizeUserInWindow } from '../../network/o-auth-2/misc';
import installPlugin from '../install-plugin';
import { cancelCurlRequest, curlRequest } from '../network/libcurl-promise';
export interface MainBridgeAPI {
restart: () => void;
authorizeUserInWindow: typeof authorizeUserInWindow;
setMenuBarVisibility: (visible: boolean) => void;
installPlugin: typeof installPlugin;
writeFile: (options: { path: string; content: string }) => Promise<string>;
cancelCurlRequest: typeof cancelCurlRequest;
curlRequest: typeof curlRequest;
}
export function registerMainHandlers() {
ipcMain.handle('authorizeUserInWindow', (_, options: Parameters<typeof authorizeUserInWindow>[0]) => {
const { url, urlSuccessRegex, urlFailureRegex, sessionId } = options;
return authorizeUserInWindow({ url, urlSuccessRegex, urlFailureRegex, sessionId });
});
ipcMain.handle('writeFile', async (_, options: { path: string; content: string }) => {
try {
await writeFile(options.path, options.content);
return options.path;
} catch (err) {
throw new Error(err);
}
});
ipcMain.handle('curlRequest', (_, options: Parameters<typeof curlRequest>[0]) => {
return curlRequest(options);
});
ipcMain.on('cancelCurlRequest', (_, requestId: string): void => {
cancelCurlRequest(requestId);
});
ipcMain.handle('installPlugin', (_, lookupName: string) => {
return installPlugin(lookupName);
});
ipcMain.on('restart', () => {
app.relaunch();
app.exit();
});
}
@@ -11,17 +11,15 @@ import mkdirp from 'mkdirp';
import path from 'path';
import { Readable, Writable } from 'stream';
import tls from 'tls';
import { ValueOf } from 'type-fest';
import { parse as urlParse } from 'url';
import { v4 as uuidv4 } from 'uuid';
import { version } from '../../package.json';
import { AUTH_AWS_IAM, AUTH_DIGEST, AUTH_NETRC, AUTH_NTLM, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED } from '../common/constants';
import { describeByteSize, hasAuthHeader, hasUserAgentHeader } from '../common/misc';
import { ClientCertificate } from '../models/client-certificate';
import { ResponseHeader } from '../models/response';
import { version } from '../../../package.json';
import { AUTH_AWS_IAM, AUTH_DIGEST, AUTH_NETRC, AUTH_NTLM, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED } from '../../common/constants';
import { describeByteSize, hasAuthHeader, hasUserAgentHeader } from '../../common/misc';
import { ClientCertificate } from '../../models/client-certificate';
import { ResponseHeader } from '../../models/response';
import { buildMultipart } from './multipart';
import { ResponsePatch } from './network';
import { parseHeaderStrings } from './parse-header-strings';
interface CurlRequestOptions {
@@ -57,8 +55,9 @@ interface SettingsUsedHere {
httpsProxy: string;
noProxy: string;
}
interface ResponseTimelineEntry {
name: ValueOf<typeof LIBCURL_DEBUG_MIGRATION_MAP>;
export interface ResponseTimelineEntry {
name: keyof typeof CurlInfoDebug;
timestamp: number;
value: string;
}
@@ -70,6 +69,26 @@ interface CurlRequestOutput {
responseBodyPath?: string;
}
export interface ResponsePatch {
bodyCompression?: 'zip' | null;
bodyPath?: string;
bytesContent?: number;
bytesRead?: number;
contentType?: string;
elapsedTime: number;
environmentId?: string | null;
error?: string;
headers?: ResponseHeader[];
httpVersion?: string;
message?: string;
parentId?: string;
settingSendCookies?: boolean;
settingStoreCookies?: boolean;
statusCode?: number;
statusMessage?: string;
timelinePath?: string;
url?: string;
}
const getDataDirectory = () => process.env.INSOMNIA_DATA_PATH || electron.app.getPath('userData');
// NOTE: this is a dictionary of functions to close open listeners
@@ -99,23 +118,23 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
if (cert) {
curl.setOpt(Curl.option.SSLCERT, cert);
curl.setOpt(Curl.option.SSLCERTTYPE, 'PEM');
debugTimeline.push({ value: 'Adding SSL PEM certificate', name: 'TEXT', timestamp: Date.now() });
debugTimeline.push({ value: 'Adding SSL PEM certificate', name: 'Text', timestamp: Date.now() });
}
if (pfx) {
curl.setOpt(Curl.option.SSLCERT, pfx);
curl.setOpt(Curl.option.SSLCERTTYPE, 'P12');
debugTimeline.push({ value: 'Adding SSL P12 certificate', name: 'TEXT', timestamp: Date.now() });
debugTimeline.push({ value: 'Adding SSL P12 certificate', name: 'Text', timestamp: Date.now() });
}
if (key) {
curl.setOpt(Curl.option.SSLKEY, key);
debugTimeline.push({ value: 'Adding SSL KEY certificate', name: 'TEXT', timestamp: Date.now() });
debugTimeline.push({ value: 'Adding SSL KEY certificate', name: 'Text', timestamp: Date.now() });
}
if (passphrase) {
curl.setOpt(Curl.option.KEYPASSWD, passphrase);
}
});
const httpVersion = getHttpVersion(settings.preferredHttpVersion);
debugTimeline.push({ value: httpVersion.log, name: 'TEXT', timestamp: Date.now() });
debugTimeline.push({ value: httpVersion.log, name: 'Text', timestamp: Date.now() });
if (httpVersion.curlHttpVersion) {
curl.setOpt(Curl.option.HTTP_VERSION, httpVersion.curlHttpVersion);
@@ -134,7 +153,7 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
const { httpProxy, httpsProxy, noProxy } = settings;
const proxyHost = protocol === 'https:' ? httpsProxy : httpProxy;
const proxy = proxyHost ? setDefaultProtocol(proxyHost) : null;
debugTimeline.push({ value: `Enable network proxy for ${protocol || ''}`, name: 'TEXT', timestamp: Date.now() });
debugTimeline.push({ value: `Enable network proxy for ${protocol || ''}`, name: 'Text', timestamp: Date.now() });
if (proxy) {
curl.setOpt(Curl.option.PROXY, proxy);
curl.setOpt(Curl.option.PROXYAUTH, CurlAuth.Any);
@@ -148,14 +167,14 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
curl.setOpt(Curl.option.TIMEOUT_MS, 0);
} else {
curl.setOpt(Curl.option.TIMEOUT_MS, timeout);
debugTimeline.push({ value: `Enable timeout of ${timeout}ms`, name: 'TEXT', timestamp: Date.now() });
debugTimeline.push({ value: `Enable timeout of ${timeout}ms`, name: 'Text', timestamp: Date.now() });
}
const { validateSSL } = settings;
if (!validateSSL) {
curl.setOpt(Curl.option.SSL_VERIFYHOST, 0);
curl.setOpt(Curl.option.SSL_VERIFYPEER, 0);
}
debugTimeline.push({ value: `${validateSSL ? 'Enable' : 'Disable'} SSL validation`, name: 'TEXT', timestamp: Date.now() });
debugTimeline.push({ value: `${validateSSL ? 'Enable' : 'Disable'} SSL validation`, name: 'Text', timestamp: Date.now() });
if (req.settingFollowRedirects === 'off') {
curl.setOpt(Curl.option.FOLLOWLOCATION, false);
@@ -178,7 +197,7 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
}
// set-cookies from previous redirects
if (cookieJar.cookies.length) {
debugTimeline.push({ value: `Enable cookie sending with jar of ${cookieJar.cookies.length} cookie${cookieJar.cookies.length !== 1 ? 's' : ''}`, name: 'TEXT', timestamp: Date.now() });
debugTimeline.push({ value: `Enable cookie sending with jar of ${cookieJar.cookies.length} cookie${cookieJar.cookies.length !== 1 ? 's' : ''}`, name: 'Text', timestamp: Date.now() });
for (const cookie of cookieJar.cookies) {
const setCookie = [
cookie.httpOnly ? `#HttpOnly_${cookie.domain}` : cookie.domain,
@@ -277,21 +296,22 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
return 0;
}
// NOTE: resolves "Text" from CurlInfoDebug[CurlInfoDebug.Text]
let name = CurlInfoDebug[infoType] as keyof typeof CurlInfoDebug;
let timelineMessage;
if (infoType === CurlInfoDebug.DataOut) {
const isRequestData = infoType === CurlInfoDebug.DataOut;
if (isRequestData) {
// Ignore large post data messages
const isLessThan10KB = buffer.length / 1024 < (settings.maxTimelineDataSizeKB || 1);
timelineMessage = isLessThan10KB ? buffer.toString('utf8') : `(${describeByteSize(buffer.length)} hidden)`;
}
if (infoType === CurlInfoDebug.DataIn) {
const isResponseData = infoType === CurlInfoDebug.DataIn;
if (isResponseData) {
timelineMessage = `Received ${describeByteSize(buffer.length)} chunk`;
name = 'Text';
}
debugTimeline.push({
name: infoType === CurlInfoDebug.DataIn ? 'TEXT' : LIBCURL_DEBUG_MIGRATION_MAP[CurlInfoDebug[infoType]],
value: timelineMessage || buffer.toString('utf8'),
timestamp: Date.now(),
});
const value = timelineMessage || buffer.toString('utf8');
debugTimeline.push({ name, value, timestamp: Date.now() });
return 0;
});
@@ -300,7 +320,7 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
curl.enable(CurlFeature.Raw);
// NOTE: legacy write end callback
curl.on('end', () => responseBodyWriteStream.end());
curl.on('end', async (_1, _2, rawHeaders: Buffer) => {
curl.on('end', async (_1: any, _2: any, rawHeaders: Buffer) => {
const patch = {
bytesContent: responseBodyBytes,
bytesRead: curl.getInfo(Curl.info.SIZE_DOWNLOAD) as number,
@@ -357,18 +377,6 @@ const closeReadFunction = (fd: number, isMultipart: boolean, path?: string) => {
}
};
// Because node-libcurl changed some names that we used in the timeline
const LIBCURL_DEBUG_MIGRATION_MAP: Record<string, string> = {
HeaderIn: 'HEADER_IN',
DataIn: 'DATA_IN',
SslDataIn: 'SSL_DATA_IN',
HeaderOut: 'HEADER_OUT',
DataOut: 'DATA_OUT',
SslDataOut: 'SSL_DATA_OUT',
Text: 'TEXT',
'': '',
};
interface HeaderResult {
headers: ResponseHeader[];
version: string;
@@ -7,7 +7,7 @@ import { lookup } from 'mime-types';
import os from 'os';
import path from 'path';
import type { RequestBodyParameter } from '../models/request';
import type { RequestBodyParameter } from '../../models/request';
export const DEFAULT_BOUNDARY = 'X-INSOMNIA-BOUNDARY';
@@ -7,7 +7,7 @@ import {
AUTH_DIGEST,
AUTH_NTLM,
CONTENT_TYPE_FORM_DATA,
} from '../common/constants';
} from '../../common/constants';
import {
getContentTypeHeader,
getHostHeader,
@@ -15,7 +15,7 @@ import {
hasAcceptHeader,
hasAuthHeader,
hasContentTypeHeader,
} from '../common/misc';
} from '../../common/misc';
import { DEFAULT_BOUNDARY } from './multipart';
// Special header value that will prevent the header being sent
+2 -9
View File
@@ -1,10 +1,9 @@
import fs from 'fs';
import { Readable } from 'stream';
import { ValueOf } from 'type-fest';
import zlib from 'zlib';
import { database as db, Query } from '../common/database';
import { LIBCURL_DEBUG_MIGRATION_MAP } from '../common/misc';
import type { ResponseTimelineEntry } from '../main/network/libcurl-promise';
import type { BaseModel } from './index';
import * as models from './index';
@@ -23,12 +22,6 @@ export interface ResponseHeader {
value: string;
}
export interface ResponseTimelineEntry {
name: ValueOf<typeof LIBCURL_DEBUG_MIGRATION_MAP>;
timestamp: number;
value: string;
}
type Compression = 'zip' | null | '__NEEDS_MIGRATION__' | undefined;
export interface BaseResponse {
@@ -234,7 +227,7 @@ export function getTimeline(response: Response, showBody?: boolean) {
const timeline = JSON.parse(rawBuffer.toString()) as ResponseTimelineEntry[];
const body: ResponseTimelineEntry[] = showBody ? [
{
name: LIBCURL_DEBUG_MIGRATION_MAP.DataOut,
name: 'DataOut',
timestamp: Date.now(),
value: fs.readFileSync(bodyPath).toString(),
},
@@ -4,7 +4,7 @@ import fs from 'fs';
import path from 'path';
import { globalBeforeEach } from '../../__jest__/before-each';
import { buildMultipart, DEFAULT_BOUNDARY } from '../multipart';
import { buildMultipart, DEFAULT_BOUNDARY } from '../../main/network/multipart';
window.app = electron.app;
@@ -17,12 +17,12 @@ import {
} from '../../common/constants';
import { filterHeaders } from '../../common/misc';
import { getRenderedRequestAndContext } from '../../common/render';
import { _parseHeaders, getHttpVersion } from '../../main/network/libcurl-promise';
import { DEFAULT_BOUNDARY } from '../../main/network/multipart';
import { _getAwsAuthHeaders } from '../../main/network/parse-header-strings';
import * as models from '../../models';
import { _parseHeaders, getHttpVersion } from '../libcurl-promise';
import { DEFAULT_BOUNDARY } from '../multipart';
import * as networkUtils from '../network';
import { getSetCookiesFromResponseHeaders } from '../network';
import { _getAwsAuthHeaders } from '../parse-header-strings';
window.app = electron.app;
@@ -1,7 +1,7 @@
import { describe, expect, it } from '@jest/globals';
import { AUTH_AWS_IAM, CONTENT_TYPE_FORM_DATA } from '../../common/constants';
import { parseHeaderStrings } from '../parse-header-strings';
import { parseHeaderStrings } from '../../main/network/parse-header-strings';
describe('parseHeaderStrings', () => {
it('should default with empty inputs', () => {
@@ -25,7 +25,7 @@ export async function getAuthHeader(renderedRequest: RenderedRequest, url: strin
const requestId = renderedRequest._id;
if (authentication.disabled) {
return null;
return;
}
if (authentication.type === AUTH_BASIC) {
@@ -51,7 +51,7 @@ export async function getAuthHeader(renderedRequest: RenderedRequest, url: strin
const token = oAuth2Token.accessToken;
return _buildBearerHeader(token, authentication.tokenPrefix);
} else {
return null;
return;
}
}
@@ -64,7 +64,7 @@ export async function getAuthHeader(renderedRequest: RenderedRequest, url: strin
value: oAuth1Token.Authorization,
};
} else {
return null;
return;
}
}
@@ -138,12 +138,12 @@ export async function getAuthHeader(renderedRequest: RenderedRequest, url: strin
});
}
return null;
return;
}
export const _buildBearerHeader = (accessToken: string, prefix: string) => {
if (!accessToken) {
return null;
return;
}
const header = {
+11 -32
View File
@@ -28,11 +28,11 @@ import {
RENDER_PURPOSE_NO_RENDER,
RENDER_PURPOSE_SEND,
} from '../common/render';
import type { ResponsePatch, ResponseTimelineEntry } from '../main/network/libcurl-promise';
import * as models from '../models';
import { ClientCertificate } from '../models/client-certificate';
import type { Environment } from '../models/environment';
import type { Request } from '../models/request';
import type { ResponseHeader, ResponseTimelineEntry } from '../models/response';
import type { Settings } from '../models/settings';
import { isWorkspace } from '../models/workspace';
import * as pluginContexts from '../plugins/context/index';
@@ -40,27 +40,6 @@ import * as plugins from '../plugins/index';
import { getAuthHeader } from './authentication';
import { urlMatchesCertHost } from './url-matches-cert-host';
export interface ResponsePatch {
bodyCompression?: 'zip' | null;
bodyPath?: string;
bytesContent?: number;
bytesRead?: number;
contentType?: string;
elapsedTime: number;
environmentId?: string | null;
error?: string;
headers?: ResponseHeader[];
httpVersion?: string;
message?: string;
parentId?: string;
settingSendCookies?: boolean;
settingStoreCookies?: boolean;
statusCode?: number;
statusMessage?: string;
timelinePath?: string;
url?: string;
}
// Time since user's last keypress to wait before making the request
const MAX_DELAY_TIME = 1000;
@@ -95,8 +74,8 @@ export async function _actuallySend(
// NOTE: conditionally use ipc bridge, renderer cannot import native modules directly
const nodejsCancelCurlRequest = process.type === 'renderer'
? window.main.cancelCurlRequest
// eslint-disable-next-line @typescript-eslint/no-var-requires
: require('./libcurl-promise').cancelCurlRequest;
: (await import('../main/network/libcurl-promise')).cancelCurlRequest;
nodejsCancelCurlRequest(renderedRequest._id);
return resolve({
elapsedTime: 0,
@@ -122,12 +101,12 @@ export async function _actuallySend(
const socketUrl = (match && match[3]) || '';
finalUrl = `${protocol}//${socketUrl}`;
}
timeline.push({ value: `Preparing request to ${finalUrl}`, name: 'TEXT', timestamp: Date.now() });
timeline.push({ value: `Current time is ${new Date().toISOString()}`, name: 'TEXT', timestamp: Date.now() });
timeline.push({ value: `${renderedRequest.settingEncodeUrl ? 'Enable' : 'Disable'} automatic URL encoding`, name: 'TEXT', timestamp: Date.now() });
timeline.push({ value: `Preparing request to ${finalUrl}`, name: 'Text', timestamp: Date.now() });
timeline.push({ value: `Current time is ${new Date().toISOString()}`, name: 'Text', timestamp: Date.now() });
timeline.push({ value: `${renderedRequest.settingEncodeUrl ? 'Enable' : 'Disable'} automatic URL encoding`, name: 'Text', timestamp: Date.now() });
if (!renderedRequest.settingSendCookies) {
timeline.push({ value: 'Disable cookie sending due to user setting', name: 'TEXT', timestamp: Date.now() });
timeline.push({ value: 'Disable cookie sending due to user setting', name: 'Text', timestamp: Date.now() });
}
const certificates = clientCertificates.filter(c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, 'https:'), renderedRequest.url));
@@ -137,8 +116,8 @@ export async function _actuallySend(
// NOTE: conditionally use ipc bridge, renderer cannot import native modules directly
const nodejsCurlRequest = process.type === 'renderer'
? window.main.curlRequest
// eslint-disable-next-line @typescript-eslint/no-var-requires
: require('./libcurl-promise').curlRequest;
: (await import('../main/network/libcurl-promise')).curlRequest;
const requestOptions = {
requestId: renderedRequest._id,
req: renderedRequest,
@@ -160,11 +139,11 @@ export async function _actuallySend(
if (totalSetCookies) {
const currentUrl = getCurrentUrl({ headerResults, finalUrl });
const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ setCookieStrings, currentUrl, cookieJar });
rejectedCookies.forEach(errorMessage => timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'TEXT', timestamp: Date.now() }));
rejectedCookies.forEach(errorMessage => timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'Text', timestamp: Date.now() }));
const hasCookiesToPersist = totalSetCookies > rejectedCookies.length;
if (hasCookiesToPersist) {
await models.cookieJar.update(cookieJar, { cookies });
timeline.push({ value: `Saved ${totalSetCookies} cookies`, name: 'TEXT', timestamp: Date.now() });
timeline.push({ value: `Saved ${totalSetCookies} cookies`, name: 'Text', timestamp: Date.now() });
}
}
}
-34
View File
@@ -1,34 +0,0 @@
const { contextBridge, ipcRenderer } = require('electron');
const main = {
restart: () => ipcRenderer.send('restart'),
authorizeUserInWindow: (/** @type {any} */ options) => ipcRenderer.invoke('authorizeUserInWindow', options),
setMenuBarVisibility: (/** @type {any} */ options) => ipcRenderer.send('setMenuBarVisibility', options),
installPlugin: (/** @type {any} */ options) => ipcRenderer.invoke('installPlugin', options),
curlRequest: (/** @type {any} */ options) => ipcRenderer.invoke('curlRequest', options),
cancelCurlRequest: (/** @type {any} */ options) => ipcRenderer.send('cancelCurlRequest', options),
writeFile: (/** @type {any} */ options) => ipcRenderer.invoke('writeFile', options),
};
const dialog = {
showOpenDialog: (/** @type {any} */ options) => ipcRenderer.invoke('showOpenDialog', options),
showSaveDialog: (/** @type {any} */ options) => ipcRenderer.invoke('showSaveDialog', options),
};
const app = {
getPath: (/** @type {any} */ options) => ipcRenderer.sendSync('getPath', options),
getAppPath: (/** @type {any} */ options) => ipcRenderer.sendSync('getAppPath', options),
};
const shell = {
showItemInFolder: (/** @type {any} */ options) => ipcRenderer.send('showItemInFolder', options),
};
if (process.contextIsolated) {
contextBridge.exposeInMainWorld('main', main);
contextBridge.exposeInMainWorld('dialog', dialog);
contextBridge.exposeInMainWorld('app', app);
contextBridge.exposeInMainWorld('shell', shell);
} else {
window.main = main;
window.dialog = dialog;
window.app = app;
window.shell = shell;
}
+34
View File
@@ -0,0 +1,34 @@
import { contextBridge, ipcRenderer } from 'electron';
const main: Window['main'] = {
restart: () => ipcRenderer.send('restart'),
authorizeUserInWindow: options => ipcRenderer.invoke('authorizeUserInWindow', options),
setMenuBarVisibility: options => ipcRenderer.send('setMenuBarVisibility', options),
installPlugin: options => ipcRenderer.invoke('installPlugin', options),
curlRequest: options => ipcRenderer.invoke('curlRequest', options),
cancelCurlRequest: options => ipcRenderer.send('cancelCurlRequest', options),
writeFile: options => ipcRenderer.invoke('writeFile', options),
};
const dialog: Window['dialog'] = {
showOpenDialog: options => ipcRenderer.invoke('showOpenDialog', options),
showSaveDialog: options => ipcRenderer.invoke('showSaveDialog', options),
};
const app: Window['app'] = {
getPath: options => ipcRenderer.sendSync('getPath', options),
getAppPath: () => ipcRenderer.sendSync('getAppPath'),
};
const shell: Window['shell'] = {
showItemInFolder: options => ipcRenderer.send('showItemInFolder', options),
};
if (process.contextIsolated) {
contextBridge.exposeInMainWorld('main', main);
contextBridge.exposeInMainWorld('dialog', dialog);
contextBridge.exposeInMainWorld('app', app);
contextBridge.exposeInMainWorld('shell', shell);
} else {
window.main = main;
window.dialog = dialog;
window.app = app;
window.shell = shell;
}
@@ -21,12 +21,12 @@ import { hotKeyRefs } from '../../../../common/hotkeys';
import { executeHotKey } from '../../../../common/hotkeys-listener';
import { markdownToHTML } from '../../../../common/markdown-to-html';
import { jsonParseOr } from '../../../../common/misc';
import type { ResponsePatch } from '../../../../main/network/libcurl-promise';
import * as models from '../../../../models/index';
import type { Request } from '../../../../models/request';
import { newBodyRaw } from '../../../../models/request';
import type { Settings } from '../../../../models/settings';
import type { Workspace } from '../../../../models/workspace';
import type { ResponsePatch } from '../../../../network/network';
import * as network from '../../../../network/network';
import { Dropdown } from '../../base/dropdown/dropdown';
import { DropdownButton } from '../../base/dropdown/dropdown-button';
@@ -136,7 +136,7 @@ export class ResponsePane extends PureComponent<Props> {
const timeline = models.response.getTimeline(response);
const headers = timeline
.filter(v => v.name === 'HEADER_IN')
.filter(v => v.name === 'HeaderIn')
.map(v => v.value)
.join('');
@@ -1,9 +1,9 @@
import React, { PureComponent } from 'react';
import { clickLink } from '../../../common/electron-helpers';
import { LIBCURL_DEBUG_MIGRATION_MAP } from '../../../common/misc';
import type { ResponseTimelineEntry } from '../../../main/network/libcurl-promise';
import * as models from '../../../models';
import { Response, ResponseTimelineEntry } from '../../../models/response';
import type { Response } from '../../../models/response';
import { CodeEditor } from '../codemirror/code-editor';
interface Props {
@@ -47,40 +47,16 @@ export class ResponseTimelineViewer extends PureComponent<Props, State> {
renderRow(row: ResponseTimelineEntry, i: number, all: ResponseTimelineEntry[]) {
const { name, value } = row;
const previousName = i > 0 ? all[i - 1].name : '';
let prefix: string | null = null;
switch (name) {
case LIBCURL_DEBUG_MIGRATION_MAP.HeaderIn:
prefix = '< ';
break;
case LIBCURL_DEBUG_MIGRATION_MAP.DataIn:
prefix = '| ';
break;
case LIBCURL_DEBUG_MIGRATION_MAP.SslDataIn:
prefix = '<< ';
break;
case LIBCURL_DEBUG_MIGRATION_MAP.HeaderOut:
prefix = '> ';
break;
case LIBCURL_DEBUG_MIGRATION_MAP.DataOut:
prefix = '| ';
break;
case LIBCURL_DEBUG_MIGRATION_MAP.SslDataOut:
prefix = '>> ';
break;
case LIBCURL_DEBUG_MIGRATION_MAP.Text:
prefix = '* ';
break;
default:
return null;
}
const prefixLookup: Record<ResponseTimelineEntry['name'], string> = {
HeaderIn: '< ',
DataIn: '| ',
SslDataIn: '<< ',
HeaderOut: '> ',
DataOut: '| ',
SslDataOut: '>> ',
Text: '* ',
};
const prefix: string = prefixLookup[name] || '* ';
const lines = (value + '').replace(/\n$/, '').split('\n');
const newLines = lines.filter(l => !l.match(/^\s*$/)).map(l => `${prefix}${l}`);
+1 -1
View File
@@ -9,7 +9,7 @@
"jsx": "react",
"experimentalDecorators": true,
"skipLibCheck": true,
"types": ["node"],
"types": ["node", "vite/client"],
},
"include": [
"**/*.d.ts",
+1 -1
View File
@@ -13,7 +13,7 @@
"package.json",
"scripts",
"send-request",
"vite.config.ts",
"vite.config.ts"
],
"exclude": [
"**/@types/mocha",