diff --git a/packages/insomnia-sdk/src/objects/insomnia.ts b/packages/insomnia-sdk/src/objects/insomnia.ts index b44e340a082..c1e35f6ed3e 100644 --- a/packages/insomnia-sdk/src/objects/insomnia.ts +++ b/packages/insomnia-sdk/src/objects/insomnia.ts @@ -31,7 +31,7 @@ export class InsomniaObject { private _test = test; // TODO: follows will be enabled after Insomnia supports them - private _globals: Environment; + private globals: Environment; private _iterationData: Environment; private _settings: Settings; @@ -53,7 +53,7 @@ export class InsomniaObject { }, log: (...msgs: any[]) => void, ) { - this._globals = rawObj.globals; + this.globals = rawObj.globals; this.environment = rawObj.environment; this.baseEnvironment = rawObj.baseEnvironment; this.collectionVariables = this.baseEnvironment; // collectionVariables is mapped to baseEnvironment @@ -85,11 +85,6 @@ export class InsomniaObject { return this._expect(exp); } - // TODO: remove this after enabled globals - get globals() { - throw unsupportedError('globals', 'base environment'); - } - // TODO: remove this after enabled iterationData get iterationData() { throw unsupportedError('iterationData', 'environment'); @@ -102,7 +97,7 @@ export class InsomniaObject { toObject = () => { return { - globals: this._globals.toObject(), + globals: this.globals.toObject(), environment: this.environment.toObject(), baseEnvironment: this.baseEnvironment.toObject(), iterationData: this._iterationData.toObject(), @@ -121,13 +116,26 @@ export async function initInsomniaObject( rawObj: RequestContext, log: (...args: any[]) => void, ) { - const globals = new Environment('globals', rawObj.globals); - const environment = new Environment(rawObj.environmentName || '', rawObj.environment); - const baseEnvironment = new Environment(rawObj.baseEnvironmentName || '', rawObj.baseEnvironment); + // Mapping rule for the global environment: + // - when one global environment is selected, `globals` points to the selected one + // Potential mapping rule for the future: + // - The base global environment could also be introduced + const globals = new Environment('globals', rawObj.globals || {}); // could be undefined + // Mapping rule for the environment and base environment: + // - If base environment is selected, both `baseEnvironment` and `environment` point to the selected one. + // - If one sub environment is selected, `baseEnvironment` points to the base env and `environment` points to the selected one. + const baseEnvironment = new Environment(rawObj.baseEnvironment.name || '', rawObj.baseEnvironment.data); + // reuse baseEnvironment when the "selected envrionment" points to the base environment + const environment = rawObj.baseEnvironment.id === rawObj.environment.id ? + baseEnvironment : + new Environment(rawObj.environment.name || '', rawObj.environment.data); + if (rawObj.baseEnvironment.id === rawObj.environment.id) { + log('warning: No environment is selected, modification of insomnia.environment will be applied to the base environment.'); + } // TODO: update "iterationData" name when it is supported const iterationData = new Environment('iterationData', rawObj.iterationData); const cookies = new CookieObject(rawObj.cookieJar); - // TODO: update follows when post-request script and iterating are introduced + // TODO: update follows when post-request script and iterationData are introduced const requestInfo = new RequestInfo({ eventName: 'prerequest', iteration: 1, diff --git a/packages/insomnia-sdk/src/objects/interfaces.ts b/packages/insomnia-sdk/src/objects/interfaces.ts index b900cd8f7a0..17241c77629 100644 --- a/packages/insomnia-sdk/src/objects/interfaces.ts +++ b/packages/insomnia-sdk/src/objects/interfaces.ts @@ -7,10 +7,16 @@ import { sendCurlAndWriteTimelineError, sendCurlAndWriteTimelineResponse } from export interface RequestContext { request: Request; timelinePath: string; - environment?: object; - environmentName?: string; - baseEnvironment?: object; - baseEnvironmentName?: string; + environment: { + id: string; + name: string; + data: object; + }; + baseEnvironment: { + id: string; + name: string; + data: object; + }; collectionVariables?: object; globals?: object; iterationData?: object; diff --git a/packages/insomnia-smoke-test/fixtures/pre-request-collection.yaml b/packages/insomnia-smoke-test/fixtures/pre-request-collection.yaml index 070568c9c5a..097025945a4 100644 --- a/packages/insomnia-smoke-test/fixtures/pre-request-collection.yaml +++ b/packages/insomnia-smoke-test/fixtures/pre-request-collection.yaml @@ -201,7 +201,7 @@ resources: name: FolderWithEnv description: "" environment: - customValue: "fromFolder" + folderEnv: "fromFolder" environmentPropertyOrder: null metaSortKey: -1668533312225 _type: request_group @@ -315,30 +315,24 @@ resources: settingRebuildPath: true settingFollowRedirects: global preRequestScript: |- - insomnia.environment.set("environmentName", insomnia.environment.name); - insomnia.environment.set("baseEnvironmentName", insomnia.baseEnvironment.name); - insomnia.environment.set("collectionVariablesName", insomnia.collectionVariables.name); - // manipulation - insomnia.baseEnvironment.set('fromBaseEnv', 'baseEnv'); + // insomnia.globals.set('fallbackToGlobal', 'fallbackToGlobal'); + insomnia.baseEnvironment.set('fallbackToBase', 'fallbackToBase'); insomnia.baseEnvironment.set('scriptValue', 'fromBase'); insomnia.environment.set('scriptValue', 'fromEnv'); // "preDefinedValue" is already defined in the base environment modal. // but it is rewritten here insomnia.baseEnvironment.set('preDefinedValue', 'fromScript'); - // "customValue" is already defined in the folder environment. + // "folderEnv" is already defined in the folder environment. // folder version will override the following one - insomnia.baseEnvironment.set('customValue', 'fromScript'); + insomnia.baseEnvironment.set('folderEnv', 'fromScript'); body: mimeType: "application/json" text: |- { - "environmentName": "{{ _.environmentName }}", - "baseEnvironmentName": "{{ _.baseEnvironmentName }}", - "collectionVariablesName": "{{ _.collectionVariablesName }}", - "fromBaseEnv": "{{ _.fromBaseEnv }}", + "fallbackToBase": "{{ _.fallbackToBase }}", "scriptValue": "{{ _.scriptValue }}", "preDefinedValue": "{{ _.preDefinedValue }}", - "customValue": "{{ _.customValue }}" + "folderEnv": "{{ _.folderEnv }}" } _type: request - _id: req_89dade2ee9ee42fbb22d588783a9df01 @@ -1170,3 +1164,33 @@ resources: text: |- {} _type: request + - _id: req_89dade2ee9ee42fbb22d588783a9df19 + parentId: fld_01de564274824ecaad272330339ea6b2 + modified: 1636707449231 + created: 1636141014552 + url: http://127.0.0.1:4010/echo + name: can manipulate global envs + description: "" + method: POST + parameters: [] + headers: + - name: 'Content-Type' + value: 'application/json' + authentication: {} + metaSortKey: -1636141014553 + isPrivate: false + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + preRequestScript: |- + insomnia.globals.set('setByScript', 'setByScript'); + body: + mimeType: "application/json" + text: |- + { + "setByScript": "{{ setByScript }}" + } + _type: request diff --git a/packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts b/packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts index f055e18902d..e6288d94b4b 100644 --- a/packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts @@ -53,9 +53,10 @@ test.describe('after-response script features tests', async () => { const bodyJson = JSON.parse(rows.join(' ')); expect(bodyJson).toEqual({ - // no environment is selected so the environment value is not persisted + // no environment is selected so the environment value will be persisted to the base environment '__fromAfterScript1': 'baseEnvironment', '__fromAfterScript2': 'collection', + '__fromAfterScript': 'environment', }); }); }); diff --git a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts index 6b393e3eac7..8d742a46f02 100644 --- a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts @@ -26,13 +26,11 @@ test.describe('pre-request features tests', async () => { { name: 'environments setting and overriding', expectedBody: { - environmentName: '', - baseEnvironmentName: 'Base Environment', - collectionVariablesName: 'Base Environment', - fromBaseEnv: 'baseEnv', + // fallbackToGlobal: 'fallbackToGlobal', + fallbackToBase: 'fallbackToBase', scriptValue: 'fromEnv', preDefinedValue: 'fromScript', - customValue: 'fromFolder', + folderEnv: 'fromFolder', }, }, { @@ -176,6 +174,12 @@ test.describe('pre-request features tests', async () => { asyncTaskDone: true, }, }, + // { + // name: 'can manipulate global envs', + // expectedBody: { + // setByScript: 'setByScript', + // }, + // }, ]; for (let i = 0; i < testCases.length; i++) { @@ -428,9 +432,10 @@ test.describe('pre-request features tests', async () => { const bodyJson = JSON.parse(rows.join(' ')); expect(bodyJson).toEqual({ - // no environment is selected so the environment value is not persisted + // no environment is selected so the environment value will be persisted to the base environment '__fromScript1': 'baseEnvironment', '__fromScript2': 'collection', + '__fromScript': 'environment', }); }); }); diff --git a/packages/insomnia/src/hidden-window.ts b/packages/insomnia/src/hidden-window.ts index bd350c624ca..55badc206be 100644 --- a/packages/insomnia/src/hidden-window.ts +++ b/packages/insomnia/src/hidden-window.ts @@ -90,12 +90,21 @@ const runScript = async ( return { ...context, - environment: mutatedContextObject.environment, - baseEnvironment: mutatedContextObject.baseEnvironment, + environment: { + id: context.environment.id, + name: context.environment.name, + data: mutatedContextObject.environment, + }, + baseEnvironment: { + id: context.baseEnvironment.id, + name: context.baseEnvironment.name, + data: mutatedContextObject.baseEnvironment, + }, request: updatedRequest, settings: updatedSettings, clientCertificates: updatedCertificates, cookieJar: updatedCookieJar, + globals: mutatedContextObject.globals, }; }; diff --git a/packages/insomnia/src/network/network.ts b/packages/insomnia/src/network/network.ts index 432e0c539e4..747a4bb7686 100644 --- a/packages/insomnia/src/network/network.ts +++ b/packages/insomnia/src/network/network.ts @@ -116,6 +116,12 @@ export const getPreRequestScriptOutput = async ({ const baseEnvironment = await models.environment.getOrCreateForParentId(workspaceId); const cookieJar = await models.cookieJar.getOrCreateForParentId(workspaceId); + const workspaceMeta = await models.workspaceMeta.getByParentId(workspaceId); + let activeGlobalEnvironment: Environment | undefined = undefined; + if (workspaceMeta?.activeGlobalEnvironmentId) { + activeGlobalEnvironment = await models.environment.getById(workspaceMeta.activeGlobalEnvironmentId) || undefined; + } + if (!request.preRequestScript) { return { request, @@ -133,6 +139,7 @@ export const getPreRequestScriptOutput = async ({ baseEnvironment, clientCertificates, cookieJar, + globals: activeGlobalEnvironment, }); if (!mutatedContext?.request) { // exiy early if there was a problem with the pre-request script @@ -140,20 +147,26 @@ export const getPreRequestScriptOutput = async ({ return null; } - await savePatchesMadeByScript(mutatedContext, environment, baseEnvironment); + await savePatchesMadeByScript(mutatedContext, environment, baseEnvironment, activeGlobalEnvironment); return { request: mutatedContext.request, environment: mutatedContext.environment, baseEnvironment: mutatedContext.baseEnvironment || baseEnvironment, clientCertificates: mutatedContext.clientCertificates || clientCertificates, settings: mutatedContext.settings || settings, + globals: mutatedContext.globals, }; }; +// savePatchesMadeByScript persists entities +// The rule for the global environment: +// - If no global environment is seleted, no operation +// - If one global environment is selected, it persists content to the selected global environment (base or sub). export async function savePatchesMadeByScript( mutatedContext: Awaited>, environment: Environment, baseEnvironment: Environment, + activeGlobalEnvironment: Environment | undefined, ) { if (!mutatedContext) { return; @@ -187,9 +200,21 @@ export async function savePatchesMadeByScript( } ); } + + if (activeGlobalEnvironment && mutatedContext) { + invariant(mutatedContext.globals, 'globals must be defined when there is selected one'); + await models.environment.update( + activeGlobalEnvironment, + { + data: mutatedContext.globals.data, + dataPropertyOrder: mutatedContext.globals.dataPropertyOrder, + } + ); + } } + export const tryToExecuteScript = async (context: RequestAndContextAndOptionalResponse) => { - const { script, request, environment, timelinePath, responseId, baseEnvironment, clientCertificates, cookieJar, response } = context; + const { script, request, environment, timelinePath, responseId, baseEnvironment, clientCertificates, cookieJar, response, globals } = context; invariant(script, 'script must be provided'); const settings = await models.settings.get(); @@ -203,27 +228,30 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe // TODO: restart the hidden browser window }, timeout + 2000); }); - // const isBaseEnvironmentSelected = environment._id === baseEnvironment._id; - // if (isBaseEnvironmentSelected) { - // // postman models base env as no env and does not persist, so we could handle that case better, but for now we throw - // throw new Error('Base environment cannot be selected for script execution. Please select an environment.'); - // } + const executionPromise = cancellableRunScript({ script, context: { request, timelinePath, timeout: settings.timeout, - // it inputs empty environment data when active environment is the base environment - // this is more deterministic and avoids that script accidently manipulates baseEnvironment instead of environment - environment: environment._id === baseEnvironment._id ? {} : (environment?.data || {}), - environmentName: environment._id === baseEnvironment._id ? '' : (environment?.name || ''), - baseEnvironment: baseEnvironment?.data || {}, - baseEnvironmentName: baseEnvironment?.name || '', + // if the selected environment points to the base environment + // script operations on the environment will be applied on the base environment + environment: { + id: environment._id, + name: environment.name, + data: environment.data || {}, + }, + baseEnvironment: { + id: baseEnvironment._id, + name: baseEnvironment.name, + data: baseEnvironment.data || {}, + }, clientCertificates, settings, cookieJar, response, + globals: globals?.data || undefined, }, }); const output = await Promise.race([timeoutPromise, executionPromise]) as { @@ -233,25 +261,36 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe settings: Settings; clientCertificates: ClientCertificate[]; cookieJar: CookieJar; + globals: Record; }; console.log('[network] script execution succeeded', output); const envPropertyOrder = orderedJSON.parse( - JSON.stringify(output.environment), + JSON.stringify(output.environment.data), JSON_ORDER_PREFIX, JSON_ORDER_SEPARATOR, ); - environment.data = output.environment; + environment.data = output.environment.data; environment.dataPropertyOrder = envPropertyOrder.map; const baseEnvPropertyOrder = orderedJSON.parse( - JSON.stringify(output.baseEnvironment), + JSON.stringify(output.baseEnvironment.data), JSON_ORDER_PREFIX, JSON_ORDER_SEPARATOR, ); - baseEnvironment.data = output.baseEnvironment; + baseEnvironment.data = output.baseEnvironment.data; baseEnvironment.dataPropertyOrder = baseEnvPropertyOrder.map; + if (globals) { + const globalEnvPropertyOrder = orderedJSON.parse( + JSON.stringify(output.globals || {}), + JSON_ORDER_PREFIX, + JSON_ORDER_SEPARATOR, + ); + globals.data = output.globals; + globals.dataPropertyOrder = globalEnvPropertyOrder.map; + } + return { request: output.request, environment, @@ -259,6 +298,7 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe settings: output.settings, clientCertificates: output.clientCertificates, cookieJar: output.cookieJar, + globals, }; } catch (err) { await fs.promises.appendFile( @@ -289,6 +329,7 @@ interface RequestContextForScript { baseEnvironment: Environment; clientCertificates: ClientCertificate[]; cookieJar: CookieJar; + globals?: Environment; // there could be no global environment } type RequestAndContextAndResponse = RequestContextForScript & { response: sendCurlAndWriteTimelineError | sendCurlAndWriteTimelineResponse; diff --git a/packages/insomnia/src/ui/components/editors/request-script-editor.tsx b/packages/insomnia/src/ui/components/editors/request-script-editor.tsx index 280edbc7d63..9349248b041 100644 --- a/packages/insomnia/src/ui/components/editors/request-script-editor.tsx +++ b/packages/insomnia/src/ui/components/editors/request-script-editor.tsx @@ -17,15 +17,15 @@ interface Props { } const getEnvVar = 'insomnia.environment.get("variable_name");'; -// const getGlbVar = 'insomnia.globals.get("variable_name");'; +const getGlbVar = 'insomnia.globals.get("variable_name");'; const getVar = 'insomnia.variables.get("variable_name");'; const getCollectionVar = 'insomnia.collectionVariables.get("variable_name");'; const setEnvVar = 'insomnia.environment.set("variable_name", "variable_value");'; -// const setGlbVar = 'insomnia.globals.set("variable_name", "variable_value");'; +const setGlbVar = 'insomnia.globals.set("variable_name", "variable_value");'; const setVar = 'insomnia.variables.set("variable_name", "variable_value");'; const setCollectionVar = 'insomnia.collectionVariables.set("variable_name", "variable_value");'; const unsetEnvVar = 'insomnia.environment.unset("variable_name");'; -// const unsetGlbVar = 'insomnia.globals.unset("variable_name");'; +const unsetGlbVar = 'insomnia.globals.unset("variable_name");'; const unsetCollectionVar = 'insomnia.collectionVariables.unset("variable_name");'; const sendReq = `const resp = await new Promise((resolve, reject) => { @@ -172,11 +172,11 @@ const variableSnippetsMenu: SnippetMenuItem = { 'name': 'Get an environment variable', 'snippet': getEnvVar, }, - // { - // "id": "get-glb-var", - // "name": "Get a global variable", - // "snippet": getGlbVar, - // }, + { + 'id': 'get-glb-var', + 'name': 'Get a global variable', + 'snippet': getGlbVar, + }, { 'id': 'get-var', 'name': 'Get a variable', @@ -198,11 +198,11 @@ const variableSnippetsMenu: SnippetMenuItem = { 'name': 'Set an environment variable', 'snippet': setEnvVar, }, - // { - // "id": "set-glb-var", - // "name": "Set a global variable", - // "snippet": setGlbVar, - // }, + { + 'id': 'set-glb-var', + 'name': 'Set a global variable', + 'snippet': setGlbVar, + }, { 'id': 'set-var', 'name': 'Set a variable', @@ -224,11 +224,11 @@ const variableSnippetsMenu: SnippetMenuItem = { 'name': 'Clear an environment variable', 'snippet': unsetEnvVar, }, - // { - // "id": "unset-glb-var", - // "name": "Clear a global variable", - // "snippet": unsetGlbVar, - // }, + { + 'id': 'unset-glb-var', + 'name': 'Clear a global variable', + 'snippet': unsetGlbVar, + }, { 'id': 'unset-collection-var', 'name': 'Clear a collection variable', diff --git a/packages/insomnia/src/ui/routes/request.tsx b/packages/insomnia/src/ui/routes/request.tsx index 68ce0fb8b56..330788f7b3f 100644 --- a/packages/insomnia/src/ui/routes/request.tsx +++ b/packages/insomnia/src/ui/routes/request.tsx @@ -423,13 +423,17 @@ export const sendAction: ActionFunction = async ({ request, params }) => { if (requestData.request.afterResponseScript) { const baseEnvironment = await models.environment.getOrCreateForParentId(workspaceId); const cookieJar = await models.cookieJar.getOrCreateForParentId(workspaceId); + const globals = mutatedContext.globals; + window.main.addExecutionStep({ requestId, stepName: 'Executing after-response script' }); + const postMutatedContext = await tryToExecuteAfterResponseScript({ ...requestData, ...mutatedContext, baseEnvironment, cookieJar, response, + globals, }); window.main.completeExecutionStep({ requestId }); if (!postMutatedContext?.request) { @@ -437,7 +441,7 @@ export const sendAction: ActionFunction = async ({ request, params }) => { // TODO: improve error message? return null; } - await savePatchesMadeByScript(postMutatedContext, requestData.environment, baseEnvironment); + await savePatchesMadeByScript(postMutatedContext, requestData.environment, baseEnvironment, globals); } if (!shouldWriteToFile) { const response = await models.response.create(responsePatch, requestData.settings.maxHistoryResponses);