diff --git a/apps/kitchen-sink/src/ensemble/scripts/common.js b/apps/kitchen-sink/src/ensemble/scripts/common.js index e4eb6f875..cb0cb5c7a 100644 --- a/apps/kitchen-sink/src/ensemble/scripts/common.js +++ b/apps/kitchen-sink/src/ensemble/scripts/common.js @@ -1,5 +1,5 @@ const productTitleName = "SgrDvr"; const getDateLabel = (val) => { - return `i am a date label ${val}` -} \ No newline at end of file + return `i am a date label ${val}`; +}; diff --git a/packages/framework/src/evaluate/evaluate.ts b/packages/framework/src/evaluate/evaluate.ts index f10ec7b15..eba7f3dba 100644 --- a/packages/framework/src/evaluate/evaluate.ts +++ b/packages/framework/src/evaluate/evaluate.ts @@ -1,4 +1,12 @@ -import { isEmpty, merge, toString } from "lodash-es"; +import { + get, + has, + isEmpty, + isUndefined, + merge, + omitBy, + toString, +} from "lodash-es"; import type { ScreenContextDefinition } from "../state/screen"; import type { InvokableMethods, WidgetState } from "../state/widget"; import { @@ -19,6 +27,10 @@ export const widgetStatesToInvokables = (widgets: { }); }; +interface InvokableWindow extends Window { + [key: string]: unknown; +} + export const buildEvaluateFn = ( screen: Partial, js?: string, @@ -38,14 +50,27 @@ export const buildEvaluateFn = ( // Need to filter out invalid JS identifiers ].filter(([key, _]) => !key.includes(".")), ); - const globalBlock = screen.model?.global; - const importedScriptBlock = screen.model?.importedScripts; + + if (has(invokableObj, "ensemble")) { + const tempEnsemble = get(invokableObj, "ensemble") as { + [key: string]: unknown; + }; + (window as unknown as InvokableWindow).ensemble = omitBy( + tempEnsemble, + isUndefined, + ); + } + + const args = Object.keys(invokableObj).join(","); + + const combinedJs = ` + return evalInClosure(() => { + ${formatJs(js)} + }, {${args}}) + `; // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func - const jsFunc = new Function( - ...Object.keys(invokableObj), - addScriptBlock(formatJs(js), globalBlock, importedScriptBlock), - ); + const jsFunc = new Function(...Object.keys(invokableObj), combinedJs); // eslint-disable-next-line @typescript-eslint/no-unsafe-return return () => jsFunc(...Object.values(invokableObj)); @@ -80,24 +105,6 @@ const formatJs = (js?: string): string => { return `return ${sanitizedJs}`; }; -const addScriptBlock = ( - js: string, - globalBlock?: string, - importedScriptBlock?: string, -): string => { - let jsString = ``; - - if (importedScriptBlock) { - jsString += `${importedScriptBlock}\n\n`; - } - - if (globalBlock) { - jsString += `${globalBlock}\n\n`; - } - - return (jsString += `${js}`); -}; - /** * @deprecated Consider using useEvaluate or createBinding which will * optimize creating the evaluation context diff --git a/packages/runtime/src/runtime/screen.tsx b/packages/runtime/src/runtime/screen.tsx index e0b329f16..58017c476 100644 --- a/packages/runtime/src/runtime/screen.tsx +++ b/packages/runtime/src/runtime/screen.tsx @@ -91,6 +91,52 @@ export const EnsembleScreen: React.FC = ({ }; }, [screen.customWidgets]); + useEffect(() => { + const globalBlock = screen.global; + const importedScripts = screen.importedScripts; + + const isScriptExist = document.getElementById("custom-scope-script"); + + const jsString = ` + // Create a base object and pin its reference + const ensembleObj = {}; + Object.defineProperty(window, 'ensemble', { + get: () => ensembleObj, + set: (value) => { + // Copy properties instead of replacing reference + Object.assign(ensembleObj, value); + return true; + }, + configurable: true, + enumerable: true + }); + + const createEvalClosure = () => { + ${importedScripts || ""} + ${globalBlock || ""} + + return (scriptToExecute, context) => { + with (context) { + return eval('(' + scriptToExecute.toString() + ')()'); + } + } + } + + const evalInClosure = createEvalClosure() + `; + + if (isScriptExist) { + isScriptExist.textContent = jsString; + } else { + const script = document.createElement("script"); + script.id = "custom-scope-script"; + script.type = "text/javascript"; + script.textContent = jsString; + + document.body.appendChild(script); + } + }, [screen.global, screen.importedScripts]); + if (!isInitialized) { return null; }