2021-07-12 19:50:39 +02:00
using System ;
2021-08-18 10:24:12 +02:00
using System.Collections.Concurrent ;
using System.Collections.Generic ;
2021-08-23 11:22:37 +02:00
using System.Linq ;
2021-08-18 10:24:12 +02:00
using System.Threading ;
2021-07-12 21:33:35 +02:00
using System.Threading.Tasks ;
2021-08-26 09:55:14 +02:00
using Microsoft.Extensions.Logging ;
2021-08-23 11:22:37 +02:00
using Newtonsoft.Json ;
using Newtonsoft.Json.Serialization ;
2021-07-12 19:50:39 +02:00
using SocketIOClient ;
2021-08-23 11:22:37 +02:00
using SocketIOClient.JsonSerializer ;
2021-07-12 19:50:39 +02:00
using SocketIOClient.Newtonsoft.Json ;
2017-10-14 17:58:16 +02:00
namespace ElectronNET.API
{
internal static class BridgeConnector
{
2021-08-18 10:24:12 +02:00
internal static class EventTasks < T >
{
//Although SocketIO already manage event handlers, we need to manage this here as well for the OnResult calls,
//because SocketIO will simply replace the existing event handler on every call to On(key, ...) , which means there is
//a race condition between On / Off calls that can lead to tasks deadlocking forever without ever triggering their On handler
private static readonly Dictionary < string , TaskCompletionSource < T > > _taskCompletionSources = new ( ) ;
private static readonly Dictionary < string , string > _eventKeys = new ( ) ;
private static readonly object _lock = new ( ) ;
/// <summary>
/// Get or add a new TaskCompletionSource<typeparamref name="T"/> for a given event key
/// </summary>
/// <param name="key"></param>
/// <param name="eventKey"></param>
/// <param name="taskCompletionSource"></param>
/// <param name="waitThisFirstAndThenTryAgain"></param>
/// <returns>Returns true if a new TaskCompletionSource<typeparamref name="T"/> was added to the dictionary</returns>
internal static bool TryGetOrAdd ( string key , string eventKey , out TaskCompletionSource < T > taskCompletionSource , out Task waitThisFirstAndThenTryAgain )
{
lock ( _lock )
{
if ( ! _taskCompletionSources . TryGetValue ( key , out taskCompletionSource ) )
{
taskCompletionSource = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
_taskCompletionSources [ key ] = taskCompletionSource ;
_eventKeys [ key ] = eventKey ;
waitThisFirstAndThenTryAgain = null ;
return true ; //Was added, so we need to also register the socket events
}
if ( _eventKeys . TryGetValue ( key , out var existingEventKey ) & & existingEventKey = = eventKey )
{
waitThisFirstAndThenTryAgain = null ;
return false ; //No need to register the socket events twice
}
waitThisFirstAndThenTryAgain = taskCompletionSource . Task ; //Will need to try again after the previous existing one is done
taskCompletionSource = null ;
return true ; //Need to register the socket events, but must first await the previous task to complete
}
}
/// <summary>
/// Clean up the TaskCompletionSource<typeparamref name="T"/> from the dictionary if and only if it is the same as the passed argument
/// </summary>
/// <param name="key"></param>
/// <param name="eventKey"></param>
/// <param name="taskCompletionSource"></param>
internal static void DoneWith ( string key , string eventKey , TaskCompletionSource < T > taskCompletionSource )
{
lock ( _lock )
{
if ( _taskCompletionSources . TryGetValue ( key , out var existingTaskCompletionSource )
& & ReferenceEquals ( existingTaskCompletionSource , taskCompletionSource ) )
{
_taskCompletionSources . Remove ( key ) ;
}
if ( _eventKeys . TryGetValue ( key , out var existingEventKey ) & & existingEventKey = = eventKey )
{
_eventKeys . Remove ( key ) ;
}
}
}
}
2021-07-12 19:50:39 +02:00
private static SocketIO _socket ;
2021-08-18 10:24:12 +02:00
2019-05-18 02:00:56 +02:00
private static object _syncRoot = new object ( ) ;
2017-10-14 17:58:16 +02:00
2021-08-26 10:31:06 +02:00
private static SemaphoreSlim _socketSemaphoreEmit = new SemaphoreSlim ( 1 , 1 ) ;
private static SemaphoreSlim _socketSemaphoreHandlers = new SemaphoreSlim ( 1 , 1 ) ;
private static TaskCompletionSource _waitForBeingConnected = new TaskCompletionSource ( ) ;
private static Task _waitForConnection = > _waitForBeingConnected . Task ;
2021-08-26 08:40:36 +02:00
2021-07-12 21:33:35 +02:00
public static void Emit ( string eventString , params object [ ] args )
{
//We don't care about waiting for the event to be emitted, so this doesn't need to be async
2021-08-26 10:31:06 +02:00
2021-07-21 10:42:04 +02:00
Task . Run ( async ( ) = >
{
2021-08-26 10:31:06 +02:00
await EmitAsync ( eventString , args ) ;
} ) ;
}
2021-08-18 10:24:12 +02:00
2021-08-26 10:31:06 +02:00
private static async Task EmitAsync ( string eventString , object [ ] args )
{
await _waitForConnection ;
2021-07-21 10:42:04 +02:00
2021-08-26 10:31:06 +02:00
if ( App . SocketDebug )
{
Log ( "Sending event {0}" , eventString ) ;
}
await _socketSemaphoreEmit . WaitAsync ( ) ;
try
{
await Socket . EmitAsync ( eventString , args ) ;
}
finally
{
_socketSemaphoreEmit . Release ( ) ;
}
if ( App . SocketDebug )
{
Log ( $"Sent event {eventString}" ) ;
}
2021-07-12 21:33:35 +02:00
}
2021-08-26 09:55:14 +02:00
internal static void Log ( string formatString , params object [ ] args )
{
if ( Logger is object )
{
Logger . LogInformation ( formatString , args ) ;
}
else
{
Console . WriteLine ( formatString , args ) ;
}
}
2021-08-26 09:55:24 +02:00
internal static void LogError ( Exception E , string formatString , params object [ ] args )
{
if ( Logger is object )
{
Logger . LogError ( E , formatString , args ) ;
}
else
{
Console . WriteLine ( formatString , args ) ;
}
}
2021-08-26 09:55:14 +02:00
2021-08-20 15:06:58 +02:00
/// <summary>
/// This method is only used on places where we need to be sure the event was sent on the socket, such as Quit, Exit, Relaunch and QuitAndInstall methods
/// </summary>
/// <param name="eventString"></param>
/// <param name="args"></param>
internal static void EmitSync ( string eventString , params object [ ] args )
{
if ( App . SocketDebug )
{
2021-08-26 09:55:14 +02:00
Log ( "Sending event {0}" , eventString ) ;
2021-08-20 15:06:58 +02:00
}
2021-08-26 10:31:06 +02:00
_waitForConnection . Wait ( ) ;
_socketSemaphoreEmit . Wait ( ) ;
2021-08-26 08:40:36 +02:00
try
{
Socket . EmitAsync ( eventString , args ) . Wait ( ) ;
}
finally
{
2021-08-26 10:31:06 +02:00
_socketSemaphoreEmit . Release ( ) ;
2021-08-26 08:40:36 +02:00
}
2021-08-20 15:06:58 +02:00
if ( App . SocketDebug )
{
2021-08-26 09:55:14 +02:00
Log ( "Sent event {0}" , eventString ) ;
2021-08-20 15:06:58 +02:00
}
}
2021-08-18 10:24:12 +02:00
public static void Off ( string eventString )
2021-07-12 19:50:39 +02:00
{
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Wait ( ) ;
2021-08-26 08:40:36 +02:00
try
{
Socket . Off ( eventString ) ;
}
finally
{
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Release ( ) ;
2021-08-26 08:40:36 +02:00
}
2021-08-18 10:24:12 +02:00
}
public static void On ( string eventString , Action fn )
{
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Wait ( ) ;
2021-08-26 08:40:36 +02:00
try
{
Socket . On ( eventString , _ = > fn ( ) ) ;
}
finally
{
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Release ( ) ;
2021-08-26 08:40:36 +02:00
}
2021-08-18 10:24:12 +02:00
}
public static void On < T > ( string eventString , Action < T > fn )
{
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Wait ( ) ;
2021-08-26 08:40:36 +02:00
try
{
Socket . On ( eventString , ( o ) = > fn ( o . GetValue < T > ( 0 ) ) ) ;
}
finally
{
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Release ( ) ;
2021-08-26 08:40:36 +02:00
}
2021-08-18 10:24:12 +02:00
}
public static void Once < T > ( string eventString , Action < T > fn )
{
On < T > ( eventString , ( o ) = >
{
Off ( eventString ) ;
fn ( o ) ;
} ) ;
}
public static async Task < T > OnResult < T > ( string triggerEvent , string completedEvent , params object [ ] args )
{
2021-08-24 05:31:46 +02:00
string eventKey = completedEvent ;
2021-08-18 10:24:12 +02:00
if ( args is object & & args . Length > 0 ) // If there are arguments passed, we generate a unique event key with the arguments
// this allow us to wait for previous events first before registering new ones
{
var hash = new HashCode ( ) ;
foreach ( var obj in args )
{
hash . Add ( obj ) ;
}
eventKey = $"{eventKey}-{(uint)hash.ToHashCode()}" ;
}
2021-08-24 05:31:46 +02:00
if ( EventTasks < T > . TryGetOrAdd ( completedEvent , eventKey , out var taskCompletionSource , out var waitThisFirstAndThenTryAgain ) )
2021-08-18 10:24:12 +02:00
{
if ( waitThisFirstAndThenTryAgain is object )
{
//There was a pending call with different parameters, so we need to wait that first and then call here again
try
{
await waitThisFirstAndThenTryAgain ;
}
catch
{
//Ignore any exceptions here so we can set a new event below
//The exception will also be visible to the original first caller due to taskCompletionSource.Task
}
//Try again to set the event
return await OnResult < T > ( triggerEvent , completedEvent , args ) ;
}
else
{
//A new TaskCompletionSource was added, so we need to register the completed event here
On < T > ( completedEvent , ( result ) = >
{
Off ( completedEvent ) ;
taskCompletionSource . SetResult ( result ) ;
2021-08-24 05:31:46 +02:00
EventTasks < T > . DoneWith ( completedEvent , eventKey , taskCompletionSource ) ;
2021-08-18 10:24:12 +02:00
} ) ;
2021-08-26 10:31:06 +02:00
await EmitAsync ( triggerEvent , args ) ;
2021-08-18 10:24:12 +02:00
}
}
return await taskCompletionSource . Task ;
}
2021-07-12 19:50:39 +02:00
2021-08-18 10:24:12 +02:00
public static async Task < T > OnResult < T > ( string triggerEvent , string completedEvent , CancellationToken cancellationToken , params object [ ] args )
{
2021-08-24 05:31:46 +02:00
string eventKey = completedEvent ;
2021-08-18 10:24:12 +02:00
2021-08-24 05:31:46 +02:00
if ( args is object & & args . Length > 0 ) // If there are arguments passed, we generate a unique event key with the arguments
// this allow us to wait for previous events first before registering new ones
2021-08-18 10:24:12 +02:00
{
2021-08-24 05:31:46 +02:00
var hash = new HashCode ( ) ;
foreach ( var obj in args )
{
hash . Add ( obj ) ;
}
eventKey = $"{eventKey}-{(uint)hash.ToHashCode()}" ;
}
if ( EventTasks < T > . TryGetOrAdd ( completedEvent , eventKey , out var taskCompletionSource , out var waitThisFirstAndThenTryAgain ) )
{
if ( waitThisFirstAndThenTryAgain is object )
{
//There was a pending call with different parameters, so we need to wait that first and then call here again
try
{
await Task . Run ( ( ) = > waitThisFirstAndThenTryAgain , cancellationToken ) ;
}
catch
{
//Ignore any exceptions here so we can set a new event below
//The exception will also be visible to the original first caller due to taskCompletionSource.Task
}
2021-08-18 10:24:12 +02:00
2021-08-24 05:31:46 +02:00
//Try again to set the event
return await OnResult < T > ( triggerEvent , completedEvent , cancellationToken , args ) ;
}
else
2021-08-18 10:24:12 +02:00
{
2021-08-24 05:31:46 +02:00
using ( cancellationToken . Register ( ( ) = > taskCompletionSource . TrySetCanceled ( ) ) )
{
//A new TaskCompletionSource was added, so we need to register the completed event here
2021-08-18 10:24:12 +02:00
2021-08-24 05:31:46 +02:00
On < T > ( completedEvent , ( result ) = >
{
Off ( completedEvent ) ;
taskCompletionSource . SetResult ( result ) ;
EventTasks < T > . DoneWith ( completedEvent , eventKey , taskCompletionSource ) ;
} ) ;
2021-08-18 10:24:12 +02:00
2021-08-24 05:31:46 +02:00
Emit ( triggerEvent , args ) ;
}
}
2021-08-18 10:24:12 +02:00
}
2021-08-24 05:31:46 +02:00
return await taskCompletionSource . Task ;
2021-08-18 10:24:12 +02:00
}
2021-07-12 19:50:39 +02:00
private static SocketIO Socket
2017-10-14 17:58:16 +02:00
{
2017-10-23 21:24:05 +02:00
get
2017-10-14 17:58:16 +02:00
{
2021-07-12 19:50:39 +02:00
if ( _socket is null )
2017-10-23 21:24:05 +02:00
{
2021-07-12 19:50:39 +02:00
if ( HybridSupport . IsElectronActive )
2017-10-23 21:24:05 +02:00
{
2021-07-12 19:50:39 +02:00
lock ( _syncRoot )
2017-11-04 00:16:14 +01:00
{
2021-07-12 19:50:39 +02:00
if ( _socket is null & & HybridSupport . IsElectronActive )
2017-11-04 00:16:14 +01:00
{
2021-07-12 19:50:39 +02:00
var socket = new SocketIO ( $"http://localhost:{BridgeSettings.SocketPort}" , new SocketIOOptions ( )
{
2021-08-26 08:40:36 +02:00
EIO = 3 ,
Reconnection = true ,
ReconnectionAttempts = int . MaxValue ,
ReconnectionDelay = 1000 ,
ReconnectionDelayMax = 5000 ,
RandomizationFactor = 0.1 ,
ConnectionTimeout = TimeSpan . FromSeconds ( 10 )
2021-07-12 19:50:39 +02:00
} ) ;
2021-08-23 11:22:37 +02:00
socket . JsonSerializer = new CamelCaseNewtonsoftJsonSerializer ( socket . Options . EIO ) ;
2021-07-12 19:50:39 +02:00
socket . OnConnected + = ( _ , __ ) = >
{
2021-08-26 09:55:14 +02:00
Log ( "ElectronNET socket connected on port {0}!" , BridgeSettings . SocketPort ) ;
2021-08-26 10:31:06 +02:00
_waitForBeingConnected . TrySetResult ( ) ;
2021-08-26 08:40:36 +02:00
} ;
socket . OnReconnectAttempt + = ( _ , __ ) = >
{
2021-08-26 09:55:14 +02:00
Log ( "ElectronNET socket is trying to reconnect on port {0}..." , BridgeSettings . SocketPort ) ;
2021-08-26 10:31:06 +02:00
_waitForBeingConnected = new ( ) ;
2021-08-26 08:40:36 +02:00
} ;
socket . OnReconnectError + = ( _ , ex ) = >
{
2021-08-26 09:55:14 +02:00
Log ( "ElectronNET socket failed to connect {0}" , ex ) ;
2021-08-26 08:40:36 +02:00
} ;
socket . OnReconnected + = ( _ , __ ) = >
{
2021-08-26 09:55:14 +02:00
Log ( "ElectronNET socket reconnected on port {0}..." , BridgeSettings . SocketPort ) ;
2021-08-26 10:31:06 +02:00
_waitForBeingConnected . TrySetResult ( ) ;
2021-08-26 08:40:36 +02:00
} ;
2021-08-26 10:08:42 +02:00
socket . OnDisconnected + = async ( _ , reason ) = >
2021-08-26 08:40:36 +02:00
{
2021-08-26 10:08:42 +02:00
Log ( "ElectronNET socket disconnected with reason {0}, trying to reconnect on port {1}!" , reason , BridgeSettings . SocketPort ) ;
2021-08-26 10:31:06 +02:00
_waitForBeingConnected = new ( ) ;
2021-08-26 10:08:42 +02:00
int i = 0 ;
2021-08-26 10:19:24 +02:00
double miliseconds = 500 ;
2021-08-26 10:08:42 +02:00
while ( true )
{
try
{
2021-08-26 10:19:24 +02:00
if ( ! socket . Connected )
{
await socket . ConnectAsync ( ) ;
2021-08-26 10:31:06 +02:00
_waitForBeingConnected . TrySetResult ( ) ; //Probably was already on the OnConnected call
2021-08-26 10:19:24 +02:00
}
return ;
2021-08-26 10:08:42 +02:00
}
catch ( Exception e )
{
2021-08-26 10:19:24 +02:00
LogError ( e , "Failed to reconnect, will try again in {0} ms." , miliseconds * 2 ) ;
2021-08-26 10:08:42 +02:00
}
2021-08-26 10:19:24 +02:00
await Task . Delay ( TimeSpan . FromMilliseconds ( miliseconds ) ) ;
miliseconds = Math . Min ( 60_000 , Math . Pow ( 2 , i ) + 500 ) ;
2021-08-26 10:08:42 +02:00
i + + ;
}
2021-07-12 19:50:39 +02:00
} ;
socket . ConnectAsync ( ) . Wait ( ) ;
_socket = socket ;
}
2017-11-04 00:16:14 +01:00
}
}
2021-07-12 19:50:39 +02:00
else
2017-11-04 00:16:14 +01:00
{
2021-07-12 19:50:39 +02:00
throw new Exception ( "Missing Socket Port" ) ;
2017-11-04 00:16:14 +01:00
}
2017-10-23 21:24:05 +02:00
}
return _socket ;
}
2017-10-14 17:58:16 +02:00
}
2021-08-23 11:22:37 +02:00
2021-08-26 09:55:14 +02:00
internal static ILogger < App > Logger { private get ; set ; }
2021-08-23 11:22:37 +02:00
private class CamelCaseNewtonsoftJsonSerializer : NewtonsoftJsonSerializer
{
public CamelCaseNewtonsoftJsonSerializer ( int eio ) : base ( eio )
{
}
public override JsonSerializerSettings CreateOptions ( )
{
return new JsonSerializerSettings ( )
{
2021-08-23 11:57:42 +02:00
ContractResolver = new CamelCasePropertyNamesContractResolver ( ) ,
NullValueHandling = NullValueHandling . Ignore ,
DefaultValueHandling = DefaultValueHandling . Ignore
2021-08-23 11:22:37 +02:00
} ;
}
}
2017-10-14 17:58:16 +02:00
}
}