mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
electron v15 pre-upgrade refactoring (#4524)
* now with 100% fat free cancellation Co-authored-by: James Gatz <jamesgatzos@gmail.com> * unblock electron 15 * fix cookielist and temp fix curl types * fix types * fix inso * default to verbose inso test * implement readdata function * fix test * revert test changes * isomorphic cancel * reduce typing issues * curl types * turns out the tests were wrong * handle errors * remove unused inso mock * remove request delay * fix lint and add logs * Revert "remove request delay" This reverts commit f07d8c90a7a7279ca10f8a8de1ea0c82caa06390. * simplify and add cancel fallback * skip cancel test * playwright is fast and insomnia is slow * trailing spaces are serious yo * cancel is flake town * hmm * unblock nunjucks and storeTimeline * fix nunjucks tests * preload writeFile * oops forgot to remove the reload * debugging CI takes all day, log stuff and pray * also warn if nunjucks is being lame * Stop using environment variables * revert debugging logs Co-authored-by: James Gatz <jamesgatzos@gmail.com> Co-authored-by: David Marby <david@dmarby.se>
This commit is contained in:
parent
82c37d7f96
commit
8585eea9e6
@ -49,15 +49,11 @@ describe('render tests', () => {
|
|||||||
expect(rendered).toBe('Hello FooBar!');
|
expect(rendered).toBe('Hello FooBar!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails on invalid template', async () => {
|
it('returns invalid template', async () => {
|
||||||
try {
|
const rendered = await renderUtils.render('Hello {{ msg }!', {
|
||||||
await renderUtils.render('Hello {{ msg }!', {
|
|
||||||
msg: 'World',
|
msg: 'World',
|
||||||
});
|
});
|
||||||
fail('Render should have failed');
|
expect(rendered).toBe('Hello {{ msg }!');
|
||||||
} catch (err) {
|
|
||||||
expect(err.message).toContain('expected variable end');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles variables using tag before tag is defined as expected (incorrect order)', async () => {
|
it('handles variables using tag before tag is defined as expected (incorrect order)', async () => {
|
||||||
@ -570,7 +566,7 @@ describe('render tests', () => {
|
|||||||
);
|
);
|
||||||
fail('Render should have failed');
|
fail('Render should have failed');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err.message).toContain('expected variable end');
|
expect(err.message).toContain('attempted to output null or undefined value');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import fuzzysort from 'fuzzysort';
|
import fuzzysort from 'fuzzysort';
|
||||||
import { join as pathJoin } from 'path';
|
import { join as pathJoin } from 'path';
|
||||||
import { Readable, Writable } from 'stream';
|
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
import zlib from 'zlib';
|
import zlib from 'zlib';
|
||||||
|
|
||||||
@ -341,27 +340,6 @@ export function fuzzyMatchAll(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function waitForStreamToFinish(stream: Readable | Writable) {
|
|
||||||
return new Promise<void>(resolve => {
|
|
||||||
// @ts-expect-error -- access of internal values that are intended to be private. We should _not_ do this.
|
|
||||||
if (stream._readableState?.finished) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-expect-error -- access of internal values that are intended to be private. We should _not_ do this.
|
|
||||||
if (stream._writableState?.finished) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.on('close', () => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
stream.on('error', () => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function chunkArray<T>(arr: T[], chunkSize: number) {
|
export function chunkArray<T>(arr: T[], chunkSize: number) {
|
||||||
const chunks: T[][] = [];
|
const chunks: T[][] = [];
|
||||||
|
|
||||||
|
14
packages/insomnia-app/app/global.d.ts
vendored
14
packages/insomnia-app/app/global.d.ts
vendored
@ -27,6 +27,20 @@ interface Window {
|
|||||||
authorizeUserInWindow: (options: { url: string; urlSuccessRegex?: RegExp; urlFailureRegex?: RegExp; sessionId: string }) => Promise<string>;
|
authorizeUserInWindow: (options: { url: string; urlSuccessRegex?: RegExp; urlFailureRegex?: RegExp; sessionId: string }) => Promise<string>;
|
||||||
setMenuBarVisibility: (visible: boolean) => void;
|
setMenuBarVisibility: (visible: boolean) => void;
|
||||||
installPlugin: (url: string) => 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: {
|
dialog: {
|
||||||
showOpenDialog: (options: Electron.OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>;
|
showOpenDialog: (options: Electron.OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
import contextMenu from 'electron-context-menu';
|
import contextMenu from 'electron-context-menu';
|
||||||
import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer';
|
import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer';
|
||||||
|
import { writeFile } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import appConfig from '../config/config.json';
|
import appConfig from '../config/config.json';
|
||||||
@ -17,6 +18,7 @@ import * as updates from './main/updates';
|
|||||||
import * as windowUtils from './main/window-utils';
|
import * as windowUtils from './main/window-utils';
|
||||||
import * as models from './models/index';
|
import * as models from './models/index';
|
||||||
import type { Stats } from './models/stats';
|
import type { Stats } from './models/stats';
|
||||||
|
import { cancelCurlRequest, curlRequest } from './network/libcurl-promise';
|
||||||
import { authorizeUserInWindow } from './network/o-auth-2/misc';
|
import { authorizeUserInWindow } from './network/o-auth-2/misc';
|
||||||
import installPlugin from './plugins/install';
|
import installPlugin from './plugins/install';
|
||||||
import type { ToastNotification } from './ui/components/toast';
|
import type { ToastNotification } from './ui/components/toast';
|
||||||
@ -259,6 +261,25 @@ async function _trackStats() {
|
|||||||
return authorizeUserInWindow({ url, urlSuccessRegex, urlFailureRegex, sessionId });
|
return authorizeUserInWindow({ url, urlSuccessRegex, urlFailureRegex, sessionId });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('writeFile', (_, options) => {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
writeFile(options.path, options.content, err => {
|
||||||
|
if (err != null) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(options.path);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('curlRequest', (_, options) => {
|
||||||
|
return curlRequest(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('cancelCurlRequest', (_, requestId: string): void => {
|
||||||
|
cancelCurlRequest(requestId);
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.once('window-ready', () => {
|
ipcMain.once('window-ready', () => {
|
||||||
const { currentVersion, launches, lastVersion } = stats;
|
const { currentVersion, launches, lastVersion } = stats;
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Curl } from '@getinsomnia/node-libcurl';
|
|
||||||
import electron, { BrowserWindow, MenuItemConstructorOptions } from 'electron';
|
import electron, { BrowserWindow, MenuItemConstructorOptions } from 'electron';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
@ -22,9 +21,6 @@ import * as log from '../common/log';
|
|||||||
import LocalStorage from './local-storage';
|
import LocalStorage from './local-storage';
|
||||||
|
|
||||||
const { app, Menu, shell, dialog, clipboard } = electron;
|
const { app, Menu, shell, dialog, clipboard } = electron;
|
||||||
// So we can use native modules in renderer
|
|
||||||
// NOTE: This was (deprecated in Electron 10)[https://github.com/electron/electron/issues/18397] and (removed in Electron 14)[https://github.com/electron/electron/pull/26874]
|
|
||||||
app.allowRendererProcessReuse = false;
|
|
||||||
|
|
||||||
const DEFAULT_WIDTH = 1280;
|
const DEFAULT_WIDTH = 1280;
|
||||||
const DEFAULT_HEIGHT = 720;
|
const DEFAULT_HEIGHT = 720;
|
||||||
@ -388,7 +384,6 @@ export function createWindow() {
|
|||||||
`Node: ${process.versions.node}`,
|
`Node: ${process.versions.node}`,
|
||||||
`V8: ${process.versions.v8}`,
|
`V8: ${process.versions.v8}`,
|
||||||
`Architecture: ${process.arch}`,
|
`Architecture: ${process.arch}`,
|
||||||
`node-libcurl: ${Curl.getVersion()}`,
|
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
const msgBox = await dialog.showMessageBox({
|
const msgBox = await dialog.showMessageBox({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { CurlHttpVersion } from '@getinsomnia/node-libcurl';
|
import { CurlHttpVersion } from '@getinsomnia/node-libcurl/dist/enum/CurlHttpVersion';
|
||||||
|
import { CurlNetrc } from '@getinsomnia/node-libcurl/dist/enum/CurlNetrc';
|
||||||
import electron from 'electron';
|
import electron from 'electron';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { HttpVersions } from 'insomnia-common';
|
import { HttpVersions } from 'insomnia-common';
|
||||||
@ -17,6 +18,7 @@ import {
|
|||||||
import { filterHeaders } from '../../common/misc';
|
import { filterHeaders } from '../../common/misc';
|
||||||
import { getRenderedRequestAndContext } from '../../common/render';
|
import { getRenderedRequestAndContext } from '../../common/render';
|
||||||
import * as models from '../../models';
|
import * as models from '../../models';
|
||||||
|
import { _parseHeaders } from '../libcurl-promise';
|
||||||
import { DEFAULT_BOUNDARY } from '../multipart';
|
import { DEFAULT_BOUNDARY } from '../multipart';
|
||||||
import * as networkUtils from '../network';
|
import * as networkUtils from '../network';
|
||||||
window.app = electron.app;
|
window.app = electron.app;
|
||||||
@ -604,7 +606,7 @@ describe('actuallySend()', () => {
|
|||||||
NOPROGRESS: true,
|
NOPROGRESS: true,
|
||||||
PROXY: '',
|
PROXY: '',
|
||||||
TIMEOUT_MS: 0,
|
TIMEOUT_MS: 0,
|
||||||
NETRC: 'Required',
|
NETRC: CurlNetrc.Required,
|
||||||
URL: '',
|
URL: '',
|
||||||
USERAGENT: `insomnia/${getAppVersion()}`,
|
USERAGENT: `insomnia/${getAppVersion()}`,
|
||||||
VERBOSE: true,
|
VERBOSE: true,
|
||||||
@ -736,7 +738,7 @@ describe('actuallySend()', () => {
|
|||||||
...settings,
|
...settings,
|
||||||
preferredHttpVersion: HttpVersions.V1_0,
|
preferredHttpVersion: HttpVersions.V1_0,
|
||||||
});
|
});
|
||||||
expect(JSON.parse(String(models.response.getBodyBuffer(responseV1))).options.HTTP_VERSION).toBe('V1_0');
|
expect(JSON.parse(String(models.response.getBodyBuffer(responseV1))).options.HTTP_VERSION).toBe(1);
|
||||||
expect(networkUtils.getHttpVersion(HttpVersions.V1_0).curlHttpVersion).toBe(CurlHttpVersion.V1_0);
|
expect(networkUtils.getHttpVersion(HttpVersions.V1_0).curlHttpVersion).toBe(CurlHttpVersion.V1_0);
|
||||||
expect(networkUtils.getHttpVersion(HttpVersions.V1_1).curlHttpVersion).toBe(CurlHttpVersion.V1_1);
|
expect(networkUtils.getHttpVersion(HttpVersions.V1_1).curlHttpVersion).toBe(CurlHttpVersion.V1_1);
|
||||||
expect(networkUtils.getHttpVersion(HttpVersions.V2PriorKnowledge).curlHttpVersion).toBe(CurlHttpVersion.V2PriorKnowledge);
|
expect(networkUtils.getHttpVersion(HttpVersions.V2PriorKnowledge).curlHttpVersion).toBe(CurlHttpVersion.V2PriorKnowledge);
|
||||||
@ -745,48 +747,6 @@ describe('actuallySend()', () => {
|
|||||||
expect(networkUtils.getHttpVersion(HttpVersions.default).curlHttpVersion).toBe(undefined);
|
expect(networkUtils.getHttpVersion(HttpVersions.default).curlHttpVersion).toBe(undefined);
|
||||||
expect(networkUtils.getHttpVersion('blah').curlHttpVersion).toBe(undefined);
|
expect(networkUtils.getHttpVersion('blah').curlHttpVersion).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('requests can be cancelled by requestId', async () => {
|
|
||||||
// GIVEN
|
|
||||||
const workspace = await models.workspace.create();
|
|
||||||
const settings = await models.settings.getOrCreate();
|
|
||||||
const request1 = Object.assign(models.request.init(), {
|
|
||||||
_id: 'req_15',
|
|
||||||
parentId: workspace._id,
|
|
||||||
url: 'http://unix:3000/requestA',
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
const request2 = Object.assign(models.request.init(), {
|
|
||||||
_id: 'req_10',
|
|
||||||
parentId: workspace._id,
|
|
||||||
url: 'http://unix:3000/requestB',
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
const renderedRequest1 = await getRenderedRequest({ request: request1 });
|
|
||||||
const renderedRequest2 = await getRenderedRequest({ request: request2 });
|
|
||||||
|
|
||||||
// WHEN
|
|
||||||
const response1Promise = networkUtils._actuallySend(
|
|
||||||
renderedRequest1,
|
|
||||||
workspace,
|
|
||||||
settings,
|
|
||||||
);
|
|
||||||
|
|
||||||
const response2Promise = networkUtils._actuallySend(
|
|
||||||
renderedRequest2,
|
|
||||||
workspace,
|
|
||||||
settings,
|
|
||||||
);
|
|
||||||
|
|
||||||
await networkUtils.cancelRequestById(renderedRequest1._id);
|
|
||||||
const response1 = await response1Promise;
|
|
||||||
const response2 = await response2Promise;
|
|
||||||
// THEN
|
|
||||||
expect(response1.statusMessage).toBe('Cancelled');
|
|
||||||
expect(response2.statusMessage).toBe('OK');
|
|
||||||
expect(networkUtils.hasCancelFunctionForId(request1._id)).toBe(false);
|
|
||||||
expect(networkUtils.hasCancelFunctionForId(request2._id)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_getAwsAuthHeaders', () => {
|
describe('_getAwsAuthHeaders', () => {
|
||||||
@ -888,7 +848,7 @@ describe('_parseHeaders', () => {
|
|||||||
const minimalHeaders = ['HTTP/1.1 301', ''];
|
const minimalHeaders = ['HTTP/1.1 301', ''];
|
||||||
|
|
||||||
it('Parses single response headers', () => {
|
it('Parses single response headers', () => {
|
||||||
expect(networkUtils._parseHeaders(Buffer.from(basicHeaders.join('\n')))).toEqual([
|
expect(_parseHeaders(Buffer.from(basicHeaders.join('\n')))).toEqual([
|
||||||
{
|
{
|
||||||
code: 301,
|
code: 301,
|
||||||
version: 'HTTP/1.1',
|
version: 'HTTP/1.1',
|
||||||
@ -936,7 +896,7 @@ describe('_parseHeaders', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Parses Windows newlines', () => {
|
it('Parses Windows newlines', () => {
|
||||||
expect(networkUtils._parseHeaders(Buffer.from(basicHeaders.join('\r\n')))).toEqual([
|
expect(_parseHeaders(Buffer.from(basicHeaders.join('\r\n')))).toEqual([
|
||||||
{
|
{
|
||||||
code: 301,
|
code: 301,
|
||||||
version: 'HTTP/1.1',
|
version: 'HTTP/1.1',
|
||||||
@ -985,7 +945,7 @@ describe('_parseHeaders', () => {
|
|||||||
|
|
||||||
it('Parses multiple responses', () => {
|
it('Parses multiple responses', () => {
|
||||||
const blobs = basicHeaders.join('\r\n') + '\n' + minimalHeaders.join('\n');
|
const blobs = basicHeaders.join('\r\n') + '\n' + minimalHeaders.join('\n');
|
||||||
expect(networkUtils._parseHeaders(Buffer.from(blobs))).toEqual([
|
expect(_parseHeaders(Buffer.from(blobs))).toEqual([
|
||||||
{
|
{
|
||||||
code: 301,
|
code: 301,
|
||||||
version: 'HTTP/1.1',
|
version: 'HTTP/1.1',
|
||||||
|
241
packages/insomnia-app/app/network/libcurl-promise.ts
Normal file
241
packages/insomnia-app/app/network/libcurl-promise.ts
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
// NOTE: this file should not be imported by electron renderer because node-libcurl is not-context-aware
|
||||||
|
// Related issue https://github.com/JCMais/node-libcurl/issues/155
|
||||||
|
if (process.type === 'renderer') throw new Error('node-libcurl unavailable in renderer');
|
||||||
|
|
||||||
|
import { Curl, CurlCode, CurlFeature, CurlInfoDebug } from '@getinsomnia/node-libcurl';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { Readable, Writable } from 'stream';
|
||||||
|
import { ValueOf } from 'type-fest';
|
||||||
|
|
||||||
|
import { describeByteSize } from '../common/misc';
|
||||||
|
import { ResponseHeader } from '../models/response';
|
||||||
|
import { ResponsePatch } from './network';
|
||||||
|
|
||||||
|
// wraps libcurl with a promise taking curl options and others required by read, write and debug callbacks
|
||||||
|
// returning a response patch, debug timeline and list of headers for each redirect
|
||||||
|
|
||||||
|
interface CurlOpt {
|
||||||
|
key: Parameters<Curl['setOpt']>[0];
|
||||||
|
value: Parameters<Curl['setOpt']>[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CurlRequestOptions {
|
||||||
|
curlOptions: CurlOpt[];
|
||||||
|
responseBodyPath: string;
|
||||||
|
maxTimelineDataSizeKB: number;
|
||||||
|
requestId: string; // for cancellation
|
||||||
|
requestBodyPath?: string; // only used for POST file path
|
||||||
|
isMultipart: boolean; // for clean up after implemention side effect
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResponseTimelineEntry {
|
||||||
|
name: ValueOf<typeof LIBCURL_DEBUG_MIGRATION_MAP>;
|
||||||
|
timestamp: number;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CurlRequestOutput {
|
||||||
|
patch: ResponsePatch;
|
||||||
|
debugTimeline: ResponseTimelineEntry[];
|
||||||
|
headerResults: HeaderResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this is a dictionary of functions to close open listeners
|
||||||
|
const cancelCurlRequestHandlers = {};
|
||||||
|
export const cancelCurlRequest = id => cancelCurlRequestHandlers[id]();
|
||||||
|
export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequestOutput>(async resolve => {
|
||||||
|
try {
|
||||||
|
// Create instance and handlers, poke value options in, set up write and debug callbacks, listen for events
|
||||||
|
const { curlOptions, responseBodyPath, requestBodyPath, maxTimelineDataSizeKB, requestId, isMultipart } = options;
|
||||||
|
const curl = new Curl();
|
||||||
|
let requestFileDescriptor;
|
||||||
|
const responseBodyWriteStream = fs.createWriteStream(responseBodyPath);
|
||||||
|
// cancel request by id map
|
||||||
|
cancelCurlRequestHandlers[requestId] = () => {
|
||||||
|
if (requestFileDescriptor && responseBodyPath) {
|
||||||
|
closeReadFunction(requestFileDescriptor, isMultipart, requestBodyPath);
|
||||||
|
}
|
||||||
|
curl.close();
|
||||||
|
};
|
||||||
|
// set the string and number options from network.ts
|
||||||
|
curlOptions.forEach(opt => curl.setOpt(opt.key, opt.value));
|
||||||
|
// read file into request and close file desriptor
|
||||||
|
if (requestBodyPath) {
|
||||||
|
requestFileDescriptor = fs.openSync(requestBodyPath, 'r');
|
||||||
|
curl.setOpt(Curl.option.READDATA, requestFileDescriptor);
|
||||||
|
curl.on('end', () => closeReadFunction(requestFileDescriptor, isMultipart, requestBodyPath));
|
||||||
|
curl.on('error', () => closeReadFunction(requestFileDescriptor, isMultipart, requestBodyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up response writer
|
||||||
|
let responseBodyBytes = 0;
|
||||||
|
curl.setOpt(Curl.option.WRITEFUNCTION, buffer => {
|
||||||
|
responseBodyBytes += buffer.length;
|
||||||
|
responseBodyWriteStream.write(buffer);
|
||||||
|
return buffer.length;
|
||||||
|
});
|
||||||
|
// set up response logger
|
||||||
|
const debugTimeline: ResponseTimelineEntry[] = [];
|
||||||
|
curl.setOpt(Curl.option.DEBUGFUNCTION, (infoType, buffer) => {
|
||||||
|
const rawName = Object.keys(CurlInfoDebug).find(k => CurlInfoDebug[k] === infoType) || '';
|
||||||
|
const infoTypeName = LIBCURL_DEBUG_MIGRATION_MAP[rawName] || rawName;
|
||||||
|
|
||||||
|
const isSSLData = infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut;
|
||||||
|
const isEmpty = buffer.length === 0;
|
||||||
|
// Don't show cookie setting because this will display every domain in the jar
|
||||||
|
const isAddCookie = infoType === CurlInfoDebug.Text && buffer.toString('utf8').indexOf('Added cookie') === 0;
|
||||||
|
if (isSSLData || isEmpty || isAddCookie) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value;
|
||||||
|
if (infoType === CurlInfoDebug.DataOut) {
|
||||||
|
// Ignore the possibly large data messages
|
||||||
|
const lessThan10KB = buffer.length / 1024 < maxTimelineDataSizeKB || 10;
|
||||||
|
value = lessThan10KB ? buffer.toString('utf8') : `(${describeByteSize(buffer.length)} hidden)`;
|
||||||
|
}
|
||||||
|
if (infoType === CurlInfoDebug.DataIn) {
|
||||||
|
value = `Received ${describeByteSize(buffer.length)} chunk`;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugTimeline.push({
|
||||||
|
name: infoType === CurlInfoDebug.DataIn ? 'TEXT' : infoTypeName,
|
||||||
|
value: value || buffer.toString('utf8'),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
return 0; // Must be here
|
||||||
|
});
|
||||||
|
|
||||||
|
// makes rawHeaders a buffer, rather than HeaderInfo[]
|
||||||
|
curl.enable(CurlFeature.Raw);
|
||||||
|
// NOTE: legacy write end callback
|
||||||
|
curl.on('end', () => responseBodyWriteStream.end());
|
||||||
|
curl.on('end', async (_1, _2, rawHeaders: Buffer) => {
|
||||||
|
const patch = {
|
||||||
|
bytesContent: responseBodyBytes,
|
||||||
|
bytesRead: curl.getInfo(Curl.info.SIZE_DOWNLOAD) as number,
|
||||||
|
elapsedTime: curl.getInfo(Curl.info.TOTAL_TIME) as number * 1000,
|
||||||
|
url: curl.getInfo(Curl.info.EFFECTIVE_URL) as string,
|
||||||
|
};
|
||||||
|
curl.close();
|
||||||
|
await waitForStreamToFinish(responseBodyWriteStream);
|
||||||
|
|
||||||
|
const headerResults = _parseHeaders(rawHeaders);
|
||||||
|
resolve({ patch, debugTimeline, headerResults });
|
||||||
|
});
|
||||||
|
// NOTE: legacy write end callback
|
||||||
|
curl.on('error', () => responseBodyWriteStream.end());
|
||||||
|
curl.on('error', async function(err, code) {
|
||||||
|
const elapsedTime = curl.getInfo(Curl.info.TOTAL_TIME) as number * 1000;
|
||||||
|
curl.close();
|
||||||
|
await waitForStreamToFinish(responseBodyWriteStream);
|
||||||
|
|
||||||
|
let error = err + '';
|
||||||
|
let statusMessage = 'Error';
|
||||||
|
|
||||||
|
if (code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
|
||||||
|
error = 'Request aborted';
|
||||||
|
statusMessage = 'Abort';
|
||||||
|
}
|
||||||
|
const patch = {
|
||||||
|
statusMessage,
|
||||||
|
error: error || 'Something went wrong',
|
||||||
|
elapsedTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: legacy, default headerResults
|
||||||
|
resolve({ patch, debugTimeline, headerResults: [{ version: '', code: -1, reason: '', headers: [] }] });
|
||||||
|
});
|
||||||
|
curl.perform();
|
||||||
|
} catch (e) {
|
||||||
|
const patch = {
|
||||||
|
statusMessage: 'Error',
|
||||||
|
error: e.message || 'Something went wrong',
|
||||||
|
elapsedTime: 0,
|
||||||
|
};
|
||||||
|
resolve({ patch, debugTimeline: [], headerResults: [{ version: '', code: -1, reason: '', headers: [] }] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeReadFunction = (fd: number, isMultipart: boolean, path?: string) => {
|
||||||
|
fs.closeSync(fd);
|
||||||
|
// NOTE: multipart files are combined before sending, so this file is deleted after
|
||||||
|
// alt implemention to send one part at a time https://github.com/JCMais/node-libcurl/blob/develop/examples/04-multi.js
|
||||||
|
if (isMultipart && path) fs.unlink(path, () => { });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Because node-libcurl changed some names that we used in the timeline
|
||||||
|
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',
|
||||||
|
'': '',
|
||||||
|
};
|
||||||
|
|
||||||
|
interface HeaderResult {
|
||||||
|
headers: ResponseHeader[];
|
||||||
|
version: string;
|
||||||
|
code: number;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
// NOTE: legacy, has tests, could be simplified
|
||||||
|
export function _parseHeaders(buffer: Buffer) {
|
||||||
|
const results: HeaderResult[] = [];
|
||||||
|
const lines = buffer.toString('utf8').split(/\r?\n|\r/g);
|
||||||
|
|
||||||
|
for (let i = 0, currentResult: HeaderResult | null = null; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
const isEmptyLine = line.trim() === '';
|
||||||
|
|
||||||
|
// If we hit an empty line, start parsing the next response
|
||||||
|
if (isEmptyLine && currentResult) {
|
||||||
|
results.push(currentResult);
|
||||||
|
currentResult = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentResult) {
|
||||||
|
const [version, code, ...other] = line.split(/ +/g);
|
||||||
|
currentResult = {
|
||||||
|
version,
|
||||||
|
code: parseInt(code, 10),
|
||||||
|
reason: other.join(' '),
|
||||||
|
headers: [],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const [name, value] = line.split(/:\s(.+)/);
|
||||||
|
const header: ResponseHeader = {
|
||||||
|
name,
|
||||||
|
value: value || '',
|
||||||
|
};
|
||||||
|
currentResult.headers.push(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
// NOTE: legacy, suspicious, could be simplified
|
||||||
|
async function waitForStreamToFinish(stream: Readable | Writable) {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
// @ts-expect-error -- access of internal values that are intended to be private. We should _not_ do this.
|
||||||
|
if (stream._readableState?.finished) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error -- access of internal values that are intended to be private. We should _not_ do this.
|
||||||
|
if (stream._writableState?.finished) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.on('close', () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
stream.on('error', () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,15 +1,8 @@
|
|||||||
import {
|
import { CurlAuth } from '@getinsomnia/node-libcurl/dist/enum/CurlAuth';
|
||||||
Curl,
|
import { CurlHttpVersion } from '@getinsomnia/node-libcurl/dist/enum/CurlHttpVersion';
|
||||||
CurlAuth,
|
import { CurlNetrc } from '@getinsomnia/node-libcurl/dist/enum/CurlNetrc';
|
||||||
CurlCode,
|
|
||||||
CurlFeature,
|
|
||||||
CurlHttpVersion,
|
|
||||||
CurlInfoDebug,
|
|
||||||
CurlNetrc,
|
|
||||||
} from '@getinsomnia/node-libcurl';
|
|
||||||
import aws4 from 'aws4';
|
import aws4 from 'aws4';
|
||||||
import clone from 'clone';
|
import clone from 'clone';
|
||||||
import crypto from 'crypto';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { HttpVersions } from 'insomnia-common';
|
import { HttpVersions } from 'insomnia-common';
|
||||||
import { cookiesFromJar, jarFromCookies } from 'insomnia-cookies';
|
import { cookiesFromJar, jarFromCookies } from 'insomnia-cookies';
|
||||||
@ -38,7 +31,6 @@ import { database as db } from '../common/database';
|
|||||||
import { getDataDirectory, getTempDir } from '../common/electron-helpers';
|
import { getDataDirectory, getTempDir } from '../common/electron-helpers';
|
||||||
import {
|
import {
|
||||||
delay,
|
delay,
|
||||||
describeByteSize,
|
|
||||||
getContentTypeHeader,
|
getContentTypeHeader,
|
||||||
getHostHeader,
|
getHostHeader,
|
||||||
getLocationHeader,
|
getLocationHeader,
|
||||||
@ -49,7 +41,6 @@ import {
|
|||||||
hasContentTypeHeader,
|
hasContentTypeHeader,
|
||||||
hasUserAgentHeader,
|
hasUserAgentHeader,
|
||||||
LIBCURL_DEBUG_MIGRATION_MAP,
|
LIBCURL_DEBUG_MIGRATION_MAP,
|
||||||
waitForStreamToFinish,
|
|
||||||
} from '../common/misc';
|
} from '../common/misc';
|
||||||
import type { ExtraRenderInfo, RenderedRequest } from '../common/render';
|
import type { ExtraRenderInfo, RenderedRequest } from '../common/render';
|
||||||
import {
|
import {
|
||||||
@ -70,6 +61,48 @@ import caCerts from './ca-certs';
|
|||||||
import { buildMultipart } from './multipart';
|
import { buildMultipart } from './multipart';
|
||||||
import { urlMatchesCertHost } from './url-matches-cert-host';
|
import { urlMatchesCertHost } from './url-matches-cert-host';
|
||||||
|
|
||||||
|
// Based on list of option properties but with callback options removed
|
||||||
|
const Curl = {
|
||||||
|
option: {
|
||||||
|
ACCEPT_ENCODING: 'ACCEPT_ENCODING',
|
||||||
|
CAINFO: 'CAINFO',
|
||||||
|
COOKIE: 'COOKIE',
|
||||||
|
COOKIEFILE: 'COOKIEFILE',
|
||||||
|
COOKIELIST: 'COOKIELIST',
|
||||||
|
CUSTOMREQUEST: 'CUSTOMREQUEST',
|
||||||
|
FOLLOWLOCATION: 'FOLLOWLOCATION',
|
||||||
|
HTTPAUTH: 'HTTPAUTH',
|
||||||
|
HTTPGET: 'HTTPGET',
|
||||||
|
HTTPHEADER: 'HTTPHEADER',
|
||||||
|
HTTPPOST: 'HTTPPOST',
|
||||||
|
HTTP_VERSION: 'HTTP_VERSION',
|
||||||
|
INFILESIZE_LARGE: 'INFILESIZE_LARGE',
|
||||||
|
KEYPASSWD: 'KEYPASSWD',
|
||||||
|
MAXREDIRS: 'MAXREDIRS',
|
||||||
|
NETRC: 'NETRC',
|
||||||
|
NOBODY: 'NOBODY',
|
||||||
|
NOPROGRESS: 'NOPROGRESS',
|
||||||
|
NOPROXY: 'NOPROXY',
|
||||||
|
PASSWORD: 'PASSWORD',
|
||||||
|
POST: 'POST',
|
||||||
|
POSTFIELDS: 'POSTFIELDS',
|
||||||
|
PATH_AS_IS: 'PATH_AS_IS',
|
||||||
|
PROXY: 'PROXY',
|
||||||
|
PROXYAUTH: 'PROXYAUTH',
|
||||||
|
SSLCERT: 'SSLCERT',
|
||||||
|
SSLCERTTYPE: 'SSLCERTTYPE',
|
||||||
|
SSLKEY: 'SSLKEY',
|
||||||
|
SSL_VERIFYHOST: 'SSL_VERIFYHOST',
|
||||||
|
SSL_VERIFYPEER: 'SSL_VERIFYPEER',
|
||||||
|
TIMEOUT_MS: 'TIMEOUT_MS',
|
||||||
|
UNIX_SOCKET_PATH: 'UNIX_SOCKET_PATH',
|
||||||
|
UPLOAD: 'UPLOAD',
|
||||||
|
URL: 'URL',
|
||||||
|
USERAGENT: 'USERAGENT',
|
||||||
|
USERNAME: 'USERNAME',
|
||||||
|
VERBOSE: 'VERBOSE',
|
||||||
|
},
|
||||||
|
};
|
||||||
export interface ResponsePatch {
|
export interface ResponsePatch {
|
||||||
bodyCompression?: 'zip' | null;
|
bodyCompression?: 'zip' | null;
|
||||||
bodyPath?: string;
|
bodyPath?: string;
|
||||||
@ -121,27 +154,13 @@ export const getHttpVersion = preferredHttpVersion => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function cancelRequestById(requestId) {
|
export async function cancelRequestById(requestId) {
|
||||||
if (hasCancelFunctionForId(requestId)) {
|
const hasCancelFunction = cancelRequestFunctionMap.hasOwnProperty(requestId) && typeof cancelRequestFunctionMap[requestId] === 'function';
|
||||||
const cancelRequestFunction = cancelRequestFunctionMap[requestId];
|
if (hasCancelFunction) {
|
||||||
|
return cancelRequestFunctionMap[requestId]();
|
||||||
if (typeof cancelRequestFunction === 'function') {
|
|
||||||
return cancelRequestFunction();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[network] Failed to cancel req=${requestId} because cancel function not found`);
|
console.log(`[network] Failed to cancel req=${requestId} because cancel function not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCancelFunctionForId(requestId) {
|
|
||||||
if (hasCancelFunctionForId(requestId)) {
|
|
||||||
delete cancelRequestFunctionMap[requestId];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasCancelFunctionForId(requestId) {
|
|
||||||
return cancelRequestFunctionMap.hasOwnProperty(requestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function _actuallySend(
|
export async function _actuallySend(
|
||||||
renderedRequest: RenderedRequest,
|
renderedRequest: RenderedRequest,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
@ -162,17 +181,17 @@ export async function _actuallySend(
|
|||||||
|
|
||||||
const addTimelineText = addTimelineItem(LIBCURL_DEBUG_MIGRATION_MAP.Text);
|
const addTimelineText = addTimelineItem(LIBCURL_DEBUG_MIGRATION_MAP.Text);
|
||||||
|
|
||||||
// Initialize the curl handle
|
|
||||||
const curl = new Curl();
|
|
||||||
|
|
||||||
/** Helper function to respond with a success */
|
/** Helper function to respond with a success */
|
||||||
async function respond(
|
async function respond(
|
||||||
patch: ResponsePatch,
|
patch: ResponsePatch,
|
||||||
bodyPath: string | null,
|
bodyPath: string | null,
|
||||||
|
debugTimeline: any[] = []
|
||||||
) {
|
) {
|
||||||
const timelinePath = await storeTimeline(timeline);
|
const timelinePath = await storeTimeline([...timeline, ...debugTimeline]);
|
||||||
// Tear Down the cancellation logic
|
// Tear Down the cancellation logic
|
||||||
clearCancelFunctionForId(renderedRequest._id);
|
if (cancelRequestFunctionMap.hasOwnProperty(renderedRequest._id)) {
|
||||||
|
delete cancelRequestFunctionMap[renderedRequest._id];
|
||||||
|
}
|
||||||
const environmentId = environment ? environment._id : null;
|
const environmentId = environment ? environment._id : null;
|
||||||
return resolve(Object.assign(
|
return resolve(Object.assign(
|
||||||
{
|
{
|
||||||
@ -191,6 +210,7 @@ export async function _actuallySend(
|
|||||||
|
|
||||||
/** Helper function to respond with an error */
|
/** Helper function to respond with an error */
|
||||||
async function handleError(err: Error) {
|
async function handleError(err: Error) {
|
||||||
|
|
||||||
await respond(
|
await respond(
|
||||||
{
|
{
|
||||||
url: renderedRequest.url,
|
url: renderedRequest.url,
|
||||||
@ -204,34 +224,32 @@ export async function _actuallySend(
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// NOTE: can have duplicate keys because of cookie options
|
||||||
/** Helper function to set Curl options */
|
const curlOptions: { key: string; value: string | string[] | number | boolean }[] = [];
|
||||||
const setOpt: typeof curl.setOpt = (opt: any, val: any) => {
|
const setOpt = (key: string, value: string | string[] | number | boolean) => {
|
||||||
try {
|
curlOptions.push({ key, value });
|
||||||
return curl.setOpt(opt, val);
|
|
||||||
} catch (err) {
|
|
||||||
const name = Object.keys(Curl.option).find(name => Curl.option[name] === opt);
|
|
||||||
throw new Error(`${err.message} (${opt} ${name || 'n/a'})`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Setup the cancellation logic
|
// Setup the cancellation logic
|
||||||
cancelRequestFunctionMap[renderedRequest._id] = async () => {
|
cancelRequestFunctionMap[renderedRequest._id] = async () => {
|
||||||
|
|
||||||
await respond(
|
await respond(
|
||||||
{
|
{
|
||||||
elapsedTime: (curl.getInfo(Curl.info.TOTAL_TIME) as number || 0) * 1000,
|
elapsedTime: 0,
|
||||||
// @ts-expect-error -- needs generic
|
bytesRead: 0,
|
||||||
bytesRead: curl.getInfo(Curl.info.SIZE_DOWNLOAD),
|
url: renderedRequest.url,
|
||||||
// @ts-expect-error -- needs generic
|
|
||||||
url: curl.getInfo(Curl.info.EFFECTIVE_URL),
|
|
||||||
statusMessage: 'Cancelled',
|
statusMessage: 'Cancelled',
|
||||||
error: 'Request was cancelled',
|
error: 'Request was cancelled',
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
// Kill it!
|
// NOTE: conditionally use ipc bridge, renderer cannot import native modules directly
|
||||||
curl.close();
|
const nodejsCancelCurlRequest = process.type === 'renderer'
|
||||||
|
? window.main.cancelCurlRequest
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
: require('./libcurl-promise').cancelCurlRequest;
|
||||||
|
nodejsCancelCurlRequest(renderedRequest._id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set all the basic options
|
// Set all the basic options
|
||||||
@ -243,9 +261,6 @@ export async function _actuallySend(
|
|||||||
// True so curl doesn't print progress
|
// True so curl doesn't print progress
|
||||||
setOpt(Curl.option.ACCEPT_ENCODING, '');
|
setOpt(Curl.option.ACCEPT_ENCODING, '');
|
||||||
|
|
||||||
// Auto decode everything
|
|
||||||
curl.enable(CurlFeature.Raw);
|
|
||||||
|
|
||||||
// Set follow redirects setting
|
// Set follow redirects setting
|
||||||
switch (renderedRequest.settingFollowRedirects) {
|
switch (renderedRequest.settingFollowRedirects) {
|
||||||
case 'off':
|
case 'off':
|
||||||
@ -292,44 +307,6 @@ export async function _actuallySend(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup debug handler
|
|
||||||
setOpt(Curl.option.DEBUGFUNCTION, (infoType, contentBuffer) => {
|
|
||||||
const content = contentBuffer.toString('utf8');
|
|
||||||
const rawName = Object.keys(CurlInfoDebug).find(k => CurlInfoDebug[k] === infoType) || '';
|
|
||||||
const name = LIBCURL_DEBUG_MIGRATION_MAP[rawName] || rawName;
|
|
||||||
const addToTimeline = addTimelineItem(name);
|
|
||||||
|
|
||||||
if (infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore the possibly large data messages
|
|
||||||
if (infoType === CurlInfoDebug.DataOut) {
|
|
||||||
if (contentBuffer.length === 0) {
|
|
||||||
// Sometimes this happens, but I'm not sure why. Just ignore it.
|
|
||||||
} else if (contentBuffer.length / 1024 < settings.maxTimelineDataSizeKB) {
|
|
||||||
addToTimeline(content);
|
|
||||||
} else {
|
|
||||||
addToTimeline(`(${describeByteSize(contentBuffer.length)} hidden)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infoType === CurlInfoDebug.DataIn) {
|
|
||||||
addTimelineText(`Received ${describeByteSize(contentBuffer.length)} chunk`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't show cookie setting because this will display every domain in the jar
|
|
||||||
if (infoType === CurlInfoDebug.Text && content.indexOf('Added cookie') === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToTimeline(content);
|
|
||||||
return 0; // Must be here
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set the headers (to be modified as we go)
|
// Set the headers (to be modified as we go)
|
||||||
const headers = clone(renderedRequest.headers);
|
const headers = clone(renderedRequest.headers);
|
||||||
// Set the URL, including the query parameters
|
// Set the URL, including the query parameters
|
||||||
@ -352,7 +329,6 @@ export async function _actuallySend(
|
|||||||
|
|
||||||
addTimelineText('Preparing request to ' + finalUrl);
|
addTimelineText('Preparing request to ' + finalUrl);
|
||||||
addTimelineText('Current time is ' + new Date().toISOString());
|
addTimelineText('Current time is ' + new Date().toISOString());
|
||||||
addTimelineText(`Using ${Curl.getVersion()}`);
|
|
||||||
|
|
||||||
const httpVersion = getHttpVersion(settings.preferredHttpVersion);
|
const httpVersion = getHttpVersion(settings.preferredHttpVersion);
|
||||||
addTimelineText(httpVersion.log);
|
addTimelineText(httpVersion.log);
|
||||||
@ -523,7 +499,8 @@ export async function _actuallySend(
|
|||||||
let noBody = false;
|
let noBody = false;
|
||||||
let requestBody: string | null = null;
|
let requestBody: string | null = null;
|
||||||
const expectsBody = ['POST', 'PUT', 'PATCH'].includes(renderedRequest.method.toUpperCase());
|
const expectsBody = ['POST', 'PUT', 'PATCH'].includes(renderedRequest.method.toUpperCase());
|
||||||
|
let requestBodyPath;
|
||||||
|
let isMultipart = false;
|
||||||
if (renderedRequest.body.mimeType === CONTENT_TYPE_FORM_URLENCODED) {
|
if (renderedRequest.body.mimeType === CONTENT_TYPE_FORM_URLENCODED) {
|
||||||
requestBody = buildQueryStringFromParams(renderedRequest.body.params || [], false);
|
requestBody = buildQueryStringFromParams(renderedRequest.body.params || [], false);
|
||||||
} else if (renderedRequest.body.mimeType === CONTENT_TYPE_FORM_DATA) {
|
} else if (renderedRequest.body.mimeType === CONTENT_TYPE_FORM_DATA) {
|
||||||
@ -531,6 +508,8 @@ export async function _actuallySend(
|
|||||||
const { filePath: multipartBodyPath, boundary, contentLength } = await buildMultipart(
|
const { filePath: multipartBodyPath, boundary, contentLength } = await buildMultipart(
|
||||||
params,
|
params,
|
||||||
);
|
);
|
||||||
|
requestBodyPath = multipartBodyPath;
|
||||||
|
isMultipart = true;
|
||||||
// Extend the Content-Type header
|
// Extend the Content-Type header
|
||||||
const contentTypeHeader = getContentTypeHeader(headers);
|
const contentTypeHeader = getContentTypeHeader(headers);
|
||||||
|
|
||||||
@ -543,36 +522,18 @@ export async function _actuallySend(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fd = fs.openSync(multipartBodyPath, 'r');
|
|
||||||
setOpt(Curl.option.INFILESIZE_LARGE, contentLength);
|
setOpt(Curl.option.INFILESIZE_LARGE, contentLength);
|
||||||
setOpt(Curl.option.UPLOAD, 1);
|
setOpt(Curl.option.UPLOAD, 1);
|
||||||
setOpt(Curl.option.READDATA, fd);
|
|
||||||
// We need this, otherwise curl will send it as a PUT
|
// We need this, otherwise curl will send it as a PUT
|
||||||
setOpt(Curl.option.CUSTOMREQUEST, renderedRequest.method);
|
setOpt(Curl.option.CUSTOMREQUEST, renderedRequest.method);
|
||||||
|
|
||||||
const fn = () => {
|
|
||||||
fs.closeSync(fd);
|
|
||||||
fs.unlink(multipartBodyPath, () => {
|
|
||||||
// Pass
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
curl.on('end', fn);
|
|
||||||
curl.on('error', fn);
|
|
||||||
} else if (renderedRequest.body.fileName) {
|
} else if (renderedRequest.body.fileName) {
|
||||||
const { size } = fs.statSync(renderedRequest.body.fileName);
|
const { size } = fs.statSync(renderedRequest.body.fileName);
|
||||||
const fileName = renderedRequest.body.fileName || '';
|
requestBodyPath = renderedRequest.body.fileName || '';
|
||||||
const fd = fs.openSync(fileName, 'r');
|
|
||||||
setOpt(Curl.option.INFILESIZE_LARGE, size);
|
setOpt(Curl.option.INFILESIZE_LARGE, size);
|
||||||
setOpt(Curl.option.UPLOAD, 1);
|
setOpt(Curl.option.UPLOAD, 1);
|
||||||
setOpt(Curl.option.READDATA, fd);
|
|
||||||
// We need this, otherwise curl will send it as a POST
|
// We need this, otherwise curl will send it as a POST
|
||||||
setOpt(Curl.option.CUSTOMREQUEST, renderedRequest.method);
|
setOpt(Curl.option.CUSTOMREQUEST, renderedRequest.method);
|
||||||
|
|
||||||
const fn = () => fs.closeSync(fd);
|
|
||||||
|
|
||||||
curl.on('end', fn);
|
|
||||||
curl.on('error', fn);
|
|
||||||
} else if (typeof renderedRequest.body.mimeType === 'string' || expectsBody) {
|
} else if (typeof renderedRequest.body.mimeType === 'string' || expectsBody) {
|
||||||
requestBody = renderedRequest.body.text || '';
|
requestBody = renderedRequest.body.text || '';
|
||||||
} else {
|
} else {
|
||||||
@ -697,39 +658,36 @@ export async function _actuallySend(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
setOpt(Curl.option.HTTPHEADER, headerStrings);
|
setOpt(Curl.option.HTTPHEADER, headerStrings);
|
||||||
let responseBodyBytes = 0;
|
|
||||||
const responsesDir = pathJoin(getDataDirectory(), 'responses');
|
const responsesDir = pathJoin(getDataDirectory(), 'responses');
|
||||||
mkdirp.sync(responsesDir);
|
mkdirp.sync(responsesDir);
|
||||||
const responseBodyPath = pathJoin(responsesDir, uuid.v4() + '.response');
|
const responseBodyPath = pathJoin(responsesDir, uuid.v4() + '.response');
|
||||||
const responseBodyWriteStream = fs.createWriteStream(responseBodyPath);
|
// NOTE: conditionally use ipc bridge, renderer cannot import native modules directly
|
||||||
curl.on('end', () => responseBodyWriteStream.end());
|
const nodejsCurlRequest = process.type === 'renderer'
|
||||||
curl.on('error', () => responseBodyWriteStream.end());
|
? window.main.curlRequest
|
||||||
setOpt(Curl.option.WRITEFUNCTION, buff => {
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
responseBodyBytes += buff.length;
|
: require('./libcurl-promise').curlRequest;
|
||||||
responseBodyWriteStream.write(buff);
|
const requestOptions = {
|
||||||
return buff.length;
|
curlOptions,
|
||||||
});
|
responseBodyPath,
|
||||||
// Handle the response ending
|
requestBodyPath,
|
||||||
curl.on('end', async (_1, _2, rawHeaders: Buffer) => {
|
isMultipart,
|
||||||
const allCurlHeadersObjects = _parseHeaders(rawHeaders);
|
maxTimelineDataSizeKB: settings.maxTimelineDataSizeKB,
|
||||||
|
requestId: renderedRequest._id,
|
||||||
|
};
|
||||||
|
const { patch, debugTimeline, headerResults } = await nodejsCurlRequest(requestOptions);
|
||||||
|
|
||||||
// Headers are an array (one for each redirect)
|
// Headers are an array (one for each redirect)
|
||||||
const lastCurlHeadersObject = allCurlHeadersObjects[allCurlHeadersObjects.length - 1];
|
const lastCurlHeadersObject = headerResults[headerResults.length - 1];
|
||||||
// Collect various things
|
|
||||||
const httpVersion = lastCurlHeadersObject.version || '';
|
|
||||||
const statusCode = lastCurlHeadersObject.code || -1;
|
|
||||||
const statusMessage = lastCurlHeadersObject.reason || '';
|
|
||||||
// Collect the headers
|
|
||||||
const headers = lastCurlHeadersObject.headers;
|
|
||||||
// Calculate the content type
|
// Calculate the content type
|
||||||
const contentTypeHeader = getContentTypeHeader(headers);
|
const contentTypeHeader = getContentTypeHeader(lastCurlHeadersObject.headers);
|
||||||
const contentType = contentTypeHeader ? contentTypeHeader.value : '';
|
|
||||||
// Update Cookie Jar
|
// Update Cookie Jar
|
||||||
let currentUrl = finalUrl;
|
let currentUrl = finalUrl;
|
||||||
let setCookieStrings: string[] = [];
|
let setCookieStrings: string[] = [];
|
||||||
const jar = jarFromCookies(renderedRequest.cookieJar.cookies);
|
const jar = jarFromCookies(renderedRequest.cookieJar.cookies);
|
||||||
|
|
||||||
for (const { headers } of allCurlHeadersObjects) {
|
for (const { headers } of headerResults) {
|
||||||
// Collect Set-Cookie headers
|
// Collect Set-Cookie headers
|
||||||
const setCookieHeaders = getSetCookieHeaders(headers);
|
const setCookieHeaders = getSetCookieHeaders(headers);
|
||||||
setCookieStrings = [...setCookieStrings, ...setCookieHeaders.map(h => h.value)];
|
setCookieStrings = [...setCookieStrings, ...setCookieHeaders.map(h => h.value)];
|
||||||
@ -769,46 +727,17 @@ export async function _actuallySend(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the response data
|
|
||||||
const responsePatch: ResponsePatch = {
|
const responsePatch: ResponsePatch = {
|
||||||
contentType,
|
contentType: contentTypeHeader ? contentTypeHeader.value : '',
|
||||||
headers,
|
headers: lastCurlHeadersObject.headers,
|
||||||
httpVersion,
|
httpVersion: lastCurlHeadersObject.version,
|
||||||
statusCode,
|
statusCode: lastCurlHeadersObject.code,
|
||||||
statusMessage,
|
statusMessage: lastCurlHeadersObject.reason,
|
||||||
bytesContent: responseBodyBytes,
|
...patch,
|
||||||
// @ts-expect-error -- TSCONVERSION appears to be a genuine error
|
|
||||||
bytesRead: curl.getInfo(Curl.info.SIZE_DOWNLOAD),
|
|
||||||
elapsedTime: curl.getInfo(Curl.info.TOTAL_TIME) as number * 1000,
|
|
||||||
// @ts-expect-error -- TSCONVERSION appears to be a genuine error
|
|
||||||
url: curl.getInfo(Curl.info.EFFECTIVE_URL),
|
|
||||||
};
|
};
|
||||||
// Close the request
|
|
||||||
curl.close();
|
|
||||||
// Make sure the response body has been fully written first
|
|
||||||
await waitForStreamToFinish(responseBodyWriteStream);
|
|
||||||
// Send response
|
|
||||||
await respond(responsePatch, responseBodyPath);
|
|
||||||
});
|
|
||||||
curl.on('error', async function(err, code) {
|
|
||||||
let error = err + '';
|
|
||||||
let statusMessage = 'Error';
|
|
||||||
|
|
||||||
if (code === CurlCode.CURLE_ABORTED_BY_CALLBACK) {
|
respond(responsePatch, responseBodyPath, debugTimeline);
|
||||||
error = 'Request aborted';
|
|
||||||
statusMessage = 'Abort';
|
|
||||||
}
|
|
||||||
|
|
||||||
await respond(
|
|
||||||
{
|
|
||||||
statusMessage,
|
|
||||||
error: error || 'Something went wrong',
|
|
||||||
elapsedTime: curl.getInfo(Curl.info.TOTAL_TIME) as number * 1000,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
curl.perform();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('[network] Error', err);
|
console.log('[network] Error', err);
|
||||||
await handleError(err);
|
await handleError(err);
|
||||||
@ -820,12 +749,12 @@ export async function sendWithSettings(
|
|||||||
requestId: string,
|
requestId: string,
|
||||||
requestPatch: Record<string, any>,
|
requestPatch: Record<string, any>,
|
||||||
) {
|
) {
|
||||||
|
console.log(`[network] Sending with settings req=${requestId}`);
|
||||||
const request = await models.request.getById(requestId);
|
const request = await models.request.getById(requestId);
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
throw new Error(`Failed to find request: ${requestId}`);
|
throw new Error(`Failed to find request: ${requestId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = await models.settings.getOrCreate();
|
const settings = await models.settings.getOrCreate();
|
||||||
const ancestors = await db.withAncestors(request, [
|
const ancestors = await db.withAncestors(request, [
|
||||||
models.request.type,
|
models.request.type,
|
||||||
@ -851,7 +780,6 @@ export async function sendWithSettings(
|
|||||||
request: RenderedRequest;
|
request: RenderedRequest;
|
||||||
context: Record<string, any>;
|
context: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
renderResult = await getRenderedRequestAndContext({ request: newRequest, environmentId });
|
renderResult = await getRenderedRequestAndContext({ request: newRequest, environmentId });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -892,12 +820,12 @@ export async function send(
|
|||||||
*/
|
*/
|
||||||
const timeSinceLastInteraction = Date.now() - lastUserInteraction;
|
const timeSinceLastInteraction = Date.now() - lastUserInteraction;
|
||||||
const delayMillis = Math.max(0, MAX_DELAY_TIME - timeSinceLastInteraction);
|
const delayMillis = Math.max(0, MAX_DELAY_TIME - timeSinceLastInteraction);
|
||||||
|
|
||||||
if (delayMillis > 0) {
|
if (delayMillis > 0) {
|
||||||
await delay(delayMillis);
|
await delay(delayMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch some things
|
// Fetch some things
|
||||||
|
|
||||||
const request = await models.request.getById(requestId);
|
const request = await models.request.getById(requestId);
|
||||||
const settings = await models.settings.getOrCreate();
|
const settings = await models.settings.getOrCreate();
|
||||||
const ancestors = await db.withAncestors(request, [
|
const ancestors = await db.withAncestors(request, [
|
||||||
@ -919,6 +847,7 @@ export async function send(
|
|||||||
extraInfo,
|
extraInfo,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderedRequestBeforePlugins = renderResult.request;
|
const renderedRequestBeforePlugins = renderResult.request;
|
||||||
const renderedContextBeforePlugins = renderResult.context;
|
const renderedContextBeforePlugins = renderResult.context;
|
||||||
const workspaceDoc = ancestors.find(isWorkspace);
|
const workspaceDoc = ancestors.find(isWorkspace);
|
||||||
@ -931,6 +860,7 @@ export async function send(
|
|||||||
let renderedRequest: RenderedRequest;
|
let renderedRequest: RenderedRequest;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('[network] Apply plugin pre hooks');
|
||||||
renderedRequest = await _applyRequestPluginHooks(
|
renderedRequest = await _applyRequestPluginHooks(
|
||||||
renderedRequestBeforePlugins,
|
renderedRequestBeforePlugins,
|
||||||
renderedContextBeforePlugins,
|
renderedContextBeforePlugins,
|
||||||
@ -955,6 +885,7 @@ export async function send(
|
|||||||
environment,
|
environment,
|
||||||
settings.validateSSL,
|
settings.validateSSL,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
response.error
|
response.error
|
||||||
? `[network] Response failed req=${requestId} err=${response.error || 'n/a'}`
|
? `[network] Response failed req=${requestId} err=${response.error || 'n/a'}`
|
||||||
@ -1038,51 +969,6 @@ async function _applyResponsePluginHooks(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderResult {
|
|
||||||
headers: ResponseHeader[];
|
|
||||||
version: string;
|
|
||||||
code: number;
|
|
||||||
reason: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function _parseHeaders(
|
|
||||||
buffer: Buffer,
|
|
||||||
) {
|
|
||||||
const results: HeaderResult[] = [];
|
|
||||||
const lines = buffer.toString('utf8').split(/\r?\n|\r/g);
|
|
||||||
|
|
||||||
for (let i = 0, currentResult: HeaderResult | null = null; i < lines.length; i++) {
|
|
||||||
const line = lines[i];
|
|
||||||
const isEmptyLine = line.trim() === '';
|
|
||||||
|
|
||||||
// If we hit an empty line, start parsing the next response
|
|
||||||
if (isEmptyLine && currentResult) {
|
|
||||||
results.push(currentResult);
|
|
||||||
currentResult = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentResult) {
|
|
||||||
const [version, code, ...other] = line.split(/ +/g);
|
|
||||||
currentResult = {
|
|
||||||
version,
|
|
||||||
code: parseInt(code, 10),
|
|
||||||
reason: other.join(' '),
|
|
||||||
headers: [],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const [name, value] = line.split(/:\s(.+)/);
|
|
||||||
const header: ResponseHeader = {
|
|
||||||
name,
|
|
||||||
value: value || '',
|
|
||||||
};
|
|
||||||
currentResult.headers.push(header);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
// exported for unit tests only
|
// exported for unit tests only
|
||||||
export function _getAwsAuthHeaders(
|
export function _getAwsAuthHeaders(
|
||||||
credentials: {
|
credentials: {
|
||||||
@ -1130,18 +1016,20 @@ export function _getAwsAuthHeaders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function storeTimeline(timeline: ResponseTimelineEntry[]) {
|
function storeTimeline(timeline: ResponseTimelineEntry[]) {
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
const timelineStr = JSON.stringify(timeline, null, '\t');
|
const timelineStr = JSON.stringify(timeline, null, '\t');
|
||||||
const timelineHash = crypto.createHash('sha1').update(timelineStr).digest('hex');
|
const timelineHash = uuid.v4();
|
||||||
const responsesDir = pathJoin(getDataDirectory(), 'responses');
|
const responsesDir = pathJoin(getDataDirectory(), 'responses');
|
||||||
mkdirp.sync(responsesDir);
|
mkdirp.sync(responsesDir);
|
||||||
const timelinePath = pathJoin(responsesDir, timelineHash + '.timeline');
|
const timelinePath = pathJoin(responsesDir, timelineHash + '.timeline');
|
||||||
|
if (process.type === 'renderer'){
|
||||||
|
return window.main.writeFile({ path: timelinePath, content: timelineStr });
|
||||||
|
}
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
fs.writeFile(timelinePath, timelineStr, err => {
|
fs.writeFile(timelinePath, timelineStr, err => {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
reject(err);
|
return reject(err);
|
||||||
} else {
|
|
||||||
resolve(timelinePath);
|
|
||||||
}
|
}
|
||||||
|
resolve(timelinePath);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,9 @@ const main = {
|
|||||||
authorizeUserInWindow: options => ipcRenderer.invoke('authorizeUserInWindow', options),
|
authorizeUserInWindow: options => ipcRenderer.invoke('authorizeUserInWindow', options),
|
||||||
setMenuBarVisibility: options => ipcRenderer.send('setMenuBarVisibility', options),
|
setMenuBarVisibility: options => ipcRenderer.send('setMenuBarVisibility', options),
|
||||||
installPlugin: options => ipcRenderer.invoke('installPlugin', 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 = {
|
const dialog = {
|
||||||
showOpenDialog: options => ipcRenderer.invoke('showOpenDialog', options),
|
showOpenDialog: options => ipcRenderer.invoke('showOpenDialog', options),
|
||||||
|
@ -44,6 +44,10 @@ export function render(
|
|||||||
renderMode?: string;
|
renderMode?: string;
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
|
const hasNunjucksInterpolationSymbols = text.includes('{{') && text.includes('}}');
|
||||||
|
const hasNunjucksCustomTagSymbols = text.includes('{%') && text.includes('%}');
|
||||||
|
const hasNunjucksCommentSymbols = text.includes('{#') && text.includes('#}');
|
||||||
|
if (!hasNunjucksInterpolationSymbols && !hasNunjucksCustomTagSymbols && !hasNunjucksCommentSymbols) return text;
|
||||||
const context = config.context || {};
|
const context = config.context || {};
|
||||||
// context needs to exist on the root for the old templating syntax, and in _ for the new templating syntax
|
// context needs to exist on the root for the old templating syntax, and in _ for the new templating syntax
|
||||||
// old: {{ arr[0].prop }}
|
// old: {{ arr[0].prop }}
|
||||||
@ -52,9 +56,13 @@ export function render(
|
|||||||
const path = config.path || null;
|
const path = config.path || null;
|
||||||
const renderMode = config.renderMode || RENDER_ALL;
|
const renderMode = config.renderMode || RENDER_ALL;
|
||||||
return new Promise<string | null>(async (resolve, reject) => {
|
return new Promise<string | null>(async (resolve, reject) => {
|
||||||
|
// NOTE: this is added as a breadcrumb because renderString sometimes hangs
|
||||||
|
const id = setTimeout(() => console.log('Warning: nunjucks failed to respond within 5 seconds'), 5000);
|
||||||
const nj = await getNunjucks(renderMode);
|
const nj = await getNunjucks(renderMode);
|
||||||
nj?.renderString(text, templatingContext, (err, result) => {
|
nj?.renderString(text, templatingContext, (err, result) => {
|
||||||
|
clearTimeout(id);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
const sanitizedMsg = err.message
|
const sanitizedMsg = err.message
|
||||||
.replace(/\(unknown path\)\s/, '')
|
.replace(/\(unknown path\)\s/, '')
|
||||||
.replace(/\[Line \d+, Column \d*]/, '')
|
.replace(/\[Line \d+, Column \d*]/, '')
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Curl } from '@getinsomnia/node-libcurl';
|
|
||||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||||
import { HotKeyRegistry } from 'insomnia-common';
|
import { HotKeyRegistry } from 'insomnia-common';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
@ -20,7 +19,6 @@ import { ImportExport } from '../settings/import-export';
|
|||||||
import { Plugins } from '../settings/plugins';
|
import { Plugins } from '../settings/plugins';
|
||||||
import { Shortcuts } from '../settings/shortcuts';
|
import { Shortcuts } from '../settings/shortcuts';
|
||||||
import { ThemePanel } from '../settings/theme-panel';
|
import { ThemePanel } from '../settings/theme-panel';
|
||||||
import { Tooltip } from '../tooltip';
|
|
||||||
import { showModal } from './index';
|
import { showModal } from './index';
|
||||||
|
|
||||||
export const TAB_INDEX_EXPORT = 1;
|
export const TAB_INDEX_EXPORT = 1;
|
||||||
@ -80,9 +78,6 @@ export class UnconnectedSettingsModal extends PureComponent<Props, State> {
|
|||||||
{getAppName()} Preferences
|
{getAppName()} Preferences
|
||||||
<span className="faint txt-sm">
|
<span className="faint txt-sm">
|
||||||
– v{getAppVersion()}
|
– v{getAppVersion()}
|
||||||
<Tooltip position="bottom" message={Curl.getVersion()}>
|
|
||||||
<i className="fa fa-info-circle" />
|
|
||||||
</Tooltip>
|
|
||||||
{email ? ` – ${email}` : null}
|
{email ? ` – ${email}` : null}
|
||||||
</span>
|
</span>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
@ -806,6 +806,7 @@ class App extends PureComponent<AppProps, State> {
|
|||||||
try {
|
try {
|
||||||
const responsePatch = await network.send(requestId, environmentId);
|
const responsePatch = await network.send(requestId, environmentId);
|
||||||
await models.response.create(responsePatch, settings.maxHistoryResponses);
|
await models.response.create(responsePatch, settings.maxHistoryResponses);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.type === 'render') {
|
if (err.type === 'render') {
|
||||||
showModal(RequestRenderErrorModal, {
|
showModal(RequestRenderErrorModal, {
|
||||||
@ -831,6 +832,7 @@ class App extends PureComponent<AppProps, State> {
|
|||||||
await updateRequestMetaByParentId(requestId, {
|
await updateRequestMetaByParentId(requestId, {
|
||||||
activeResponseId: null,
|
activeResponseId: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stop loading
|
// Stop loading
|
||||||
handleStopLoading(requestId);
|
handleStopLoading(requestId);
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1 @@
|
|||||||
jest.mock('@getinsomnia/node-libcurl');
|
|
||||||
process.env.DEFAULT_APP_NAME = process.env.DEFAULT_APP_NAME || 'insomnia-app';
|
process.env.DEFAULT_APP_NAME = process.env.DEFAULT_APP_NAME || 'insomnia-app';
|
||||||
|
@ -34,6 +34,7 @@ describe.each(compact([npmPackageBinPath, ...binaries]))('inso with %s', binPath
|
|||||||
srcInsoNedb,
|
srcInsoNedb,
|
||||||
['--env', 'Dev'],
|
['--env', 'Dev'],
|
||||||
'Echo Test Suite',
|
'Echo Test Suite',
|
||||||
|
'--verbose',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(failed).toBe(false);
|
expect(failed).toBe(false);
|
||||||
|
@ -7,7 +7,7 @@ resources:
|
|||||||
parentId: fld_0e50ba4426bb4540ade91e0525ea1f29
|
parentId: fld_0e50ba4426bb4540ade91e0525ea1f29
|
||||||
modified: 1645664215605
|
modified: 1645664215605
|
||||||
created: 1645544268127
|
created: 1645544268127
|
||||||
url: "{{ _.oidc_base_path }}/me"
|
url: "http://127.0.0.1:4010/oidc/me"
|
||||||
name: No PKCE
|
name: No PKCE
|
||||||
description: ""
|
description: ""
|
||||||
method: GET
|
method: GET
|
||||||
@ -15,13 +15,13 @@ resources:
|
|||||||
parameters: []
|
parameters: []
|
||||||
headers: []
|
headers: []
|
||||||
authentication:
|
authentication:
|
||||||
accessTokenUrl: "{{ _.oidc_base_path }}/token"
|
accessTokenUrl: "http://127.0.0.1:4010/oidc/token"
|
||||||
authorizationUrl: "{{ _.oidc_base_path }}/auth"
|
authorizationUrl: "http://127.0.0.1:4010/oidc/auth"
|
||||||
clientId: "{{ _.client_id_authorization_code }}"
|
clientId: "authorization_code"
|
||||||
clientSecret: "{{ _.client_secret }}"
|
clientSecret: "secret"
|
||||||
disabled: false
|
disabled: false
|
||||||
grantType: authorization_code
|
grantType: authorization_code
|
||||||
redirectUrl: "{{ _.oidc_callback }}"
|
redirectUrl: "http://127.0.0.1:4010/callback"
|
||||||
responseType: id_token
|
responseType: id_token
|
||||||
scope: openid offline_access
|
scope: openid offline_access
|
||||||
state: ""
|
state: ""
|
||||||
@ -60,7 +60,7 @@ resources:
|
|||||||
parentId: fld_0e50ba4426bb4540ade91e0525ea1f29
|
parentId: fld_0e50ba4426bb4540ade91e0525ea1f29
|
||||||
modified: 1645664217727
|
modified: 1645664217727
|
||||||
created: 1645220819802
|
created: 1645220819802
|
||||||
url: "{{ _.oidc_base_path }}/me"
|
url: "http://127.0.0.1:4010/oidc/me"
|
||||||
name: PKCE SHA256
|
name: PKCE SHA256
|
||||||
description: ""
|
description: ""
|
||||||
method: GET
|
method: GET
|
||||||
@ -68,13 +68,13 @@ resources:
|
|||||||
parameters: []
|
parameters: []
|
||||||
headers: []
|
headers: []
|
||||||
authentication:
|
authentication:
|
||||||
accessTokenUrl: "{{ _.oidc_base_path }}/token"
|
accessTokenUrl: "http://127.0.0.1:4010/oidc/token"
|
||||||
authorizationUrl: "{{ _.oidc_base_path }}/auth"
|
authorizationUrl: "http://127.0.0.1:4010/oidc/auth"
|
||||||
clientId: "{{ _.client_id_authorization_code_pkce }}"
|
clientId: "authorization_code_pkce"
|
||||||
clientSecret: "{{ _.client_secret }}"
|
clientSecret: "secret"
|
||||||
disabled: false
|
disabled: false
|
||||||
grantType: authorization_code
|
grantType: authorization_code
|
||||||
redirectUrl: "{{ _.oidc_callback }}"
|
redirectUrl: "http://127.0.0.1:4010/callback"
|
||||||
responseType: id_token
|
responseType: id_token
|
||||||
scope: openid offline_access
|
scope: openid offline_access
|
||||||
state: ""
|
state: ""
|
||||||
@ -93,7 +93,7 @@ resources:
|
|||||||
parentId: fld_0e50ba4426bb4540ade91e0525ea1f29
|
parentId: fld_0e50ba4426bb4540ade91e0525ea1f29
|
||||||
modified: 1645664218264
|
modified: 1645664218264
|
||||||
created: 1645543526615
|
created: 1645543526615
|
||||||
url: "{{ _.oidc_base_path }}/me"
|
url: "http://127.0.0.1:4010/oidc/me"
|
||||||
name: PKCE Plain
|
name: PKCE Plain
|
||||||
description: ""
|
description: ""
|
||||||
method: GET
|
method: GET
|
||||||
@ -101,13 +101,13 @@ resources:
|
|||||||
parameters: []
|
parameters: []
|
||||||
headers: []
|
headers: []
|
||||||
authentication:
|
authentication:
|
||||||
accessTokenUrl: "{{ _.oidc_base_path }}/token"
|
accessTokenUrl: "http://127.0.0.1:4010/oidc/token"
|
||||||
authorizationUrl: "{{ _.oidc_base_path }}/auth"
|
authorizationUrl: "http://127.0.0.1:4010/oidc/auth"
|
||||||
clientId: "{{ _.client_id_authorization_code_pkce }}"
|
clientId: "authorization_code_pkce"
|
||||||
clientSecret: "{{ _.client_secret }}"
|
clientSecret: "secret"
|
||||||
disabled: false
|
disabled: false
|
||||||
grantType: authorization_code
|
grantType: authorization_code
|
||||||
redirectUrl: "{{ _.oidc_callback }}"
|
redirectUrl: "http://127.0.0.1:4010/callback"
|
||||||
responseType: id_token
|
responseType: id_token
|
||||||
scope: openid offline_access
|
scope: openid offline_access
|
||||||
state: ""
|
state: ""
|
||||||
@ -128,7 +128,7 @@ resources:
|
|||||||
parentId: fld_d34790add1584643b6688c3add5bbe85
|
parentId: fld_d34790add1584643b6688c3add5bbe85
|
||||||
modified: 1645664218947
|
modified: 1645664218947
|
||||||
created: 1645545802379
|
created: 1645545802379
|
||||||
url: "{{ _.oidc_base_path }}/id-token"
|
url: "http://127.0.0.1:4010/oidc/id-token"
|
||||||
name: ID Token
|
name: ID Token
|
||||||
description: ""
|
description: ""
|
||||||
method: GET
|
method: GET
|
||||||
@ -136,13 +136,13 @@ resources:
|
|||||||
parameters: []
|
parameters: []
|
||||||
headers: []
|
headers: []
|
||||||
authentication:
|
authentication:
|
||||||
accessTokenUrl: "{{ _.oidc_base_path }}/token"
|
accessTokenUrl: "http://127.0.0.1:4010/oidc/token"
|
||||||
authorizationUrl: "{{ _.oidc_base_path }}/auth"
|
authorizationUrl: "http://127.0.0.1:4010/oidc/auth"
|
||||||
clientId: "{{ _.client_id_implicit }}"
|
clientId: "implicit"
|
||||||
clientSecret: "{{ _.client_secret }}"
|
clientSecret: "secret"
|
||||||
disabled: false
|
disabled: false
|
||||||
grantType: implicit
|
grantType: implicit
|
||||||
redirectUrl: "{{ _.oidc_callback }}"
|
redirectUrl: "http://127.0.0.1:4010/callback"
|
||||||
responseType: id_token
|
responseType: id_token
|
||||||
scope: openid
|
scope: openid
|
||||||
state: ""
|
state: ""
|
||||||
@ -173,7 +173,7 @@ resources:
|
|||||||
parentId: fld_d34790add1584643b6688c3add5bbe85
|
parentId: fld_d34790add1584643b6688c3add5bbe85
|
||||||
modified: 1645664219446
|
modified: 1645664219446
|
||||||
created: 1645567186775
|
created: 1645567186775
|
||||||
url: "{{ _.oidc_base_path }}/me"
|
url: "http://127.0.0.1:4010/oidc/me"
|
||||||
name: ID and Access Token
|
name: ID and Access Token
|
||||||
description: ""
|
description: ""
|
||||||
method: GET
|
method: GET
|
||||||
@ -181,13 +181,13 @@ resources:
|
|||||||
parameters: []
|
parameters: []
|
||||||
headers: []
|
headers: []
|
||||||
authentication:
|
authentication:
|
||||||
accessTokenUrl: "{{ _.oidc_base_path }}/token"
|
accessTokenUrl: "http://127.0.0.1:4010/oidc/token"
|
||||||
authorizationUrl: "{{ _.oidc_base_path }}/auth"
|
authorizationUrl: "http://127.0.0.1:4010/oidc/auth"
|
||||||
clientId: "{{ _.client_id_implicit }}"
|
clientId: "implicit"
|
||||||
clientSecret: "{{ _.client_secret }}"
|
clientSecret: "secret"
|
||||||
disabled: false
|
disabled: false
|
||||||
grantType: implicit
|
grantType: implicit
|
||||||
redirectUrl: "{{ _.oidc_callback }}"
|
redirectUrl: "http://127.0.0.1:4010/callback"
|
||||||
responseType: id_token token
|
responseType: id_token token
|
||||||
scope: openid
|
scope: openid
|
||||||
state: ""
|
state: ""
|
||||||
@ -208,7 +208,7 @@ resources:
|
|||||||
parentId: wrk_392055e2aa29457b9d2904396cd7631f
|
parentId: wrk_392055e2aa29457b9d2904396cd7631f
|
||||||
modified: 1645664219861
|
modified: 1645664219861
|
||||||
created: 1645637343873
|
created: 1645637343873
|
||||||
url: "{{ _.oidc_base_path }}/client-credential"
|
url: "http://127.0.0.1:4010/oidc/client-credential"
|
||||||
name: Client Credentials
|
name: Client Credentials
|
||||||
description: ""
|
description: ""
|
||||||
method: GET
|
method: GET
|
||||||
@ -216,13 +216,13 @@ resources:
|
|||||||
parameters: []
|
parameters: []
|
||||||
headers: []
|
headers: []
|
||||||
authentication:
|
authentication:
|
||||||
accessTokenUrl: "{{ _.oidc_base_path }}/token"
|
accessTokenUrl: "http://127.0.0.1:4010/oidc/token"
|
||||||
authorizationUrl: "{{ _.oidc_base_path }}/auth"
|
authorizationUrl: "http://127.0.0.1:4010/oidc/auth"
|
||||||
clientId: "{{ _.client_id_client_creds }}"
|
clientId: "client_credentials"
|
||||||
clientSecret: "{{ _.client_secret }}"
|
clientSecret: "secret"
|
||||||
disabled: false
|
disabled: false
|
||||||
grantType: client_credentials
|
grantType: client_credentials
|
||||||
redirectUrl: "{{ _.oidc_callback }}"
|
redirectUrl: "http://127.0.0.1:4010/callback"
|
||||||
responseType: id_token
|
responseType: id_token
|
||||||
scope: openid
|
scope: openid
|
||||||
state: ""
|
state: ""
|
||||||
@ -245,7 +245,7 @@ resources:
|
|||||||
parentId: wrk_392055e2aa29457b9d2904396cd7631f
|
parentId: wrk_392055e2aa29457b9d2904396cd7631f
|
||||||
modified: 1645664220407
|
modified: 1645664220407
|
||||||
created: 1645636233910
|
created: 1645636233910
|
||||||
url: "{{ _.oidc_base_path }}/me"
|
url: "http://127.0.0.1:4010/oidc/me"
|
||||||
name: Resource Owner Password Credentials
|
name: Resource Owner Password Credentials
|
||||||
description: ""
|
description: ""
|
||||||
method: GET
|
method: GET
|
||||||
@ -253,13 +253,13 @@ resources:
|
|||||||
parameters: []
|
parameters: []
|
||||||
headers: []
|
headers: []
|
||||||
authentication:
|
authentication:
|
||||||
accessTokenUrl: "{{ _.oidc_base_path }}/token"
|
accessTokenUrl: "http://127.0.0.1:4010/oidc/token"
|
||||||
authorizationUrl: "{{ _.oidc_base_path }}/auth"
|
authorizationUrl: "http://127.0.0.1:4010/oidc/auth"
|
||||||
clientId: "{{ _.client_id_resource_owner }}"
|
clientId: "resource_owner"
|
||||||
clientSecret: "{{ _.client_secret }}"
|
clientSecret: "secret"
|
||||||
disabled: false
|
disabled: false
|
||||||
grantType: password
|
grantType: password
|
||||||
redirectUrl: "{{ _.oidc_callback }}"
|
redirectUrl: "http://127.0.0.1:4010/callback"
|
||||||
responseType: id_token
|
responseType: id_token
|
||||||
scope: openid
|
scope: openid
|
||||||
state: ""
|
state: ""
|
||||||
@ -283,27 +283,8 @@ resources:
|
|||||||
modified: 1645661876119
|
modified: 1645661876119
|
||||||
created: 1645220798237
|
created: 1645220798237
|
||||||
name: Base Environment
|
name: Base Environment
|
||||||
data:
|
data: {}
|
||||||
base_url: http://127.0.0.1:4010
|
dataPropertyOrder: null
|
||||||
oidc_base_path: "{{ _.base_url }}/oidc"
|
|
||||||
oidc_callback: "{{ _.base_url }}/callback"
|
|
||||||
client_id_authorization_code: authorization_code
|
|
||||||
client_id_authorization_code_pkce: authorization_code_pkce
|
|
||||||
client_id_implicit: implicit
|
|
||||||
client_id_client_creds: client_credentials
|
|
||||||
client_id_resource_owner: resource_owner
|
|
||||||
client_secret: secret
|
|
||||||
dataPropertyOrder:
|
|
||||||
"&":
|
|
||||||
- base_url
|
|
||||||
- oidc_base_path
|
|
||||||
- oidc_callback
|
|
||||||
- client_id_authorization_code
|
|
||||||
- client_id_authorization_code_pkce
|
|
||||||
- client_id_implicit
|
|
||||||
- client_id_client_creds
|
|
||||||
- client_id_resource_owner
|
|
||||||
- client_secret
|
|
||||||
color: null
|
color: null
|
||||||
isPrivate: false
|
isPrivate: false
|
||||||
metaSortKey: 1639556944617
|
metaSortKey: 1639556944617
|
||||||
|
@ -57,7 +57,7 @@ export const test = baseTest.extend<{
|
|||||||
page: async ({ app }, use) => {
|
page: async ({ app }, use) => {
|
||||||
const page = await app.firstWindow();
|
const page = await app.firstWindow();
|
||||||
|
|
||||||
if (process.platform === 'win32') await page.reload();
|
await page.waitForLoadState();
|
||||||
|
|
||||||
await page.click("text=Don't share usage analytics");
|
await page.click("text=Don't share usage analytics");
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ test('can send requests', async ({ app, page }) => {
|
|||||||
await expect(responseBody).toContainText('Set-Cookie: insomnia-test-cookie=value123');
|
await expect(responseBody).toContainText('Set-Cookie: insomnia-test-cookie=value123');
|
||||||
});
|
});
|
||||||
|
|
||||||
// This feature is unsafe to place beside other tests, cancelling a request causes node-libcurl to block
|
// This feature is unsafe to place beside other tests, cancelling a request can cause network code to block
|
||||||
// related to https://linear.app/insomnia/issue/INS-973
|
// related to https://linear.app/insomnia/issue/INS-973
|
||||||
test('can cancel requests', async ({ app, page }) => {
|
test('can cancel requests', async ({ app, page }) => {
|
||||||
await page.click('[data-testid="project"]');
|
await page.click('[data-testid="project"]');
|
||||||
@ -73,7 +73,8 @@ test('can cancel requests', async ({ app, page }) => {
|
|||||||
|
|
||||||
await page.click('button:has-text("GETdelayed request")');
|
await page.click('button:has-text("GETdelayed request")');
|
||||||
await page.click('text=http://127.0.0.1:4010/delay/seconds/20Send >> button');
|
await page.click('text=http://127.0.0.1:4010/delay/seconds/20Send >> button');
|
||||||
await page.click('text=Loading...Cancel Request >> button');
|
|
||||||
|
await page.click('[data-testid="response-pane"] button:has-text("Cancel Request")');
|
||||||
await page.click('text=Request was cancelled');
|
await page.click('text=Request was cancelled');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user