Skip to content

Commit ef41329

Browse files
authored
Merge pull request #938 from softworkz/submit_ipc_types
Fix API break: public API must not expose JsonElement objects
2 parents edafa8c + be518a7 commit ef41329

File tree

4 files changed

+183
-46
lines changed

4 files changed

+183
-46
lines changed

src/ElectronNET.API/API/IpcMain.cs

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
1-
using ElectronNET.API.Serialization;
2-
using System;
3-
using System.Collections.Generic;
4-
using System.Linq;
5-
using System.Text.Json;
6-
using System.Threading.Tasks;
7-
81
namespace ElectronNET.API
92
{
3+
using System;
4+
using System.Diagnostics;
5+
using System.Linq;
6+
using System.Text.Json;
7+
using System.Text.Json.Serialization;
8+
using System.Threading.Tasks;
9+
using ElectronNET.Serialization;
10+
1011
/// <summary>
1112
/// Communicate asynchronously from the main process to renderer processes.
1213
/// </summary>
1314
public sealed class IpcMain
1415
{
1516
private static IpcMain _ipcMain;
1617
private static object _syncRoot = new object();
18+
private static readonly JsonSerializerOptions BoxedObjectSerializationOptions = new JsonSerializerOptions
19+
{
20+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
21+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
22+
WriteIndented = false,
23+
Converters =
24+
{
25+
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase),
26+
new JsonToBoxedPrimitivesConverter(),
27+
}
28+
};
29+
1730

1831
internal IpcMain()
1932
{
@@ -50,24 +63,23 @@ public async Task On(string channel, Action<object> listener)
5063
BridgeConnector.Socket.Off(channel);
5164
BridgeConnector.Socket.On<JsonElement>(channel, (args) =>
5265
{
53-
List<object> objectArray = FormatArguments(args);
54-
55-
if (objectArray.Count == 1)
56-
{
57-
listener(objectArray.First());
58-
}
59-
else
60-
{
61-
listener(objectArray);
62-
}
66+
var arg = FormatArguments(args);
67+
listener(arg);
6368
});
6469
}
6570

66-
private static List<object> FormatArguments(JsonElement args)
71+
private static object FormatArguments(JsonElement args)
6772
{
68-
var objectArray = args.Deserialize<object[]>(ElectronJson.Options).ToList();
69-
objectArray.RemoveAll(item => item is null);
70-
return objectArray;
73+
var objectArray = args.Deserialize<object[]>(BoxedObjectSerializationOptions).ToList();
74+
75+
Debug.Assert(objectArray.Count <= 2);
76+
77+
if (objectArray.Count == 2)
78+
{
79+
return objectArray[1];
80+
}
81+
82+
return null;
7183
}
7284

7385
/// <summary>
@@ -84,18 +96,8 @@ public void OnSync(string channel, Func<object, object> listener)
8496
BridgeConnector.Socket.Emit("registerSyncIpcMainChannel", channel);
8597
BridgeConnector.Socket.On<JsonElement>(channel, (args) =>
8698
{
87-
List<object> objectArray = FormatArguments(args);
88-
object parameter;
89-
if (objectArray.Count == 1)
90-
{
91-
parameter = objectArray.First();
92-
}
93-
else
94-
{
95-
parameter = objectArray;
96-
}
97-
98-
var result = listener(parameter);
99+
var arg = FormatArguments(args);
100+
var result = listener(arg);
99101
BridgeConnector.Socket.Emit(channel + "Sync", result);
100102
});
101103
}
@@ -111,16 +113,8 @@ public void Once(string channel, Action<object> listener)
111113
BridgeConnector.Socket.Emit("registerOnceIpcMainChannel", channel);
112114
BridgeConnector.Socket.Once<JsonElement>(channel, (args) =>
113115
{
114-
List<object> objectArray = FormatArguments(args);
115-
116-
if (objectArray.Count == 1)
117-
{
118-
listener(objectArray.First());
119-
}
120-
else
121-
{
122-
listener(objectArray);
123-
}
116+
var arg = FormatArguments(args);
117+
listener(arg);
124118
});
125119
}
126120

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
namespace ElectronNET.Serialization
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
8+
public sealed class JsonToBoxedPrimitivesConverter : JsonConverter<object>
9+
{
10+
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
11+
{
12+
return ReadValue(ref reader);
13+
}
14+
15+
private static object ReadValue(ref Utf8JsonReader r)
16+
{
17+
switch (r.TokenType)
18+
{
19+
case JsonTokenType.StartObject:
20+
21+
var obj = new Dictionary<string, object>();
22+
while (r.Read())
23+
{
24+
if (r.TokenType == JsonTokenType.EndObject)
25+
{
26+
return obj;
27+
}
28+
29+
if (r.TokenType != JsonTokenType.PropertyName)
30+
{
31+
throw new JsonException("Expected property name.");
32+
}
33+
34+
string name = r.GetString()!;
35+
if (!r.Read())
36+
{
37+
throw new JsonException("Unexpected end while reading property value.");
38+
}
39+
40+
obj[name] = ReadValue(ref r);
41+
}
42+
43+
throw new JsonException("Unexpected end while reading object.");
44+
45+
case JsonTokenType.StartArray:
46+
47+
var list = new List<object>();
48+
while (r.Read())
49+
{
50+
if (r.TokenType == JsonTokenType.EndArray)
51+
{
52+
return list;
53+
}
54+
55+
list.Add(ReadValue(ref r));
56+
}
57+
58+
throw new JsonException("Unexpected end while reading array.");
59+
60+
case JsonTokenType.True: return true;
61+
case JsonTokenType.False: return false;
62+
case JsonTokenType.Null: return null;
63+
64+
case JsonTokenType.Number:
65+
66+
if (r.TryGetInt32(out int i))
67+
{
68+
return i;
69+
}
70+
71+
if (r.TryGetInt64(out long l))
72+
{
73+
return l;
74+
}
75+
76+
if (r.TryGetDouble(out double d))
77+
{
78+
return d;
79+
}
80+
81+
return r.GetDecimal();
82+
83+
case JsonTokenType.String:
84+
85+
string s = r.GetString()!;
86+
87+
if (DateTimeOffset.TryParse(s, out var dto))
88+
{
89+
return dto;
90+
}
91+
92+
if (DateTime.TryParse(s, out var dt))
93+
{
94+
return dt;
95+
}
96+
97+
if (TimeSpan.TryParse(s, out var ts))
98+
{
99+
return ts;
100+
}
101+
102+
if (Guid.TryParse(s, out var g))
103+
{
104+
return g;
105+
}
106+
107+
return s;
108+
109+
default:
110+
throw new JsonException($"Unsupported token {r.TokenType}");
111+
}
112+
}
113+
114+
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
115+
{
116+
if (value is null)
117+
{
118+
writer.WriteNullValue();
119+
return;
120+
}
121+
122+
writer.WriteStartObject();
123+
writer.WriteEndObject();
124+
}
125+
}
126+
}

src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<PropertyGroup>
1111
<TargetFramework>net10.0</TargetFramework>
1212
<ImplicitUsings>enable</ImplicitUsings>
13-
<Nullable>enable</Nullable>
13+
<Nullable>disable</Nullable>
1414
<IsPackable>false</IsPackable>
1515
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!-- https://github.com/Tyrrrz/GitHubActionsTestLogger/issues/5 -->
1616
</PropertyGroup>

src/ElectronNET.IntegrationTests/Tests/IpcMainTests.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,21 @@ public IpcMainTests(ElectronFixture fx)
1515
[Fact(Timeout = 20000)]
1616
public async Task Ipc_On_receives_message_from_renderer()
1717
{
18+
object received = null;
19+
1820
var tcs = new TaskCompletionSource<string>();
19-
await Electron.IpcMain.On("ipc-on-test", obj => tcs.TrySetResult(obj?.ToString() ?? string.Empty));
21+
await Electron.IpcMain.On("ipc-on-test", obj =>
22+
{
23+
received = obj;
24+
tcs.TrySetResult(obj as string);
25+
});
26+
2027
await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("require('electron').ipcRenderer.send('ipc-on-test','payload123')");
28+
2129
var result = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
30+
31+
received.Should().BeOfType<string>();
32+
received.Should().Be("payload123");
2233
result.Should().Be("payload123");
2334
}
2435

@@ -46,12 +57,18 @@ public async Task Ipc_RemoveAllListeners_stops_receiving()
4657
[Fact(Timeout = 20000)]
4758
public async Task Ipc_OnSync_returns_value()
4859
{
60+
object received = null;
61+
4962
Electron.IpcMain.OnSync("ipc-sync-test", (obj) =>
5063
{
51-
obj.Should().NotBeNull();
64+
received = obj;
5265
return "pong";
5366
});
5467
var ret = await this.fx.MainWindow.WebContents.ExecuteJavaScriptAsync<string>("require('electron').ipcRenderer.sendSync('ipc-sync-test','ping')");
68+
69+
received.Should().BeOfType<string>();
70+
received.Should().Be("ping");
71+
5572
ret.Should().Be("pong");
5673
}
5774

0 commit comments

Comments
 (0)