This is a mobile optimized page that loads fast, if you want to load the real page, click this text.

Мануал [C#][Client] Кастомный ExecuteJS из поддержкой результата, Storage C#

DaVilka

Старожил
BackEnd developer
16 Сен 2020
771
284
128
Кастомная реализация вызова js кода на клиенте из C# клиента, которая так же поддерживает результат вызова.
Для начала в index.js или другом файле добавим ивент который будет просто вызывать eval и возвращать результат
в виде json строки.

JavaScript:
mp.events.add("executeJS", (code, id) => {
    let result = eval(code)
    if(id != undefined) mp.events.callLocal("executeJSCallback", id, JSON.stringify(result));
});

На стороне клиента создадим .cs файл ExecuteJS.cs
И запишем него код

Код:
using HardLife.Utils;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace HardLife.Utils
{
    internal class ExecuteJSDate
    {
        public Type Type { get; private set; }
        public Action<object> Action { get; private set; }
        public ExecuteJSDate(Type type, Action<object> action)
        {
            Type = type;
            Action = action;
        }
    }
}
internal static class ExecuteJS
{
    private static readonly Dictionary<int, ExecuteJSDate> _callback = new Dictionary<int, ExecuteJSDate>();
    private static int _id = 0;
    static ExecuteJS()
    {
        RAGE.Events.Add("executeJSCallback", ExecuteJSCallback);
    }
    private static void ExecuteJSCallback(object[] args)
    {
        //RAGE.Ui.Console.Log(RAGE.Ui.ConsoleVerbosity.Info, $"ExecuteJSCallback: {args[1]}");
        if (_callback.TryGetValue((int)args[0], out ExecuteJSDate data) && data.Action != null)
        {
            object result = Convert.ChangeType(JsonConvert.DeserializeObject((string)args[1]), data.Type);
            data.Action.Invoke(result);
        }
        if (_callback.ContainsKey((int)args[0])) _callback.Remove((int)args[0]);
    }
    public static void Execute<T>(string code, Action<T> result = null)
    {
        void action(object o) => result?.Invoke((T)o);
        _callback.Add(_id, new ExecuteJSDate(typeof(T), action));
        RAGE.Events.CallLocal("executeJS", code, _id);
        _id++;
    }
    public static void Execute(string code)
    {
        RAGE.Events.CallLocal("executeJS", code);
    }
}
Апи просто, две функции Execute
Первая предназначена для вызова функций которые хотят получить результат
C#:
Execute<T>(string code, Action<T> result = null)
Вторая просто для вызова кода на клиенте
C#:
public static void Execute(string code)

Пример использования:
Если на клиенте есть условная функция:
JavaScript:
function test(){
    return 228;
}
То вызов будет таким, где в r будет передано число (<int>) которое вернет функция на клиенте js
C#:
ExecuteJS.Execute<int>("test()", (r) => { RAGE.Ui.Console.Log(RAGE.Ui.ConsoleVerbosity.Info, $"Result: {r}"); });
Вызов без получения результата.
C#:
ExecuteJS.Execute("test()");
Пример реализации Strorage на C# с использованием ExecuteJS:

API - две функции, первая для записи по ключу, вторая для чтения
Как юзать:

Код:
internal class Test
{
    public string Name { get; set; }
    public int Id {  get; set; }
    public bool IsAlive { get; set; }
}
//Записываем обьект с ключем "test"
Storage.Write("test", new Test() { IsAlive = false, Id = 5435, Name = "Hello World" });
//получаем обьект с ключем "test"
Storage.Read("test", (Test t) =>
{
    RAGE.Ui.Console.Log(RAGE.Ui.ConsoleVerbosity.Info, $"Result: {JsonConvert.SerializeObject(t)}");
});
Код:
using Newtonsoft.Json;
using System;

namespace HardLife.Utils
{
    internal static class Storage
    {
        public static void Write<T>(string key, T data)
        {
            ExecuteJS.Execute($"mp.storage.data.{key} = '{JsonConvert.SerializeObject(data)}';");
            ExecuteJS.Execute("mp.storage.flush();");
        }
        public static void Read<T>(string key, Action<T> result)
        {
            ExecuteJS.Execute<string>($"mp.storage.data.{key}", (r) => { result?.Invoke(JsonConvert.DeserializeObject<T>(r)); });
        }
    }
}

ExecuteJS Github
 

Bloodlust

Специалист
25 Апр 2021
311
91
112
Круто
Чисто ради интереса, для чего это может понадобиться?
 

DaVilka

Старожил
BackEnd developer
16 Сен 2020
771
284
128
Круто
Чисто ради интереса, для чего это может понадобиться?
Ну вопевых storage, его нету на c#, токо js, так же есть не очень популярные апи гта которые работают не корректно на шарпе(не тот типо возаращаемого значения, или вообще не все ref можно передать и тд), а на жс норм
 
Реакции: vpn и Bloodlust

sonnyk

Участник портала
26 Ноя 2022
60
8
43
А какой смысл от этого? Ведь можно хранилище сделать на js и потом эвентить с c# на js, будет аналогичный эффект
А юзать eval я вообще не рекомендую, ибо крайне не безопасный
 

Dmitry_V

Гений
23 Июн 2023
2,399
397
131
28
А нахуя апать темы двухлетней давности?)
Ты если хочешь что-то предложить - залей в ресурсы.
 

sonnyk

Участник портала
26 Ноя 2022
60
8
43
Я просто сказал, что данный метод крайне небезопасный
Двухлетней давности, а что то поменялось, может я чего то не знаю, добавлялись только апишки и всё?
 

DaVilka

Старожил
BackEnd developer
16 Сен 2020
771
284
128
смысл в том что бы делать клиентку только на c#, а в чем опасность использования eval?
 

sonnyk

Участник портала
26 Ноя 2022
60
8
43
В том, что уже была ситуация со сборкой state99, где eval использовался и этим воспользовались читеры, они просто в эксекуторе передовали команды через eval и наносили вред серверу, вплоть до выкачивания файлов и запуском скримеров на экран игроков
 

DaVilka

Старожил
BackEnd developer
16 Сен 2020
771
284
128
какие файлы, исходники клиентки? Дак они дампятся и без евалов всяких, скримеры были и на редаге, а на ней нету eval