mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Refactor/decouple-db-and-request (#5572)
* move certs out of actually send * extract fetcher * extract interpolate try catch * extract plugin transformer * remove delay hack * extract response transform * extract url transformer * extract cookies * move custom logic into tranformer function * decouple cancellation logic * add bytes read * only render can cancel requests * order by usage * fix tests * use abort controller for cancellation * raise methods to fix inso * fix * comments * base env and rename cancel file * fix import order
This commit is contained in:
parent
bdce88d4d3
commit
4eae103503
@ -450,6 +450,10 @@ export async function getRenderedGrpcRequestMessage(
|
||||
}
|
||||
|
||||
type RenderRequestOptions = BaseRenderContextOptions & RenderRequest<Request>;
|
||||
export interface RequestAndContext {
|
||||
request: RenderedRequest;
|
||||
context: Record<string, any>;
|
||||
}
|
||||
export async function getRenderedRequestAndContext(
|
||||
{
|
||||
request,
|
||||
@ -457,7 +461,7 @@ export async function getRenderedRequestAndContext(
|
||||
extraInfo,
|
||||
purpose,
|
||||
}: RenderRequestOptions,
|
||||
) {
|
||||
): Promise<RequestAndContext> {
|
||||
const ancestors = await getRenderContextAncestors(request);
|
||||
const workspace = ancestors.find(isWorkspace);
|
||||
const parentId = workspace ? workspace._id : 'n/a';
|
||||
|
@ -2,9 +2,17 @@ import { BaseModel, types as modelTypes } from '../models';
|
||||
import * as models from '../models';
|
||||
import { getBodyBuffer } from '../models/response';
|
||||
import { Settings } from '../models/settings';
|
||||
import { send } from '../network/network';
|
||||
import { isWorkspace } from '../models/workspace';
|
||||
import {
|
||||
responseTransform,
|
||||
sendCurlAndWriteTimeline,
|
||||
tryToInterpolateRequest,
|
||||
tryToTransformRequestWithPlugins,
|
||||
} from '../network/network';
|
||||
import * as plugins from '../plugins';
|
||||
import { invariant } from '../utils/invariant';
|
||||
import { database } from './database';
|
||||
import { RENDER_PURPOSE_SEND } from './render';
|
||||
|
||||
// The network layer uses settings from the settings model
|
||||
// We want to give consumers the ability to override certain settings
|
||||
@ -35,14 +43,48 @@ export async function getSendRequestCallbackMemDb(environmentId: string, memDB:
|
||||
upsert: docs,
|
||||
remove: [],
|
||||
});
|
||||
const fetchInsoRequestData = async (requestId: string) => {
|
||||
const request = await models.request.getById(requestId);
|
||||
invariant(request, 'failed to find request');
|
||||
const ancestors = await database.withAncestors(request, [
|
||||
models.request.type,
|
||||
models.requestGroup.type,
|
||||
models.workspace.type,
|
||||
]);
|
||||
const workspaceDoc = ancestors.find(isWorkspace);
|
||||
const workspaceId = workspaceDoc ? workspaceDoc._id : 'n/a';
|
||||
const workspace = await models.workspace.getById(workspaceId);
|
||||
invariant(workspace, 'failed to find workspace');
|
||||
|
||||
const settings = await models.settings.getOrCreate();
|
||||
invariant(settings, 'failed to create settings');
|
||||
const clientCertificates = await models.clientCertificate.findByParentId(workspaceId);
|
||||
const caCert = await models.caCertificate.findByParentId(workspaceId);
|
||||
|
||||
return { request, settings, clientCertificates, caCert };
|
||||
};
|
||||
// Return callback helper to send requests
|
||||
return async function sendRequest(requestId: string) {
|
||||
try {
|
||||
plugins.ignorePlugin('insomnia-plugin-kong-declarative-config');
|
||||
plugins.ignorePlugin('insomnia-plugin-kong-kubernetes-config');
|
||||
plugins.ignorePlugin('insomnia-plugin-kong-portal');
|
||||
const res = await send(requestId, environmentId);
|
||||
const {
|
||||
request,
|
||||
settings,
|
||||
clientCertificates,
|
||||
caCert,
|
||||
} = await fetchInsoRequestData(requestId);
|
||||
// NOTE: inso ignores active environment, using the one passed in
|
||||
const renderResult = await tryToInterpolateRequest(request, environmentId, RENDER_PURPOSE_SEND);
|
||||
const renderedRequest = await tryToTransformRequestWithPlugins(renderResult);
|
||||
const response = await sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
clientCertificates,
|
||||
caCert,
|
||||
settings,
|
||||
);
|
||||
const res = await responseTransform(response, renderedRequest, renderResult.context);
|
||||
const { statusCode: status, statusMessage, headers: headerArray, elapsedTime: responseTime } = res;
|
||||
const headers = headerArray?.reduce((acc, { name, value }) => ({ ...acc, [name.toLowerCase() || '']: value || '' }), []);
|
||||
const bodyBuffer = await getBodyBuffer(res) as Buffer;
|
||||
|
@ -1,8 +1,7 @@
|
||||
// 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 { invariant } from '../../utils/invariant';
|
||||
invariant(process.type !== 'renderer', 'Native abstractions for Nodejs module unavailable in renderer');
|
||||
|
||||
import { Curl, CurlAuth, CurlCode, CurlFeature, CurlHttpVersion, CurlInfoDebug, CurlNetrc } from '@getinsomnia/node-libcurl';
|
||||
import electron from 'electron';
|
||||
@ -22,7 +21,7 @@ import { ResponseHeader } from '../../models/response';
|
||||
import { buildMultipart } from './multipart';
|
||||
import { parseHeaderStrings } from './parse-header-strings';
|
||||
|
||||
interface CurlRequestOptions {
|
||||
export interface CurlRequestOptions {
|
||||
requestId: string; // for cancellation
|
||||
req: RequestUsedHere;
|
||||
finalUrl: string;
|
||||
@ -63,7 +62,7 @@ export interface ResponseTimelineEntry {
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface CurlRequestOutput {
|
||||
export interface CurlRequestOutput {
|
||||
patch: ResponsePatch;
|
||||
debugTimeline: ResponseTimelineEntry[];
|
||||
headerResults: HeaderResult[];
|
||||
@ -234,9 +233,7 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
|
||||
const { authentication } = req;
|
||||
if (requestBodyPath) {
|
||||
// AWS IAM file upload not supported
|
||||
if (authentication.type === AUTH_AWS_IAM) {
|
||||
throw new Error('AWS authentication not supported for provided body type');
|
||||
}
|
||||
invariant(authentication.type !== AUTH_AWS_IAM, 'AWS authentication not supported for provided body type');
|
||||
const { size: contentLength } = fs.statSync(requestBodyPath);
|
||||
curl.setOpt(Curl.option.INFILESIZE_LARGE, contentLength);
|
||||
curl.setOpt(Curl.option.UPLOAD, 1);
|
||||
@ -338,7 +335,7 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
|
||||
});
|
||||
// NOTE: legacy write end callback
|
||||
curl.on('error', () => responseBodyWriteStream.end());
|
||||
curl.on('error', async function(err, code) {
|
||||
curl.on('error', async (err, code) => {
|
||||
const elapsedTime = curl.getInfo(Curl.info.TOTAL_TIME) as number * 1000;
|
||||
curl.close();
|
||||
await waitForStreamToFinish(responseBodyWriteStream);
|
||||
@ -380,7 +377,7 @@ const closeReadFunction = (fd: number, isMultipart: boolean, path?: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
interface HeaderResult {
|
||||
export interface HeaderResult {
|
||||
headers: ResponseHeader[];
|
||||
version: string;
|
||||
code: number;
|
||||
|
@ -197,12 +197,8 @@ describe('API Key', () => {
|
||||
value: 'test',
|
||||
addTo: 'queryParams',
|
||||
};
|
||||
const request = {
|
||||
url: 'https://insomnia.rest/',
|
||||
method: 'GET',
|
||||
authentication,
|
||||
};
|
||||
const header = await getAuthQueryParams(request, 'https://insomnia.rest/');
|
||||
|
||||
const header = getAuthQueryParams(authentication, 'https://insomnia.rest/');
|
||||
expect(header).toEqual({
|
||||
'name': 'x-api-key',
|
||||
'value': 'test',
|
||||
|
@ -28,7 +28,7 @@ window.app = electron.app;
|
||||
|
||||
const getRenderedRequest = async (args: Parameters<typeof getRenderedRequestAndContext>[0]) => (await getRenderedRequestAndContext(args)).request;
|
||||
|
||||
describe('actuallySend()', () => {
|
||||
describe('sendCurlAndWriteTimeline()', () => {
|
||||
beforeEach(async () => {
|
||||
await globalBeforeEach();
|
||||
await models.project.all();
|
||||
@ -101,9 +101,10 @@ describe('actuallySend()', () => {
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -176,9 +177,10 @@ describe('actuallySend()', () => {
|
||||
url: 'http://localhost',
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -276,9 +278,10 @@ describe('actuallySend()', () => {
|
||||
settingSendCookies: false,
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -336,9 +339,10 @@ describe('actuallySend()', () => {
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -416,9 +420,10 @@ describe('actuallySend()', () => {
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -477,9 +482,10 @@ describe('actuallySend()', () => {
|
||||
method: 'GET',
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -517,9 +523,10 @@ describe('actuallySend()', () => {
|
||||
method: 'HEAD',
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -556,9 +563,10 @@ describe('actuallySend()', () => {
|
||||
method: 'GET',
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -596,9 +604,10 @@ describe('actuallySend()', () => {
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
settings,
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -693,9 +702,10 @@ describe('actuallySend()', () => {
|
||||
},
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const response = await networkUtils._actuallySend(
|
||||
const response = await networkUtils.sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
'',
|
||||
[],
|
||||
null,
|
||||
{ ...settings, validateSSL: false },
|
||||
);
|
||||
const bodyBuffer = models.response.getBodyBuffer(response);
|
||||
@ -745,7 +755,7 @@ describe('actuallySend()', () => {
|
||||
parentId: workspace._id,
|
||||
});
|
||||
const renderedRequest = await getRenderedRequest({ request });
|
||||
const responseV1 = await networkUtils._actuallySend(renderedRequest, '', {
|
||||
const responseV1 = await networkUtils.sendCurlAndWriteTimeline(renderedRequest, [], null, {
|
||||
...settings,
|
||||
preferredHttpVersion: HttpVersions.V1_0,
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
AUTH_OAUTH_2,
|
||||
} from '../common/constants';
|
||||
import type { RenderedRequest } from '../common/render';
|
||||
import { RequestParameter } from '../models/request';
|
||||
import { RequestAuthentication, RequestParameter } from '../models/request';
|
||||
import { COOKIE, HEADER, QUERY_PARAMS } from './api-key/constants';
|
||||
import { getBasicAuthHeader } from './basic-auth/get-header';
|
||||
import { getBearerAuthHeader } from './bearer-auth/get-header';
|
||||
@ -160,9 +160,7 @@ export async function getAuthHeader(renderedRequest: RenderedRequest, url: strin
|
||||
return;
|
||||
}
|
||||
|
||||
export async function getAuthQueryParams(renderedRequest: RenderedRequest) {
|
||||
const { authentication } = renderedRequest;
|
||||
|
||||
export function getAuthQueryParams(authentication: RequestAuthentication) {
|
||||
if (authentication.disabled) {
|
||||
return;
|
||||
}
|
||||
|
44
packages/insomnia/src/network/cancellation.ts
Normal file
44
packages/insomnia/src/network/cancellation.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { CurlRequestOptions, CurlRequestOutput } from '../main/network/libcurl-promise';
|
||||
const cancelRequestFunctionMap = new Map<string, () => void>();
|
||||
export async function cancelRequestById(requestId: string) {
|
||||
const cancel = cancelRequestFunctionMap.get(requestId);
|
||||
if (cancel) {
|
||||
return cancel();
|
||||
}
|
||||
console.log(`[network] Failed to cancel req=${requestId} because cancel function not found`);
|
||||
}
|
||||
export const cancellableCurlRequest = async (requestOptions: CurlRequestOptions) => {
|
||||
const requestId = requestOptions.requestId;
|
||||
const controller = new AbortController();
|
||||
const cancelRequest = () => {
|
||||
window.main.cancelCurlRequest(requestId);
|
||||
controller.abort();
|
||||
};
|
||||
cancelRequestFunctionMap.set(requestId, cancelRequest);
|
||||
try {
|
||||
const result = await cancellablePromise({ signal: controller.signal, fn: window.main.curlRequest(requestOptions) });
|
||||
return result as CurlRequestOutput;
|
||||
} catch (err) {
|
||||
cancelRequestFunctionMap.delete(requestId);
|
||||
if (err.name === 'AbortError') {
|
||||
return { statusMessage: 'Cancelled', error: 'Request was cancelled' };
|
||||
}
|
||||
console.log('[network] Error', err);
|
||||
return { statusMessage: 'Error', error: err.message || 'Something went wrong' };
|
||||
}
|
||||
};
|
||||
const cancellablePromise = ({ signal, fn }: { signal: AbortSignal; fn: Promise<any> }) => {
|
||||
if (signal?.aborted) {
|
||||
return Promise.reject(new DOMException('Aborted', 'AbortError'));
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const abortHandler = () => {
|
||||
reject(new DOMException('Aborted', 'AbortError'));
|
||||
};
|
||||
fn.then(res => {
|
||||
resolve(res);
|
||||
signal?.removeEventListener('abort', abortHandler);
|
||||
}, reject);
|
||||
signal?.addEventListener('abort', abortHandler);
|
||||
});
|
||||
};
|
@ -4,33 +4,31 @@ import mkdirp from 'mkdirp';
|
||||
import { join as pathJoin } from 'path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import {
|
||||
STATUS_CODE_PLUGIN_ERROR,
|
||||
} from '../common/constants';
|
||||
import { cookiesFromJar, jarFromCookies } from '../common/cookies';
|
||||
import { database as db } from '../common/database';
|
||||
import { getDataDirectory } from '../common/electron-helpers';
|
||||
import {
|
||||
delay,
|
||||
getContentTypeHeader,
|
||||
getLocationHeader,
|
||||
getSetCookieHeaders,
|
||||
} from '../common/misc';
|
||||
import type { ExtraRenderInfo, RenderedRequest } from '../common/render';
|
||||
import type { ExtraRenderInfo, RenderedRequest, RenderPurpose, RequestAndContext } from '../common/render';
|
||||
import {
|
||||
getRenderedRequestAndContext,
|
||||
RENDER_PURPOSE_NO_RENDER,
|
||||
RENDER_PURPOSE_SEND,
|
||||
} from '../common/render';
|
||||
import type { ResponsePatch, ResponseTimelineEntry } from '../main/network/libcurl-promise';
|
||||
import type { HeaderResult, ResponsePatch, ResponseTimelineEntry } from '../main/network/libcurl-promise';
|
||||
import * as models from '../models';
|
||||
import { Cookie, CookieJar } from '../models/cookie-jar';
|
||||
import type { Environment } from '../models/environment';
|
||||
import type { Request } from '../models/request';
|
||||
import { CaCertificate } from '../models/ca-certificate';
|
||||
import { ClientCertificate } from '../models/client-certificate';
|
||||
import { Cookie } from '../models/cookie-jar';
|
||||
import type { Request, RequestAuthentication, RequestParameter } from '../models/request';
|
||||
import type { Settings } from '../models/settings';
|
||||
import { isWorkspace } from '../models/workspace';
|
||||
import * as pluginContexts from '../plugins/context/index';
|
||||
import * as plugins from '../plugins/index';
|
||||
import { invariant } from '../utils/invariant';
|
||||
import { setDefaultProtocol } from '../utils/url/protocol';
|
||||
import {
|
||||
buildQueryStringFromParams,
|
||||
@ -38,160 +36,243 @@ import {
|
||||
smartEncodeUrl,
|
||||
} from '../utils/url/querystring';
|
||||
import { getAuthHeader, getAuthQueryParams } from './authentication';
|
||||
import { cancellableCurlRequest } from './cancellation';
|
||||
import { urlMatchesCertHost } from './url-matches-cert-host';
|
||||
|
||||
// Time since user's last keypress to wait before making the request
|
||||
const MAX_DELAY_TIME = 1000;
|
||||
// used for oauth grant types
|
||||
// creates a new request with the patch args
|
||||
// and uses env and settings from workspace
|
||||
// not cancellable but currently is
|
||||
// used indirectly by send and getAuthHeader to fetch tokens
|
||||
// @TODO unpack oauth into regular timeline and remove oauth timeine dialog
|
||||
export async function sendWithSettings(
|
||||
requestId: string,
|
||||
requestPatch: Record<string, any>,
|
||||
) {
|
||||
console.log(`[network] Sending with settings req=${requestId}`);
|
||||
const { request,
|
||||
environment,
|
||||
settings,
|
||||
clientCertificates,
|
||||
caCert } = await fetchRequestData(requestId);
|
||||
|
||||
const cancelRequestFunctionMap: Record<string, () => void> = {};
|
||||
const newRequest: Request = await models.initModel(models.request.type, requestPatch, {
|
||||
_id: request._id + '.other',
|
||||
parentId: request._id,
|
||||
});
|
||||
|
||||
let lastUserInteraction = Date.now();
|
||||
const renderResult = await tryToInterpolateRequest(newRequest, environment._id);
|
||||
const renderedRequest = await tryToTransformRequestWithPlugins(renderResult);
|
||||
|
||||
export async function cancelRequestById(requestId: string) {
|
||||
const hasCancelFunction = cancelRequestFunctionMap.hasOwnProperty(requestId) && typeof cancelRequestFunctionMap[requestId] === 'function';
|
||||
if (hasCancelFunction) {
|
||||
return cancelRequestFunctionMap[requestId]();
|
||||
}
|
||||
console.log(`[network] Failed to cancel req=${requestId} because cancel function not found`);
|
||||
const response = await sendCurlAndWriteTimeline(
|
||||
renderResult.request,
|
||||
clientCertificates,
|
||||
caCert,
|
||||
{ ...settings, validateSSL: settings.validateAuthSSL },
|
||||
);
|
||||
return responseTransform(response, renderedRequest, renderResult.context);
|
||||
}
|
||||
|
||||
export async function _actuallySend(
|
||||
// used by test feature, inso, and plugin api
|
||||
// not all need to be cancellable or to use curl
|
||||
export async function send(
|
||||
requestId: string,
|
||||
environmentId?: string,
|
||||
extraInfo?: ExtraRenderInfo,
|
||||
) {
|
||||
console.log(`[network] Sending req=${requestId} env=${environmentId || 'null'}`);
|
||||
|
||||
const { request,
|
||||
environment,
|
||||
settings,
|
||||
clientCertificates,
|
||||
caCert } = await fetchRequestData(requestId);
|
||||
|
||||
const renderResult = await tryToInterpolateRequest(request, environment._id, RENDER_PURPOSE_SEND, extraInfo);
|
||||
const renderedRequest = await tryToTransformRequestWithPlugins(renderResult);
|
||||
const response = await sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
clientCertificates,
|
||||
caCert,
|
||||
settings,
|
||||
);
|
||||
return responseTransform(response, renderedRequest, renderResult.context);
|
||||
}
|
||||
|
||||
const fetchRequestData = async (requestId: string) => {
|
||||
const request = await models.request.getById(requestId);
|
||||
invariant(request, 'failed to find request');
|
||||
const ancestors = await db.withAncestors(request, [
|
||||
models.request.type,
|
||||
models.requestGroup.type,
|
||||
models.workspace.type,
|
||||
]);
|
||||
const workspaceDoc = ancestors.find(isWorkspace);
|
||||
const workspaceId = workspaceDoc ? workspaceDoc._id : 'n/a';
|
||||
const workspace = await models.workspace.getById(workspaceId);
|
||||
invariant(workspace, 'failed to find workspace');
|
||||
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspace._id);
|
||||
|
||||
// fallback to base environment
|
||||
const environment = workspaceMeta.activeEnvironmentId ?
|
||||
await models.environment.getById(workspaceMeta.activeEnvironmentId)
|
||||
: await models.environment.getOrCreateForParentId(workspace._id);
|
||||
invariant(environment, 'failed to find environment');
|
||||
|
||||
const settings = await models.settings.getOrCreate();
|
||||
invariant(settings, 'failed to create settings');
|
||||
const clientCertificates = await models.clientCertificate.findByParentId(workspaceId);
|
||||
const caCert = await models.caCertificate.findByParentId(workspaceId);
|
||||
|
||||
return { request, environment, settings, clientCertificates, caCert };
|
||||
};
|
||||
|
||||
export const tryToInterpolateRequest = async (request: Request, environmentId: string, purpose?: RenderPurpose, extraInfo?: ExtraRenderInfo) => {
|
||||
try {
|
||||
return await getRenderedRequestAndContext({
|
||||
request: request,
|
||||
environmentId,
|
||||
purpose,
|
||||
extraInfo,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to render request: ${request._id}`);
|
||||
}
|
||||
};
|
||||
export const tryToTransformRequestWithPlugins = async (renderResult: RequestAndContext) => {
|
||||
const { request, context } = renderResult;
|
||||
try {
|
||||
return await _applyRequestPluginHooks(request, context);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to transform request with plugins: ${request._id}`);
|
||||
}
|
||||
};
|
||||
export async function sendCurlAndWriteTimeline(
|
||||
renderedRequest: RenderedRequest,
|
||||
workspaceId: string,
|
||||
clientCertificates: ClientCertificate[],
|
||||
caCert: CaCertificate | null,
|
||||
settings: Settings,
|
||||
) {
|
||||
return new Promise<ResponsePatch>(async resolve => {
|
||||
const timeline: ResponseTimelineEntry[] = [];
|
||||
const requestId = renderedRequest._id;
|
||||
const timeline: ResponseTimelineEntry[] = [];
|
||||
|
||||
try {
|
||||
// Setup the cancellation logic
|
||||
cancelRequestFunctionMap[renderedRequest._id] = async () => {
|
||||
const timelinePath = await storeTimeline(timeline);
|
||||
// Tear Down the cancellation logic
|
||||
if (cancelRequestFunctionMap.hasOwnProperty(renderedRequest._id)) {
|
||||
delete cancelRequestFunctionMap[renderedRequest._id];
|
||||
}
|
||||
// NOTE: conditionally use ipc bridge, renderer cannot import native modules directly
|
||||
const nodejsCancelCurlRequest = process.type === 'renderer'
|
||||
? window.main.cancelCurlRequest
|
||||
: (await import('../main/network/libcurl-promise')).cancelCurlRequest;
|
||||
const { finalUrl, socketPath } = transformUrl(renderedRequest.url, renderedRequest.parameters, renderedRequest.authentication, renderedRequest.settingEncodeUrl);
|
||||
|
||||
nodejsCancelCurlRequest(renderedRequest._id);
|
||||
return resolve({
|
||||
elapsedTime: 0,
|
||||
bytesRead: 0,
|
||||
url: renderedRequest.url,
|
||||
statusMessage: 'Cancelled',
|
||||
error: 'Request was cancelled',
|
||||
timelinePath,
|
||||
});
|
||||
};
|
||||
const authQueryParam = await getAuthQueryParams(renderedRequest);
|
||||
// Set the URL, including the query parameters
|
||||
const qs = buildQueryStringFromParams(
|
||||
authQueryParam
|
||||
? renderedRequest.parameters.concat([authQueryParam])
|
||||
: renderedRequest.parameters
|
||||
);
|
||||
const url = joinUrlAndQueryString(renderedRequest.url, qs);
|
||||
const isUnixSocket = url.match(/https?:\/\/unix:\//);
|
||||
let finalUrl, socketPath;
|
||||
if (!isUnixSocket) {
|
||||
finalUrl = smartEncodeUrl(url, renderedRequest.settingEncodeUrl);
|
||||
} else {
|
||||
// URL prep will convert "unix:/path" hostname to "unix/path"
|
||||
const match = smartEncodeUrl(url, renderedRequest.settingEncodeUrl).match(/(https?:)\/\/unix:?(\/[^:]+):\/(.+)/);
|
||||
const protocol = (match && match[1]) || '';
|
||||
socketPath = (match && match[2]) || '';
|
||||
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() });
|
||||
}
|
||||
const clientCertificates = await models.clientCertificate.findByParentId(workspaceId);
|
||||
const certificates = clientCertificates.filter(c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, 'https:'), renderedRequest.url));
|
||||
const caCert = await models.caCertificate.findByParentId(workspaceId);
|
||||
const caCertficatePath = caCert?.disabled === false ? caCert.path : null;
|
||||
const authHeader = await getAuthHeader(renderedRequest, finalUrl);
|
||||
if (!renderedRequest.settingSendCookies) {
|
||||
timeline.push({ value: 'Disable cookie sending due to user setting', name: 'Text', timestamp: Date.now() });
|
||||
}
|
||||
|
||||
// NOTE: conditionally use ipc bridge, renderer cannot import native modules directly
|
||||
const nodejsCurlRequest = process.type === 'renderer'
|
||||
? window.main.curlRequest
|
||||
: (await import('../main/network/libcurl-promise')).curlRequest;
|
||||
const authHeader = await getAuthHeader(renderedRequest, finalUrl);
|
||||
const requestOptions = {
|
||||
requestId,
|
||||
req: renderedRequest,
|
||||
finalUrl,
|
||||
socketPath,
|
||||
settings,
|
||||
certificates: clientCertificates.filter(c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, 'https:'), renderedRequest.url)),
|
||||
caCertficatePath: caCert?.disabled === false ? caCert.path : null,
|
||||
authHeader,
|
||||
};
|
||||
|
||||
const requestOptions = {
|
||||
requestId: renderedRequest._id,
|
||||
req: renderedRequest,
|
||||
finalUrl,
|
||||
socketPath,
|
||||
settings,
|
||||
certificates,
|
||||
caCertficatePath,
|
||||
authHeader,
|
||||
};
|
||||
const { patch, debugTimeline, headerResults, responseBodyPath } = await nodejsCurlRequest(requestOptions);
|
||||
const { cookieJar, settingStoreCookies } = renderedRequest;
|
||||
// NOTE: conditionally use ipc bridge, renderer cannot import native modules directly
|
||||
const nodejsCurlRequest = process.type === 'renderer'
|
||||
? cancellableCurlRequest
|
||||
: (await import('../main/network/libcurl-promise')).curlRequest;
|
||||
const output = await nodejsCurlRequest(requestOptions);
|
||||
|
||||
// add set-cookie headers to file(cookiejar) and database
|
||||
if (settingStoreCookies) {
|
||||
// supports many set-cookies over many redirects
|
||||
const redirects: string[][] = headerResults.map(({ headers }: any) => getSetCookiesFromResponseHeaders(headers));
|
||||
const setCookieStrings: string[] = redirects.flat();
|
||||
const totalSetCookies = setCookieStrings.length;
|
||||
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() }));
|
||||
const hasCookiesToPersist = totalSetCookies > rejectedCookies.length;
|
||||
if (hasCookiesToPersist) {
|
||||
const patch: Partial<CookieJar> = { cookies };
|
||||
await models.cookieJar.update(cookieJar, patch);
|
||||
timeline.push({ value: `Saved ${totalSetCookies} cookies`, name: 'Text', timestamp: Date.now() });
|
||||
}
|
||||
}
|
||||
}
|
||||
if ('error' in output) {
|
||||
const timelinePath = await storeTimeline(timeline);
|
||||
|
||||
const lastRedirect = headerResults[headerResults.length - 1];
|
||||
const responsePatch: ResponsePatch = {
|
||||
contentType: getContentTypeHeader(lastRedirect.headers)?.value || '',
|
||||
headers: lastRedirect.headers,
|
||||
httpVersion: lastRedirect.version,
|
||||
statusCode: lastRedirect.code,
|
||||
statusMessage: lastRedirect.reason,
|
||||
...patch,
|
||||
};
|
||||
const timelinePath = await storeTimeline([...timeline, ...debugTimeline]);
|
||||
// Tear Down the cancellation logic
|
||||
if (cancelRequestFunctionMap.hasOwnProperty(renderedRequest._id)) {
|
||||
delete cancelRequestFunctionMap[renderedRequest._id];
|
||||
}
|
||||
return resolve({
|
||||
timelinePath,
|
||||
bodyPath: responseBodyPath,
|
||||
...responsePatch,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('[network] Error', err);
|
||||
const timelinePath = await storeTimeline(timeline);
|
||||
// Tear Down the cancellation logic
|
||||
if (cancelRequestFunctionMap.hasOwnProperty(renderedRequest._id)) {
|
||||
delete cancelRequestFunctionMap[renderedRequest._id];
|
||||
}
|
||||
return resolve({
|
||||
url: renderedRequest.url,
|
||||
error: err.message || 'Something went wrong',
|
||||
elapsedTime: 0, // 0 because this path is hit during plugin calls
|
||||
statusMessage: 'Error',
|
||||
timelinePath,
|
||||
});
|
||||
}
|
||||
});
|
||||
return {
|
||||
parentId: requestId,
|
||||
url: requestOptions.finalUrl,
|
||||
error: output.error,
|
||||
elapsedTime: 0, // 0 because this path is hit during plugin calls
|
||||
bytesRead: 0,
|
||||
statusMessage: output.statusMessage,
|
||||
timelinePath,
|
||||
};
|
||||
}
|
||||
const { patch, debugTimeline, headerResults, responseBodyPath } = output;
|
||||
const timelinePath = await storeTimeline([...timeline, ...debugTimeline]);
|
||||
// transform output
|
||||
const { cookies, rejectedCookies, totalSetCookies } = await extractCookies(headerResults, renderedRequest.cookieJar, finalUrl, renderedRequest.settingStoreCookies);
|
||||
rejectedCookies.forEach(errorMessage => timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'Text', timestamp: Date.now() }));
|
||||
if (cookies) {
|
||||
await models.cookieJar.update(renderedRequest.cookieJar, { cookies });
|
||||
timeline.push({ value: `Saved ${totalSetCookies} cookies`, name: 'Text', timestamp: Date.now() });
|
||||
}
|
||||
const lastRedirect = headerResults[headerResults.length - 1];
|
||||
|
||||
return {
|
||||
parentId: renderedRequest._id,
|
||||
timelinePath,
|
||||
bodyPath: responseBodyPath,
|
||||
contentType: getContentTypeHeader(lastRedirect.headers)?.value || '',
|
||||
headers: lastRedirect.headers,
|
||||
httpVersion: lastRedirect.version,
|
||||
statusCode: lastRedirect.code,
|
||||
statusMessage: lastRedirect.reason,
|
||||
...patch,
|
||||
};
|
||||
}
|
||||
export const responseTransform = (patch: ResponsePatch, renderedRequest: RenderedRequest, context: Record<string, any>) => {
|
||||
const response = {
|
||||
...patch,
|
||||
environmentId: patch.environmentId,
|
||||
bodyCompression: null,
|
||||
settingSendCookies: renderedRequest.settingSendCookies,
|
||||
settingStoreCookies: renderedRequest.settingStoreCookies,
|
||||
};
|
||||
|
||||
if (response.error) {
|
||||
console.log(`[network] Response failed req=${patch.parentId} err=${response.error || 'n/a'}`);
|
||||
return response;
|
||||
}
|
||||
console.log(`[network] Response succeeded req=${patch.parentId} status=${response.statusCode || '?'}`,);
|
||||
return _applyResponsePluginHooks(
|
||||
response,
|
||||
renderedRequest,
|
||||
context,
|
||||
);
|
||||
};
|
||||
export const transformUrl = (url: string, params: RequestParameter[], authentication: RequestAuthentication, shouldEncode: boolean) => {
|
||||
const authQueryParam = getAuthQueryParams(authentication);
|
||||
const customUrl = joinUrlAndQueryString(url, buildQueryStringFromParams(authQueryParam ? params.concat([authQueryParam]) : params));
|
||||
const isUnixSocket = customUrl.match(/https?:\/\/unix:\//);
|
||||
if (!isUnixSocket) {
|
||||
return { finalUrl: smartEncodeUrl(customUrl, shouldEncode) };
|
||||
}
|
||||
// URL prep will convert "unix:/path" hostname to "unix/path"
|
||||
const match = smartEncodeUrl(customUrl, shouldEncode).match(/(https?:)\/\/unix:?(\/[^:]+):\/(.+)/);
|
||||
const protocol = (match && match[1]) || '';
|
||||
const socketPath = (match && match[2]) || '';
|
||||
const socketUrl = (match && match[3]) || '';
|
||||
return { finalUrl: `${protocol}//${socketUrl}`, socketPath };
|
||||
};
|
||||
|
||||
const extractCookies = async (headerResults: HeaderResult[], cookieJar: any, finalUrl: string, settingStoreCookies: boolean) => {
|
||||
// add set-cookie headers to file(cookiejar) and database
|
||||
if (settingStoreCookies) {
|
||||
// supports many set-cookies over many redirects
|
||||
const redirects: string[][] = headerResults.map(({ headers }: any) => getSetCookiesFromResponseHeaders(headers));
|
||||
const setCookieStrings: string[] = redirects.flat();
|
||||
const totalSetCookies = setCookieStrings.length;
|
||||
if (totalSetCookies) {
|
||||
const currentUrl = getCurrentUrl({ headerResults, finalUrl });
|
||||
const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ setCookieStrings, currentUrl, cookieJar });
|
||||
const hasCookiesToPersist = totalSetCookies > rejectedCookies.length;
|
||||
if (hasCookiesToPersist) {
|
||||
return { cookies, rejectedCookies, totalSetCookies };
|
||||
}
|
||||
}
|
||||
}
|
||||
return { cookies: [], rejectedCookies: [], totalSetCookies: 0 };
|
||||
};
|
||||
|
||||
export const getSetCookiesFromResponseHeaders = (headers: any[]) => getSetCookieHeaders(headers).map(h => h.value);
|
||||
|
||||
@ -227,169 +308,6 @@ export const addSetCookiesToToughCookieJar = async ({ setCookieStrings, currentU
|
||||
return { cookies, rejectedCookies };
|
||||
};
|
||||
|
||||
export async function sendWithSettings(
|
||||
requestId: string,
|
||||
requestPatch: Record<string, any>,
|
||||
) {
|
||||
console.log(`[network] Sending with settings req=${requestId}`);
|
||||
const request = await models.request.getById(requestId);
|
||||
|
||||
if (!request) {
|
||||
throw new Error(`Failed to find request: ${requestId}`);
|
||||
}
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const ancestors = await db.withAncestors(request, [
|
||||
models.request.type,
|
||||
models.requestGroup.type,
|
||||
models.workspace.type,
|
||||
]);
|
||||
const workspaceDoc = ancestors.find(isWorkspace);
|
||||
const workspaceId = workspaceDoc ? workspaceDoc._id : 'n/a';
|
||||
const workspace = await models.workspace.getById(workspaceId);
|
||||
|
||||
if (!workspace) {
|
||||
throw new Error(`Failed to find workspace for: ${requestId}`);
|
||||
}
|
||||
|
||||
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspace._id);
|
||||
const environmentId: string = workspaceMeta.activeEnvironmentId || 'n/a';
|
||||
const newRequest: Request = await models.initModel(models.request.type, requestPatch, {
|
||||
_id: request._id + '.other',
|
||||
parentId: request._id,
|
||||
});
|
||||
let renderResult: {
|
||||
request: RenderedRequest;
|
||||
context: Record<string, any>;
|
||||
};
|
||||
try {
|
||||
renderResult = await getRenderedRequestAndContext({ request: newRequest, environmentId });
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to render request: ${requestId}`);
|
||||
}
|
||||
|
||||
const environment: Environment | null = await models.environment.getById(environmentId || 'n/a');
|
||||
const responseEnvironmentId = environment ? environment._id : null;
|
||||
|
||||
const response = await _actuallySend(
|
||||
renderResult.request,
|
||||
workspace._id,
|
||||
{ ...settings, validateSSL: settings.validateAuthSSL },
|
||||
);
|
||||
response.parentId = renderResult.request._id;
|
||||
response.environmentId = responseEnvironmentId;
|
||||
response.bodyCompression = null;
|
||||
response.settingSendCookies = renderResult.request.settingSendCookies;
|
||||
response.settingStoreCookies = renderResult.request.settingStoreCookies;
|
||||
if (response.error) {
|
||||
return response;
|
||||
}
|
||||
return _applyResponsePluginHooks(
|
||||
response,
|
||||
renderResult.request,
|
||||
renderResult.context,
|
||||
);
|
||||
}
|
||||
|
||||
export async function send(
|
||||
requestId: string,
|
||||
environmentId?: string,
|
||||
extraInfo?: ExtraRenderInfo,
|
||||
) {
|
||||
console.log(`[network] Sending req=${requestId} env=${environmentId || 'null'}`);
|
||||
// HACK: wait for all debounces to finish
|
||||
|
||||
/*
|
||||
* TODO: Do this in a more robust way
|
||||
* The following block adds a "long" delay to let potential debounces and
|
||||
* database updates finish before making the request. This is done by tracking
|
||||
* the time of the user's last keypress and making sure the request is sent a
|
||||
* significant time after the last press.
|
||||
*/
|
||||
const timeSinceLastInteraction = Date.now() - lastUserInteraction;
|
||||
const delayMillis = Math.max(0, MAX_DELAY_TIME - timeSinceLastInteraction);
|
||||
if (delayMillis > 0) {
|
||||
await delay(delayMillis);
|
||||
}
|
||||
|
||||
// Fetch some things
|
||||
|
||||
const request = await models.request.getById(requestId);
|
||||
const settings = await models.settings.getOrCreate();
|
||||
const ancestors = await db.withAncestors(request, [
|
||||
models.request.type,
|
||||
models.requestGroup.type,
|
||||
models.workspace.type,
|
||||
]);
|
||||
|
||||
if (!request) {
|
||||
throw new Error(`Failed to find request to send for ${requestId}`);
|
||||
}
|
||||
const renderResult = await getRenderedRequestAndContext(
|
||||
{
|
||||
request,
|
||||
environmentId,
|
||||
purpose: RENDER_PURPOSE_SEND,
|
||||
extraInfo,
|
||||
},
|
||||
);
|
||||
|
||||
const renderedRequestBeforePlugins = renderResult.request;
|
||||
const renderedContextBeforePlugins = renderResult.context;
|
||||
const workspaceDoc = ancestors.find(isWorkspace);
|
||||
const workspace = await models.workspace.getById(workspaceDoc ? workspaceDoc._id : 'n/a');
|
||||
|
||||
if (!workspace) {
|
||||
throw new Error(`Failed to find workspace for request: ${requestId}`);
|
||||
}
|
||||
|
||||
const environment: Environment | null = await models.environment.getById(environmentId || 'n/a');
|
||||
const responseEnvironmentId = environment ? environment._id : null;
|
||||
|
||||
let renderedRequest: RenderedRequest;
|
||||
|
||||
try {
|
||||
renderedRequest = await _applyRequestPluginHooks(
|
||||
renderedRequestBeforePlugins,
|
||||
renderedContextBeforePlugins,
|
||||
);
|
||||
} catch (err) {
|
||||
return {
|
||||
environmentId: responseEnvironmentId,
|
||||
error: err.message || 'Something went wrong',
|
||||
parentId: renderedRequestBeforePlugins._id,
|
||||
settingSendCookies: renderedRequestBeforePlugins.settingSendCookies,
|
||||
settingStoreCookies: renderedRequestBeforePlugins.settingStoreCookies,
|
||||
statusCode: STATUS_CODE_PLUGIN_ERROR,
|
||||
statusMessage: err.plugin ? `Plugin ${err.plugin.name}` : 'Plugin',
|
||||
url: renderedRequestBeforePlugins.url,
|
||||
} as ResponsePatch;
|
||||
}
|
||||
const response = await _actuallySend(
|
||||
renderedRequest,
|
||||
workspace._id,
|
||||
settings,
|
||||
);
|
||||
response.parentId = renderResult.request._id;
|
||||
response.environmentId = responseEnvironmentId;
|
||||
response.bodyCompression = null;
|
||||
response.settingSendCookies = renderedRequest.settingSendCookies;
|
||||
response.settingStoreCookies = renderedRequest.settingStoreCookies;
|
||||
|
||||
console.log(
|
||||
response.error
|
||||
? `[network] Response failed req=${requestId} err=${response.error || 'n/a'}`
|
||||
: `[network] Response succeeded req=${requestId} status=${response.statusCode || '?'}`,
|
||||
);
|
||||
if (response.error) {
|
||||
return response;
|
||||
}
|
||||
return _applyResponsePluginHooks(
|
||||
response,
|
||||
renderedRequest,
|
||||
renderedContextBeforePlugins,
|
||||
);
|
||||
}
|
||||
|
||||
async function _applyRequestPluginHooks(
|
||||
renderedRequest: RenderedRequest,
|
||||
renderedContext: Record<string, any>,
|
||||
@ -475,16 +393,3 @@ export function storeTimeline(timeline: ResponseTimelineEntry[]): Promise<string
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (global.document) {
|
||||
document.addEventListener('keydown', (event: KeyboardEvent) => {
|
||||
if (event.ctrlKey || event.metaKey || event.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastUserInteraction = Date.now();
|
||||
});
|
||||
document.addEventListener('paste', () => {
|
||||
lastUserInteraction = Date.now();
|
||||
});
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ export function getSendRequestCallback(environmentId?: string) {
|
||||
plugins.ignorePlugin('insomnia-plugin-kong-declarative-config');
|
||||
plugins.ignorePlugin('insomnia-plugin-kong-kubernetes-config');
|
||||
plugins.ignorePlugin('insomnia-plugin-kong-portal');
|
||||
// NOTE: unit tests will use the UI selected environment
|
||||
const res = await send(requestId, environmentId);
|
||||
const { statusCode: status, statusMessage, headers: headerArray, elapsedTime: responseTime } = res;
|
||||
const headers = headerArray?.reduce((acc, { name, value }) => ({ ...acc, [name.toLowerCase() || '']: value || '' }), []);
|
||||
|
@ -9,7 +9,7 @@ import { getSetCookieHeaders } from '../../../common/misc';
|
||||
import * as models from '../../../models';
|
||||
import type { Request } from '../../../models/request';
|
||||
import type { Response } from '../../../models/response';
|
||||
import { cancelRequestById } from '../../../network/network';
|
||||
import { cancelRequestById } from '../../../network/cancellation';
|
||||
import { jsonPrettify } from '../../../utils/prettify/json';
|
||||
import { updateRequestMetaByParentId } from '../../hooks/create-request';
|
||||
import { selectActiveResponse, selectResponseFilter, selectResponseFilterHistory, selectResponsePreviewMode, selectSettings } from '../../redux/selectors';
|
||||
|
Loading…
Reference in New Issue
Block a user