Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: integrate post-request script to the engine - INS-3785,INS-3786 #7329

Merged
merged 10 commits into from May 15, 2024
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 { sendCurlAndWriteTimelineError, sendCurlAndWriteTimelineResponse } 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?: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError;
}
62 changes: 62 additions & 0 deletions packages/insomnia-sdk/src/objects/response.ts
@@ -1,4 +1,5 @@
import { RESPONSE_CODE_REASONS } from 'insomnia/src/common/constants';
import { sendCurlAndWriteTimelineError, type sendCurlAndWriteTimelineResponse } from 'insomnia/src/network/network';

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

export async function toScriptResponse(
originalRequest: Request,
partialInsoResponse: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError,
): Promise<Response | undefined> {
if ('error' in partialInsoResponse) {
// it is sendCurlAndWriteTimelineError and basically doesn't contain anything useful
return undefined;
}
const partialResponse = partialInsoResponse as sendCurlAndWriteTimelineResponse;

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

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

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;
}
}
ihexxa marked this conversation as resolved.
Show resolved Hide resolved

const responseOption = {
code: partialResponse.statusCode || 0,
// 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
82 changes: 77 additions & 5 deletions packages/insomnia/src/network/network.ts
Expand Up @@ -73,22 +73,25 @@ export const fetchRequestData = async (requestId: string) => {
return { request, environment, settings, clientCertificates, caCert, activeEnvironmentId, timelinePath, responseId };
};

export const tryToExecutePreRequestScript = async (
export const tryToExecuteScript = async (
script: string,
request: Request,
environment: Environment,
timelinePath: string,
responseId: string,
baseEnvironment: Environment,
clientCertificates: ClientCertificate[],
cookieJar: CookieJar,
response?: sendCurlAndWriteTimelineError | sendCurlAndWriteTimelineResponse,
) => {
if (!request.preRequestScript) {
if (!script) {
return {
request,
environment: undefined,
baseEnvironment: undefined,
};
}

const settings = await models.settings.get();

try {
Expand All @@ -101,7 +104,7 @@ export const tryToExecutePreRequestScript = async (
}, timeout + 1000);
});
const preRequestPromise = cancellableRunPreRequestScript({
ihexxa marked this conversation as resolved.
Show resolved Hide resolved
script: request.preRequestScript,
script,
context: {
request,
timelinePath,
Expand All @@ -115,6 +118,7 @@ export const tryToExecutePreRequestScript = async (
clientCertificates,
settings,
cookieJar,
response,
},
});
const output = await Promise.race([timeoutPromise, preRequestPromise]) as {
Expand Down Expand Up @@ -152,7 +156,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 All @@ -169,6 +176,50 @@ export const tryToExecutePreRequestScript = async (
}
};

export async function tryToExecutePreRequestScript(
request: Request,
environment: Environment,
timelinePath: string,
responseId: string,
baseEnvironment: Environment,
clientCertificates: ClientCertificate[],
cookieJar: CookieJar,
) {
return tryToExecuteScript(
request.preRequestScript,
request,
environment,
timelinePath,
responseId,
baseEnvironment,
clientCertificates,
cookieJar,
);
};

export async function tryToExecutePostRequestScript(
request: Request,
environment: Environment,
timelinePath: string,
responseId: string,
baseEnvironment: Environment,
clientCertificates: ClientCertificate[],
cookieJar: CookieJar,
response: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError,
) {
return tryToExecuteScript(
request.postRequestScript,
request,
environment,
timelinePath,
responseId,
baseEnvironment,
clientCertificates,
cookieJar,
response,
);
}

export const tryToInterpolateRequest = async (
request: Request,
environment: string | Environment,
Expand Down Expand Up @@ -202,14 +253,34 @@ export const tryToTransformRequestWithPlugins = async (renderResult: RequestAndC
throw new Error(`Failed to transform request with plugins: ${request._id}`);
}
};

export interface sendCurlAndWriteTimelineError {
_id: string;
parentId: string;
timelinePath: string;
statusMessage: string;
// additional
url: string;
error: string;
elapsedTime: number;
bytesRead: number;
}

export interface sendCurlAndWriteTimelineResponse extends ResponsePatch {
_id: string;
parentId: string;
timelinePath: string;
statusMessage: string;
}

export async function sendCurlAndWriteTimeline(
renderedRequest: RenderedRequest,
clientCertificates: ClientCertificate[],
caCert: CaCertificate | null,
settings: Settings,
timelinePath: string,
responseId: string,
) {
): Promise<sendCurlAndWriteTimelineError | sendCurlAndWriteTimelineResponse> {
const requestId = renderedRequest._id;
const timelineStrings: string[] = [];
const authentication = renderedRequest.authentication as RequestAuthentication;
Expand Down Expand Up @@ -282,6 +353,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