diff --git a/contributing.md b/contributing.md index 6575e36ea..cc788db23 100644 --- a/contributing.md +++ b/contributing.md @@ -57,6 +57,9 @@ npm run build:graphql-docs npm run build:bruno-query npm run build:bruno-common +# bundle js sandbox libraries +npm run sandbox:bundle-libraries --workspace=packages/bruno-js + # run next app (terminal 1) npm run dev:web diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 53d3a6afd..8ee39c199 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -83,7 +83,7 @@ const getEnvVars = (environment = {}) => { const getJsSandboxRuntime = (collection) => { const securityConfig = get(collection, 'securityConfig', {}); - return securityConfig.jsSandboxMode === 'safe' ? 'isolated-vm' : 'vm2'; + return securityConfig.jsSandboxMode === 'safe' ? 'quickjs' : 'vm2'; }; const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/; diff --git a/packages/bruno-js/.gitignore b/packages/bruno-js/.gitignore index c7c43f854..22322eb1b 100644 --- a/packages/bruno-js/.gitignore +++ b/packages/bruno-js/.gitignore @@ -1 +1 @@ -src/bundle-browser-rollup.js \ No newline at end of file +src/sandbox/bundle-browser-rollup.js \ No newline at end of file diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index b3d94f519..a0dd1546d 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -12,11 +12,10 @@ }, "scripts": { "test": "jest --testPathIgnorePatterns test.js", - "postinstall": "npm run isolated-vm:install && npm run isolated-vm:bundle-libraries", + "sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js", "isolated-vm:install": "cd ./node_modules/isolated-vm && npm install", "isolated-vm:prebuild:dev": "node ./scripts/prebuild-isolated-vm-for-dev.js", - "isolated-vm:prebuild:prod": "node ./scripts/prebuild-isolated-vm-for-prod-builds.js", - "isolated-vm:bundle-libraries": "node ./src/sandbox/isolatedvm/utils/bundle-libraries.js" + "isolated-vm:prebuild:prod": "node ./scripts/prebuild-isolated-vm-for-prod-builds.js" }, "dependencies": { "@usebruno/common": "0.1.0", diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index a2a68bc1e..01b85a972 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -31,6 +31,7 @@ const fetch = require('node-fetch'); const CryptoJS = require('crypto-js'); const NodeVault = require('node-vault'); const { executeInIsolatedVMAsync } = require('../sandbox/isolatedvm'); +const { executeQuickJsVmAsync } = require('../sandbox/quickjs'); class TestRuntime { constructor(props) { @@ -115,54 +116,51 @@ class TestRuntime { modules: {}, scriptType: 'jsScript' }); - - return { - request, - envVariables: cleanJson(envVariables), - runtimeVariables: cleanJson(runtimeVariables), - results: cleanJson(__brunoTestResults.getResults()), - nextRequestName: bru.nextRequest - }; - } - - // default runtime is vm2 - const vm = new NodeVM({ - sandbox: context, - require: { - context: 'sandbox', - external: true, - root: [collectionPath, ...additionalContextRootsAbsolute], - mock: { - // node libs - path, - stream, - util, - url, - http, - https, - punycode, - zlib, - // 3rd party libs - ajv, - 'ajv-formats': addFormats, - btoa, - atob, - lodash, - moment, - uuid, - nanoid, - axios, - chai, - 'node-fetch': fetch, - 'crypto-js': CryptoJS, - ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined, - 'node-vault': NodeVault + } else if(this.runtime === 'quickjs') { + await executeQuickJsVmAsync({ + script: testsFile, + context: context + }); + } else { + // default runtime is vm2 + const vm = new NodeVM({ + sandbox: context, + require: { + context: 'sandbox', + external: true, + root: [collectionPath, ...additionalContextRootsAbsolute], + mock: { + // node libs + path, + stream, + util, + url, + http, + https, + punycode, + zlib, + // 3rd party libs + ajv, + 'ajv-formats': addFormats, + btoa, + atob, + lodash, + moment, + uuid, + nanoid, + axios, + chai, + 'node-fetch': fetch, + 'crypto-js': CryptoJS, + ...whitelistedModules, + fs: allowScriptFilesystemAccess ? fs : undefined, + 'node-vault': NodeVault + } } - } - }); - const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js')); - await asyncVM(); + }); + const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js')); + await asyncVM(); + } return { request, diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/utils/bundle-libraries.js b/packages/bruno-js/src/sandbox/bundle-libraries.js similarity index 94% rename from packages/bruno-js/src/sandbox/isolatedvm copy/utils/bundle-libraries.js rename to packages/bruno-js/src/sandbox/bundle-libraries.js index 2d650ecca..23c11c426 100644 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/utils/bundle-libraries.js +++ b/packages/bruno-js/src/sandbox/bundle-libraries.js @@ -54,7 +54,7 @@ const bundleLibraries = async () => { ] }, output: { - file: './src/bundle-browser-rollup.js', + file: './src/sandbox/bundle-browser-rollup.js', format: 'iife', name: 'MyBundle' } @@ -64,7 +64,7 @@ const bundleLibraries = async () => { const bundle = await rollup.rollup(config.input); const { output } = await bundle.generate(config.output); fs.writeFileSync( - './src/bundle-browser-rollup.js', + './src/sandbox/bundle-browser-rollup.js', ` const getBundledCode = () => { return function(){ diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/index.js b/packages/bruno-js/src/sandbox/isolatedvm copy/index.js deleted file mode 100644 index cb35a3024..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/index.js +++ /dev/null @@ -1,167 +0,0 @@ -const ivm = require('isolated-vm'); -const addBruShimToContext = require('./shims/bru'); -const addBrunoRequestShimToContext = require('./shims/bruno-request'); -const addConsoleShimToContext = require('./shims/console'); -const addBrunoResponseShimToContext = require('./shims/bruno-response'); -const addTestShimToContext = require('./shims/test'); -const addLibraryShimsToContext = require('./shims/lib'); - -// execute `npm run build:isolated-vm:inbuilt-modules` if the below file doesn't exist -const getBundledCode = require('../../bundle-browser-rollup'); -const addSleepShimToContext = require('./shims/sleep'); - -const toNumber = (value) => { - const num = Number(value); - return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value); -}; - -const executeInIsolatedVMStrict = ({ script: externalScript, context: externalContext, scriptType = 'script' }) => { - if (!isNaN(Number(externalScript))) { - return Number(externalScript); - } - let result; - const isolate = new ivm.Isolate(); - try { - const context = isolate.createContextSync(); - context.global.setSync('global', context.global.derefInto()); - - const { bru, req, res } = externalContext; - - context.evalSync(` - let bru = {}; - let req = {}; - let res = {}; - `); - - bru && addBruShimToContext(context, bru); - req && addBrunoRequestShimToContext(context, req); - res && addBrunoResponseShimToContext(context, res); - - context.global.setSync('setResult', function (arg) { - result = arg; - }); - - const templateLiteralText = ` - let value = \`${externalScript}\`; - setResult(value); - `; - - const jsExpressionText = ` - let value = ${externalScript}; - setResult(value); - `; - - let scriptText = scriptType === 'template-literal' ? templateLiteralText : jsExpressionText; - - const script = isolate.compileScriptSync(scriptText); - script.runSync(context); - return result; - } catch (error) { - console.error('Error executing the script!', error); - } - isolate.dispose(); -}; - -const executeInIsolatedVMAsync = async ({ - script: externalScript, - context: externalContext, - modules = {}, - scriptType = 'script' -}) => { - if (!isNaN(Number(externalScript))) { - return toNumber(externalScript); - } - let result; - const isolate = new ivm.Isolate(); - try { - const context = await isolate.createContext(); - await context.global.set('global', context.global.derefInto()); - - context.evalSync(` - let bru = {}; - let req = {}; - let res = {}; - let console = {}; - global.requireObject = {}; - `); - - context.global.setSync('log', function (...args) { - console.debug(...args); - }); - - try { - const bundledCode = getBundledCode?.toString() || ''; - await context.eval(`(${bundledCode})()`); - } catch (err) { - console.debug('Error bundling libraries', err); - } - - const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext; - - bru && addBruShimToContext(context, bru); - req && addBrunoRequestShimToContext(context, req); - res && addBrunoResponseShimToContext(context, res); - consoleFn && addConsoleShimToContext(context, consoleFn); - addSleepShimToContext(context); - - await context.eval( - ` - global.require = (module) => { - return global.requireObject[module]; - } - ` - ); - - await addLibraryShimsToContext(context); - - test && __brunoTestResults && (await addTestShimToContext(context, __brunoTestResults)); - - context.global.setSync('setResult', function (arg) { - result = arg; - }); - - const jsScriptText = ` - new Promise(async (resolve, reject) => { - // modify the setTimeout function with the shim to work-around the callback-function clone issues - setTimeout = global.setTimeout; - console?.debug && console.debug('isolated-vm:execution-start:'); - try { - ${externalScript} - } catch (error) { - console?.debug && console.debug('isolated-vm:execution-end:with-error', error?.message); - } - console?.debug && console.debug('isolated-vm:execution-end:'); - resolve(); - }); - `; - - const templateLiteralText = ` - let value = \`${externalScript}\`; - setResult(value); - `; - - const jsExpressionText = ` - let value = ${externalScript}; - setResult(value); - `; - - let scriptText = - scriptType === 'template-literal' - ? templateLiteralText - : scriptType === 'expression' - ? jsExpressionText - : jsScriptText; - - const script = await isolate.compileScript(scriptText); - await script.run(context); - return result; - } catch (error) { - console.error('Error executing the script!', error); - } - isolate.dispose(); -}; - -module.exports = { - executeInIsolatedVMStrict, - executeInIsolatedVMAsync -}; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bru.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bru.js deleted file mode 100644 index 17bae399e..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bru.js +++ /dev/null @@ -1,59 +0,0 @@ -const addBruShimToContext = (context, bru) => { - context.global.setSync('cwd', function () { - return bru.cwd(); - }); - - context.global.setSync('getEnvName', function () { - return bru.getEnvName(); - }); - - context.global.setSync('getProcessEnv', function (key) { - return bru.getProcessEnv(key); - }); - - context.global.setSync('getEnvVar', function (key) { - return bru.getEnvVar(key); - }); - - context.global.setSync('setEnvVar', function (key, value) { - bru.setEnvVar(key, value); - }); - - context.global.setSync('setVar', function (key, value) { - bru.setVar(key, value); - }); - - context.global.setSync('getVar', function (key) { - return bru.getVar(key); - }); - - context.global.setSync('setNextRequest', function (nextRequest) { - bru.setNextRequest(nextRequest); - }); - - context.global.setSync('visualize', function (htmlString) { - bru.visualize(htmlString); - }); - - context.global.setSync('getSecretVar', function (key) { - return bru.getSecretVar(key); - }); - - context.evalSync(` - bru = { - ...bru || {}, - cwd: global.cwd, - getEnvName: global.getEnvName, - getProcessEnv: global.getProcessEnv, - getEnvVar: global.getEnvVar, - setEnvVar: global.setEnvVar, - setVar: global.setVar, - getVar: global.getVar, - setNextRequest: global.setNextRequest, - visualize: global.visualize, - getSecretVar: global.getSecretVar - } - `); -}; - -module.exports = addBruShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bruno-request.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bruno-request.js deleted file mode 100644 index f8a1f9b9e..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bruno-request.js +++ /dev/null @@ -1,79 +0,0 @@ -const addBrunoRequestShimToContext = (context, req) => { - context.global.setSync('getUrl', function () { - return req.getUrl(); - }); - - context.global.setSync('setUrl', function (url) { - req.setUrl(url); - }); - - context.global.setSync('getMethod', function () { - return req.getMethod(); - }); - - context.global.setSync('getAuthMode', function () { - return req.getAuthMode(); - }); - - context.global.setSync('setMethod', function (method) { - req.setMethod(method); - }); - - context.global.setSync('getHeaders', function () { - return req.getHeaders(); - }); - - context.global.setSync('setHeaders', function (headers) { - req.setHeaders(headers); - }); - - context.global.setSync('getHeader', function (name) { - return req.getHeader(name); - }); - - context.global.setSync('setHeader', function (name, value) { - req.setHeader(name, value); - }); - - context.global.setSync('getBody', function () { - return req.getBody(); - }); - - context.global.setSync('setBody', function (data) { - req.setBody(data); - }); - - context.global.setSync('setMaxRedirects', function (maxRedirects) { - req.setMaxRedirects(maxRedirects); - }); - - context.global.setSync('getTimeout', function () { - return req.getTimeout(); - }); - - context.global.setSync('setTimeout', function (timeout) { - req.setTimeout(timeout); - }); - - context.evalSync(` - req = { - ...req || {}, - getUrl: global.getUrl, - setUrl: global.setUrl, - getMethod: global.getMethod, - getAuthMode: global.getAuthMode, - setMethod: global.setMethod, - getHeaders: global.getHeaders, - setHeaders: global.setHeaders, - getHeader: global.getHeader, - setHeader: global.setHeader, - getBody: global.getBody, - setBody: global.setBody, - setMaxRedirects: global.setMaxRedirects, - getTimeout: global.getTimeout, - setTimeout: global.setTimeout - } -`); -}; - -module.exports = addBrunoRequestShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bruno-response.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bruno-response.js deleted file mode 100644 index 7ac8d5901..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/bruno-response.js +++ /dev/null @@ -1,46 +0,0 @@ -const ivm = require('isolated-vm'); - -const addBrunoResponseShimToContext = (context, res) => { - context.global.setSync('status', new ivm.ExternalCopy(res?.status).copyInto()); - context.global.setSync('headers', new ivm.ExternalCopy(res?.headers).copyInto()); - context.global.setSync('body', new ivm.ExternalCopy(res?.body).copyInto()); - context.global.setSync('responseTime', new ivm.ExternalCopy(res?.responseTime).copyInto()); - - context.global.setSync('getStatus', function () { - return res?.getStatus(); - }); - - context.global.setSync('getHeader', function (name) { - return res?.getHeader(name); - }); - - context.global.setSync('getHeaders', function () { - return res?.getHeaders(); - }); - - context.global.setSync('getBody', function () { - return res?.getBody(); - }); - - context.global.setSync('getResponseTime', function () { - return res?.getResponseTime(); - }); - - context.evalSync(` - res = { - ...res || {}, - status: global.status, - statusText: global.statusText, - headers: global.headers, - body: global.body, - responseTime: global.responseTime, - getStatus: global.getStatus, - getHeader: global.getHeader, - getHeaders: global.getHeaders, - getBody: global.getBody, - getResponseTime: global.getResponseTime - } - `); -}; - -module.exports = addBrunoResponseShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/console.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/console.js deleted file mode 100644 index ead852992..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/console.js +++ /dev/null @@ -1,39 +0,0 @@ -const addConsoleShimToContext = (context, console) => { - context.global.setSync('log', function (...args) { - console?.log && console.log(...args); - return args; - }); - - context.global.setSync('debug', function (...args) { - console?.debug && console.debug(...args); - return args; - }); - - context.global.setSync('info', function (...args) { - console?.info && console.info(...args); - return args; - }); - - context.global.setSync('warn', function (...args) { - console?.warn && console.warn(...args); - return args; - }); - - context.global.setSync('error', function (...args) { - console?.error && console.error(...args); - return args; - }); - - context.evalSync(` - console = { - ...console || {}, - log: global.log, - debug: global.debug, - info: global.info, - warn: global.warn, - error: global.error - } - `); -}; - -module.exports = addConsoleShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/axios-min.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/axios-min.js deleted file mode 100644 index b3949b167..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/axios-min.js +++ /dev/null @@ -1,57 +0,0 @@ -const axios = require('axios'); -const ivm = require('isolated-vm'); -const { cleanJson } = require('../../../../utils'); - -const addAxiosShimToContext = async (context) => { - await context.evalClosure( - ` - globalThis.axios = (...args) => $0.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg))); - ${['get', 'post', 'put', 'patch', 'delete'] - ?.map( - (method, idx) => - `globalThis.axios.${method} = (...args) => $${ - idx + 1 - }.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)))` - ) - .join('\n')} - globalThis.requireObject = { - ...globalThis.requireObject, - axios: globalThis.axios, - } - `, - [ - async (...argStrings) => { - console.log(argStrings); - let args = argStrings?.map((arg) => JSON.parse(arg)); - const res = await axios(...args) - .then((response) => { - return cleanJson(response?.data); - }) - .catch((err) => { - return { - message: err.message - // response: cleanJson(err.response) - }; - }); - return new ivm.ExternalCopy(res).copyInto({ release: true }); - }, - ...['get', 'post', 'put', 'patch', 'delete']?.map((method) => async (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - const res = await axios[method](...args) - .then((response) => { - return cleanJson(response?.data); - }) - .catch((err) => { - return { - message: err.message - // response: cleanJson(err.response) - }; - }); - return new ivm.ExternalCopy(res).copyInto({ release: true }); - }) - ], - { arguments: { reference: true } } - ); -}; - -module.exports = addAxiosShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/axios.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/axios.js deleted file mode 100644 index b6e9d1888..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/axios.js +++ /dev/null @@ -1,121 +0,0 @@ -const axios = require('axios'); -const ivm = require('isolated-vm'); -const { cleanJson } = require('../../../../utils'); - -const addAxiosShimToContext = async (context) => { - await context.evalClosure( - ` - globalThis.axios = (...args) => $0.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.axios.get = (...args) => $1.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.axios.post = (...args) => $2.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.axios.put = (...args) => $3.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.axios.delete = (...args) => $4.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.axios.patch = (...args) => $5.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.requireObject = { - ...globalThis.requireObject, - axios: globalThis.axios, - } - `, - [ - async (...argStrings) => { - console.log(argStrings); - let args = argStrings?.map((arg) => JSON.parse(arg)); - const res = await axios(...args) - .then((response) => { - const { status, headers, data } = response || {}; - return cleanJson({ status, headers, data }); - }) - .catch((err) => { - return { - message: err.message - // response: cleanJson(err.response) - }; - }); - return new ivm.ExternalCopy(res).copyInto({ release: true }); - }, - async (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - const res = await axios - .get(...args) - .then((response) => { - const { status, headers, data } = response || {}; - return cleanJson({ status, headers, data }); - }) - .catch((err) => { - return { - message: err.message - // response: cleanJson(err.response) - }; - }); - return new ivm.ExternalCopy(res).copyInto({ release: true }); - }, - async (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - const res = await axios - .post(...args) - .then((response) => { - const { status, headers, data } = response || {}; - return cleanJson({ status, headers, data }); - }) - .catch((err) => { - return { - message: err.message - // response: cleanJson(err.response) - }; - }); - return new ivm.ExternalCopy(res).copyInto({ release: true }); - }, - async (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - const res = await axios - .put(...args) - .then((response) => { - const { status, headers, data } = response || {}; - return cleanJson({ status, headers, data }); - }) - .catch((err) => { - return { - message: err.message - // response: cleanJson(err.response) - }; - }); - return new ivm.ExternalCopy(res).copyInto({ release: true }); - }, - async (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - const res = await axios - .delete(...args) - .then((response) => { - const { status, headers, data } = response || {}; - return cleanJson({ status, headers, data }); - }) - .catch((err) => { - return { - message: err.message - // response: cleanJson(err.response) - }; - }); - return new ivm.ExternalCopy(res).copyInto({ release: true }); - }, - async (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - const res = await axios - .patch(...args) - .then((response) => { - const { status, headers, data } = response || {}; - return cleanJson({ status, headers, data }); - }) - .catch((err) => { - return { - message: err.message - // response: cleanJson(err.response) - }; - }); - return new ivm.ExternalCopy(res).copyInto({ release: true }); - } - ], - { arguments: { reference: true } } - ); -}; - -module.exports = addAxiosShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/index.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/index.js deleted file mode 100644 index 341b4860d..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/index.js +++ /dev/null @@ -1,11 +0,0 @@ -const addAxiosShimToContext = require('./axios'); -const addNanoidShimToContext = require('./nanoid'); -const addUuidShimToContext = require('./uuid'); - -const addLibraryShimsToContext = async (context) => { - await addAxiosShimToContext(context); - await addNanoidShimToContext(context); - await addUuidShimToContext(context); -}; - -module.exports = addLibraryShimsToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/nanoid.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/nanoid.js deleted file mode 100644 index 3481c816d..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/nanoid.js +++ /dev/null @@ -1,23 +0,0 @@ -const ivm = require('isolated-vm'); -const { nanoid } = require('nanoid'); - -const addNanoidShimToContext = async (context) => { - await context.evalClosure( - ` - globalThis.nanoid = {}; - globalThis.nanoid.nanoid = () => $0.applySync(undefined); - globalThis.requireObject = { - ...globalThis.requireObject, - nanoid: globalThis.nanoid - } - `, - [ - () => { - return new ivm.ExternalCopy(nanoid()).copyInto({ release: true }); - } - ], - { arguments: { reference: true } } - ); -}; - -module.exports = addNanoidShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/uuid-min.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/uuid-min.js deleted file mode 100644 index 2e1c010e5..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/uuid-min.js +++ /dev/null @@ -1,39 +0,0 @@ -const ivm = require('isolated-vm'); -const uuid = require('uuid'); -const { MAX, NIL } = uuid; - -const addUuidShimToContext = async (context) => { - await context.evalClosure( - ` - globalThis.uuid = {}; - globalThis.uuid.MAX = $0; - globalThis.uuid.NIL = $1; - ${['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate'] - ?.map( - (fn, idx) => - `globalThis.uuid.${fn} = (...args) => $${ - idx + 2 - }.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));` - ) - .join('\n')} - globalThis.requireObject = { - ...globalThis.requireObject, - uuid: globalThis.uuid, - } - `, - [ - new ivm.ExternalCopy(MAX).copyInto({ release: true }), - new ivm.ExternalCopy(NIL).copyInto({ release: true }), - ...['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate']?.map( - (fn) => - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(uuid[fn](...args)).copyInto({ release: true }); - } - ) - ], - { arguments: { reference: true } } - ); -}; - -module.exports = addUuidShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/uuid.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/uuid.js deleted file mode 100644 index 151498bb5..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/lib/uuid.js +++ /dev/null @@ -1,83 +0,0 @@ -const ivm = require('isolated-vm'); -const { MAX, NIL, parse, stringify, v1, v1ToV6, v3, v4, v5, v6, v6ToV1, v7, validate, version } = require('uuid'); - -const addUuidShimToContext = async (context) => { - await context.evalClosure( - ` - globalThis.uuid = {}; - globalThis.uuid.MAX = $0; - globalThis.uuid.NIL = $1; - globalThis.uuid.version = (...args) => $2.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.parse = (...args) => $3.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.stringify = (...args) => $4.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.v1 = (...args) => $5.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.v1ToV6 = (...args) => $6.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.v3 = (...args) => $7.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.v4 = (...args) => $8.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.v5 = (...args) => $9.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.v6 = (...args) => $10.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.v6ToV1 = (...args) => $11.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.v7 = (...args) => $12.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.uuid.validate = (...args) => $13.applySync(undefined, args?.map(arg=>JSON.stringify(arg))); - globalThis.requireObject = { - ...globalThis.requireObject, - uuid: globalThis.uuid, - } - `, - [ - new ivm.ExternalCopy(MAX).copyInto({ release: true }), - new ivm.ExternalCopy(NIL).copyInto({ release: true }), - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(version(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(parse(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(stringify(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(v1(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(v1ToV6(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(v3(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(v4(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(v5(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(v6(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(v6ToV1(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(v7(...args)).copyInto({ release: true }); - }, - (...argStrings) => { - let args = argStrings?.map((arg) => JSON.parse(arg)); - return new ivm.ExternalCopy(validate(...args)).copyInto({ release: true }); - } - ], - { arguments: { reference: true } } - ); -}; - -module.exports = addUuidShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/sleep.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/sleep.js deleted file mode 100644 index 14ce92e30..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/sleep.js +++ /dev/null @@ -1,32 +0,0 @@ -const ivm = require('isolated-vm'); -const addSleepShimToContext = (context, console) => { - context.evalClosureSync( - ` - global.sleep = (...args) => $0.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg))); - `, - [ - async (...argStrings) => { - await new Promise((resolve) => { - const timer = Number(argStrings?.[0]); - if (!Number.isInteger(timer) || timer < 0) { - resolve(); - } - setTimeout(() => { - resolve(); - }, timer); - }); - return new ivm.ExternalCopy('done').copyInto({ release: true }); - } - ], - { arguments: { reference: true } } - ); - - context.evalSync(` - global.setTimeout = async (fn, timer) => { - await sleep(timer); - await fn.apply(); - } - `); -}; - -module.exports = addSleepShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/test.js b/packages/bruno-js/src/sandbox/isolatedvm copy/shims/test.js deleted file mode 100644 index 126f9b346..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm copy/shims/test.js +++ /dev/null @@ -1,57 +0,0 @@ -const addTestShimToContext = async (context, __brunoTestResults) => { - context.global.setSync('addResult', function (v) { - __brunoTestResults.addResult(v); - }); - - context.global.setSync('getResults', function () { - return __brunoTestResults.getResults(); - }); - - context.evalSync(` - global.expect = require('chai').expect; - global.assert = require('chai').assert; - - global.__brunoTestResults = { - addResult: global.addResult, - getResults: global.getResults, - } - - - global.DummyChaiAssertionError = class DummyChaiAssertionError extends Error { - constructor(message, props, ssf) { - super(message); - this.name = "AssertionError"; - Object.assign(this, props); - } - } - - global.Test = (__brunoTestResults) => async (description, callback) => { - try { - await callback(); - __brunoTestResults.addResult({ description, status: "pass" }); - } catch (error) { - if (error instanceof DummyChaiAssertionError) { - const { message, actual, expected } = error; - __brunoTestResults.addResult({ - description, - status: "fail", - error: message, - actual, - expected, - }); - } else { - __brunoTestResults.addResult({ - description, - status: "fail", - error: error.message || "An unexpected error occurred.", - }); - } - console.log(error); - } - }; - - global.test = Test(__brunoTestResults); - `); -}; - -module.exports = addTestShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm/index.js b/packages/bruno-js/src/sandbox/isolatedvm/index.js index 804cab2df..db410d5d1 100644 --- a/packages/bruno-js/src/sandbox/isolatedvm/index.js +++ b/packages/bruno-js/src/sandbox/isolatedvm/index.js @@ -1,20 +1,13 @@ const ivm = require('isolated-vm'); const addBruShimToContext = require('./shims/bru'); -const addQuickBruShimToContext = require('./shims/bru-quick'); - const addBrunoRequestShimToContext = require('./shims/bruno-request'); -const addQuickBrunoRequestShimToContext = require('./shims/bruno-request-quick'); - -const addBrunoResponseShimToContext = require('./shims/bruno-response'); -const addQuickBrunoResponseShimToContext = require('./shims/bruno-response-quick'); - const addConsoleShimToContext = require('./shims/console'); -const addTestShimToContext = require('./shims/test-quick'); +const addBrunoResponseShimToContext = require('./shims/bruno-response'); +const addTestShimToContext = require('./shims/test'); const addLibraryShimsToContext = require('./shims/lib'); -const { getQuickJS, newQuickJSAsyncWASMModule } = require('quickjs-emscripten'); // execute `npm run build:isolated-vm:inbuilt-modules` if the below file doesn't exist -const getBundledCode = require('../../bundle-browser-rollup'); +const getBundledCode = require('../bundle-browser-rollup'); const addSleepShimToContext = require('./shims/sleep'); const toNumber = (value) => { @@ -79,151 +72,93 @@ const executeInIsolatedVMAsync = async ({ return toNumber(externalScript); } let result; + const isolate = new ivm.Isolate(); try { - // const QuickJS = await getQuickJS() - // const vm = QuickJS.newContext(); + const context = await isolate.createContext(); + await context.global.set('global', context.global.derefInto()); - const module = await newQuickJSAsyncWASMModule() - const runtime = module.newRuntime() - const vm = runtime.newContext() - // context.evalSync(` - // let bru = {}; - // let req = {}; - // let res = {}; - // let console = {}; - // global.requireObject = {}; - // `); - - const bundledCode = getBundledCode?.toString() || ''; - let bundledScript = `(${bundledCode})()`; - - bundledScript += ` - globalThis.require = (module) => { - return globalThis.requireObject[module]; - } - `; - - bundledScript += ` - let bru = { - cwd: __bruno__cwd, - getEnvName: __bruno__getEnvName, - getProcessEnv: __bruno__getProcessEnv, - getEnvVar: __bruno__getEnvVar, - setEnvVar: __bruno__setEnvVar, - getVar: __bruno__getVar, - setVar: __bruno__setVar, - setNextRequest: __bruno__setNextRequest, - visualize: __bruno__visualize, - getSecretVar: __bruno__getSecretVar - }; - let req = { - url: __bruno__req__url, - method: __bruno__req__method, - headers: __bruno__req__headers, - body: __bruno__req__body, - timeout: __bruno__req__timeout, - getUrl: __bruno__req__getUrl, - setUrl: __bruno__req__setUrl, - getMethod: __bruno__req__getMethod, - getAuthMode: __bruno__req__getAuthMode, - setMethod: __bruno__req__setMethod, - getHeaders: __bruno__req__getHeaders, - setHeaders: __bruno__req__setHeaders, - getHeader: __bruno__req__getHeader, - setHeader: __bruno__req__setHeader - getBody: __bruno__req__getBody, - setBody: __bruno__req__setBody, - setMaxRedirects: __bruno__req__setMaxRedirects, - getTimeout: __bruno__req__getTimeout, - setTimeout: __bruno__req__setTimeout - }; - let res = { - status: __bruno__res__status, - headers: __bruno__res__headers, - body: __bruno__res__body, - responseTime: __bruno__res__responseTime, - getStatus: __bruno__res__getStatus, - getHeader: __bruno__res__getHeader, - getHeaders: __bruno__res__getHeaders, - getBody: __bruno__res__getBody, - getResponseTime: __bruno__res__getResponseTime - }; + context.evalSync(` + let bru = {}; + let req = {}; + let res = {}; let console = {}; - `; + global.requireObject = {}; + `); + + context.global.setSync('log', function (...args) { + console.debug(...args); + }); + + try { + const bundledCode = getBundledCode?.toString() || ''; + await context.eval(`(${bundledCode})()`); + } catch (err) { + console.debug('Error bundling libraries', err); + } const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext; - bru && addQuickBruShimToContext(vm, bru); - req && addQuickBrunoRequestShimToContext(vm, req); - res && addQuickBrunoResponseShimToContext(vm, res); - // consoleFn && addConsoleShimToContext(context, consoleFn); - // addSleepShimToContext(context); + bru && addBruShimToContext(context, bru); + req && addBrunoRequestShimToContext(context, req); + res && addBrunoResponseShimToContext(context, res); + consoleFn && addConsoleShimToContext(context, consoleFn); + addSleepShimToContext(context); - // await addLibraryShimsToContext(context); - - test && __brunoTestResults && addTestShimToContext(vm, __brunoTestResults); - - bundledScript += ` - globalThis.expect = require('chai').expect; - globalThis.assert = require('chai').assert; - - globalThis.__brunoTestResults = { - addResult: globalThis.__bruno__addResult, - getResults: globalThis.__bruno__getResults, - } - - globalThis.DummyChaiAssertionError = class DummyChaiAssertionError extends Error { - constructor(message, props, ssf) { - super(message); - this.name = "AssertionError"; - Object.assign(this, props); + await context.eval( + ` + global.require = (module) => { + return global.requireObject[module]; } - } + ` + ); - globalThis.Test = (__brunoTestResults) => (description, callback) => { + await addLibraryShimsToContext(context); + + test && __brunoTestResults && (await addTestShimToContext(context, __brunoTestResults)); + + context.global.setSync('setResult', function (arg) { + result = arg; + }); + + const jsScriptText = ` + new Promise(async (resolve, reject) => { + // modify the setTimeout function with the shim to work-around the callback-function clone issues + setTimeout = global.setTimeout; + console?.debug && console.debug('isolated-vm:execution-start:'); try { - callback(); - __brunoTestResults.addResult({ description, status: "pass" }); + ${externalScript} } catch (error) { - if (error instanceof DummyChaiAssertionError) { - const { message, actual, expected } = error; - __brunoTestResults.addResult({ - description, - status: "fail", - error: message, - actual, - expected, - }); - } else { - globalThis.__bruno__addResult({ - description, - status: "fail", - error: error.message || "An unexpected error occurred.", - }); - } - // console.log(error); + console?.debug && console.debug('isolated-vm:execution-end:with-error', error?.message); } - }; - - globalThis.test = Test(__brunoTestResults); + console?.debug && console.debug('isolated-vm:execution-end:'); + resolve(); + }); `; - bundledScript += externalScript; + const templateLiteralText = ` + let value = \`${externalScript}\`; + setResult(value); + `; - const result = await vm.evalCodeAsync(bundledScript); - console.log('Result:', result); - if (result.error) { - console.log("Execution failed:", vm.dump(result.error)) - result.error.dispose() - } else { - console.log("Success:", vm.dump(result.value)) - result.value.dispose(); - } - vm.dispose(); + const jsExpressionText = ` + let value = ${externalScript}; + setResult(value); + `; + + let scriptText = + scriptType === 'template-literal' + ? templateLiteralText + : scriptType === 'expression' + ? jsExpressionText + : jsScriptText; + + const script = await isolate.compileScript(scriptText); + await script.run(context); return result; } catch (error) { console.error('Error executing the script!', error); } + isolate.dispose(); }; module.exports = { diff --git a/packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-request-quick.js b/packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-request-quick.js deleted file mode 100644 index 03848284c..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-request-quick.js +++ /dev/null @@ -1,107 +0,0 @@ -const { marshallToVm } = require('../utils'); - -const addBrunoRequestShimToContext = (vm, req) => { - const url = marshallToVm(req.getUrl(), vm); - const method = marshallToVm(req.getMethod(), vm); - const headers = marshallToVm(req.getHeaders(), vm); - const body = marshallToVm(req.getBody(), vm); - const timeout = marshallToVm(req.getTimeout(), vm); - - vm.setProp(vm.global, '__bruno__requrl', url); - vm.setProp(vm.global, '__bruno__reqmethod', method); - vm.setProp(vm.global, '__bruno__reqheaders', headers); - vm.setProp(vm.global, '__bruno__reqbody', body); - vm.setProp(vm.global, '__bruno__reqtimeout', timeout); - - url.dispose(); - method.dispose(); - headers.dispose(); - body.dispose(); - timeout.dispose(); - - // let getUrl = vm.newFunction('__bruno__req__getUrl', function () { - // return marshallToVm(req.getUrl(), vm); - // }); - // vm.setProp(vm.global, 'getUrl', getUrl); - // getUrl.dispose(); - - // let setUrl = vm.newFunction('__bruno__req__setUrl', function (url) { - // req.setUrl(vm.dump(url)); - // }); - // vm.setProp(vm.global, 'setUrl', setUrl); - // setUrl.dispose(); - - // let getMethod = vm.newFunction('__bruno__req__getMethod', function () { - // return marshallToVm(req.getMethod(), vm); - // }); - // vm.setProp(vm.global, 'getMethod', getMethod); - // getMethod.dispose(); - - // let getAuthMode = vm.newFunction('__bruno__req__getAuthMode', function () { - // return marshallToVm(req.getAuthMode(), vm); - // }); - // vm.setProp(vm.global, 'getAuthMode', getAuthMode); - // getAuthMode.dispose(); - - // let setMethod = vm.newFunction('__bruno__req__setMethod', function (method) { - // req.setMethod(vm.dump(method)); - // }); - // vm.setProp(vm.global, 'setMethod', setMethod); - // setMethod.dispose(); - - // let getHeaders = vm.newFunction('__bruno__req__getHeaders', function () { - // return marshallToVm(req.getHeaders(), vm); - // }); - // vm.setProp(vm.global, 'getHeaders', getHeaders); - // getHeaders.dispose(); - - // let setHeaders = vm.newFunction('__bruno__req__setHeaders', function (headers) { - // req.setHeaders(vm.dump(headers)); - // }); - // vm.setProp(vm.global, 'setHeaders', setHeaders); - // setHeaders.dispose(); - - // let getHeader = vm.newFunction('__bruno__req__getHeader', function (name) { - // return marshallToVm(req.getHeader(vm.dump(name)), vm); - // }); - // vm.setProp(vm.global, 'getHeader', getHeader); - // getHeader.dispose(); - - // let setHeader = vm.newFunction('__bruno__req__setHeader', function (name, value) { - // req.setHeader(vm.dump(name), vm.dump(value)); - // }); - // vm.setProp(vm.global, 'setHeader', setHeader); - // setHeader.dispose(); - - // let getBody = vm.newFunction('__bruno__req__getBody', function () { - // return marshallToVm(req.getBody(), vm); - // }); - // vm.setProp(vm.global, 'getBody', getBody); - // getBody.dispose(); - - // let setBody = vm.newFunction('__bruno__req__setBody', function (data) { - // req.setBody(vm.dump(data)); - // }); - // vm.setProp(vm.global, 'setBody', setBody); - // setBody.dispose(); - - // let setMaxRedirects = vm.newFunction('__bruno__req__setMaxRedirects', function (maxRedirects) { - // req.setMaxRedirects(vm.dump(maxRedirects)); - // }); - // vm.setProp(vm.global, 'setMaxRedirects', setMaxRedirects); - // setMaxRedirects.dispose(); - - // let getTimeout = vm.newFunction('__bruno__req__getTimeout', function () { - // return marshallToVm(req.getTimeout(), vm); - // }); - // vm.setProp(vm.global, 'getTimeout', getTimeout); - // getTimeout.dispose(); - - // let setTimeout = vm.newFunction('__bruno__req__setTimeout', function (timeout) { - // req.setTimeout(vm.dump(timeout)); - // }); - // vm.setProp(vm.global, 'setTimeout', setTimeout); - // setTimeout.dispose(); -}; - -module.exports = addBrunoRequestShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm/utils/bundle-libraries.js b/packages/bruno-js/src/sandbox/isolatedvm/utils/bundle-libraries.js deleted file mode 100644 index 2d650ecca..000000000 --- a/packages/bruno-js/src/sandbox/isolatedvm/utils/bundle-libraries.js +++ /dev/null @@ -1,84 +0,0 @@ -const rollup = require('rollup'); -const { nodeResolve } = require('@rollup/plugin-node-resolve'); -const commonjs = require('@rollup/plugin-commonjs'); -const fs = require('fs'); -const { terser } = require('rollup-plugin-terser'); - -const bundleLibraries = async () => { - const codeScript = ` - import { expect, assert } from 'chai'; - import { Buffer } from "buffer"; - import moment from "moment"; - import btoa from "btoa"; - import atob from "atob"; - globalThis.expect = expect; - globalThis.assert = assert; - globalThis.moment = moment; - globalThis.btoa = btoa; - globalThis.atob = atob; - globalThis.Buffer = Buffer; - globalThis.requireObject = { - 'chai': { expect, assert }, - 'moment': moment, - 'buffer': { Buffer }, - 'btoa': btoa, - 'atob': atob, - }; -`; - - const config = { - input: { - input: 'inline-code', - plugins: [ - { - name: 'inline-code-plugin', - resolveId(id) { - if (id === 'inline-code') { - return id; - } - return null; - }, - load(id) { - if (id === 'inline-code') { - return codeScript; - } - return null; - } - }, - nodeResolve({ - preferBuiltins: false, - browser: false - }), - commonjs(), - terser() - ] - }, - output: { - file: './src/bundle-browser-rollup.js', - format: 'iife', - name: 'MyBundle' - } - }; - - try { - const bundle = await rollup.rollup(config.input); - const { output } = await bundle.generate(config.output); - fs.writeFileSync( - './src/bundle-browser-rollup.js', - ` - const getBundledCode = () => { - return function(){ - ${output?.map((o) => o.code).join('\n')} - }() - } - module.exports = getBundledCode; - ` - ); - } catch (error) { - console.error('Error while bundling:', error); - } -}; - -bundleLibraries(); - -module.exports = bundleLibraries; diff --git a/packages/bruno-js/src/sandbox/quickjs/index.js b/packages/bruno-js/src/sandbox/quickjs/index.js new file mode 100644 index 000000000..dea71d525 --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/index.js @@ -0,0 +1,203 @@ +const ivm = require('isolated-vm'); +const addBruShimToContext = require('./shims/bru'); +const addBrunoRequestShimToContext = require('./shims/bruno-request'); +const addBrunoResponseShimToContext = require('./shims/bruno-response'); +const addTestShimToContext = require('./shims/test'); +const { newQuickJSAsyncWASMModule } = require('quickjs-emscripten'); + +// execute `npm run build:isolated-vm:inbuilt-modules` if the below file doesn't exist +const getBundledCode = require('../bundle-browser-rollup'); + +const toNumber = (value) => { + const num = Number(value); + return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value); +}; + +const executeQuickJsVm = ({ script: externalScript, context: externalContext }) => { + if (!isNaN(Number(externalScript))) { + return Number(externalScript); + } + let result; + const isolate = new ivm.Isolate(); + try { + const context = isolate.createContextSync(); + context.global.setSync('global', context.global.derefInto()); + + const { bru, req, res } = externalContext; + + context.evalSync(` + let bru = {}; + let req = {}; + let res = {}; + `); + + bru && addBruShimToContext(context, bru); + req && addBrunoRequestShimToContext(context, req); + res && addBrunoResponseShimToContext(context, res); + + context.global.setSync('setResult', function (arg) { + result = arg; + }); + + const templateLiteralText = ` + let value = \`${externalScript}\`; + setResult(value); + `; + + const jsExpressionText = ` + let value = ${externalScript}; + setResult(value); + `; + + let scriptText = scriptType === 'template-literal' ? templateLiteralText : jsExpressionText; + + const script = isolate.compileScriptSync(scriptText); + script.runSync(context); + return result; + } catch (error) { + console.error('Error executing the script!', error); + } + isolate.dispose(); +}; + +const executeQuickJsVmAsync = async ({ + script: externalScript, + context: externalContext +}) => { + if (!isNaN(Number(externalScript))) { + return toNumber(externalScript); + } + try { + const module = await newQuickJSAsyncWASMModule() + const runtime = module.newRuntime() + const vm = runtime.newContext() + + const bundledCode = getBundledCode?.toString() || ''; + let bundledScript = `(${bundledCode})()`; + + bundledScript += ` + globalThis.require = (module) => { + return globalThis.requireObject[module]; + } + `; + + bundledScript += ` + let bru = { + cwd: __bruno__cwd, + getEnvName: __bruno__getEnvName, + getProcessEnv: __bruno__getProcessEnv, + getEnvVar: __bruno__getEnvVar, + setEnvVar: __bruno__setEnvVar, + getVar: __bruno__getVar, + setVar: __bruno__setVar, + setNextRequest: __bruno__setNextRequest, + visualize: __bruno__visualize, + getSecretVar: __bruno__getSecretVar + }; + let req = { + url: __bruno__req__url, + method: __bruno__req__method, + headers: __bruno__req__headers, + body: __bruno__req__body, + timeout: __bruno__req__timeout, + getUrl: __bruno__req__getUrl, + setUrl: __bruno__req__setUrl, + getMethod: __bruno__req__getMethod, + setMethod: __bruno__req__setMethod, + getAuthMode: __bruno__req__getAuthMode, + getHeaders: __bruno__req__getHeaders, + setHeaders: __bruno__req__setHeaders, + getHeader: __bruno__req__getHeader, + setHeader: __bruno__req__setHeader, + getBody: __bruno__req__getBody, + setBody: __bruno__req__setBody, + setMaxRedirects: __bruno__req__setMaxRedirects, + getTimeout: __bruno__req__getTimeout, + setTimeout: __bruno__req__setTimeout + }; + let res = { + status: globalThis.__bruno__res__status, + headers: globalThis.__bruno__res__headers, + body: globalThis.__bruno__res__body, + responseTime: globalThis.__bruno__res__responseTime, + getStatus: globalThis.__bruno__res__getStatus, + getHeader: globalThis.__bruno__res__getHeader, + getHeaders: globalThis.__bruno__res__getHeaders, + getBody: globalThis.__bruno__res__getBody, + getResponseTime: globalThis.__bruno__res__getResponseTime + }; + `; + + const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext; + + bru && addBruShimToContext(vm, bru); + req && addBrunoRequestShimToContext(vm, req); + res && addBrunoResponseShimToContext(vm, res); + + test && __brunoTestResults && addTestShimToContext(vm, __brunoTestResults); + + bundledScript += ` + globalThis.expect = require('chai').expect; + globalThis.assert = require('chai').assert; + + globalThis.__brunoTestResults = { + addResult: globalThis.__bruno__addResult, + getResults: globalThis.__bruno__getResults, + } + + globalThis.DummyChaiAssertionError = class DummyChaiAssertionError extends Error { + constructor(message, props, ssf) { + super(message); + this.name = "AssertionError"; + Object.assign(this, props); + } + } + + globalThis.Test = (__brunoTestResults) => (description, callback) => { + try { + callback(); + __brunoTestResults.addResult({ description, status: "pass" }); + } catch (error) { + if (error instanceof DummyChaiAssertionError) { + const { message, actual, expected } = error; + __brunoTestResults.addResult({ + description, + status: "fail", + error: message, + actual, + expected, + }); + } else { + __brunoTestResults.addResult({ + description, + status: "fail", + error: error.message || "An unexpected error occurred.", + }); + } + // console.log(error); + } + }; + + globalThis.test = Test(__brunoTestResults); + `; + + bundledScript += externalScript; + + const result = await vm.evalCodeAsync(bundledScript); + if (result.error) { + console.log("Execution failed:", vm.dump(result.error)) + result.error.dispose() + } else { + result.value.dispose(); + } + vm.dispose(); + return result; + } catch (error) { + console.error('Error executing the script!', error); + } +}; + +module.exports = { + executeQuickJsVm, + executeQuickJsVmAsync +}; diff --git a/packages/bruno-js/src/sandbox/isolatedvm/shims/bru-quick.js b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js similarity index 100% rename from packages/bruno-js/src/sandbox/isolatedvm/shims/bru-quick.js rename to packages/bruno-js/src/sandbox/quickjs/shims/bru.js diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js new file mode 100644 index 000000000..509097a25 --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js @@ -0,0 +1,107 @@ +const { marshallToVm } = require('../utils'); + +const addBrunoRequestShimToContext = (vm, req) => { + const url = marshallToVm(req.getUrl(), vm); + const method = marshallToVm(req.getMethod(), vm); + const headers = marshallToVm(req.getHeaders(), vm); + const body = marshallToVm(req.getBody(), vm); + const timeout = marshallToVm(req.getTimeout(), vm); + + vm.setProp(vm.global, '__bruno__req__url', url); + vm.setProp(vm.global, '__bruno__req__method', method); + vm.setProp(vm.global, '__bruno__req__headers', headers); + vm.setProp(vm.global, '__bruno__req__body', body); + vm.setProp(vm.global, '__bruno__req__timeout', timeout); + + url.dispose(); + method.dispose(); + headers.dispose(); + body.dispose(); + timeout.dispose(); + + let getUrl = vm.newFunction('getUrl', function () { + return marshallToVm(req.getUrl(), vm); + }); + vm.setProp(vm.global, '__bruno__req__getUrl', getUrl); + getUrl.dispose(); + + let setUrl = vm.newFunction('setUrl', function (url) { + req.setUrl(vm.dump(url)); + }); + vm.setProp(vm.global, '__bruno__req__setUrl', setUrl); + setUrl.dispose(); + + let getMethod = vm.newFunction('getMethod', function () { + return marshallToVm(req.getMethod(), vm); + }); + vm.setProp(vm.global, '__bruno__req__getMethod', getMethod); + getMethod.dispose(); + + let getAuthMode = vm.newFunction('getAuthMode', function () { + return marshallToVm(req.getAuthMode(), vm); + }); + vm.setProp(vm.global, '__bruno__req__getAuthMode', getAuthMode); + getAuthMode.dispose(); + + let setMethod = vm.newFunction('setMethod', function (method) { + req.setMethod(vm.dump(method)); + }); + vm.setProp(vm.global, '__bruno__req__setMethod', setMethod); + setMethod.dispose(); + + let getHeaders = vm.newFunction('getHeaders', function () { + return marshallToVm(req.getHeaders(), vm); + }); + vm.setProp(vm.global, '__bruno__req__getHeaders', getHeaders); + getHeaders.dispose(); + + let setHeaders = vm.newFunction('setHeaders', function (headers) { + req.setHeaders(vm.dump(headers)); + }); + vm.setProp(vm.global, '__bruno__req__setHeaders', setHeaders); + setHeaders.dispose(); + + let getHeader = vm.newFunction('getHeader', function (name) { + return marshallToVm(req.getHeader(vm.dump(name)), vm); + }); + vm.setProp(vm.global, '__bruno__req__getHeader', getHeader); + getHeader.dispose(); + + let setHeader = vm.newFunction('setHeader', function (name, value) { + req.setHeader(vm.dump(name), vm.dump(value)); + }); + vm.setProp(vm.global, '__bruno__req__setHeader', setHeader); + setHeader.dispose(); + + let getBody = vm.newFunction('getBody', function () { + return marshallToVm(req.getBody(), vm); + }); + vm.setProp(vm.global, '__bruno__req__getBody', getBody); + getBody.dispose(); + + let setBody = vm.newFunction('setBody', function (data) { + req.setBody(vm.dump(data)); + }); + vm.setProp(vm.global, '__bruno__req__setBody', setBody); + setBody.dispose(); + + let setMaxRedirects = vm.newFunction('setMaxRedirects', function (maxRedirects) { + req.setMaxRedirects(vm.dump(maxRedirects)); + }); + vm.setProp(vm.global, '__bruno__req__setMaxRedirects', setMaxRedirects); + setMaxRedirects.dispose(); + + let getTimeout = vm.newFunction('getTimeout', function () { + return marshallToVm(req.getTimeout(), vm); + }); + vm.setProp(vm.global, '__bruno__req__getTimeout', getTimeout); + getTimeout.dispose(); + + let setTimeout = vm.newFunction('setTimeout', function (timeout) { + req.setTimeout(vm.dump(timeout)); + }); + vm.setProp(vm.global, '__bruno__req__setTimeout', setTimeout); + setTimeout.dispose(); +}; + +module.exports = addBrunoRequestShimToContext; diff --git a/packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-response-quick.js b/packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js similarity index 100% rename from packages/bruno-js/src/sandbox/isolatedvm/shims/bruno-response-quick.js rename to packages/bruno-js/src/sandbox/quickjs/shims/bruno-response.js diff --git a/packages/bruno-js/src/sandbox/isolatedvm/shims/test-quick.js b/packages/bruno-js/src/sandbox/quickjs/shims/test.js similarity index 100% rename from packages/bruno-js/src/sandbox/isolatedvm/shims/test-quick.js rename to packages/bruno-js/src/sandbox/quickjs/shims/test.js diff --git a/packages/bruno-js/src/sandbox/isolatedvm/utils/index.js b/packages/bruno-js/src/sandbox/quickjs/utils/index.js similarity index 79% rename from packages/bruno-js/src/sandbox/isolatedvm/utils/index.js rename to packages/bruno-js/src/sandbox/quickjs/utils/index.js index 17a16fadd..22ea7d922 100644 --- a/packages/bruno-js/src/sandbox/isolatedvm/utils/index.js +++ b/packages/bruno-js/src/sandbox/quickjs/utils/index.js @@ -3,6 +3,10 @@ const marshallToVm = (value, vm) => { return vm.undefined; } + if (value === null) { + return vm.null; + } + if (typeof value === "string") { return vm.newString(value); } else if (typeof value === "number") { @@ -13,13 +17,13 @@ const marshallToVm = (value, vm) => { if (Array.isArray(value)) { const arr = vm.newArray(); for (let i = 0; i < value.length; i++) { - vm.setElement(arr, i, marshallToVm(value[i])); + vm.setProp(arr, i, marshallToVm(value[i], vm)); } return arr; } else { const obj = vm.newObject(); for (const key in value) { - vm.setProp(obj, key, marshallToVm(value[key])); + vm.setProp(obj, key, marshallToVm(value[key], vm)); } return obj; } diff --git a/packages/bruno-tests/collection/ping.bru b/packages/bruno-tests/collection/ping.bru index 281c75074..3abc7a2d4 100644 --- a/packages/bruno-tests/collection/ping.bru +++ b/packages/bruno-tests/collection/ping.bru @@ -5,28 +5,7 @@ meta { } get { - url: https://testbench-sanity.usebruno.com/ping + url: {{host}}/ping body: none auth: none } - -script:pre-request { - // const moment = require("moment"); - // function a() { - // return moment().format("hh:mm"); - // return test; - // } - // a(); -} - -tests { - test("should get var in scripts", function() { - expect("bruno-test-87267").to.equal("bruno-test-87267"); - }); - // const moment = require("moment"); - // function a() { - // return moment().format("hh:mm"); - // return test; - // } - // a(); -} diff --git a/packages/bruno-tests/collection/scripting/api/req/getUrl.bru b/packages/bruno-tests/collection/scripting/api/req/getUrl.bru index 3b044b85b..155a40b7a 100644 --- a/packages/bruno-tests/collection/scripting/api/req/getUrl.bru +++ b/packages/bruno-tests/collection/scripting/api/req/getUrl.bru @@ -10,7 +10,6 @@ get { auth: none } - assert { res.status: eq 200 res.body: eq pong @@ -21,4 +20,4 @@ tests { const url = req.getUrl(); expect(url).to.equal("https://testbench-sanity.usebruno.com/ping"); }); -} \ No newline at end of file +}