2024-03-12 16:47:58 +00:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
|
using System.Text.Encodings.Web;
|
2023-07-14 17:19:41 +00:00
|
|
|
using System.Text.Json;
|
2023-02-03 13:29:44 +00:00
|
|
|
using System.Text.Json.Serialization;
|
2023-03-15 11:29:07 +00:00
|
|
|
using System.Text.RegularExpressions;
|
2024-03-12 16:47:58 +00:00
|
|
|
using System.Text.Unicode;
|
2023-10-13 08:34:21 +00:00
|
|
|
using Elsa.Expressions.Helpers;
|
2023-01-20 18:26:38 +00:00
|
|
|
using Elsa.Expressions.Models;
|
2022-12-31 15:16:43 +00:00
|
|
|
using Elsa.Extensions;
|
2023-02-13 11:15:20 +00:00
|
|
|
using Elsa.JavaScript.Contracts;
|
2024-01-31 20:10:13 +00:00
|
|
|
using Elsa.JavaScript.Helpers;
|
2022-05-26 10:47:31 +00:00
|
|
|
using Elsa.JavaScript.Notifications;
|
|
|
|
|
using Elsa.JavaScript.Options;
|
2023-03-03 21:47:23 +00:00
|
|
|
using Elsa.Mediator.Contracts;
|
2024-04-25 08:26:35 +00:00
|
|
|
using Esprima;
|
|
|
|
|
using Esprima.Ast;
|
2022-12-15 21:53:56 +00:00
|
|
|
using Humanizer;
|
2022-01-12 10:43:46 +00:00
|
|
|
using Jint;
|
2024-01-31 18:48:00 +00:00
|
|
|
using Jint.Runtime.Interop;
|
2024-04-25 08:26:35 +00:00
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
2024-03-18 22:20:12 +00:00
|
|
|
using Microsoft.Extensions.Configuration;
|
2022-01-12 10:43:46 +00:00
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
|
2023-02-03 13:29:44 +00:00
|
|
|
// ReSharper disable ConvertClosureToMethodGroup
|
2023-02-13 11:15:20 +00:00
|
|
|
namespace Elsa.JavaScript.Services;
|
2023-01-04 19:36:38 +00:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Provides a JavaScript evaluator using Jint.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class JintJavaScriptEvaluator : IJavaScriptEvaluator
|
2022-01-12 10:43:46 +00:00
|
|
|
{
|
2023-07-14 17:19:41 +00:00
|
|
|
private readonly INotificationSender _mediator;
|
2024-04-25 08:26:35 +00:00
|
|
|
private readonly IMemoryCache _memoryCache;
|
2023-01-04 19:36:38 +00:00
|
|
|
private readonly JintOptions _jintOptions;
|
2024-03-18 22:20:12 +00:00
|
|
|
private readonly IConfiguration _configuration;
|
2023-01-04 19:36:38 +00:00
|
|
|
|
2022-11-30 20:52:41 +00:00
|
|
|
/// <summary>
|
2023-01-04 19:36:38 +00:00
|
|
|
/// Constructor.
|
2022-11-30 20:52:41 +00:00
|
|
|
/// </summary>
|
2024-04-25 08:26:35 +00:00
|
|
|
public JintJavaScriptEvaluator(IConfiguration configuration, INotificationSender mediator, IOptions<JintOptions> scriptOptions, IMemoryCache memoryCache)
|
2022-01-12 10:43:46 +00:00
|
|
|
{
|
2023-01-04 19:36:38 +00:00
|
|
|
_mediator = mediator;
|
2024-04-25 08:26:35 +00:00
|
|
|
_memoryCache = memoryCache;
|
2023-01-04 19:36:38 +00:00
|
|
|
_jintOptions = scriptOptions.Value;
|
2024-03-18 22:20:12 +00:00
|
|
|
_configuration = configuration;
|
2023-01-04 19:36:38 +00:00
|
|
|
}
|
2022-01-12 10:43:46 +00:00
|
|
|
|
2023-01-04 19:36:38 +00:00
|
|
|
/// <inheritdoc />
|
|
|
|
|
public async Task<object?> EvaluateAsync(string expression,
|
|
|
|
|
Type returnType,
|
|
|
|
|
ExpressionExecutionContext context,
|
2024-01-30 20:22:32 +00:00
|
|
|
ExpressionEvaluatorOptions? options = default,
|
2023-01-04 19:36:38 +00:00
|
|
|
Action<Engine>? configureEngine = default,
|
|
|
|
|
CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
2023-12-14 09:45:37 +00:00
|
|
|
var engine = await GetConfiguredEngine(configureEngine, context, options, cancellationToken);
|
2023-01-04 19:36:38 +00:00
|
|
|
var result = ExecuteExpressionAndGetResult(engine, expression);
|
2023-11-03 19:28:10 +00:00
|
|
|
|
2023-10-13 08:34:21 +00:00
|
|
|
return result.ConvertTo(returnType);
|
2023-01-04 19:36:38 +00:00
|
|
|
}
|
2022-01-12 10:43:46 +00:00
|
|
|
|
2024-01-30 20:22:32 +00:00
|
|
|
private async Task<Engine> GetConfiguredEngine(Action<Engine>? configureEngine, ExpressionExecutionContext context, ExpressionEvaluatorOptions? options, CancellationToken cancellationToken)
|
2023-01-04 19:36:38 +00:00
|
|
|
{
|
2024-01-30 20:22:32 +00:00
|
|
|
options ??= new ExpressionEvaluatorOptions();
|
|
|
|
|
|
2023-01-04 19:36:38 +00:00
|
|
|
var engine = new Engine(opts =>
|
2022-01-12 10:43:46 +00:00
|
|
|
{
|
2023-01-04 19:36:38 +00:00
|
|
|
if (_jintOptions.AllowClrAccess)
|
|
|
|
|
opts.AllowClr();
|
2024-01-31 18:48:00 +00:00
|
|
|
|
|
|
|
|
// Wrap objects in ObjectWrapper instances and set their prototype to Array.prototype if they are array-like.
|
|
|
|
|
opts.SetWrapObjectHandler((engine, target, type) =>
|
|
|
|
|
{
|
|
|
|
|
var instance = new ObjectWrapper(engine, target);
|
2024-01-31 20:10:13 +00:00
|
|
|
|
|
|
|
|
if (ObjectArrayHelper.DetermineIfObjectIsArrayLikeClrCollection(target.GetType()))
|
|
|
|
|
instance.Prototype = engine.Intrinsics.Array.PrototypeObject;
|
2024-01-31 18:48:00 +00:00
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
|
});
|
2023-01-04 19:36:38 +00:00
|
|
|
});
|
2022-01-12 10:43:46 +00:00
|
|
|
|
2023-01-04 19:36:38 +00:00
|
|
|
configureEngine?.Invoke(engine);
|
2023-02-03 13:29:44 +00:00
|
|
|
|
2023-01-04 19:36:38 +00:00
|
|
|
// Add common functions.
|
2023-02-03 13:29:44 +00:00
|
|
|
engine.SetValue("getWorkflowInstanceId", (Func<string>)(() => context.GetActivityExecutionContext().WorkflowExecutionContext.Id));
|
|
|
|
|
engine.SetValue("setCorrelationId", (Action<string?>)(value => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId = value));
|
|
|
|
|
engine.SetValue("getCorrelationId", (Func<string?>)(() => context.GetActivityExecutionContext().WorkflowExecutionContext.CorrelationId));
|
2023-11-03 19:28:10 +00:00
|
|
|
engine.SetValue("setVariable", (Action<string, object>)((name, value) => context.SetVariableInScope(name, value)));
|
|
|
|
|
engine.SetValue("getVariable", (Func<string, object?>)(name => context.GetVariableInScope(name)));
|
|
|
|
|
engine.SetValue("getInput", (Func<string, object?>)(name => context.GetInput(name)));
|
|
|
|
|
engine.SetValue("getOutputFrom", (Func<string, string?, object?>)((activityIdName, outputName) => context.GetOutput(activityIdName, outputName)));
|
|
|
|
|
engine.SetValue("getLastResult", (Func<object?>)(() => context.GetLastResult()));
|
2023-09-06 20:04:58 +00:00
|
|
|
|
2023-03-24 21:25:48 +00:00
|
|
|
// Create variable getters and setters for each variable.
|
|
|
|
|
CreateVariableAccessors(engine, context);
|
2023-09-06 20:04:58 +00:00
|
|
|
|
2023-09-06 11:50:08 +00:00
|
|
|
// Create workflow input accessors - only if the current activity is not part of a composite activity definition.
|
|
|
|
|
// Otherwise, the workflow input accessors will hide the composite activity input accessors which rely on variable accessors created above.
|
2023-11-03 19:28:10 +00:00
|
|
|
CreateWorkflowInputAccessors(engine, context);
|
2023-02-03 13:29:44 +00:00
|
|
|
|
2023-06-09 20:10:56 +00:00
|
|
|
// Create output getters for each activity.
|
2023-09-06 20:04:58 +00:00
|
|
|
CreateActivityOutputAccessors(engine, context);
|
2024-01-30 20:22:32 +00:00
|
|
|
|
2023-12-14 09:45:37 +00:00
|
|
|
// Create argument getters for each argument.
|
|
|
|
|
foreach (var argument in options.Arguments)
|
|
|
|
|
engine.SetValue($"get{argument.Key}", (Func<object?>)(() => argument.Value));
|
2024-01-30 20:22:32 +00:00
|
|
|
|
2023-12-14 09:45:37 +00:00
|
|
|
// Add common functions.
|
2023-02-03 13:29:44 +00:00
|
|
|
engine.SetValue("isNullOrWhiteSpace", (Func<string, bool>)(value => string.IsNullOrWhiteSpace(value)));
|
|
|
|
|
engine.SetValue("isNullOrEmpty", (Func<string, bool>)(value => string.IsNullOrEmpty(value)));
|
|
|
|
|
engine.SetValue("toJson", (Func<object, string>)(value => Serialize(value)));
|
2023-05-08 20:38:59 +00:00
|
|
|
engine.SetValue("parseGuid", (Func<string, Guid>)(value => Guid.Parse(value)));
|
|
|
|
|
engine.SetValue("newGuid", (Func<Guid>)(() => Guid.NewGuid()));
|
|
|
|
|
engine.SetValue("newGuidString", (Func<string>)(() => Guid.NewGuid().ToString()));
|
|
|
|
|
engine.SetValue("newShortGuid", (Func<string>)(() => Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "")));
|
2023-09-06 20:04:58 +00:00
|
|
|
|
2023-08-14 12:34:40 +00:00
|
|
|
// Deprecated, use newGuidString instead.
|
|
|
|
|
engine.SetValue("getGuidString", (Func<string>)(() => Guid.NewGuid().ToString()));
|
2023-09-06 20:04:58 +00:00
|
|
|
|
2023-08-14 12:34:40 +00:00
|
|
|
// Deprecated, use newShortGuid instead.
|
|
|
|
|
engine.SetValue("getShortGuid", (Func<string>)(() => Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "")));
|
2022-01-12 10:43:46 +00:00
|
|
|
|
2024-03-18 22:20:12 +00:00
|
|
|
// Create configuration value accessor
|
|
|
|
|
if (_jintOptions.AllowConfigurationAccess)
|
|
|
|
|
engine.SetValue("getConfig", (Func<string, object?>)(name => _configuration.GetSection(name).Value));
|
|
|
|
|
|
2023-01-04 19:36:38 +00:00
|
|
|
// Add common .NET types.
|
|
|
|
|
engine.RegisterType<DateTime>();
|
|
|
|
|
engine.RegisterType<DateTimeOffset>();
|
|
|
|
|
engine.RegisterType<TimeSpan>();
|
2023-02-03 13:29:44 +00:00
|
|
|
engine.RegisterType<Guid>();
|
2022-01-12 10:43:46 +00:00
|
|
|
|
2023-10-07 10:47:36 +00:00
|
|
|
// Invoke registered configuration callback.
|
|
|
|
|
_jintOptions.ConfigureEngineCallback(engine, context);
|
|
|
|
|
|
2023-01-04 19:36:38 +00:00
|
|
|
// Allow listeners invoked by the mediator to configure the engine.
|
2023-07-14 17:19:41 +00:00
|
|
|
await _mediator.SendAsync(new EvaluatingJavaScript(engine, context), cancellationToken);
|
2022-01-12 10:43:46 +00:00
|
|
|
|
2023-01-04 19:36:38 +00:00
|
|
|
return engine;
|
|
|
|
|
}
|
2022-12-15 21:53:56 +00:00
|
|
|
|
2023-11-03 19:28:10 +00:00
|
|
|
private void CreateWorkflowInputAccessors(Engine engine, ExpressionExecutionContext context)
|
2023-09-06 11:50:08 +00:00
|
|
|
{
|
2023-11-03 19:28:10 +00:00
|
|
|
if (context.IsInsideCompositeActivity())
|
|
|
|
|
return;
|
2023-09-06 11:50:08 +00:00
|
|
|
|
2024-01-17 21:11:03 +00:00
|
|
|
var inputs = context.GetWorkflowInputs().ToDictionary(x => x.Name);
|
2024-02-02 20:09:48 +00:00
|
|
|
|
|
|
|
|
if (!context.TryGetWorkflowExecutionContext(out var workflowExecutionContext))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var inputDefinitions = workflowExecutionContext.Workflow.Inputs;
|
2023-09-06 11:50:08 +00:00
|
|
|
|
2024-01-17 21:11:03 +00:00
|
|
|
foreach (var inputDefinition in inputDefinitions)
|
|
|
|
|
{
|
|
|
|
|
var input = inputs.GetValueOrDefault(inputDefinition.Name);
|
|
|
|
|
engine.SetValue($"get{inputDefinition.Name}", (Func<object?>)(() => input?.Value));
|
|
|
|
|
}
|
2023-08-22 12:59:31 +00:00
|
|
|
}
|
2023-06-09 20:10:56 +00:00
|
|
|
|
2023-03-24 21:25:48 +00:00
|
|
|
private static void CreateVariableAccessors(Engine engine, ExpressionExecutionContext context)
|
2023-01-04 19:36:38 +00:00
|
|
|
{
|
2023-11-03 19:28:10 +00:00
|
|
|
var variableNames = context.GetVariableNamesInScope().ToList();
|
2022-12-15 21:53:56 +00:00
|
|
|
|
2023-03-22 21:50:26 +00:00
|
|
|
foreach (var variableName in variableNames)
|
2022-01-12 10:43:46 +00:00
|
|
|
{
|
2023-03-22 21:50:26 +00:00
|
|
|
var pascalName = variableName.Pascalize();
|
2023-11-03 19:28:10 +00:00
|
|
|
engine.SetValue($"get{pascalName}", (Func<object?>)(() => context.GetVariableInScope(variableName)));
|
|
|
|
|
engine.SetValue($"set{pascalName}", (Action<object?>)(value => context.SetVariableInScope(variableName, value)));
|
2023-03-22 21:50:26 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-06 20:04:58 +00:00
|
|
|
private static void CreateActivityOutputAccessors(Engine engine, ExpressionExecutionContext context)
|
2023-06-09 20:10:56 +00:00
|
|
|
{
|
2023-11-03 19:28:10 +00:00
|
|
|
var activityOutputs = context.GetActivityOutputs();
|
2023-08-02 20:52:02 +00:00
|
|
|
|
2023-11-03 19:28:10 +00:00
|
|
|
foreach (var activityOutput in activityOutputs)
|
|
|
|
|
foreach (var outputName in activityOutput.OutputNames)
|
|
|
|
|
engine.SetValue($"get{outputName}From{activityOutput.ActivityName}", (Func<object?>)(() => context.GetOutput(activityOutput.ActivityId, outputName)));
|
2023-06-09 20:10:56 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-25 08:26:35 +00:00
|
|
|
private object? ExecuteExpressionAndGetResult(Engine engine, string expression)
|
2023-01-04 19:36:38 +00:00
|
|
|
{
|
2024-04-25 08:26:35 +00:00
|
|
|
var cacheKey = "jint:script:" + expression.GetHashCode(StringComparison.Ordinal);
|
|
|
|
|
|
|
|
|
|
var parsedScript = _memoryCache.GetOrCreate(cacheKey, entry =>
|
|
|
|
|
{
|
|
|
|
|
if (_jintOptions.ScriptCacheTimeout.HasValue)
|
|
|
|
|
entry.SetAbsoluteExpiration(_jintOptions.ScriptCacheTimeout.Value);
|
|
|
|
|
|
2024-04-26 18:07:34 +00:00
|
|
|
var prepareOptions = new ScriptPreparationOptions
|
|
|
|
|
{
|
|
|
|
|
ParsingOptions = new ScriptParsingOptions
|
|
|
|
|
{
|
|
|
|
|
AllowReturnOutsideFunction = true
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return Engine.PrepareScript(expression, options: prepareOptions);
|
2024-04-25 08:26:35 +00:00
|
|
|
})!;
|
|
|
|
|
|
|
|
|
|
var result = engine.Evaluate(parsedScript);
|
2023-01-04 19:36:38 +00:00
|
|
|
return result.ToObject();
|
|
|
|
|
}
|
2023-02-03 13:29:44 +00:00
|
|
|
|
2024-03-12 16:47:58 +00:00
|
|
|
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)")]
|
2023-02-21 10:47:20 +00:00
|
|
|
private static string Serialize(object value)
|
2023-02-03 13:29:44 +00:00
|
|
|
{
|
2024-03-12 16:47:58 +00:00
|
|
|
var options = new JsonSerializerOptions
|
|
|
|
|
{
|
|
|
|
|
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
|
|
|
|
|
};
|
2023-02-03 13:29:44 +00:00
|
|
|
options.Converters.Add(new JsonStringEnumConverter());
|
|
|
|
|
|
|
|
|
|
return JsonSerializer.Serialize(value, options);
|
|
|
|
|
}
|
2022-01-12 10:43:46 +00:00
|
|
|
}
|