Revert "move default templating into a web worker (#8447)"

This reverts commit 3385bc5719.
This commit is contained in:
Jay Wu
2025-03-13 18:57:28 +08:00
parent ba6275a07b
commit 6640e08f07
37 changed files with 4580 additions and 750 deletions

View File

@@ -1,6 +1,7 @@
import { exec, ExecException } from 'child_process';
import path from 'path';
import { beforeAll, describe, expect, it } from 'vitest';
import { describe, expect, it } from 'vitest';
// Tests both bundle and packaged versions of the CLI with the same commands and expectations.
// Intended to be coarse grained (only checks for success or failure) smoke test to ensure packaging worked as expected.
@@ -58,10 +59,7 @@ const shouldReturnErrorCode = [
// after-response script and test
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/after-response-failed-test.yml wrk_616795 --verbose',
];
beforeAll(async () => {
// ensure the test server is running
await fetch('http://localhost:4010');
});
describe('inso dev bundle', () => {
describe('exit codes are consistent', () => {
it.each(shouldReturnSuccessCode)('exit code should be 0: %p', async input => {

View File

@@ -11,7 +11,7 @@ import { isWorkspace, type Workspace } from '../models/workspace';
import { getAuthHeader } from '../network/authentication';
import * as plugins from '../plugins';
import * as pluginContexts from '../plugins/context/index';
import { RenderError } from '../templating/render-error';
import { RenderError } from '../templating/index';
import { parseGraphQLReqeustBody } from '../utils/graph-ql';
import { smartEncodeUrl } from '../utils/url/querystring';
import { getAppVersion } from './constants';

View File

@@ -1,3 +1,4 @@
import * as Sentry from '@sentry/electron/renderer';
import clone from 'clone';
import orderedJSON from 'json-order';
@@ -12,7 +13,6 @@ import type { WebSocketRequest } from '../models/websocket-request';
import { isWorkspace, type Workspace } from '../models/workspace';
import { getOrInheritAuthentication, getOrInheritHeaders } from '../network/network';
import * as templating from '../templating';
import { RenderError } from '../templating/render-error';
import * as templatingUtils from '../templating/utils';
import { setDefaultProtocol } from '../utils/url/protocol';
import { CONTENT_TYPE_GRAPHQL, JSON_ORDER_SEPARATOR } from './constants';
@@ -220,7 +220,7 @@ export async function buildRenderContext(
if (finalRenderContext[vaultEnvironmentPath]) {
if (finalRenderContext[vaultEnvironmentRuntimePath] && typeof finalRenderContext[vaultEnvironmentRuntimePath] !== 'object') {
const errorMsg = `${vaultEnvironmentRuntimePath} is a reserved key for insomnia vault, please rename your environment with vault as key.`;
const newError = new RenderError(errorMsg);
const newError = new templating.RenderError(errorMsg);
newError.type = 'render';
newError.message = errorMsg;
throw newError;
@@ -267,13 +267,7 @@ export async function buildRenderContext(
return finalRenderContext;
}
const renderInThisProcess = async (input: { input: string; context: Record<string, any>; path: string; ignoreUndefinedEnvVariable: boolean }) => {
return templating.render(input.input, {
context: input.context,
path: input.path,
ignoreUndefinedEnvVariable: input.ignoreUndefinedEnvVariable,
});
};
/**
* Recursively render any JS object and return a new one
* @param {*} obj - object to render
@@ -296,12 +290,12 @@ export async function render<T>(
const undefinedEnvironmentVariables: string[] = [];
async function next<T>(input: T, path: string, first = false) {
async function next<T>(x: T, path: string, first = false) {
if (blacklistPathRegex && path.match(blacklistPathRegex)) {
return input;
return x;
}
const asStr = Object.prototype.toString.call(input);
const asStr = Object.prototype.toString.call(x);
// Leave these types alone
if (
@@ -314,87 +308,71 @@ export async function render<T>(
asStr === '[object Undefined]'
) {
// Do nothing to these types
} else if (typeof input === 'string') {
const hasNunjucksInterpolationSymbols = input.includes('{{') && input.includes('}}');
const hasNunjucksCustomTagSymbols = input.includes('{%') && input.includes('%}');
const hasNunjucksCommentSymbols = input.includes('{#') && input.includes('#}');
if (!hasNunjucksInterpolationSymbols && !hasNunjucksCustomTagSymbols && !hasNunjucksCommentSymbols) {
return input;
}
if (input === '') {
return input;
} else if (typeof x === 'string') {
// Detect if the string contains a require statement
if (/require\s*\(/ig.test(x)) {
console.warn('Short-circuiting `render`; string contains possible "require" invocation:', x);
Sentry.captureException(new Error(`Short-circuiting 'render'; string contains possible "require" invocation: ${x}`));
return x;
}
try {
// Some plugins may, at the moment, require unique and intrusive access. Templates exposed by these
// plugins will not function correctly when rendering in a separate process or thread. The user can
// explicitly configure rendering to happen on the same thread/process as the rest of the app, in
// which case it's okay to render locally.
const settings = await models.settings.get();
const pluginsAllowElevatedAccess = settings?.pluginsAllowElevatedAccess;
const shouldUseWorker = process.type === 'renderer' && pluginsAllowElevatedAccess === false;
const renderFork = shouldUseWorker
? (await import('../ui/worker/templating-handler')).renderInWorker
: renderInThisProcess;
// @ts-expect-error -- TSCONVERSION
input = await renderFork({ input, context, path, ignoreUndefinedEnvVariable });
x = await templating.render(x, { context, path, ignoreUndefinedEnvVariable });
// If the variable outputs a tag, render it again. This is a common use
// case for environment variables:
// {{ foo }} => {% uuid 'v4' %} => dd265685-16a3-4d76-a59c-e8264c16835a
// @ts-expect-error -- TSCONVERSION
if (input.includes('{%')) {
if (x.includes('{%')) {
// @ts-expect-error -- TSCONVERSION
input = await renderFork({ input, context, path, ignoreUndefinedEnvVariable });
x = await templating.render(x, { context, path });
}
} catch (err) {
console.log(`Failed to render element ${path}`, input);
console.log(`Failed to render element ${path}`, x);
if (errorMode !== KEEP_ON_ERROR) {
if (err?.extraInfo?.subType === 'environmentVariable') {
if (err?.extraInfo?.subType === templating.RenderErrorSubType.EnvironmentVariable) {
undefinedEnvironmentVariables.push(...err.extraInfo.undefinedEnvironmentVariables);
} else {
throw err;
}
}
}
} else if (Array.isArray(input)) {
for (let i = 0; i < input.length; i++) {
input[i] = await next(input[i], `${path}[${i}]`);
} else if (Array.isArray(x)) {
for (let i = 0; i < x.length; i++) {
x[i] = await next(x[i], `${path}[${i}]`);
}
} else if (typeof input === 'object' && input !== null) {
} else if (typeof x === 'object' && x !== null) {
// Don't even try rendering disabled objects
// Note, this logic probably shouldn't be here, but w/e for now
// @ts-expect-error -- TSCONVERSION
if (input.disabled) {
return input;
if (x.disabled) {
return x;
}
const keys = Object.keys(input);
const keys = Object.keys(x);
for (const key of keys) {
if (first && key.indexOf('_') === 0) {
// @ts-expect-error -- mapping unsoundness
input[key] = await next(input[key], path);
x[key] = await next(x[key], path);
} else {
const pathPrefix = path ? path + '.' : '';
// @ts-expect-error -- mapping unsoundness
input[key] = await next(input[key], `${pathPrefix}${key}`);
x[key] = await next(x[key], `${pathPrefix}${key}`);
}
}
}
return input;
return x;
}
const renderResult = await next<T>(newObj, name, true);
if (undefinedEnvironmentVariables.length > 0) {
const error = new RenderError(`Failed to render environment variables: ${undefinedEnvironmentVariables.join(', ')}`);
const error = new templating.RenderError(`Failed to render environment variables: ${undefinedEnvironmentVariables.join(', ')}`);
error.type = 'render';
error.extraInfo = {
subType: 'environmentVariable',
subType: templating.RenderErrorSubType.EnvironmentVariable,
undefinedEnvironmentVariables,
};
throw error;

View File

@@ -137,7 +137,6 @@ export interface Settings {
pluginConfig: PluginConfigMap;
pluginNodeExtraCerts: string;
pluginPath: string;
pluginsAllowElevatedAccess: boolean;
preferredHttpVersion: HttpVersion;
proxyEnabled: boolean;
showPasswords: boolean;

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="font-src 'self' data:; connect-src * data: api: insomnia-event-source: insomnia-templating-worker-database:; default-src * insomnia://*; img-src blob: data: * insomnia://*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src blob: data: mediastream: * insomnia://*;"
content="font-src 'self' data:; connect-src * data: api: insomnia-event-source:; default-src * insomnia://*; img-src blob: data: * insomnia://*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src blob: data: mediastream: * insomnia://*;"
/>
<title>Hidden Browser Window</title>
</head>

View File

@@ -13,7 +13,6 @@
'self'
data:
insomnia-event-source:
insomnia-templating-worker-database:
https:
http:
;

View File

@@ -1,7 +1,6 @@
import { app, net, protocol } from 'electron';
import { getApiBaseURL } from '../common/constants';
import { resolveDbByKey } from './templating-worker-database';
export interface RegisterProtocolOptions {
scheme: string;
@@ -10,7 +9,6 @@ export interface RegisterProtocolOptions {
const insomniaStreamScheme = 'insomnia-event-source';
const httpsScheme = 'https';
const httpScheme = 'http';
const templatingWorkerDatabaseInterface = 'insomnia-templating-worker-database';
export async function registerInsomniaProtocols() {
protocol.registerSchemesAsPrivileged([{
@@ -22,9 +20,6 @@ export async function registerInsomniaProtocols() {
}, {
scheme: httpScheme,
privileges: { secure: true, standard: true, supportFetchAPI: true },
}, {
scheme: templatingWorkerDatabaseInterface,
privileges: { secure: true, standard: true, supportFetchAPI: true },
}]);
await app.whenReady();
@@ -48,7 +43,4 @@ export async function registerInsomniaProtocols() {
return net.fetch(request, { bypassCustomProtocolHandlers: true });
});
}
if (!protocol.isProtocolHandled(templatingWorkerDatabaseInterface)) {
protocol.handle(templatingWorkerDatabaseInterface, resolveDbByKey);
}
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,34 +0,0 @@
import { database as db } from '../common/database';
import * as models from '../models';
import type { Request as DBRequest } from '../models/request';
import type { RequestGroup } from '../models/request-group';
import type { Workspace } from '../models/workspace';
export const resolveDbByKey = async (request: Request) => {
const url = new URL(request.url);
let result;
const body = await request.json();
if (url.host === 'request.getById'.toLowerCase()) {
result = await models.request.getById(body.id);
}
if (url.host === 'request.getAncestors'.toLowerCase()) {
result = await db.withAncestors<DBRequest | RequestGroup | Workspace>(body.request, body.types);
}
if (url.host === 'workspace.getById'.toLowerCase()) {
result = await models.workspace.getById(body.id);
}
if (url.host === 'oAuth2Token.getByRequestId'.toLowerCase()) {
result = await models.oAuth2Token.getByParentId(body.parentId);
}
if (url.host === 'cookieJar.getOrCreateForWorkspace'.toLowerCase()) {
result = await models.cookieJar.getOrCreateForParentId(body.id);
}
if (url.host === 'response.getLatestForRequestId'.toLowerCase()) {
result = await models.response.getLatestForRequest(body.requestId, body.environmentId);
}
if (url.host === 'response.getBodyBuffer'.toLowerCase()) {
result = await models.response.getBodyBuffer(body.response, body.readFailureValue);
}
return new Response(JSON.stringify(result));
};

View File

@@ -56,7 +56,6 @@ export function init(): BaseSettings {
maxRedirects: 10,
maxTimelineDataSizeKB: 10,
pluginNodeExtraCerts: '',
pluginsAllowElevatedAccess: false,
noProxy: '',
nunjucksPowerUserMode: false,
pluginConfig: {},

View File

@@ -34,7 +34,6 @@ import type { WebSocketRequest } from '../models/websocket-request';
import { isWorkspace, type Workspace } from '../models/workspace';
import * as pluginContexts from '../plugins/context/index';
import * as plugins from '../plugins/index';
import { RenderError } from '../templating/render-error';
import { maskOrDecryptContextIfNecessary } from '../templating/utils';
import { defaultSendActionRuntime, type SendActionRuntime } from '../ui/routes/request';
import { invariant } from '../utils/invariant';
@@ -631,7 +630,7 @@ export const tryToInterpolateRequest = async ({
ignoreUndefinedEnvVariable,
});
} catch (err) {
if (err instanceof RenderError) {
if ('type' in err && err.type === 'render') {
throw err;
}
throw new Error(`Failed to render request: ${request._id}`);

View File

@@ -1,6 +1,5 @@
import { describe, expect, it } from 'vitest';
import { extractUndefinedVariableKey } from '../render-error';
import * as utils from '../utils';
describe('forceBracketNotation()', () => {
@@ -340,9 +339,9 @@ describe('decodeEncoding()', () => {
describe('extractUndefinedVariableKey()', () => {
it('extract nunjucks variable key', () => {
expect(extractUndefinedVariableKey('{{name}}', {})).toEqual(['name']);
expect(extractUndefinedVariableKey('{{name}}', { name: '' })).toEqual([]);
expect(extractUndefinedVariableKey('aaaaaa{{a}}{{b}}{{c}}', { a: 1 })).toEqual(['b', 'c']);
expect(extractUndefinedVariableKey('{{a.b}}\n\n{{c}} {{d}}', { a: { b: 1 } })).toEqual(['c', 'd']);
expect(utils.extractUndefinedVariableKey('{{name}}', {})).toEqual(['name']);
expect(utils.extractUndefinedVariableKey('{{name}}', { name: '' })).toEqual([]);
expect(utils.extractUndefinedVariableKey('aaaaaa{{a}}{{b}}{{c}}', { a: 1 })).toEqual(['b', 'c']);
expect(utils.extractUndefinedVariableKey('{{a.b}}\n\n{{c}} {{d}}', { a: { b: 1 } })).toEqual(['c', 'd']);
});
});

View File

@@ -1,228 +0,0 @@
import type { PluginTemplateTag } from './extensions';
import * as templating from './worker';
export function decodeEncoding<T>(value: T) {
if (typeof value !== 'string') {
return value;
}
const results = value.match(/^b64::(.+)::46b$/);
if (results) {
return Buffer.from(results[1], 'base64').toString('utf8');
}
return value;
}
const EMPTY_ARG = '__EMPTY_NUNJUCKS_ARG__';
export interface HelperContext {
context: any;
meta: any;
renderPurpose: any;
util: any;
}
export default class BaseExtension {
_ext: PluginTemplateTag | null = null;
_plugin: Plugin | null = null;
tags: PluginTemplateTag['name'][] = [];
constructor(ext: PluginTemplateTag, plugin: Plugin) {
this._ext = ext;
this._plugin = plugin;
const tag = this.getTag();
this.tags = [
...(tag === null ? [] : [tag]),
];
}
getTag() {
return this._ext?.name || null;
}
getPriority() {
return this._ext?.priority || -1;
}
getName() {
return typeof this._ext?.displayName === 'string' ? this._ext?.displayName : this.getTag();
}
getDescription() {
return this._ext?.description || 'no description';
}
getLiveDisplayName() {
return (
// @ts-expect-error -- TSCONVERSION
this._ext?.liveDisplayName ||
(() => '')
);
}
getDisablePreview() {
return this._ext?.disablePreview || (() => false);
}
getArgs() {
return this._ext?.args || [];
}
getActions() {
return this._ext?.actions || [];
}
isDeprecated() {
return this._ext?.deprecated || false;
}
run(...args: any[]) {
// @ts-expect-error -- TSCONVERSION
return this._ext?.run(...args);
}
parse(parser: any, nodes: any, lexer: any) {
const tok = parser.nextToken();
let args;
if (parser.peekToken().type !== lexer.TOKEN_BLOCK_END) {
args = parser.parseSignature(null, true);
} else {
// Not sure why this is needed, but it fails without it
args = new nodes.NodeList(tok.lineno, tok.colno);
args.addChild(new nodes.Literal(0, 0, EMPTY_ARG));
}
parser.advanceAfterBlockEnd(tok.value);
return new nodes.CallExtensionAsync(this, 'asyncRun', args);
}
asyncRun({ ctx: renderContext }: any, ...runArgs: any[]) {
// Pull the callback off the end
const callback = runArgs[runArgs.length - 1];
// Pull out the meta helper
const renderMeta = renderContext.getMeta ? renderContext.getMeta() : {};
// Pull out the purpose
const renderPurpose = renderContext.getPurpose ? renderContext.getPurpose() : null;
// Extract the rest of the args
const args = runArgs
.slice(0, runArgs.length - 1)
.filter(a => a !== EMPTY_ARG)
.map(decodeEncoding);
// Define a helper context with utils
const helperContext: HelperContext = {
// ...pluginContexts.app.init(renderPurpose),
// ...pluginContexts.store.init(this._plugin),
// ...pluginContexts.network.init(),
context: renderContext,
meta: renderMeta,
renderPurpose,
util: {
render: (str: string) =>
templating.render(str, {
context: renderContext,
}),
models: {
request: {
getById: async (id: string) => {
const resp = await fetch('insomnia-templating-worker-database://request.getById', {
method: 'post',
body: JSON.stringify({ id }),
});
const req = await resp.json();
return req;
},
getAncestors: async (request: any) => {
const resp = await fetch('insomnia-templating-worker-database://request.getAncestors', {
method: 'post',
body: JSON.stringify({ request, types: ['RequestGroup', 'Workspace'] }),
});
const ancestors = await resp.json();
return ancestors;
},
},
workspace: {
getById: async (id: string) => {
const resp = await fetch('insomnia-templating-worker-database://workspace.getById', {
method: 'post',
body: JSON.stringify({ id }),
});
const workspace = await resp.json();
return workspace;
},
},
oAuth2Token: {
getByRequestId: async (parentId: string) => {
const resp = await fetch('insomnia-templating-worker-database://oAuth2Token.getByRequestId', {
method: 'post',
body: JSON.stringify({ parentId }),
});
const oAuth2Token = await resp.json();
return oAuth2Token;
},
},
cookieJar: {
getOrCreateForWorkspace: async (workspace: any) => {
const resp = await fetch('insomnia-templating-worker-database://cookieJar.getOrCreateForParentId', {
method: 'post',
body: JSON.stringify({ parentId: workspace._id }),
});
const cookieJar = await resp.json();
return cookieJar;
},
},
response: {
getLatestForRequestId: async (requestId: string, environmentId: string | null) => {
const resp = await fetch('insomnia-templating-worker-database://response.getLatestForRequestId', {
method: 'post',
body: JSON.stringify({ requestId, environmentId }),
});
const latest = await resp.json();
return latest;
},
getBodyBuffer: async (response?: { bodyPath?: string; bodyCompression?: 'zip' | null | '__NEEDS_MIGRATION__' | undefined },
readFailureValue?: string) => {
const resp = await fetch('insomnia-templating-worker-database://response.getBodyBuffer', {
method: 'post',
body: JSON.stringify({ response, readFailureValue }),
});
const buffer = await resp.json();
return buffer;
},
},
},
},
};
let result;
try {
result = this.run(helperContext, ...args);
} catch (err) {
// Catch sync errors
callback(err);
return;
}
// FIX THIS: this is throwing unhandled exceptions
// If the result is a promise, resolve it async
if (result instanceof Promise) {
result
.then(r => {
callback(null, r);
})
.catch(err => {
callback(err);
});
return;
}
// If the result is not a Promise, return it synchronously
callback(null, result);
}
}

View File

@@ -49,7 +49,9 @@ export default class BaseExtension {
return (
// @ts-expect-error -- TSCONVERSION
this._ext?.liveDisplayName ||
(() => '')
function() {
return '';
}
);
}

View File

@@ -1,9 +1,34 @@
import type { Environment } from 'nunjucks';
import { Environment } from 'nunjucks';
import nunjucks from 'nunjucks/browser/nunjucks';
import * as plugins from '../plugins/index';
import { localTemplateTags } from '../ui/components/templating/local-template-tags';
import BaseExtension from './base-extension';
import { extractUndefinedVariableKey, RenderError } from './render-error';
import { extractUndefinedVariableKey, type NunjucksParsedTag } from './utils';
export enum RenderErrorSubType {
EnvironmentVariable = 'environmentVariable'
}
export class RenderError extends Error {
// TODO: unsound definite assignment assertions
// This is easy to fix, but be careful: extending from Error has especially tricky behavior.
message!: string;
path!: string | null;
location!: {
line: number;
column: number;
};
type!: string;
reason!: string;
extraInfo?: Record<string, any>;
constructor(message: string) {
super(message);
this.message = message;
}
}
// Some constants
export const RENDER_ALL = 'all';
@@ -12,7 +37,7 @@ export const RENDER_TAGS = 'tags';
export const NUNJUCKS_TEMPLATE_GLOBAL_PROPERTY_NAME = '_';
type NunjucksEnvironment = Environment & {
extensions: Record<string, any>;
extensions: Record<string, BaseExtension>;
};
// Cached globals
@@ -56,38 +81,39 @@ export function render(
const nj = await getNunjucks(renderMode, config.ignoreUndefinedEnvVariable);
nj?.renderString(text, templatingContext, (err: Error | null, result: any) => {
clearTimeout(id);
if (!err) {
return resolve(result);
}
console.warn('[templating] Error rendering template', err);
const sanitizedMsg = err.message
.replace(/\(unknown path\)\s/, '')
.replace(/\[Line \d+, Column \d*]/, '')
.replace(/^\s*Error:\s*/, '')
.trim();
const location = err.message.match(/\[Line (\d+), Column (\d+)*]/);
const line = location ? parseInt(location[1]) : 1;
const column = location ? parseInt(location[2]) : 1;
const reason = err.message.includes('attempted to output null or undefined value')
? 'undefined'
: 'error';
const newError = new RenderError(sanitizedMsg);
newError.path = path || '';
newError.message = sanitizedMsg;
newError.location = {
line,
column,
};
newError.type = 'render';
newError.reason = reason;
// regard as environment variable missing
if (hasNunjucksInterpolationSymbols && reason === 'undefined') {
newError.extraInfo = {
subType: 'environmentVariable',
undefinedEnvironmentVariables: extractUndefinedVariableKey(text, templatingContext),
if (err) {
console.warn('[templating] Error rendering template', err);
const sanitizedMsg = err.message
.replace(/\(unknown path\)\s/, '')
.replace(/\[Line \d+, Column \d*]/, '')
.replace(/^\s*Error:\s*/, '')
.trim();
const location = err.message.match(/\[Line (\d+), Column (\d+)*]/);
const line = location ? parseInt(location[1]) : 1;
const column = location ? parseInt(location[2]) : 1;
const reason = err.message.includes('attempted to output null or undefined value')
? 'undefined'
: 'error';
const newError = new RenderError(sanitizedMsg);
newError.path = path || '';
newError.message = sanitizedMsg;
newError.location = {
line,
column,
};
newError.type = 'render';
newError.reason = reason;
// regard as environment variable missing
if (hasNunjucksInterpolationSymbols && reason === 'undefined') {
newError.extraInfo = {
subType: RenderErrorSubType.EnvironmentVariable,
undefinedEnvironmentVariables: extractUndefinedVariableKey(text, templatingContext),
};
}
reject(newError);
} else {
resolve(result);
}
reject(newError);
});
});
}
@@ -111,7 +137,7 @@ export async function getTagDefinitions() {
.map(k => env.extensions[k])
.filter(ext => !ext.isDeprecated())
.sort((a, b) => (a.getPriority() > b.getPriority() ? 1 : -1))
.map(ext => ({
.map<NunjucksParsedTag>(ext => ({
name: ext.getTag() || '',
displayName: ext.getName() || '',
liveDisplayName: ext.getLiveDisplayName(),
@@ -172,20 +198,15 @@ async function getNunjucks(renderMode: string, ignoreUndefinedEnvVariable?: bool
// Create Env with Extensions //
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ //
const nunjucksEnvironment = nunjucks.configure(config) as NunjucksEnvironment;
const pluginTemplateTags = await (await import('../plugins')).getTemplateTags();
const allExtensions = [
...localTemplateTags,
const pluginTemplateTags = await plugins.getTemplateTags();
// Spread after local tags to allow plugins to override them.
// TODO: Determine if this is in fact the behavior we've explicitly decided to support.
...pluginTemplateTags,
];
const allExtensions = [...pluginTemplateTags, ...localTemplateTags];
for (const extension of allExtensions) {
const { templateTag, plugin } = extension;
templateTag.priority = templateTag.priority || allExtensions.indexOf(extension);
// @ts-expect-error -- TODO
// @ts-expect-error -- TSCONVERSION
const instance = new BaseExtension(templateTag, plugin);
nunjucksEnvironment.addExtension(instance.getTag() || '', instance);
// Hidden helper filter to debug complicated things

View File

@@ -1,40 +0,0 @@
import { get as _get } from 'lodash';
export class RenderError extends Error {
// TODO: unsound definite assignment assertions
// This is easy to fix, but be careful: extending from Error has especially tricky behavior.
message!: string;
path!: string | null;
location!: {
line: number;
column: number;
};
type!: string;
reason!: string;
extraInfo?: { subType: 'environmentVariable'; undefinedEnvironmentVariables: string[] };
constructor(message: string) {
super(message);
this.message = message;
}
}
// because nunjucks only report the first error, we need to extract all missing variables that are not present in the context
// for example, if the text is `{{ a }} {{ b }}`, nunjucks only report `a` is missing, but we need to report both `a` and `b`
export function extractUndefinedVariableKey(text: string = '', templatingContext: Record<string, any>): string[] {
const regexVariable = /{{\s*([^ }]+)\s*}}/g;
const missingVariables: string[] = [];
let match;
while ((match = regexVariable.exec(text)) !== null) {
let variable = match[1];
if (variable.includes('_.')) {
variable = variable.split('_.')[1];
}
// Check if the variable is not present in the context
if (_get(templatingContext, variable) === undefined) {
missingVariables.push(variable);
}
}
return missingVariables;
}

View File

@@ -1,4 +1,5 @@
import type { EditorFromTextArea, MarkerRange } from 'codemirror';
import _ from 'lodash';
import type { RenderPurpose } from '../common/render';
import type { BaseModel } from '../models';
@@ -287,6 +288,26 @@ export function decodeEncoding<T>(value: T) {
return value;
}
// because nunjucks only report the first error, we need to extract all missing variables that are not present in the context
// for example, if the text is `{{ a }} {{ b }}`, nunjucks only report `a` is missing, but we need to report both `a` and `b`
export function extractUndefinedVariableKey(text: string = '', templatingContext: Record<string, any>): string[] {
const regexVariable = /{{\s*([^ }]+)\s*}}/g;
const missingVariables: string[] = [];
let match;
while ((match = regexVariable.exec(text)) !== null) {
let variable = match[1];
if (variable.includes('_.')) {
variable = variable.split('_.')[1];
}
// Check if the variable is not present in the context
if (_.get(templatingContext, variable) === undefined) {
missingVariables.push(variable);
}
}
return missingVariables;
}
export async function maskOrDecryptContextIfNecessary(context: Record<string, any> & { getPurpose: () => RenderPurpose | undefined }) {
// all secret variables are under vaultEnvironmentPath property in context
const vaultEnvironmentData = context[vaultEnvironmentPath];

View File

@@ -1,207 +0,0 @@
import type { Environment } from 'nunjucks';
import nunjucks from 'nunjucks/browser/nunjucks';
import { localTemplateTags } from '../ui/components/templating/local-template-tags';
import BaseExtensionWorker from './base-extension-worker';
import { extractUndefinedVariableKey, RenderError } from './render-error';
// Some constants
export const RENDER_ALL = 'all';
export const RENDER_VARS = 'variables';
export const RENDER_TAGS = 'tags';
export const NUNJUCKS_TEMPLATE_GLOBAL_PROPERTY_NAME = '_';
type NunjucksEnvironment = Environment & {
extensions: Record<string, any>;
};
// Cached globals
let nunjucksVariablesOnly: NunjucksEnvironment | null = null;
let nunjucksTagsOnly: NunjucksEnvironment | null = null;
let nunjucksAll: NunjucksEnvironment | null = null;
/**
* Render text based on stuff
* @param {String} text - Nunjucks template in text form
* @param {Object} [config] - Config options for rendering
* @param {Object} [config.context] - Context to render with
* @param {Object} [config.path] - Path to include in the error message
* @param {Object} [config.renderMode] - Only render variables (not tags)
*/
export function render(
text: string,
config: {
context?: Record<string, any>;
path?: string;
renderMode?: string;
ignoreUndefinedEnvVariable?: boolean;
} = {},
) {
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 || {};
// context needs to exist on the root for the old templating syntax, and in _ for the new templating syntax
// old: {{ arr[0].prop }}
// new: {{ _['arr-name-with-dash'][0].prop }}
const templatingContext = { ...context, [NUNJUCKS_TEMPLATE_GLOBAL_PROPERTY_NAME]: context };
const path = config.path || null;
const renderMode = config.renderMode || RENDER_ALL;
return new Promise<string | null>(async (resolve, reject) => {
// NOTE: this is added as a breadcrumb because renderString sometimes hangs
const id = setTimeout(() => console.log('[templating] Warning: nunjucks failed to respond within 5 seconds'), 5000);
const nj = await getNunjucks(renderMode, config.ignoreUndefinedEnvVariable);
nj?.renderString(text, templatingContext, (err: Error | null, result: any) => {
clearTimeout(id);
if (!err) {
return resolve(result);
}
console.warn('[templating] Error rendering template', err);
const sanitizedMsg = err.message
.replace(/\(unknown path\)\s/, '')
.replace(/\[Line \d+, Column \d*]/, '')
.replace(/^\s*Error:\s*/, '')
.trim();
const location = err.message.match(/\[Line (\d+), Column (\d+)*]/);
const line = location ? parseInt(location[1]) : 1;
const column = location ? parseInt(location[2]) : 1;
const reason = err.message.includes('attempted to output null or undefined value')
? 'undefined'
: 'error';
const newError = new RenderError(sanitizedMsg);
newError.path = path || '';
newError.message = sanitizedMsg;
newError.location = {
line,
column,
};
newError.type = 'render';
newError.reason = reason;
// regard as environment variable missing
if (hasNunjucksInterpolationSymbols && reason === 'undefined') {
newError.extraInfo = {
subType: 'environmentVariable',
undefinedEnvironmentVariables: extractUndefinedVariableKey(text, templatingContext),
};
}
reject(newError);
});
});
}
/**
* Reload Nunjucks environments. Useful for if plugins change.
*/
export function reload() {
nunjucksAll = null;
nunjucksVariablesOnly = null;
nunjucksTagsOnly = null;
}
/**
* Get definitions of template tags
*/
export async function getTagDefinitions() {
const env = await getNunjucks(RENDER_ALL);
return Object.keys(env.extensions)
.map(k => env.extensions[k])
.filter(ext => !ext.isDeprecated())
.sort((a, b) => (a.getPriority() > b.getPriority() ? 1 : -1))
.map(ext => ({
name: ext.getTag() || '',
displayName: ext.getName() || '',
liveDisplayName: ext.getLiveDisplayName(),
description: ext.getDescription(),
disablePreview: ext.getDisablePreview(),
args: ext.getArgs(),
actions: ext.getActions(),
}));
}
async function getNunjucks(renderMode: string, ignoreUndefinedEnvVariable?: boolean): Promise<NunjucksEnvironment> {
let throwOnUndefined = true;
if (ignoreUndefinedEnvVariable) {
throwOnUndefined = false;
} else {
if (renderMode === RENDER_VARS && nunjucksVariablesOnly) {
return nunjucksVariablesOnly;
}
if (renderMode === RENDER_TAGS && nunjucksTagsOnly) {
return nunjucksTagsOnly;
}
if (renderMode === RENDER_ALL && nunjucksAll) {
return nunjucksAll;
}
}
// ~~~~~~~~~~~~ //
// Setup Config //
// ~~~~~~~~~~~~ //
const config = {
autoescape: false,
// Don't escape HTML
throwOnUndefined,
// Strict mode
tags: {
blockStart: '{%',
blockEnd: '%}',
variableStart: '{{',
variableEnd: '}}',
commentStart: '{#',
commentEnd: '#}',
},
};
if (renderMode === RENDER_VARS) {
// Set tag syntax to something that will never happen naturally
config.tags.blockStart = '<[{[{[{[{[$%';
config.tags.blockEnd = '%$]}]}]}]}]>';
}
if (renderMode === RENDER_TAGS) {
// Set tag syntax to something that will never happen naturally
config.tags.variableStart = '<[{[{[{[{[$%';
config.tags.variableEnd = '%$]}]}]}]}]>';
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ //
// Create Env with Extensions //
// ~~~~~~~~~~~~~~~~~~~~~~~~~~ //
const nunjucksEnvironment = nunjucks.configure(config) as NunjucksEnvironment;
const allExtensions = [
...localTemplateTags,
];
for (const extension of allExtensions) {
const { templateTag, plugin } = extension;
templateTag.priority = templateTag.priority || allExtensions.indexOf(extension);
// @ts-expect-error -- TODO
const instance = new BaseExtensionWorker(templateTag, plugin);
nunjucksEnvironment.addExtension(instance.getTag() || '', instance);
// Hidden helper filter to debug complicated things
// eg. `{{ foo | urlencode | debug | upper }}`
nunjucksEnvironment.addFilter('debug', (o: any) => o);
}
// ~~~~~~~~~~~~~~~~~~~~ //
// Cache Env and Return (when ignoreUndefinedEnvVariable is false) //
// ~~~~~~~~~~~~~~~~~~~~ //
if (ignoreUndefinedEnvVariable) {
return nunjucksEnvironment;
}
if (renderMode === RENDER_VARS) {
nunjucksVariablesOnly = nunjucksEnvironment;
} else if (renderMode === RENDER_TAGS) {
nunjucksTagsOnly = nunjucksEnvironment;
} else {
nunjucksAll = nunjucksEnvironment;
}
return nunjucksEnvironment;
}

View File

@@ -482,9 +482,7 @@ export const CodeEditor = memo(forwardRef<CodeEditorHandle, CodeEditorProps>(({
codeMirror.current.foldCode(from, to);
}
}
// settings.pluginsAllowElevatedAccess is not used here but we want to trigger this effect when it changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hideGutters, hideLineNumbers, placeholder, settings.editorLineWrapping, settings.editorKeyMap, settings.hotKeyRegistry, settings.autocompleteDelay, settings.nunjucksPowerUserMode, settings.pluginsAllowElevatedAccess, settings.showVariableSourceAndValue, noLint, readOnly, noMatchBrackets, indentSize, hintOptions, infoOptions, dynamicHeight, jumpOptions, noStyleActiveLine, indentWithTabs, extraKeys, handleRender, mode, getAutocompleteConstants, getAutocompleteSnippets, persistState, maybePrettifyAndSetValue, defaultValue, filter, onClickLink, uniquenessKey, handleGetRenderContext, pinToBottom, onPaste, id]);
}, [hideGutters, hideLineNumbers, placeholder, settings.editorLineWrapping, settings.editorKeyMap, settings.hotKeyRegistry, settings.autocompleteDelay, settings.nunjucksPowerUserMode, settings.showVariableSourceAndValue, noLint, readOnly, noMatchBrackets, indentSize, hintOptions, infoOptions, dynamicHeight, jumpOptions, noStyleActiveLine, indentWithTabs, extraKeys, handleRender, mode, getAutocompleteConstants, getAutocompleteSnippets, persistState, maybePrettifyAndSetValue, defaultValue, filter, onClickLink, uniquenessKey, handleGetRenderContext, pinToBottom, onPaste, id]);
const cleanUpEditor = useCallback(() => {
codeMirror.current?.toTextArea();

View File

@@ -130,7 +130,6 @@ async function _highlightNunjucksTags(this: CodeMirror.Editor, render: HandleRen
el.setAttribute('draggable', 'true');
el.setAttribute('data-error', 'off');
el.setAttribute('data-template', tok.string);
el.innerHTML = '<label></label>' + tok.string;
const mark = this.markText(start, end, {
// @ts-expect-error not a known property of TextMarkerOptions
__nunjucks: true,
@@ -275,7 +274,7 @@ async function _updateElementText(
showVariableSourceAndValue: boolean
) {
const el = mark.replacedWith!;
let innerHTML = text;
let innerHTML = '';
let title = '';
let dataIgnore = '';
let dataError = '';
@@ -295,6 +294,7 @@ async function _updateElementText(
if (tagDefinition) {
// Try rendering these so we can show errors if needed
// @ts-expect-error -- TSCONVERSION
const liveDisplayName = tagDefinition.liveDisplayName(tagData.args);
const firstArg = tagDefinition.args[0];
@@ -304,6 +304,7 @@ async function _updateElementText(
const argData = tagData.args[0];
// @ts-expect-error -- TSCONVERSION
const foundOption = firstArg.options.find(d => d.value === argData.value);
// @ts-expect-error -- TSCONVERSION
const option = foundOption || firstArg.options[0];
innerHTML = `${tagDefinition.displayName} &rArr; ${option.displayName}`;
} else {
@@ -311,6 +312,7 @@ async function _updateElementText(
}
const preview = await render(text);
// @ts-expect-error -- TSCONVERSION
title = tagDefinition.disablePreview(tagData.args) ? preview.replace(/./g, '*') : preview;
} else {
innerHTML = cleanedStr;

View File

@@ -211,9 +211,7 @@ export const OneLineEditor = forwardRef<OneLineEditorHandle, OneLineEditorProps>
id,
);
}
// settings.pluginsAllowElevatedAccess is not used here but we want to trigger this effect when it changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultValue, getAutocompleteConstants, handleGetRenderContext, handleRender, onBlur, onKeyDown, onPaste, placeholder, readOnly, settings.autocompleteDelay, getKeyMap, settings.hotKeyRegistry, settings.nunjucksPowerUserMode, settings.pluginsAllowElevatedAccess, settings.showVariableSourceAndValue, eventListeners, id]);
}, [defaultValue, getAutocompleteConstants, handleGetRenderContext, handleRender, onBlur, onKeyDown, onPaste, placeholder, readOnly, settings.autocompleteDelay, getKeyMap, settings.hotKeyRegistry, settings.nunjucksPowerUserMode, settings.showVariableSourceAndValue, eventListeners, id]);
const cleanUpEditor = useCallback(() => {
codeMirror.current?.toTextArea();

View File

@@ -5,7 +5,7 @@ import { docsTemplateTags } from '../../../common/documentation';
import type { GrpcRequest } from '../../../models/grpc-request';
import type { Request } from '../../../models/request';
import type { WebSocketRequest } from '../../../models/websocket-request';
import { RenderError } from '../../../templating/render-error';
import { RenderError } from '../../../templating';
import { Link } from '../base/link';
import { Modal, type ModalHandle, type ModalProps } from '../base/modal';
import { ModalBody } from '../base/modal-body';

View File

@@ -15,7 +15,6 @@ import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspa
import { isRequestGroup, type RequestGroup } from '../../../models/request-group';
import { getOrInheritHeaders } from '../../../network/network';
import { urlMatchesCertHost } from '../../../network/url-matches-cert-host';
import { RenderError } from '../../../templating/render-error';
import { getGrpcConnectionErrorDetails } from '../../../utils/grpc';
import { tryToInterpolateRequestOrShowRenderErrorModal } from '../../../utils/try-interpolate';
import { setDefaultProtocol } from '../../../utils/url/protocol';
@@ -145,7 +144,7 @@ export const GrpcRequestPane: FunctionComponent<Props> = ({
error: undefined,
});
} catch (err) {
if (err instanceof RenderError) {
if (err.type === 'render') {
showModal(RequestRenderErrorModal, {
request: activeRequest,
error: err,

View File

@@ -20,7 +20,6 @@ import { HelpTooltip } from '../help-tooltip';
import { Icon } from '../icon';
import { showAlert, showPrompt } from '../modals';
import { Tooltip } from '../tooltip';
import { BooleanSetting } from './boolean-setting';
interface State {
plugins: Plugin[];
npmPluginValue: string;
@@ -77,19 +76,6 @@ export const Plugins: FC = () => {
Plugins is still an experimental feature. See{' '}
<Link href={docsPlugins}>Documentation</Link> for more info.
</p>
<div className="notice warning text-left margin-bottom">
<div className="selectable force-pre-wrap">
<p>
Plugins with elevated access can access anything Insomnia can. It's recommended that elevated access remain disabled.
</p>
<BooleanSetting
label="Allow elevated access for plugins"
setting="pluginsAllowElevatedAccess"
/>
</div>
</div>
{plugins.length === 0 ? (
<div className="text-center faint italic pad">No Plugins Added</div>
) : (

View File

@@ -745,7 +745,7 @@ const localTemplatePlugins: { templateTag: PluginTemplateTag }[] = [
}
const sanitizedFilter = filter.trim();
const bodyBuffer = await context.util.models.response.getBodyBuffer(response, '');
const bodyBuffer = context.util.models.response.getBodyBuffer(response, '');
const match = response.contentType && response.contentType.match(/charset=([\w-]+)/);
const charset = match && match.length >= 2 ? match[1] : 'utf-8';
if (field === 'url') {

View File

@@ -10,7 +10,6 @@ import type { Environment } from '../../../models/environment';
import { type AuthTypes, getCombinedPathParametersFromUrl, type RequestPathParameter } from '../../../models/request';
import type { WebSocketRequest } from '../../../models/websocket-request';
import { getAuthObjectOrNull } from '../../../network/authentication';
import { RenderError } from '../../../templating/render-error';
import { tryToInterpolateRequestOrShowRenderErrorModal } from '../../../utils/try-interpolate';
import { buildQueryStringFromParams, deconstructQueryStringToParams, extractQueryStringFromUrl, joinUrlAndQueryString } from '../../../utils/url/querystring';
import { useReadyState } from '../../hooks/use-ready-state';
@@ -108,7 +107,7 @@ const WebSocketRequestForm: FC<FormProps> = ({
}
window.main.webSocket.event.send({ requestId: request._id, payload: renderedMessage });
} catch (err) {
if (err instanceof RenderError) {
if (err.type === 'render') {
showModal(RequestRenderErrorModal, {
request,
error: err,

View File

@@ -30,7 +30,7 @@ import { VCSInstance } from '../../sync/vcs/insomnia-sync';
import { insomniaFetch } from '../../ui/insomniaFetch';
import { invariant } from '../../utils/invariant';
import { SegmentEvent } from '../analytics';
import { SpectralRunner } from '../worker/spectral-handler';
import { SpectralRunner } from '../worker/spectral-run';
// Project
export const createNewProjectAction: ActionFunction = async ({ request, params }) => {

View File

@@ -75,7 +75,7 @@ import {
useActiveApiSpecSyncVCSVersion,
useGitVCSVersion,
} from '../hooks/use-vcs-version';
import { SpectralRunner } from '../worker/spectral-handler';
import { SpectralRunner } from '../worker/spectral-run';
import { useRootLoaderData } from './root';
import type { WorkspaceLoaderData } from './workspace';

View File

@@ -35,6 +35,7 @@ import { isWebSocketRequest, isWebSocketRequestId, type WebSocketRequest } from
import { isWebSocketResponse, type WebSocketResponse } from '../../models/websocket-response';
import { getAuthHeader } from '../../network/authentication';
import { fetchRequestData, responseTransform, sendCurlAndWriteTimeline, tryToExecuteAfterResponseScript, tryToExecutePreRequestScript, tryToInterpolateRequest, tryToTransformRequestWithPlugins } from '../../network/network';
import { RenderErrorSubType } from '../../templating';
import { parseGraphQLReqeustBody } from '../../utils/graph-ql';
import { invariant } from '../../utils/invariant';
import { SegmentEvent } from '../analytics';
@@ -448,7 +449,7 @@ export const sendAction: ActionFunction = async ({ request, params }) => {
} else {
// if the error is not from response, we need to set it to url param and show it in modal
url.searchParams.set('error', e);
if (e?.extraInfo && e?.extraInfo?.subType === 'environmentVariable') {
if (e?.extraInfo && e?.extraInfo?.subType === RenderErrorSubType.EnvironmentVariable) {
url.searchParams.set('envVariableMissing', '1');
url.searchParams.set('undefinedEnvironmentVariables', e?.extraInfo?.undefinedEnvironmentVariables);
}

View File

@@ -260,8 +260,7 @@ export const workspaceLoader: LoaderFunction = async ({
}
const userSession = await models.userSession.getOrCreate();
const isLoggedinIsCloudProjectAndIsNotGitRepo = userSession.id && activeProject.remoteId && !gitRepository;
if (isLoggedinIsCloudProjectAndIsNotGitRepo) {
if (userSession.id && !gitRepository) {
try {
const vcs = VCSInstance();
await vcs.switchAndCreateBackendProjectIfNotExist(workspaceId, activeWorkspace.name);

View File

@@ -1,16 +1,19 @@
import type { ISpectralDiagnostic } from '@stoplight/spectral-core';
import type { SpectralResponse } from './spectral-worker';
import type { SpectralResponse } from './spectral';
export class SpectralRunner {
private worker: Worker;
private taskId = 0;
constructor() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- see below
// @ts-ignore -- inso transpiles to commonjs so doesn't play nice with this
this.worker = new Worker(new URL('./spectral-worker.ts', import.meta.url), { type: 'module' });
// @INFO: This is only for inso cli since it ends up in the import map and gets type-checked against a node environment
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.worker = new Worker(new URL('./spectral.ts', import.meta.url), {
type: 'module',
});
}
terminate() {

View File

@@ -1,50 +0,0 @@
import { extractUndefinedVariableKey, RenderError } from '../../templating/render-error';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- see below
// @ts-ignore -- inso transpiles to commonjs so doesn't play nice with this
const worker = new Worker(new URL('./templating-worker.ts', import.meta.url), { type: 'module' });
// Triggered by a mistake in the work initialization code above
worker.addEventListener('error', event => {
console.error('Error from worker:', event.message);
});
export function renderInWorker({ input, context, path, ignoreUndefinedEnvVariable }: { input: string; context: Record<string, any>; path: string; ignoreUndefinedEnvVariable: boolean }): Promise<string> {
const newContext = {
...context,
serializedFunctions: {
requestId: context.getMeta().requestId,
workspaceId: context.getMeta().workspaceId,
environmentId: context.getEnvironmentId(),
extraInfo: context.getExtraInfo(),
globalEnvironmentId: context.getGlobalEnvironmentId(),
keysContext: context.getKeysContext(),
projectId: context.getProjectId(),
purpose: context.getPurpose(),
},
};
// Id to avoid race conditions
const id = window.crypto.randomUUID();
const payloadWithHash = JSON.stringify({ id, input, context: newContext, path, ignoreUndefinedEnvVariable });
worker.postMessage(payloadWithHash);
return new Promise((resolve, reject) => {
const messageHandler = (event: MessageEvent) => {
if (event.data.id === id) {
worker.removeEventListener('message', messageHandler);
if (event.data.err) {
const error = new RenderError(event.data.err);
error.type = 'render';
error.extraInfo = {
subType: 'environmentVariable',
undefinedEnvironmentVariables: extractUndefinedVariableKey(input, newContext),
};
return reject(error);
}
return resolve(event.data.result);
}
};
worker.addEventListener('message', messageHandler);
});
}

View File

@@ -1,32 +0,0 @@
import * as templating from '../../templating/worker';
const originalRequire = self.require;
const interceptor: any = (moduleName: string): NodeRequire => {
const allowList = ['crypto', 'date-fns', 'fs', 'iconv-lite', 'jsonpath-plus', 'os', 'tough-cookie', 'uuid'];
if (allowList.includes(moduleName)) {
return originalRequire(moduleName);
}
throw new Error(`Cannot find module '${moduleName}', untrusted modules are not available in protected mode, this can be enabled in plugin settings`);
};
async function performJob(input: { input: string; context: Record<string, any>; path: string; ignoreUndefinedEnvVariable: boolean }) {
self.require = interceptor;
return templating.render(input.input, { context: input.context, path: input.path, ignoreUndefinedEnvVariable: input.ignoreUndefinedEnvVariable });
}
// Listen for messages from the main thread
self.onmessage = async event => {
const { id, input, context, path, ignoreUndefinedEnvVariable } = JSON.parse(event.data);
try {
context.getMeta = () => ({ requestId: context.serializedFunctions.requestId, workspaceId: context.serializedFunctions.workspaceId });
context.getEnvironmentId = () => context.serializedFunctions.environmentId;
context.getExtraInfo = () => context.serializedFunctions.extraInfo;
context.getGlobalEnvironmentId = () => context.serializedFunctions.globalEnvironmentId;
context.getKeysContext = () => context.serializedFunctions.keysContext;
context.getProjectId = () => context.serializedFunctions.projectId;
context.getPurpose = () => context.serializedFunctions.purpose;
const result = await performJob({ input, context, path, ignoreUndefinedEnvVariable });
self.postMessage({ id, result });
} catch (err) {
self.postMessage({ id, err });
}
};

View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@ import { getRenderContext, render } from '../common/render';
import type { GrpcRequest } from '../models/grpc-request';
import type { Request } from '../models/request';
import type { WebSocketRequest } from '../models/websocket-request';
import { RenderError } from '../templating/render-error';
import { showModal } from '../ui/components/modals';
import { RequestRenderErrorModal } from '../ui/components/modals/request-render-error-modal';
@@ -12,7 +11,7 @@ export const tryToInterpolateRequestOrShowRenderErrorModal = async ({ request, e
const renderContext = await getRenderContext({ request, environment: environmentId, purpose: 'send' });
return await render(payload, renderContext);
} catch (error) {
if (error instanceof RenderError) {
if (error.type === 'render') {
showModal(RequestRenderErrorModal, { request, error });
return;
}

View File

@@ -60,7 +60,6 @@ export default defineConfig(({ mode }) => {
react(),
],
worker: {
format: 'es',
plugins: () => [
electronNodeRequire({
modules: ['fs'],