diff --git a/src/ElectronNET.API/API/IpcMain.cs b/src/ElectronNET.API/API/IpcMain.cs index 239098b8..0260a889 100644 --- a/src/ElectronNET.API/API/IpcMain.cs +++ b/src/ElectronNET.API/API/IpcMain.cs @@ -1,12 +1,13 @@ -using ElectronNET.API.Serialization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; - namespace ElectronNET.API { + using System; + using System.Diagnostics; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Threading.Tasks; + using ElectronNET.Serialization; + /// /// Communicate asynchronously from the main process to renderer processes. /// @@ -14,6 +15,18 @@ public sealed class IpcMain { private static IpcMain _ipcMain; private static object _syncRoot = new object(); + private static readonly JsonSerializerOptions BoxedObjectSerializationOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = false, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), + new JsonToBoxedPrimitivesConverter(), + } + }; + internal IpcMain() { @@ -50,24 +63,23 @@ public async Task On(string channel, Action listener) BridgeConnector.Socket.Off(channel); BridgeConnector.Socket.On(channel, (args) => { - List objectArray = FormatArguments(args); - - if (objectArray.Count == 1) - { - listener(objectArray.First()); - } - else - { - listener(objectArray); - } + var arg = FormatArguments(args); + listener(arg); }); } - private static List FormatArguments(JsonElement args) + private static object FormatArguments(JsonElement args) { - var objectArray = args.Deserialize(ElectronJson.Options).ToList(); - objectArray.RemoveAll(item => item is null); - return objectArray; + var objectArray = args.Deserialize(BoxedObjectSerializationOptions).ToList(); + + Debug.Assert(objectArray.Count <= 2); + + if (objectArray.Count == 2) + { + return objectArray[1]; + } + + return null; } /// @@ -84,18 +96,8 @@ public void OnSync(string channel, Func listener) BridgeConnector.Socket.Emit("registerSyncIpcMainChannel", channel); BridgeConnector.Socket.On(channel, (args) => { - List objectArray = FormatArguments(args); - object parameter; - if (objectArray.Count == 1) - { - parameter = objectArray.First(); - } - else - { - parameter = objectArray; - } - - var result = listener(parameter); + var arg = FormatArguments(args); + var result = listener(arg); BridgeConnector.Socket.Emit(channel + "Sync", result); }); } @@ -111,16 +113,8 @@ public void Once(string channel, Action listener) BridgeConnector.Socket.Emit("registerOnceIpcMainChannel", channel); BridgeConnector.Socket.Once(channel, (args) => { - List objectArray = FormatArguments(args); - - if (objectArray.Count == 1) - { - listener(objectArray.First()); - } - else - { - listener(objectArray); - } + var arg = FormatArguments(args); + listener(arg); }); } diff --git a/src/ElectronNET.API/Serialization/JsonToBoxedPrimitivesConverter.cs b/src/ElectronNET.API/Serialization/JsonToBoxedPrimitivesConverter.cs new file mode 100644 index 00000000..5db53047 --- /dev/null +++ b/src/ElectronNET.API/Serialization/JsonToBoxedPrimitivesConverter.cs @@ -0,0 +1,126 @@ +namespace ElectronNET.Serialization +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + using System.Text.Json.Serialization; + + public sealed class JsonToBoxedPrimitivesConverter : JsonConverter + { + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return ReadValue(ref reader); + } + + private static object ReadValue(ref Utf8JsonReader r) + { + switch (r.TokenType) + { + case JsonTokenType.StartObject: + + var obj = new Dictionary(); + while (r.Read()) + { + if (r.TokenType == JsonTokenType.EndObject) + { + return obj; + } + + if (r.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Expected property name."); + } + + string name = r.GetString()!; + if (!r.Read()) + { + throw new JsonException("Unexpected end while reading property value."); + } + + obj[name] = ReadValue(ref r); + } + + throw new JsonException("Unexpected end while reading object."); + + case JsonTokenType.StartArray: + + var list = new List(); + while (r.Read()) + { + if (r.TokenType == JsonTokenType.EndArray) + { + return list; + } + + list.Add(ReadValue(ref r)); + } + + throw new JsonException("Unexpected end while reading array."); + + case JsonTokenType.True: return true; + case JsonTokenType.False: return false; + case JsonTokenType.Null: return null; + + case JsonTokenType.Number: + + if (r.TryGetInt32(out int i)) + { + return i; + } + + if (r.TryGetInt64(out long l)) + { + return l; + } + + if (r.TryGetDouble(out double d)) + { + return d; + } + + return r.GetDecimal(); + + case JsonTokenType.String: + + string s = r.GetString()!; + + if (DateTimeOffset.TryParse(s, out var dto)) + { + return dto; + } + + if (DateTime.TryParse(s, out var dt)) + { + return dt; + } + + if (TimeSpan.TryParse(s, out var ts)) + { + return ts; + } + + if (Guid.TryParse(s, out var g)) + { + return g; + } + + return s; + + default: + throw new JsonException($"Unsupported token {r.TokenType}"); + } + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartObject(); + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj b/src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj index c12d1e57..ad9d60c3 100644 --- a/src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj +++ b/src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj @@ -10,7 +10,7 @@ net10.0 enable - enable + disable false true diff --git a/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs b/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs index 2596e9bd..60f1d62a 100644 --- a/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs +++ b/src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs @@ -15,10 +15,21 @@ public IpcMainTests(ElectronFixture fx) [Fact(Timeout = 20000)] public async Task Ipc_On_receives_message_from_renderer() { + object received = null; + var tcs = new TaskCompletionSource(); - await Electron.IpcMain.On("ipc-on-test", obj => tcs.TrySetResult(obj?.ToString() ?? string.Empty)); + await Electron.IpcMain.On("ipc-on-test", obj => + { + received = obj; + tcs.TrySetResult(obj as string); + }); + await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.send('ipc-on-test','payload123')"); + var result = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5)); + + received.Should().BeOfType(); + received.Should().Be("payload123"); result.Should().Be("payload123"); } @@ -46,12 +57,18 @@ public async Task Ipc_RemoveAllListeners_stops_receiving() [Fact(Timeout = 20000)] public async Task Ipc_OnSync_returns_value() { + object received = null; + Electron.IpcMain.OnSync("ipc-sync-test", (obj) => { - obj.Should().NotBeNull(); + received = obj; return "pong"; }); var ret = await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync("require('electron').ipcRenderer.sendSync('ipc-sync-test','ping')"); + + received.Should().BeOfType(); + received.Should().Be("ping"); + ret.Should().Be("pong"); }