mirror of
https://github.com/Kong/insomnia.git
synced 2026-07-31 09:46:49 -04:00
* feat(templating): (H1) in-sandbox response-hook API + marshaling First increment of PR 10b (response hooks). Adds the sandbox-side machinery to run a user plugin's response hook, without yet wiring the network adapters: - __buildResponseApi(resp): faithful ES5 rebuild of plugins/context/response.ts getters (status/headers/time, case-insensitive getHeader returning string|string[]|null). getBody() bridges to the existing response.getBodyBuffer path; getBodyStream() throws (a Node Readable can't cross the sandbox); setBody() base64-encodes the bytes over a new response.setBody bridge path (no fs in the sandbox) and updates bytesContent locally. - __invokeHook: for response hooks, attaches context.response and a READ-ONLY context.request (matches pluginRequest.init(..., true)), and marshals both the request and response back out. - host-bridge: response.setBody mapped at baseline (models.read, grouped with the other response ops) — a bounded write to the host-set bodyPath, matching the ungated in-process behavior; not a general fs write. Unit test drives a response hook that reads status/headers, reads the body via the bridge, and rewrites it via setBody (asserting the base64 round-trip and the read-only request). Host runner + adapter integration + e2e are the next increment. * feat(templating): (H1) route user-plugin response hooks through the sandbox Wires the response-hook sandbox core into both hook paths so a user plugin's response hook runs in QuickJS (bundle plugins and the flag-off path unchanged). - templating-worker-database: runResponseHookInSandbox + the plugin.runUserResponseHook handler; the response.setBody bridge handler (base64-decode -> fs.writeFileSync) guarded to the responses directory as defense in depth (bodyPath is host-set); pickHookResponseFields. - network-adapter.node.ts (main/CLI): runs the response hook in the sandbox for user plugins, merging the returned response fields onto newResponse. - invoke-method.ts (plugin window): reaches the same runner over the protocol. E2E: a user plugin response hook rewrites the body via setBody; the echo response pane shows the rewrite. Flag off it reports ranin-mainprocess (control), flag on ranin-sandboxed. Completes H1 (request hooks in PR 10a). * fix(templating): (H1) revive bridged Buffer in response getBody + reject empty setBody Addresses Copilot review on #10286: - in-sandbox-bootstrap.ts: context.response.getBody() now revives the JSON-marshaled Buffer shape ({ type: 'Buffer', data: [...] }) back into a (shimmed) Buffer, so hooks consume it exactly like the in-process response API instead of a plain object. - templating-worker-database.ts: response.setBody rejects a missing/non-string bodyBase64 (TypeError) instead of defaulting to '' — a write primitive must not silently truncate the body to zero bytes. An empty string stays a valid empty body. - sandbox-hooks.test.ts: the getBodyBuffer stub now returns the real marshaled Buffer shape and the hook decodes with toString('utf8'), exercising the revive end-to-end.
80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
import type { Response } from 'insomnia-data';
|
|
import { database as db, models } from 'insomnia-data';
|
|
|
|
import * as requestHelpers from './helpers/request-operations';
|
|
import * as requestVersionService from './request-version';
|
|
import * as settingsService from './settings';
|
|
|
|
const { type } = models.response;
|
|
|
|
export function getById(id: string) {
|
|
return db.findOne<Response>(type, { _id: id });
|
|
}
|
|
|
|
// Finds whichever response already owns a given on-disk body file, to verify ownership before overwrite.
|
|
export function getByBodyPath(bodyPath: string) {
|
|
return db.findOne<Response>(type, { bodyPath });
|
|
}
|
|
|
|
export function findByParentId(parentId: string) {
|
|
return db.find<Response>(type, { parentId: parentId });
|
|
}
|
|
|
|
export async function all() {
|
|
return db.find<Response>(type);
|
|
}
|
|
|
|
export async function getLatestForRequestId(
|
|
requestId: string,
|
|
environmentId: string | null,
|
|
): Promise<Response | undefined> {
|
|
// Filter responses by environment if setting is enabled
|
|
const shouldFilter = (await settingsService.get()).filterResponsesByEnv;
|
|
|
|
const response = await db.findOne<Response>(
|
|
type,
|
|
{
|
|
parentId: requestId,
|
|
...(shouldFilter ? { environmentId } : {}),
|
|
},
|
|
{ modified: -1 },
|
|
);
|
|
return response;
|
|
}
|
|
|
|
export async function create(patch: Partial<Response> = {}, maxResponses = 20): Promise<Response> {
|
|
if (!patch.parentId) {
|
|
console.log('[db] Attempted to create response without `parentId`', patch);
|
|
throw new Error('New Response missing `parentId`');
|
|
}
|
|
|
|
const { parentId } = patch;
|
|
// Create request version snapshot
|
|
const request = await requestHelpers.getRequestById(parentId);
|
|
const requestVersion = request ? await requestVersionService.create(request) : null;
|
|
patch.requestVersionId = requestVersion ? requestVersion._id : null;
|
|
// Filter responses by environment if setting is enabled
|
|
const settings = await settingsService.get();
|
|
const shouldQueryByEnvId = 'environmentId' in patch && settings.filterResponsesByEnv;
|
|
const query = {
|
|
parentId,
|
|
...(shouldQueryByEnvId ? { environmentId: patch.environmentId } : {}),
|
|
};
|
|
|
|
// Delete all other responses before creating the new one
|
|
const responsesToShow = Math.max(1, maxResponses);
|
|
|
|
const allResponses = await db.find<Response>(type, query, { modified: -1 }, responsesToShow);
|
|
|
|
const recentIds = allResponses.map(r => r._id);
|
|
// Remove all that were in the last query, except the first `maxResponses` IDs
|
|
await db.removeWhere(type, {
|
|
...query,
|
|
_id: {
|
|
$nin: recentIds,
|
|
},
|
|
});
|
|
// Actually create the new response
|
|
return db.docCreate(type, patch);
|
|
}
|