Skip to content

Commit

Permalink
feat: integrate post-request script to the engine
Browse files Browse the repository at this point in the history
  • Loading branch information
ihexxa committed Apr 28, 2024
1 parent 2b2699d commit ab97c3b
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 36 deletions.
10 changes: 9 additions & 1 deletion packages/insomnia-sdk/src/objects/insomnia.ts
Expand Up @@ -11,6 +11,7 @@ import { unsupportedError } from './properties';
import { Request as ScriptRequest, RequestOptions, toScriptRequestBody } from './request';
import { RequestInfo } from './request-info';
import { Response as ScriptResponse } from './response';
import { toScriptResponse } from './response';
import { sendRequest } from './send-request';
import { test } from './test';
import { toUrlObject } from './urls';
Expand All @@ -23,6 +24,7 @@ export class InsomniaObject {
public request: ScriptRequest;
public cookies: CookieObject;
public info: RequestInfo;
public response?: ScriptResponse;

private clientCertificates: ClientCertificate[];
private _expect = expect;
Expand All @@ -47,6 +49,7 @@ export class InsomniaObject {
clientCertificates: ClientCertificate[];
cookies: CookieObject;
requestInfo: RequestInfo;
response?: ScriptResponse;
},
log: (...msgs: any[]) => void,
) {
Expand All @@ -57,6 +60,7 @@ export class InsomniaObject {
this._iterationData = rawObj.iterationData;
this.variables = rawObj.variables;
this.cookies = rawObj.cookies;
this.response = rawObj.response;

this.info = rawObj.requestInfo;
this.request = rawObj.request;
Expand Down Expand Up @@ -108,11 +112,12 @@ export class InsomniaObject {
clientCertificates: this.clientCertificates,
cookieJar: this.cookies.jar().toInsomniaCookieJar(),
info: this.info.toObject(),
response: this.response ? this.response.toObject() : undefined,
};
};
}

export function initInsomniaObject(
export async function initInsomniaObject(
rawObj: RequestContext,
log: (...args: any[]) => void,
) {
Expand Down Expand Up @@ -206,6 +211,8 @@ export function initInsomniaObject(
};
const request = new ScriptRequest(reqOpt);

const response = rawObj.response ? await toScriptResponse(request, rawObj.response) : undefined;

return new InsomniaObject(
{
globals,
Expand All @@ -218,6 +225,7 @@ export function initInsomniaObject(
clientCertificates: rawObj.clientCertificates,
cookies,
requestInfo,
response,
},
log,
);
Expand Down
3 changes: 3 additions & 0 deletions packages/insomnia-sdk/src/objects/interfaces.ts
Expand Up @@ -2,6 +2,7 @@ import { CookieJar as InsomniaCookieJar } from 'insomnia/src//models/cookie-jar'
import { ClientCertificate } from 'insomnia/src/models/client-certificate';
import type { Request } from 'insomnia/src/models/request';
import { Settings } from 'insomnia/src/models/settings';
import { sendCurlAndWriteTimeline } from 'insomnia/src/network/network';

export interface RequestContext {
request: Request;
Expand All @@ -17,4 +18,6 @@ export interface RequestContext {
settings: Settings;
clientCertificates: ClientCertificate[];
cookieJar: InsomniaCookieJar;
// only for the post-request script
response?: Awaited<ReturnType<typeof sendCurlAndWriteTimeline>>;
}
70 changes: 70 additions & 0 deletions packages/insomnia-sdk/src/objects/response.ts
@@ -1,4 +1,6 @@
import { RESPONSE_CODE_REASONS } from 'insomnia/src/common/constants';
import { Compression, ResponseHeader } from 'insomnia/src/models/response';
import { sendCurlAndWriteTimeline } from 'insomnia/src/network/network';

import { Cookie, CookieOptions } from './cookies';
import { CookieList } from './cookies';
Expand Down Expand Up @@ -183,3 +185,71 @@ export class Response extends Property {
return this.body.toString();
}
}

export async function toScriptResponse(
originalRequest: Request,
partialInsoResponse: Awaited<ReturnType<typeof sendCurlAndWriteTimeline>>,
): Promise<Response | undefined> {
if (partialInsoResponse.error) {
// response basically doesn't contain anything
return undefined;
}

// TODO: improve the type from sendCurlAndWriteTimeline a bit
// so that typing in downstream logic could be improved
const partialResponse = partialInsoResponse as {
headers: ResponseHeader[];
bodyPath: string;
bodyCompression: Compression;
statusCode: number;
elapsedTime: number;
statusMessage: string;
};

const headers = partialResponse.headers.map(
insoHeader => ({
key: insoHeader.name,
value: insoHeader.value,
}),
{},
);

const insoCookieOptions = partialResponse.headers
.filter(
header => {
return header.name.toLowerCase() === 'set-cookie';
},
{},
).map(
setCookieHeader => Cookie.parse(setCookieHeader.value)
);

// TODO: handle default path
let responseBody = '';
if (partialResponse.bodyPath) {
const readResponseResult = await window.bridge.readCurlResponse({
bodyPath: partialResponse.bodyPath,
bodyCompression: partialResponse.bodyCompression,
});

if (readResponseResult.error) {
throw Error(`Failed to read body: ${readResponseResult.error}`);
} else {
responseBody = readResponseResult.body;
}
}

const responseOption = {
code: partialResponse.statusCode,
// reason is not provided
header: headers,
cookie: insoCookieOptions,
body: responseBody,
// stream is duplicated with body
responseTime: partialResponse.elapsedTime,
status: partialResponse.statusMessage,
originalRequest,
};

return new Response(responseOption);
};
6 changes: 3 additions & 3 deletions packages/insomnia/src/hidden-window-preload.ts
Expand Up @@ -34,7 +34,7 @@ export interface HiddenBrowserWindowToMainBridgeAPI {
curlRequest: (options: any) => Promise<any>;
readCurlResponse: (options: { bodyPath: string; bodyCompression: Compression }) => Promise<{ body: string; error: string }>;
setBusy: (busy: boolean) => void;
writeFile: (logPath: string, logContent: string) => Promise<void>;
appendFile: (logPath: string, logContent: string) => Promise<void>;
asyncTasksAllSettled: () => Promise<void>;
resetAsyncTasks: () => void;
stopMonitorAsyncTasks: () => void;
Expand Down Expand Up @@ -115,8 +115,8 @@ const bridge: HiddenBrowserWindowToMainBridgeAPI = {
setBusy: busy => ipcRenderer.send('set-hidden-window-busy-status', busy),
// TODO: following methods are for simulating current behavior of running async tasks
// in the future, it should be better to keep standard way of handling async tasks to avoid confusion
writeFile: (logPath: string, logContent: string) => {
return fs.promises.writeFile(logPath, logContent);
appendFile: (logPath: string, logContent: string) => {
return fs.promises.appendFile(logPath, logContent);
},
Promise: OriginalPromise,
asyncTasksAllSettled,
Expand Down
4 changes: 2 additions & 2 deletions packages/insomnia/src/hidden-window.ts
Expand Up @@ -34,7 +34,7 @@ const runPreRequestScript = async (
console.log(script);
const scriptConsole = new Console();

const executionContext = initInsomniaObject(context, scriptConsole.log);
const executionContext = await initInsomniaObject(context, scriptConsole.log);

const evalInterceptor = (script: string) => {
invariant(script && typeof script === 'string', 'eval is called with invalid or empty value');
Expand Down Expand Up @@ -83,7 +83,7 @@ const runPreRequestScript = async (
const updatedCertificates = mergeClientCertificates(context.clientCertificates, mutatedContextObject.request);
const updatedCookieJar = mergeCookieJar(context.cookieJar, mutatedContextObject.cookieJar);

await window.bridge.writeFile(context.timelinePath, scriptConsole.dumpLogs());
await window.bridge.appendFile(context.timelinePath, scriptConsole.dumpLogs());

console.log('mutatedInsomniaObject', mutatedContextObject);
console.log('context', context);
Expand Down
11 changes: 9 additions & 2 deletions packages/insomnia/src/network/network.ts
Expand Up @@ -74,13 +74,15 @@ export const fetchRequestData = async (requestId: string) => {
};

export const tryToExecutePreRequestScript = async (
isPreRequest: boolean,
request: Request,
environment: Environment,
timelinePath: string,
responseId: string,
baseEnvironment: Environment,
clientCertificates: ClientCertificate[],
cookieJar: CookieJar,
response?: Awaited<ReturnType<typeof sendCurlAndWriteTimeline>>,
) => {
if (!request.preRequestScript) {
return {
Expand All @@ -101,7 +103,7 @@ export const tryToExecutePreRequestScript = async (
}, timeout + 1000);
});
const preRequestPromise = cancellableRunPreRequestScript({
script: request.preRequestScript,
script: isPreRequest ? request.preRequestScript : request.postRequestScript,
context: {
request,
timelinePath,
Expand All @@ -115,6 +117,7 @@ export const tryToExecutePreRequestScript = async (
clientCertificates,
settings,
cookieJar,
response,
},
});
const output = await Promise.race([timeoutPromise, preRequestPromise]) as {
Expand Down Expand Up @@ -152,7 +155,10 @@ export const tryToExecutePreRequestScript = async (
cookieJar: output.cookieJar,
};
} catch (err) {
await fs.promises.appendFile(timelinePath, JSON.stringify({ value: err.message, name: 'Text', timestamp: Date.now() }) + '\n');
await fs.promises.appendFile(
timelinePath,
JSON.stringify({ value: err.message, name: 'Text', timestamp: Date.now() }) + '\n',
);

const requestId = request._id;
const responsePatch = {
Expand Down Expand Up @@ -282,6 +288,7 @@ export async function sendCurlAndWriteTimeline(
...patch,
};
}

export const responseTransform = async (patch: ResponsePatch, environmentId: string | null, renderedRequest: RenderedRequest, context: Record<string, any>) => {
const response: ResponsePatch = {
...patch,
Expand Down
91 changes: 63 additions & 28 deletions packages/insomnia/src/ui/routes/request.tsx
Expand Up @@ -14,6 +14,7 @@ import { ResponsePatch } from '../../main/network/libcurl-promise';
import * as models from '../../models';
import { BaseModel } from '../../models';
import { CookieJar } from '../../models/cookie-jar';
import { Environment } from '../../models/environment';
import { GrpcRequest, isGrpcRequestId } from '../../models/grpc-request';
import { GrpcRequestMeta } from '../../models/grpc-request-meta';
import * as requestOperations from '../../models/helpers/request-operations';
Expand Down Expand Up @@ -381,6 +382,7 @@ export const sendAction: ActionFunction = async ({ request, params }) => {
try {
const { shouldPromptForPathAfterResponse, ignoreUndefinedEnvVariable } = await request.json() as SendActionParams;
const mutatedContext = await tryToExecutePreRequestScript(
true,
req,
environment,
timelinePath,
Expand All @@ -394,34 +396,7 @@ export const sendAction: ActionFunction = async ({ request, params }) => {
// TODO: improve error message?
return null;
} else {
// persist updated cookieJar if needed
if (mutatedContext.cookieJar) {
await models.cookieJar.update(
mutatedContext.cookieJar,
{ cookies: mutatedContext.cookieJar.cookies },
);
}
// when base environment is activated, `mutatedContext.environment` points to it
const isActiveEnvironmentBase = mutatedContext.environment?._id === baseEnvironment._id;
const hasEnvironmentAndIsNotBase = mutatedContext.environment && !isActiveEnvironmentBase;
if (hasEnvironmentAndIsNotBase) {
await models.environment.update(
environment,
{
data: mutatedContext.environment.data,
dataPropertyOrder: mutatedContext.environment.dataPropertyOrder,
}
);
}
if (mutatedContext.baseEnvironment) {
await models.environment.update(
baseEnvironment,
{
data: mutatedContext.baseEnvironment.data,
dataPropertyOrder: mutatedContext.baseEnvironment.dataPropertyOrder,
}
);
}
await savePatchesMadeByScript(mutatedContext, environment, baseEnvironment);
}

const renderedResult = await tryToInterpolateRequest(
Expand Down Expand Up @@ -461,12 +436,33 @@ export const sendAction: ActionFunction = async ({ request, params }) => {
const responsePatch = await responseTransform(response, activeEnvironmentId, renderedRequest, renderedResult.context);
const is2XXWithBodyPath = responsePatch.statusCode && responsePatch.statusCode >= 200 && responsePatch.statusCode < 300 && responsePatch.bodyPath;
const shouldWriteToFile = shouldPromptForPathAfterResponse && is2XXWithBodyPath;

const postMutatedContext = await tryToExecutePreRequestScript(
false,
req,
environment,
timelinePath,
responseId,
baseEnvironment,
clientCertificates,
cookieJar,
response,
);
if (!postMutatedContext?.request) {
// exiy early if there was a problem with the pre-request script
// TODO: improve error message?
return null;
} else {
await savePatchesMadeByScript(postMutatedContext, environment, baseEnvironment);
}

if (!shouldWriteToFile) {
const response = await models.response.create(responsePatch, settings.maxHistoryResponses);
await models.requestMeta.update(requestMeta, { activeResponseId: response._id });
// setLoading(false);
return null;
}

if (requestMeta.downloadPath) {
const header = getContentDispositionHeader(responsePatch.headers || []);
const name = header
Expand Down Expand Up @@ -500,6 +496,45 @@ export const sendAction: ActionFunction = async ({ request, params }) => {
}
};

async function savePatchesMadeByScript(
mutatedContext: Awaited<ReturnType<typeof tryToExecutePreRequestScript>>,
environment: Environment,
baseEnvironment: Environment,
) {
if (!mutatedContext) {
return;
}

// persist updated cookieJar if needed
if (mutatedContext.cookieJar) {
await models.cookieJar.update(
mutatedContext.cookieJar,
{ cookies: mutatedContext.cookieJar.cookies },
);
}
// when base environment is activated, `mutatedContext.environment` points to it
const isActiveEnvironmentBase = mutatedContext.environment?._id === baseEnvironment._id;
const hasEnvironmentAndIsNotBase = mutatedContext.environment && !isActiveEnvironmentBase;
if (hasEnvironmentAndIsNotBase) {
await models.environment.update(
environment,
{
data: mutatedContext.environment.data,
dataPropertyOrder: mutatedContext.environment.dataPropertyOrder,
}
);
}
if (mutatedContext.baseEnvironment) {
await models.environment.update(
baseEnvironment,
{
data: mutatedContext.baseEnvironment.data,
dataPropertyOrder: mutatedContext.baseEnvironment.dataPropertyOrder,
}
);
}
}

export const createAndSendToMockbinAction: ActionFunction = async ({ request }) => {
const patch = await request.json() as Partial<Request>;
invariant(typeof patch.url === 'string', 'URL is required');
Expand Down

0 comments on commit ab97c3b

Please sign in to comment.