feat: scripting support almost done

This commit is contained in:
Anoop M D 2023-01-29 04:49:31 +05:30
parent 4a403a253e
commit b1d2b798ba
8 changed files with 140 additions and 25 deletions

View File

@ -7,7 +7,8 @@ import {
collectionUnlinkFileEvent,
collectionUnlinkDirectoryEvent,
collectionUnlinkEnvFileEvent,
requestSentEvent
requestSentEvent,
scriptEnvironmentUpdateEvent
} from 'providers/ReduxStore/slices/collections';
import toast from 'react-hot-toast';
import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions';
@ -85,6 +86,10 @@ const useCollectionTreeSync = () => {
dispatch(requestSentEvent(val));
};
const _scriptEnvironmentUpdate = (val) => {
dispatch(scriptEnvironmentUpdateEvent(val));
};
ipcRenderer.invoke('renderer:ready');
const removeListener1 = ipcRenderer.on('main:collection-opened', _openCollection);
@ -92,6 +97,7 @@ const useCollectionTreeSync = () => {
const removeListener3 = ipcRenderer.on('main:collection-already-opened', _collectionAlreadyOpened);
const removeListener4 = ipcRenderer.on('main:display-error', _displayError);
const removeListener5 = ipcRenderer.on('main:http-request-sent', _httpRequestSent);
const removeListener6 = ipcRenderer.on('main:script-environment-update', _scriptEnvironmentUpdate);
return () => {
removeListener1();
@ -99,6 +105,7 @@ const useCollectionTreeSync = () => {
removeListener3();
removeListener4();
removeListener5();
removeListener6();
};
}, [isElectron]);
};

View File

@ -2,6 +2,7 @@ import path from 'path';
import { uuid } from 'utils/common';
import find from 'lodash/find';
import map from 'lodash/map';
import forOwn from 'lodash/forOwn';
import concat from 'lodash/concat';
import filter from 'lodash/filter';
import each from 'lodash/each';
@ -161,6 +162,25 @@ export const collectionsSlice = createSlice({
}
}
},
scriptEnvironmentUpdateEvent: (state, action) => {
const { collectionUid, environment } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
if (collection) {
const activeEnvironmentUid = collection.activeEnvironmentUid;
const activeEnvironment = findEnvironmentInCollection(collection, activeEnvironmentUid);
if (activeEnvironment) {
forOwn(environment, (value, key) => {
const variable = find(activeEnvironment.variables, (v) => v.name === key);
if (variable) {
variable.value = value;
}
});
}
}
},
requestCancelled: (state, action) => {
const { itemUid, collectionUid } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
@ -796,6 +816,7 @@ export const {
renameItem,
cloneItem,
requestSentEvent,
scriptEnvironmentUpdateEvent,
requestCancelled,
responseReceived,
saveRequest,

View File

@ -1,13 +1,35 @@
const axios = require('axios');
const Mustache = require('mustache');
const FormData = require('form-data');
const { ipcMain } = require('electron');
const { forOwn, extend } = require('lodash');
const { forOwn, extend, each } = require('lodash');
const { ScriptRuntime } = require('@usebruno/js');
const prepareRequest = require('./prepare-request');
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
const { uuid } = require('../../utils/common');
const interpolateVars = require('./interpolate-vars');
// override the default escape function to prevent escaping
Mustache.escape = function (value) {
return value;
};
const getEnvVars = (environment = {}) => {
const variables = environment.variables;
if (!variables || !variables.length) {
return {};
}
const envVars = {};
each(variables, (variable) => {
if(variable.enabled) {
envVars[variable.name] = Mustache.escape(variable.value);
}
});
return envVars;
};
const registerNetworkIpc = (mainWindow, watcher, lastOpenedCollections) => {
// handler for sending http request
ipcMain.handle('send-http-request', async (event, item, collectionUid, environment) => {
@ -15,7 +37,7 @@ const registerNetworkIpc = (mainWindow, watcher, lastOpenedCollections) => {
try {
const _request = item.draft ? item.draft.request : item.request;
const request = prepareRequest(_request, environment);
const request = prepareRequest(_request);
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
@ -32,10 +54,17 @@ const registerNetworkIpc = (mainWindow, watcher, lastOpenedCollections) => {
request.cancelToken = cancelToken.token;
saveCancelToken(cancelTokenUid, cancelToken);
const envVars = getEnvVars(environment);
if(request.script && request.script.length) {
request.script = request.script += '\n onRequest(brunoRequest);';
let script = request.script + '\n if (typeof onRequest === "function") {onRequest(brunoRequest);}';
const scriptRuntime = new ScriptRuntime();
scriptRuntime.run(request.script, request, environment);
const res = scriptRuntime.runRequestScript(script, request, envVars);
mainWindow.webContents.send('main:script-environment-update', {
environment: res.environment,
collectionUid
});
}
mainWindow.webContents.send('main:http-request-sent', {
@ -50,10 +79,21 @@ const registerNetworkIpc = (mainWindow, watcher, lastOpenedCollections) => {
cancelTokenUid
});
interpolateVars(request, environment);
interpolateVars(request, envVars);
const result = await axios(request);
if(request.script && request.script.length) {
let script = request.script + '\n if (typeof onResponse === "function") {onResponse(brunoResponse);}';
const scriptRuntime = new ScriptRuntime();
const res = scriptRuntime.runResponseScript(script, result, envVars);
mainWindow.webContents.send('main:script-environment-update', {
environment: res.environment,
collectionUid
});
}
deleteCancelToken(cancelTokenUid);
return {

View File

@ -6,21 +6,7 @@ Mustache.escape = function (value) {
return value;
};
const interpolateVars = (request, environment) => {
if(!environment) {
return request;
}
const variables = environment.variables;
if(!variables || !variables.length) {
return request;
}
const envVars = {};
each(variables, (variable) => {
envVars[variable.name] = Mustache.escape(variable.value);
});
const interpolateVars = (request, envVars = {}) => {
const interpolate = (str) => {
if(!str || !str.length || typeof str !== "string") {
return str;

View File

@ -1,7 +1,7 @@
const { get, each, filter } = require('lodash');
const qs = require('qs');
const prepareRequest = (request, environment) => {
const prepareRequest = (request) => {
const headers = {};
each(request.headers, (h) => {
if (h.enabled) {

View File

@ -3,7 +3,21 @@ class Bru {
this._environment = environment;
}
setVar(key, value) {
getEnvVar(key) {
return this._environment[key];
}
setEnvVar(key, value) {
if(!key) {
throw new Error('Key is required');
}
// gracefully ignore if key is not present in environment
if(!this._environment.hasOwnProperty(key)) {
return;
}
this._environment[key] = value;
}
}

View File

@ -0,0 +1,23 @@
class BrunoResponse {
constructor(response) {
this._response = response;
}
getStatus() {
return this._response.status;
}
getHeader(name) {
return this._response.header[name];
}
getHeaders() {
return this._response.headers;
}
getData() {
return this._response.data;
}
}
module.exports = BrunoResponse;

View File

@ -1,12 +1,13 @@
const { NodeVM } = require('vm2');
const Bru = require('./bru');
const BrunoRequest = require('./bruno-request');
const BrunoResponse = require('./bruno-response');
class ScriptRuntime {
constructor() {
}
run(script, request, environment) {
runRequestScript(script, request, environment) {
const bru = new Bru(environment);
const brunoRequest = new BrunoRequest(request);
@ -20,7 +21,30 @@ class ScriptRuntime {
vm.run(script);
return request;
return {
request,
environment
};
}
runResponseScript(script, response, environment) {
const bru = new Bru(environment);
const brunoResponse = new BrunoResponse(response);
const context = {
bru,
brunoResponse
};
const vm = new NodeVM({
sandbox: context
});
vm.run(script);
return {
response,
environment
};
}
}