Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Execute dynamically evaluated JavaScript (callbacks, `_call_script`, and `_call_function`) within an explicit function environment providing required runtime dependencies (`addEvents`, `ReflexEvent`, `applyEventActions`, and `refs`), avoiding reliance on module lexical scope, and replace dynamic component evaluation with native dynamic imports.
4 changes: 2 additions & 2 deletions packages/reflex-base/src/reflex_base/utils/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,8 @@ def format_queue_events(

The resulting code can be passed to interfaces that expect a callback
function and when triggered it dispatches the events through `addEvents`.
The name resolves via the attached imports in compiled modules and via a
local alias in the `applyResultCallback` eval context.
The name resolves via the attached imports in compiled modules and via an
explicit environment in the `applyResultCallback` execution context.

Args:
events: The events to queue.
Expand Down
172 changes: 136 additions & 36 deletions packages/reflex-frontend/src/utils/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ export const evalReactComponent = async (component) => {
}
const encodedJs = encodeURIComponent(component);
const dataUri = "data:text/javascript;charset=utf-8," + encodedJs;
const module = await eval(`import(dataUri)`);
const module = await import(/* @vite-ignore */ dataUri);
return module.default;
};

Expand Down Expand Up @@ -508,14 +508,117 @@ function urlFrom(string) {
}
}

/**
* Cached fallback evaluator for arbitrary scripts and statement fallbacks, constructed
* lazily on first use without access to module or caller lexical scope.
*/
let fallbackEval = null;

/**
* Get or initialize the cached script evaluator.
*
* @returns The cached script evaluator function.
*/
const getScriptEvaluator = () => {
if (!fallbackEval) {
fallbackEval = new Function(
"addEvents",
"ReflexEvent",
"applyEventActions",
"refs",
"socket",
"token",
`"use strict"; return eval(arguments[6]);`,
);
}
return fallbackEval;
};

/**
* Execute a complete JavaScript script within an explicit function environment,
* preserving ECMAScript script completion value semantics and strict-mode isolation.
*
* @param code The JavaScript script to evaluate.
* @param env Explicit environment containing runtime dependencies.
* @returns The evaluated script completion value.
*/
const executeScriptInExplicitEnvironment = (code, env) => {
const evaluator = getScriptEvaluator();
return evaluator(
env.addEvents,
env.ReflexEvent,
env.applyEventActions,
env.refs,
env.socket,
env.token,
code,
);
};

/**
* Evaluate dynamic JavaScript within an explicit function environment.
*
* Tries expression evaluation first to avoid eval entirely; if the code contains
* statement syntax that cannot be evaluated as an expression, falls back to the
* isolated script evaluator while preserving completion value semantics and providing
* only explicit dependencies.
*
* @param code The JavaScript code to evaluate.
* @param env Explicit environment containing runtime dependencies.
* @returns The evaluated completion value.
*/
const evaluateInExplicitEnvironment = (code, env) => {
const trimmed = code.trim().replace(/;+$/, "");
let fn;
try {
fn = new Function(
"addEvents",
"ReflexEvent",
"applyEventActions",
"refs",
"socket",
"token",
Comment thread
somiljain2006 marked this conversation as resolved.
Resolved
`"use strict"; return (${trimmed});`,
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
);
} catch (e) {
if (e instanceof SyntaxError) {
return executeScriptInExplicitEnvironment(code, env);
}
throw e;
}
return fn(
env.addEvents,
env.ReflexEvent,
env.applyEventActions,
env.refs,
env.socket,
env.token,
);
};

/**
* Construct the explicit execution environment for dynamic JavaScript.
*
* @param addEvents Late-bound event loop dispatcher.
* @returns An object containing explicit runtime dependencies.
*/
const createExecutionContext = (addEvents, socket) => ({
addEvents,
socket,
token,
ReflexEvent: typeof ReflexEvent !== "undefined" ? ReflexEvent : undefined,
applyEventActions:
typeof applyEventActions !== "undefined" ? applyEventActions : undefined,
refs: typeof refs !== "undefined" ? refs : (globalThis.refs ?? {}),
});

/**
* Invoke an event's result callback, if it declared one.
*
* The callback arrives as a string built by ``format_queue_events``, which
* references ``queueEvents``/``processEvent`` (module-level here) plus ``socket``,
* ``navigate`` and ``params``. Those three MUST stay the parameter names below:
* the ``eval`` resolves them from this function's scope, so renaming them breaks
* every callback.
* The callback arrives as a function or a string built by ``format_queue_events``,
* which references ``addEvents`` and ``ReflexEvent``. It is evaluated within an
* explicit function environment rather than relying on module or caller lexical scope.
*
* @param event The event whose callback to run.
* @param eval_result The value to pass to the callback, awaited if thenable.
* @param socket The socket object to send events on.
Expand All @@ -532,20 +635,21 @@ const applyResultCallback = async (
if (!event.payload.callback) {
return;
}
// Eval'd user code dispatches through addEvents like compiled event triggers
// do; late-bound so a remounted EventLoopProvider is picked up. Declared in
// every scope holding an eval, since that is the only scope chain the eval'd
// code can resolve it from.
// User code dispatches through addEvents like compiled event triggers
// do; late-bound so a remounted EventLoopProvider is picked up.
const addEvents = (...args) => eventLoop.addEvents(...args);
const final_result =
!!eval_result && typeof eval_result.then === "function"
? await eval_result
: eval_result;
const callback =
typeof event.payload.callback === "string"
? eval(event.payload.callback)
: event.payload.callback;
callback(final_result);
let callback = event.payload.callback;
if (typeof callback === "string") {
callback = evaluateInExplicitEnvironment(
callback,
createExecutionContext(addEvents, socket),
);
}
await callback(final_result);
};

/**
Expand Down Expand Up @@ -588,10 +692,8 @@ const storageDefault = (state_key) => {
* @param params The params object from useParams
*/
export const applyEvent = async (event, socket, navigate, params) => {
// Eval'd user code dispatches through addEvents like compiled event triggers
// do; late-bound so a remounted EventLoopProvider is picked up. Declared in
// every scope holding an eval, since that is the only scope chain the eval'd
// code can resolve it from.
// User code dispatches through addEvents like compiled event triggers
// do; late-bound so a remounted EventLoopProvider is picked up.
const addEvents = (...args) => eventLoop.addEvents(...args);
// Handle special events
if (event.name === "_redirect") {
Expand Down Expand Up @@ -761,18 +863,17 @@ export const applyEvent = async (event, socket, navigate, params) => {
return;
}

if (
event.name === "_call_function" &&
typeof event.payload.function !== "string"
) {
if (event.name === "_call_function") {
try {
await applyResultCallback(
event,
event.payload.function(),
socket,
navigate,
params,
);
let func = event.payload.function;
if (typeof func === "string") {
func = evaluateInExplicitEnvironment(
func,
createExecutionContext(addEvents, socket),
);
}
const eval_result = func();
await applyResultCallback(event, eval_result, socket, navigate, params);
} catch (e) {
console.log("_call_function", e);
if (window?.onerror) {
Expand All @@ -782,13 +883,12 @@ export const applyEvent = async (event, socket, navigate, params) => {
return;
}

if (event.name === "_call_script" || event.name === "_call_function") {
if (event.name === "_call_script") {
try {
const eval_result =
event.name === "_call_script"
? eval(event.payload.javascript_code)
: eval(event.payload.function)();

const eval_result = executeScriptInExplicitEnvironment(
event.payload.javascript_code,
createExecutionContext(addEvents, socket),
);
await applyResultCallback(event, eval_result, socket, navigate, params);
} catch (e) {
console.log("_call_script", e);
Expand Down
Loading
Loading