using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using System.Text; using Acornima.Ast; using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; using Elsa.JavaScript.Contracts; using Elsa.JavaScript.Helpers; using Elsa.JavaScript.Notifications; using Elsa.JavaScript.ObjectConverters; using Elsa.JavaScript.Options; using Elsa.Mediator.Contracts; using Jint; using Jint.Runtime.Interop; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; // ReSharper disable ConvertClosureToMethodGroup namespace Elsa.JavaScript.Services; /// /// Provides a JavaScript evaluator using Jint. /// public class JintJavaScriptEvaluator(IConfiguration configuration, INotificationSender mediator, IOptions scriptOptions, IMemoryCache memoryCache) : IJavaScriptEvaluator { private readonly JintOptions _jintOptions = scriptOptions.Value; /// [RequiresUnreferencedCode("The Jint library uses reflection and can't be statically analyzed.")] public async Task EvaluateAsync(string expression, Type returnType, ExpressionExecutionContext context, ExpressionEvaluatorOptions? options = default, Action? configureEngine = default, CancellationToken cancellationToken = default) { var engine = await GetConfiguredEngine(configureEngine, context, options, cancellationToken); await mediator.SendAsync(new EvaluatingJavaScript(engine, context, expression), cancellationToken); var result = ExecuteExpressionAndGetResult(engine, expression); await mediator.SendAsync(new EvaluatedJavaScript(engine, context, expression, result), cancellationToken); return result.ConvertTo(returnType); } private async Task GetConfiguredEngine(Action? configureEngine, ExpressionExecutionContext context, ExpressionEvaluatorOptions? options, CancellationToken cancellationToken) { options ??= new ExpressionEvaluatorOptions(); var engineOptions = new Jint.Options { ExperimentalFeatures = ExperimentalFeature.TaskInterop }; if (_jintOptions.AllowClrAccess) engineOptions.AllowClr(); ConfigureClrAccess(engineOptions); ConfigureObjectWrapper(engineOptions); ConfigureObjectConverters(engineOptions); engineOptions.Interop.ObjectConverters.Add(new ByteArrayConverter()); await mediator.SendAsync(new CreatingJavaScriptEngine(engineOptions, context), cancellationToken); _jintOptions.ConfigureEngineOptionsCallback(engineOptions, context); var engine = new Engine(engineOptions); configureEngine?.Invoke(engine); ConfigureArgumentGetters(engine, options); ConfigureConfigurationAccess(engine); _jintOptions.ConfigureEngineCallback(engine, context); return engine; } private void ConfigureClrAccess(Jint.Options options) { if (_jintOptions.AllowClrAccess) options.AllowClr(); } private void ConfigureObjectWrapper(Jint.Options options) { options.SetWrapObjectHandler((engine, target, type) => { var instance = ObjectWrapper.Create(engine, target); if (ObjectArrayHelper.DetermineIfObjectIsArrayLikeClrCollection(target.GetType())) instance.Prototype = engine.Intrinsics.Array.PrototypeObject; return instance; }); } private void ConfigureObjectConverters(Jint.Options options) { options.Interop.ObjectConverters.AddRange([new ByteArrayConverter()]); } private void ConfigureArgumentGetters(Engine engine, ExpressionEvaluatorOptions options) { foreach (var argument in options.Arguments) engine.SetValue($"get{argument.Key}", (Func)(() => argument.Value)); } private void ConfigureConfigurationAccess(Engine engine) { if (_jintOptions.AllowConfigurationAccess) engine.SetValue("getConfig", (Func)(name => configuration.GetSection(name).Value)); } private object? ExecuteExpressionAndGetResult(Engine engine, string expression) { var preparedScript = GetOrCreatePrepareScript(expression); var result = engine.Evaluate(preparedScript); return result.UnwrapIfPromise().ToObject(); } private Prepared