diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index a0dd1546d..b368cfe19 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -33,7 +33,7 @@ "lodash": "^4.17.21", "moment": "^2.29.4", "nanoid": "3.3.4", - "node-fetch": "2.*", + "node-fetch": "^2.7.0", "node-vault": "^0.10.2", "quickjs-emscripten": "^0.29.2", "uuid": "^9.0.0" diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index 2584cecf1..d6a6c6771 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -96,14 +96,14 @@ class ScriptRuntime { modules: {}, scriptType: 'jsScript' }); - } - return { - request, - envVariables: cleanJson(envVariables), - runtimeVariables: cleanJson(runtimeVariables), - nextRequestName: bru.nextRequest - }; + return { + request, + envVariables: cleanJson(envVariables), + runtimeVariables: cleanJson(runtimeVariables), + nextRequestName: bru.nextRequest + }; + } // default runtime is vm2 const vm = new NodeVM({ diff --git a/packages/bruno-js/src/sandbox/quickjs/index.js b/packages/bruno-js/src/sandbox/quickjs/index.js index 5f09ffe99..6aa3b5df8 100644 --- a/packages/bruno-js/src/sandbox/quickjs/index.js +++ b/packages/bruno-js/src/sandbox/quickjs/index.js @@ -8,6 +8,7 @@ const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscrip // 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'); let QuickJSSyncContext; const loader = memoizePromiseFactory(() => newQuickJSWASMModule()); @@ -33,17 +34,6 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc req && addBrunoRequestShimToContext(vm, req); res && addBrunoResponseShimToContext(vm, res); - //////////////////////////////////////////////////////////////////////////////// - - const logHandle = vm.newFunction('log', (...args) => { - const nativeArgs = args.map(vm.dump); - console.log(...nativeArgs); - }); - vm.setProp(vm.global, 'log', logHandle); - logHandle.dispose(); - - //////////////////////////////////////////////////////////////////////////////// - const templateLiteralText = `\`${externalScript}\`;`; const jsExpressionText = `${externalScript};`; @@ -81,15 +71,15 @@ const executeQuickJsVmAsync = async ({ const vm = module.newContext(); const bundledCode = getBundledCode?.toString() || ''; - let bundledScript = ` - (${bundledCode})() - `; - bundledScript += ` - globalThis.require = (module) => { - return globalThis.requireObject[module]; - } - `; + vm.evalCode( + ` + (${bundledCode})() + globalThis.require = (module) => { + return globalThis.requireObject[module]; + } + ` + ); const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext; @@ -97,72 +87,13 @@ const executeQuickJsVmAsync = async ({ req && addBrunoRequestShimToContext(vm, req); res && addBrunoResponseShimToContext(vm, res); consoleFn && addConsoleShimToContext(vm, consoleFn); + addSleepShimToContext(vm); - // await addLibraryShimsToContext(context); + await addLibraryShimsToContext(vm); 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) => 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 { - globalThis.__bruno__addResult({ - description, - status: "fail", - error: error.message || "An unexpected error occurred.", - }); - } - } - }; - - globalThis.test = Test(__brunoTestResults); - `; - - //////////////////////////////////////////////////////////////////////////////// - - const sleep = vm.newFunction('sleep', (timer) => { - const t = vm.getString(timer); - const promise = vm.newPromise(); - setTimeout(() => { - promise.resolve(vm.newString('slept')); - }, t); - promise.settled.then(vm.runtime.executePendingJobs); - return promise.handle; - }); - sleep.consume((handle) => vm.setProp(vm.global, 'sleep', handle)); - - //////////////////////////////////////////////////////////////////////////////// - const script = ` - ${bundledScript} (async () => { const setTimeout = async(fn, timer) => { v = await sleep(timer); diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/axios.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/axios.js new file mode 100644 index 000000000..2f0fc0789 --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/lib/axios.js @@ -0,0 +1,72 @@ +const axios = require('axios'); +const { cleanJson } = require('../../../../utils'); +const { marshallToVm } = require('../../utils'); + +const methods = ['get', 'post', 'put', 'patch', 'delete']; + +const addAxiosShimToContext = async (vm) => { + methods?.forEach((method) => { + const axiosHandle = vm.newFunction(method, (...args) => { + const nativeArgs = args.map(vm.dump); + const promise = vm.newPromise(); + axios[method](...nativeArgs) + .then((response) => { + const { status, headers, data } = response || {}; + promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm)); + }) + .catch((err) => { + promise.resolve( + marshallToVm( + cleanJson({ + message: err.message + }), + vm + ) + ); + }); + promise.settled.then(vm.runtime.executePendingJobs); + return promise.handle; + }); + axiosHandle.consume((handle) => vm.setProp(vm.global, `__bruno__axios__${method}`, handle)); + }); + + const axiosHandle = vm.newFunction('axios', (...args) => { + const nativeArgs = args.map(vm.dump); + const promise = vm.newPromise(); + axios(...nativeArgs) + .then((response) => { + const { status, headers, data } = response || {}; + promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm)); + }) + .catch((err) => { + promise.resolve( + marshallToVm( + cleanJson({ + message: err.message + }), + vm + ) + ); + }); + promise.settled.then(vm.runtime.executePendingJobs); + return promise.handle; + }); + axiosHandle.consume((handle) => vm.setProp(vm.global, `__bruno__axios`, handle)); + + vm.evalCode( + ` + globalThis.axios = __bruno__axios; + ${methods + ?.map((method) => { + return `globalThis.axios.${method} = __bruno__axios__${method};`; + }) + ?.join('\n')} + globalThis.requireObject = { + ...globalThis.requireObject, + axios: globalThis.axios, + } + ` + ); +}; + +module.exports = addAxiosShimToContext; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js index f15d1ef6e..1744e3570 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js @@ -1,7 +1,13 @@ +const addAxiosShimToContext = require('./axios'); const addNanoidShimToContext = require('./nanoid'); +const addNodeFetchShimToContext = require('./node-fetch'); +const addUuidShimToContext = require('./uuid'); -const addLibraryShimsToContext = async (context) => { - await addNanoidShimToContext(context); +const addLibraryShimsToContext = async (vm) => { + await addNanoidShimToContext(vm); + await addAxiosShimToContext(vm); + await addNodeFetchShimToContext(vm); + await addUuidShimToContext(vm); }; module.exports = addLibraryShimsToContext; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/nanoid.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/nanoid.js index 04107bb75..7a83d37fe 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/lib/nanoid.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/lib/nanoid.js @@ -1,5 +1,24 @@ const { nanoid } = require('nanoid'); +const { marshallToVm } = require('../../utils'); -const addNanoidShimToContext = async (context) => {}; +const addNanoidShimToContext = async (vm) => { + let _nanoid = vm.newFunction('nanoid', function () { + let v = nanoid(); + return marshallToVm(v, vm); + }); + vm.setProp(vm.global, '__bruno__nanoid', _nanoid); + _nanoid.dispose(); + + vm.evalCode( + ` + globalThis.nanoid = {}; + globalThis.nanoid.nanoid = globalThis.__bruno__nanoid; + globalThis.requireObject = { + ...globalThis.requireObject, + 'nanoid': globalThis.nanoid + } + ` + ); +}; module.exports = addNanoidShimToContext; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/node-fetch.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/node-fetch.js new file mode 100644 index 000000000..073b1e3c9 --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/lib/node-fetch.js @@ -0,0 +1,41 @@ +const fetch = require('node-fetch'); +const { cleanJson } = require('../../../../utils'); +const { marshallToVm } = require('../../utils'); + +const addNodeFetchShimToContext = async (vm) => { + const nodeFetchHandle = vm.newFunction('node_fetch', (...args) => { + const nativeArgs = args.map(vm.dump); + const promise = vm.newPromise(); + fetch(...nativeArgs) + .then(async (response) => { + const { status, headers } = response || {}; + const data = await response.json(); + promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm)); + }) + .catch((err) => { + promise.resolve( + marshallToVm( + cleanJson({ + message: err.message + }), + vm + ) + ); + }); + promise.settled.then(vm.runtime.executePendingJobs); + return promise.handle; + }); + + nodeFetchHandle.consume((handle) => vm.setProp(vm.global, `__bruno__node_fetch`, handle)); + vm.evalCode( + ` + globalThis.nodeFetch = __bruno__node_fetch; + globalThis.requireObject = { + ...globalThis.requireObject, + 'node-fetch': globalThis.nodeFetch, + } + ` + ); +}; + +module.exports = addNodeFetchShimToContext; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.js b/packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.js new file mode 100644 index 000000000..23f830311 --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.js @@ -0,0 +1,30 @@ +const uuid = require('uuid'); +const { marshallToVm } = require('../../utils'); + +const fns = ['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate']; + +const addUuidShimToContext = async (vm) => { + fns.forEach((fn) => { + let fnHandle = vm.newFunction(fn, function (...args) { + const nativeArgs = args.map(vm.dump); + return marshallToVm(uuid[fn](...nativeArgs), vm); + }); + vm.setProp(vm.global, `__bruno__uuid__${fn}`, fnHandle); + fnHandle.dispose(); + }); + + vm.evalCode( + ` + globalThis.uuid = {}; + ${['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate'] + ?.map((fn, idx) => `globalThis.uuid.${fn} = __bruno__uuid__${fn}`) + .join('\n')} + globalThis.requireObject = { + ...globalThis.requireObject, + uuid: globalThis.uuid, + } + ` + ); +}; + +module.exports = addUuidShimToContext; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/sleep.js b/packages/bruno-js/src/sandbox/quickjs/shims/sleep.js new file mode 100644 index 000000000..79fd146f9 --- /dev/null +++ b/packages/bruno-js/src/sandbox/quickjs/shims/sleep.js @@ -0,0 +1,14 @@ +const addSleepShimToContext = (vm) => { + const sleepHandle = vm.newFunction('sleep', (timer) => { + const t = vm.getString(timer); + const promise = vm.newPromise(); + setTimeout(() => { + promise.resolve(vm.newString('slept')); + }, t); + promise.settled.then(vm.runtime.executePendingJobs); + return promise.handle; + }); + sleepHandle.consume((handle) => vm.setProp(vm.global, 'sleep', handle)); +}; + +module.exports = addSleepShimToContext; diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/test.js b/packages/bruno-js/src/sandbox/quickjs/shims/test.js index f7a928101..9da224a39 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/test.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/test.js @@ -13,53 +13,51 @@ const addBruShimToContext = (vm, __brunoTestResults) => { vm.setProp(vm.global, '__bruno__getResults', getResults); getResults.dispose(); - // vm.evalCode( - // ` - // globalThis.expect = require('chai').expect; - // globalThis.assert = require('chai').assert; + vm.evalCode( + ` + globalThis.expect = require('chai').expect; + globalThis.assert = require('chai').assert; - // globalThis.__brunoTestResults = { - // addResult: globalThis.addResult, - // getResults: globalThis.getResults, - // } + 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.DummyChaiAssertionError = class DummyChaiAssertionError extends Error { + constructor(message, props, ssf) { + super(message); + this.name = "AssertionError"; + Object.assign(this, props); + } + } - // globalThis.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); - // } - // }; - // let foobar = 'foobar3000'; - // log("from test shim"); - // globalThis.test = Test(__brunoTestResults); - // ` - // ); + globalThis.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 { + globalThis.__bruno__addResult({ + description, + status: "fail", + error: error.message || "An unexpected error occurred.", + }); + } + } + }; + + globalThis.test = Test(__brunoTestResults); + ` + ); }; module.exports = addBruShimToContext; diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/nanoid/nanoid.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/nanoid/nanoid.bru new file mode 100644 index 000000000..47f08dc67 --- /dev/null +++ b/packages/bruno-tests/collection/scripting/inbuilt modules/nanoid/nanoid.bru @@ -0,0 +1,17 @@ +meta { + name: nanoid + type: http + seq: 1 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:pre-request { + const { nanoid } = require("nanoid"); + + req.setHeader("transaction-id", nanoid()); +} diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/node-fetch/node-fetch-pre-req-script.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/node-fetch/node-fetch-pre-req-script.bru new file mode 100644 index 000000000..9821a055c --- /dev/null +++ b/packages/bruno-tests/collection/scripting/inbuilt modules/node-fetch/node-fetch-pre-req-script.bru @@ -0,0 +1,36 @@ +meta { + name: node-fetch-pre-req-script + type: http + seq: 1 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:pre-request { + const fetch = require("node-fetch"); + + const url = "https://testbench-sanity.usebruno.com/api/echo/json"; + const response = await fetch(url, { + method: 'post', + body: JSON.stringify({hello:'bruno'}), + headers: {'Content-Type': 'application/json'} + }); + + req.setBody(response.data); + req.setMethod("POST"); + req.setUrl(url); +} + +tests { + test("req.getBody()", function() { + const data = res.getBody(); + expect(data).to.eql({ + "hello": "bruno" + }); + }); + +} diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/uuid/uuid.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/uuid/uuid.bru new file mode 100644 index 000000000..0a7c622a5 --- /dev/null +++ b/packages/bruno-tests/collection/scripting/inbuilt modules/uuid/uuid.bru @@ -0,0 +1,17 @@ +meta { + name: uuid + type: http + seq: 1 +} + +get { + url: {{host}}/ping + body: none + auth: none +} + +script:pre-request { + const { v4 } = require("uuid"); + + req.setHeader("transaction-id", v4()); +}