|
using Newtonsoft.Json; |
|
using Newtonsoft.Json.Linq; |
|
using System; |
|
using System.Collections.Generic; |
|
using System.Threading.Tasks; |
|
using UnityEngine; |
|
using Vuplex.WebView; |
|
|
|
namespace UI.Interop |
|
{ |
|
#pragma warning disable CS8632 |
|
|
|
public class Event |
|
{ |
|
[JsonRequired] |
|
[JsonProperty("name")] |
|
public string Name { get; set; } |
|
|
|
[JsonRequired] |
|
[JsonProperty("data")] |
|
public JToken Data { get; set; } |
|
} |
|
|
|
public class Call |
|
{ |
|
[JsonRequired] |
|
[JsonProperty("id")] |
|
public string Id { get; set; } |
|
|
|
[JsonRequired] |
|
[JsonProperty("method")] |
|
public string Method { get; set; } |
|
|
|
[JsonRequired] |
|
[JsonProperty("params")] |
|
public JToken Params { get; set; } |
|
} |
|
|
|
public class Error |
|
{ |
|
[JsonProperty("code")] |
|
public int? Code { get; set; } |
|
|
|
[JsonRequired] |
|
[JsonProperty("message")] |
|
public string Message { get; set; } |
|
} |
|
|
|
public class Result |
|
{ |
|
[JsonRequired] |
|
[JsonProperty("id")] |
|
public string Id { get; set; } |
|
|
|
[JsonProperty("error")] |
|
public Error? Error { get; set; } |
|
|
|
[JsonProperty("result")] |
|
public JToken Value { get; set; } |
|
} |
|
|
|
public class Bridge |
|
{ |
|
private delegate object InvokeDelegate(object[] @params); |
|
private delegate void ResultDelegate(JToken result, Error? error); |
|
private delegate void EventDelegate(JToken value); |
|
|
|
private IWebView view; |
|
|
|
private readonly Dictionary<string, ResultDelegate> pending = new(); |
|
private readonly Dictionary<string, InvokeDelegate> methods = new(); |
|
private readonly Dictionary<string, List<EventDelegate>> events = new(); |
|
|
|
public void Initialize(IWebView view) |
|
{ |
|
this.view = view; |
|
this.view.MessageEmitted += OnMessage; |
|
} |
|
|
|
public void Set<T>(string name, T value) |
|
{ |
|
this.view.ExecuteJavaScript($"window.bridge.set({JsonConvert.SerializeObject(name)}, {JsonConvert.SerializeObject(value)})"); |
|
} |
|
|
|
public void On(string name, Action action) |
|
{ |
|
if (!this.events.TryGetValue(name, out List<EventDelegate> events)) |
|
{ |
|
events = this.events[name] = new List<EventDelegate>(); |
|
} |
|
events.Add((JToken value) => action()); |
|
} |
|
|
|
public void On<T>(string name, Action<T> action) |
|
{ |
|
if (!this.events.TryGetValue(name, out List<EventDelegate> events)) |
|
{ |
|
events = this.events[name] = new List<EventDelegate>(); |
|
} |
|
events.Add((JToken value) => action(value.ToObject<T>())); |
|
} |
|
|
|
public void Procedure(string name, Action action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1>(string name, Action<T1> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2>(string name, Action<T1, T2> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3>(string name, Action<T1, T2, T3> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4>(string name, Action<T1, T2, T3, T4> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5>(string name, Action<T1, T2, T3, T4, T5> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6>(string name, Action<T1, T2, T3, T4, T5, T6> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7>(string name, Action<T1, T2, T3, T4, T5, T6, T7> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8, T9>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Procedure<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(string name, Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<R>(string name, Func<R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, R>(string name, Func<T1, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, R>(string name, Func<T1, T2, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, R>(string name, Func<T1, T2, T3, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, R>(string name, Func<T1, T2, T3, T4, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, R>(string name, Func<T1, T2, T3, T4, T5, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, R>(string name, Func<T1, T2, T3, T4, T5, T6, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public void Function<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R>(string name, Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, R> action) |
|
{ |
|
this.RegisterDelegate(name, action); |
|
} |
|
|
|
public Task Invoke(string method, params object[] @params) |
|
{ |
|
var id = Guid.NewGuid().ToString(); |
|
var task = new TaskCompletionSource<bool>(); |
|
var payload = new JObject |
|
{ |
|
{ "type", "call" }, |
|
{ "data", |
|
new JObject { |
|
{ "id", id }, |
|
{ "method", method }, |
|
{ "params", new JArray(@params) } |
|
} |
|
} |
|
}; |
|
|
|
pending[id] = (JToken _, Error? error) => { |
|
this.Resolve(task, error); |
|
pending.Remove(id); |
|
}; |
|
|
|
this.Dispatch(payload); |
|
return task.Task; |
|
} |
|
|
|
public Task<T?> Invoke<T>(string method, params object[] @params) |
|
{ |
|
var id = Guid.NewGuid().ToString(); |
|
var task = new TaskCompletionSource<T?>(); |
|
var payload = new JObject |
|
{ |
|
{ "type", "call" }, |
|
{ "data", |
|
new JObject { |
|
{ "id", id }, |
|
{ "method", method }, |
|
{ "params", new JArray(@params) } |
|
} |
|
} |
|
}; |
|
|
|
pending[id] = (JToken token, Error? error) => { |
|
this.Resolve<T>(task, token, error); |
|
pending.Remove(id); |
|
}; |
|
|
|
this.Dispatch(payload); |
|
return task.Task; |
|
} |
|
|
|
public void Emit<T>(string name, T data) |
|
{ |
|
var value = JToken.FromObject(data); |
|
var payload = new JObject |
|
{ |
|
{ "type", "event" }, |
|
{ "data", |
|
new JObject { |
|
{ "name", name }, |
|
{ "data", value } |
|
} |
|
} |
|
}; |
|
this.Dispatch(payload); |
|
} |
|
|
|
private void OnMessage(object sender, EventArgs<string> e) |
|
{ |
|
this.Receive(e.Value); |
|
} |
|
|
|
private void Receive(string json) |
|
{ |
|
var message = JObject.Parse(json); |
|
if (message == null) |
|
{ |
|
throw new Exception("Invalid UI message received"); |
|
} |
|
|
|
var type = message["type"].ToString(); |
|
if (type == "result") |
|
{ |
|
var data = JsonConvert.DeserializeObject<Result>(message["data"].ToString()); |
|
if (data == null) |
|
{ |
|
throw new Exception("Failed to deserialize call message"); |
|
} |
|
|
|
if (!this.pending.TryGetValue(data.Id, out ResultDelegate handler)) |
|
{ |
|
throw new Exception($"Unknown call '{data.Id}'"); |
|
} |
|
|
|
handler(data.Value, data.Error); |
|
} |
|
else if (type == "call") |
|
{ |
|
var data = JsonConvert.DeserializeObject<Call>(message["data"].ToString()); |
|
if (data == null) |
|
{ |
|
throw new Exception("Failed to deserialize call message"); |
|
} |
|
|
|
if (!this.methods.TryGetValue(data.Method, out InvokeDelegate handler)) |
|
{ |
|
this.DispatchError(data.Id, new Exception($"Unknown method: {data.Method}")); |
|
return; |
|
} |
|
|
|
try |
|
{ |
|
var result = handler(data.Params.ToObject<object[]>()); |
|
this.DispatchResult(data.Id, result); |
|
} |
|
catch (Exception ex) |
|
{ |
|
this.DispatchError(data.Id, ex); |
|
} |
|
} |
|
else if (type == "event") |
|
{ |
|
var data = JsonConvert.DeserializeObject<Event>(message["data"].ToString()); |
|
if (data == null) |
|
{ |
|
throw new Exception("Failed to deserialize call message"); |
|
} |
|
|
|
if (!this.events.TryGetValue(data.Name, out List<EventDelegate> events)) |
|
{ |
|
return; |
|
} |
|
|
|
foreach (var e in events) |
|
{ |
|
try |
|
{ |
|
e.Invoke(data.Data); |
|
} |
|
catch (Exception) |
|
{ |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
Debug.LogWarning($"Unhandled Message: {json}"); |
|
} |
|
} |
|
|
|
private void RegisterDelegate(string name, Delegate delg) |
|
{ |
|
this.methods[name] = (object[] @params) => |
|
{ |
|
return delg.DynamicInvoke(@params); |
|
}; |
|
} |
|
|
|
private void Resolve(TaskCompletionSource<bool> task, Error? error) |
|
{ |
|
try |
|
{ |
|
if (error != null) |
|
{ |
|
task.SetException(new Exception(error.Message)); |
|
} |
|
else |
|
{ |
|
task.SetResult(true); |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
task.SetException(new Exception(ex.Message)); |
|
} |
|
} |
|
|
|
private void Resolve<T>(TaskCompletionSource<T?> task, JToken token, Error? error) |
|
{ |
|
try |
|
{ |
|
if (error != null) |
|
{ |
|
task.SetException(new Exception(error.Message)); |
|
} |
|
else |
|
{ |
|
task.SetResult(token.ToObject<T>()); |
|
} |
|
} |
|
catch (Exception ex) |
|
{ |
|
task.SetException(new Exception(ex.Message)); |
|
} |
|
} |
|
|
|
private void DispatchResult(string id, object result) |
|
{ |
|
this.Dispatch(new JObject { |
|
{ "type", "result" }, |
|
{ "data", |
|
new JObject { |
|
{ "id", id }, |
|
{ "result", JToken.FromObject(result) } |
|
} |
|
} |
|
}); |
|
} |
|
|
|
private void DispatchError(string id, Exception exception) |
|
{ |
|
this.Dispatch(new JObject { |
|
{ "type", "result" }, |
|
{ "data", |
|
new JObject { |
|
{ "id", id }, |
|
{ "error", |
|
new JObject { |
|
{ "message", exception.Message } |
|
} |
|
} |
|
} |
|
} |
|
}); |
|
} |
|
|
|
private void Dispatch(object data) |
|
{ |
|
var json = JsonConvert.SerializeObject(data); |
|
this.view.ExecuteJavaScript($"vuplex._emit('message', JSON.stringify({json}))", null); |
|
} |
|
} |
|
|
|
#pragma warning restore CS8632 |
|
} |