mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-20 22:27:24 -04:00
Revert "move default templating into a web worker (#8447)"
This reverts commit 3385bc5719.
This commit is contained in:
@@ -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 => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -137,7 +137,6 @@ export interface Settings {
|
||||
pluginConfig: PluginConfigMap;
|
||||
pluginNodeExtraCerts: string;
|
||||
pluginPath: string;
|
||||
pluginsAllowElevatedAccess: boolean;
|
||||
preferredHttpVersion: HttpVersion;
|
||||
proxyEnabled: boolean;
|
||||
showPasswords: boolean;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
'self'
|
||||
data:
|
||||
insomnia-event-source:
|
||||
insomnia-templating-worker-database:
|
||||
https:
|
||||
http:
|
||||
;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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));
|
||||
};
|
||||
@@ -56,7 +56,6 @@ export function init(): BaseSettings {
|
||||
maxRedirects: 10,
|
||||
maxTimelineDataSizeKB: 10,
|
||||
pluginNodeExtraCerts: '',
|
||||
pluginsAllowElevatedAccess: false,
|
||||
noProxy: '',
|
||||
nunjucksPowerUserMode: false,
|
||||
pluginConfig: {},
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,9 @@ export default class BaseExtension {
|
||||
return (
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
this._ext?.liveDisplayName ||
|
||||
(() => '')
|
||||
function() {
|
||||
return '';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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} ⇒ ${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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ export default defineConfig(({ mode }) => {
|
||||
react(),
|
||||
],
|
||||
worker: {
|
||||
format: 'es',
|
||||
plugins: () => [
|
||||
electronNodeRequire({
|
||||
modules: ['fs'],
|
||||
|
||||
Reference in New Issue
Block a user