Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 37 additions & 43 deletions src/ElectronNET.API/API/IpcMain.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
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;

/// <summary>
/// Communicate asynchronously from the main process to renderer processes.
/// </summary>
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()
{
Expand Down Expand Up @@ -50,24 +63,23 @@ public async Task On(string channel, Action<object> listener)
BridgeConnector.Socket.Off(channel);
BridgeConnector.Socket.On<JsonElement>(channel, (args) =>
{
List<object> objectArray = FormatArguments(args);

if (objectArray.Count == 1)
{
listener(objectArray.First());
}
else
{
listener(objectArray);
}
var arg = FormatArguments(args);
listener(arg);
});
}

private static List<object> FormatArguments(JsonElement args)
private static object FormatArguments(JsonElement args)
{
var objectArray = args.Deserialize<object[]>(ElectronJson.Options).ToList();
objectArray.RemoveAll(item => item is null);
return objectArray;
var objectArray = args.Deserialize<object[]>(BoxedObjectSerializationOptions).ToList();

Debug.Assert(objectArray.Count <= 2);

if (objectArray.Count == 2)
{
return objectArray[1];
}

return null;
}

/// <summary>
Expand All @@ -84,18 +96,8 @@ public void OnSync(string channel, Func<object, object> listener)
BridgeConnector.Socket.Emit("registerSyncIpcMainChannel", channel);
BridgeConnector.Socket.On<JsonElement>(channel, (args) =>
{
List<object> 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);
});
}
Expand All @@ -111,16 +113,8 @@ public void Once(string channel, Action<object> listener)
BridgeConnector.Socket.Emit("registerOnceIpcMainChannel", channel);
BridgeConnector.Socket.Once<JsonElement>(channel, (args) =>
{
List<object> objectArray = FormatArguments(args);

if (objectArray.Count == 1)
{
listener(objectArray.First());
}
else
{
listener(objectArray);
}
var arg = FormatArguments(args);
listener(arg);
});
}

Expand Down
126 changes: 126 additions & 0 deletions src/ElectronNET.API/Serialization/JsonToBoxedPrimitivesConverter.cs
Original file line number Diff line number Diff line change
@@ -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<object>
{
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<string, object>();
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<object>();
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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
<IsPackable>false</IsPackable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!-- https://github.com/Tyrrrz/GitHubActionsTestLogger/issues/5 -->
</PropertyGroup>
Expand Down
21 changes: 19 additions & 2 deletions src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();
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<string>("require('electron').ipcRenderer.send('ipc-on-test','payload123')");

var result = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5));

received.Should().BeOfType<string>();
received.Should().Be("payload123");
result.Should().Be("payload123");
}

Expand Down Expand Up @@ -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<string>("require('electron').ipcRenderer.sendSync('ipc-sync-test','ping')");

received.Should().BeOfType<string>();
received.Should().Be("ping");

ret.Should().Be("pong");
}

Expand Down