diff --git a/package-lock.json b/package-lock.json index d304aa9d4..d3a7b406f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3127,6 +3127,11 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jetbrains/websandbox": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@jetbrains/websandbox/-/websandbox-1.0.10.tgz", + "integrity": "sha512-D4rF56fRGIY43SOHUWgg2IgtBqzgSriu5PjYeEep5Nh/YAPpaaTOpiPG/JoE6oGssW3NGSYdbubsLjXyTeLiwg==" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", "dev": true, @@ -21992,6 +21997,7 @@ "license": "SEE LICENSE IN LICENSE", "dependencies": { "@carbon/grid": "^11.11.0", + "@jetbrains/websandbox": "^1.0.10", "big.js": "^6.2.1", "classnames": "^2.3.1", "didi": "^10.0.1", @@ -23567,6 +23573,7 @@ "version": "file:packages/form-js-viewer", "requires": { "@carbon/grid": "^11.11.0", + "@jetbrains/websandbox": "^1.0.10", "big.js": "^6.2.1", "classnames": "^2.3.1", "didi": "^10.0.1", @@ -24262,6 +24269,11 @@ "@sinclair/typebox": "^0.27.8" } }, + "@jetbrains/websandbox": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@jetbrains/websandbox/-/websandbox-1.0.10.tgz", + "integrity": "sha512-D4rF56fRGIY43SOHUWgg2IgtBqzgSriu5PjYeEep5Nh/YAPpaaTOpiPG/JoE6oGssW3NGSYdbubsLjXyTeLiwg==" + }, "@jridgewell/gen-mapping": { "version": "0.1.1", "dev": true, diff --git a/packages/form-js-viewer/package.json b/packages/form-js-viewer/package.json index 4c5457d58..ac78b3bed 100644 --- a/packages/form-js-viewer/package.json +++ b/packages/form-js-viewer/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "@carbon/grid": "^11.11.0", + "@jetbrains/websandbox": "^1.0.10", "big.js": "^6.2.1", "classnames": "^2.3.1", "didi": "^10.0.1", diff --git a/packages/form-js-viewer/src/render/components/form-fields/JSFunctionField.js b/packages/form-js-viewer/src/render/components/form-fields/JSFunctionField.js index 6d8c3d3fc..000330f8b 100644 --- a/packages/form-js-viewer/src/render/components/form-fields/JSFunctionField.js +++ b/packages/form-js-viewer/src/render/components/form-fields/JSFunctionField.js @@ -1,72 +1,61 @@ +import Sandbox from 'websandbox'; import { useCallback, useEffect, useState } from 'preact/hooks'; -import { useExpressionEvaluation, useDeepCompareMemoize, usePrevious } from '../../hooks'; +import { useExpressionEvaluation, useDeepCompareMemoize } from '../../hooks'; import { isObject } from 'min-dash'; -const type = 'script'; export function JSFunctionField(props) { const { field, onChange } = props; - const { jsFunction, functionParameters, onLoadOnly } = field; + const { jsFunction, functionParameters } = field; - const [ loadLatch, setLoadLatch ] = useState(false); + const [ sandbox, setSandbox ] = useState(null); const paramsEval = useExpressionEvaluation(functionParameters); const params = useDeepCompareMemoize(isObject(paramsEval) ? paramsEval : {}); - const functionMemo = useCallback((params) => { - - const cleanupCallbacks = []; - - try { - - setLoadLatch(true); - const func = new Function('data', 'setValue', 'onCleanup', jsFunction); - func(params, value => onChange({ field, value }), callback => cleanupCallbacks.push(callback)); - - } catch (error) { - - // invalid expression definition, may happen during editing - if (error instanceof SyntaxError) { - return; + const rebuildSandbox = useCallback(() => { + const localApi = { + setValue: function(value) { + onChange({ field, value }); } - - console.error('Error evaluating expression:', error); - onChange({ field, value: null }); - } - - return () => { - cleanupCallbacks.forEach(fn => fn()); }; - }, [ jsFunction, field, onChange ]); - - const previousFunctionMemo = usePrevious(functionMemo); - const previousParams = usePrevious(params); + const newSandbox = Sandbox.create(localApi, { + frameContainer: '.iframe__container', + frameClassName: 'simple__iframe' + }); - useEffect(() => { + newSandbox.promise.then((sandboxInstance) => { + setSandbox(sandboxInstance); + sandboxInstance.run(` + Websandbox.connection.setLocalApi({ + onInit: () => Websandbox.connection.remote.onInit(), + onData: (data) => Websandbox.connection.remote.onData(data), + }); - // reset load latch - if (!onLoadOnly && loadLatch) { - setLoadLatch(false); - } + // Custom user code + ${jsFunction} + `); - const functionChanged = previousFunctionMemo !== functionMemo; - const paramsChanged = previousParams !== params; - const alreadyLoaded = onLoadOnly && loadLatch; + sandboxInstance.connection.remote.onInit(); + }); + }, [ jsFunction, onChange, field ]); - const shouldExecute = functionChanged || paramsChanged && !alreadyLoaded; + useEffect(() => { + rebuildSandbox(); + }, [ rebuildSandbox ]); - if (shouldExecute) { - return functionMemo(params); + useEffect(() => { + if (sandbox && sandbox.connection && sandbox.connection.remote.onData) { + sandbox.connection.remote.onData(params); } - - }, [ previousFunctionMemo, functionMemo, previousParams, params, loadLatch, onLoadOnly ]); + }, [ params, sandbox ]); return null; } JSFunctionField.config = { - type, + type: 'script', label: 'JS Function', group: 'basic-input', keyed: true,