2017-10-14 17:58:16 +02:00
using System ;
2021-08-18 10:24:12 +02:00
using System.Collections.Generic ;
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-12-13 14:10:38 +01:00
using Nito.AsyncEx ;
2021-07-12 19:50:39 +02:00
using SocketIOClient ;
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
}
2022-07-28 11:51:18 +02:00
if ( _eventKeys . TryGetValue ( key , out var existingEventKey ) & & existingEventKey = = eventKey )
2021-08-18 10:24:12 +02:00
{
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
2021-09-15 11:14:45 +02:00
private static readonly object _syncRoot = new ( ) ;
2017-10-14 17:58:16 +02:00
2021-09-15 11:14:45 +02:00
private static readonly SemaphoreSlim _socketSemaphoreEmit = new ( 1 , 1 ) ;
private static readonly SemaphoreSlim _socketSemaphoreHandlers = new ( 1 , 1 ) ;
2021-12-13 14:10:38 +01:00
private static AsyncManualResetEvent _connectedSocketEvent = new AsyncManualResetEvent ( ) ;
2021-12-13 14:43:04 +01:00
2022-07-28 11:51:18 +02:00
private static Dictionary < string , Action < SocketIOResponse > > _eventHandlers = new ( ) ;
2021-12-13 14:43:04 +01:00
2021-09-02 20:37:22 +02:00
private static Task < SocketIO > _waitForConnection
2021-09-02 20:06:01 +02:00
{
get
{
2021-09-02 20:37:22 +02:00
EnsureSocketTaskIsCreated ( ) ;
2021-12-13 14:10:38 +01:00
return GetSocket ( ) ;
2021-09-02 20:06:01 +02:00
}
}
2021-08-26 08:40:36 +02:00
2021-12-13 14:10:38 +01:00
private static async Task < SocketIO > GetSocket ( )
{
await _connectedSocketEvent . WaitAsync ( ) ;
2021-12-15 10:44:59 +01:00
return _socket ;
2021-12-13 14:10:38 +01:00
}
2021-09-02 18:20:11 +02:00
public static bool IsConnected = > _waitForConnection is Task task & & task . IsCompletedSuccessfully ;
2017-10-14 17:58:16 +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-09-02 20:37:22 +02:00
Task . Run ( ( ) = > EmitAsync ( eventString , args ) ) ;
2021-08-26 10:31:06 +02:00
}
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 )
{
if ( App . SocketDebug )
2021-07-21 10:42:04 +02:00
{
2021-08-26 10:31:06 +02:00
Log ( "Sending event {0}" , eventString ) ;
}
2021-08-18 10:24:12 +02:00
2021-09-02 20:37:22 +02:00
var socket = await _waitForConnection ;
2021-07-21 10:42:04 +02:00
2021-08-26 10:31:06 +02:00
await _socketSemaphoreEmit . WaitAsync ( ) ;
2021-09-02 20:37:22 +02:00
2021-08-26 10:31:06 +02:00
try
{
2021-09-02 20:37:22 +02:00
await socket . EmitAsync ( eventString , args ) ;
2021-08-26 10:31:06 +02:00
}
finally
{
_socketSemaphoreEmit . Release ( ) ;
}
if ( App . SocketDebug )
{
Log ( $"Sent event {eventString}" ) ;
}
2021-07-12 21:33:35 +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-09-02 20:37:22 +02:00
Task . Run ( async ( ) = >
2021-08-26 08:40:36 +02:00
{
2021-09-02 20:37:22 +02:00
var socket = await _waitForConnection ;
try
{
await _socketSemaphoreEmit . WaitAsync ( ) ;
await socket . EmitAsync ( eventString , args ) ;
}
finally
{
_socketSemaphoreEmit . Release ( ) ;
}
} ) . Wait ( ) ;
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-09-02 20:37:22 +02:00
EnsureSocketTaskIsCreated ( ) ;
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Wait ( ) ;
2021-08-26 08:40:36 +02:00
try
{
2021-12-13 14:43:04 +01:00
if ( _eventHandlers . ContainsKey ( eventString ) )
{
_eventHandlers . Remove ( eventString ) ;
}
2021-09-02 20:37:22 +02:00
_socket . Off ( eventString ) ;
2021-08-26 08:40:36 +02:00
}
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-09-02 20:37:22 +02:00
EnsureSocketTaskIsCreated ( ) ;
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Wait ( ) ;
2021-08-26 08:40:36 +02:00
try
{
2021-12-13 14:43:04 +01:00
if ( _eventHandlers . ContainsKey ( eventString ) )
{
_eventHandlers . Remove ( eventString ) ;
}
_eventHandlers . Add ( eventString , _ = >
2021-09-01 18:28:44 +02:00
{
try
{
fn ( ) ;
}
catch ( Exception E )
{
2021-09-01 18:29:20 +02:00
LogError ( E , "Error running handler for event {0}" , eventString ) ;
2021-09-01 18:28:44 +02:00
}
} ) ;
2021-12-13 14:43:04 +01:00
_socket . On ( eventString , _eventHandlers [ eventString ] ) ;
2021-08-26 08:40:36 +02:00
}
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-09-02 20:37:22 +02:00
EnsureSocketTaskIsCreated ( ) ;
2021-08-26 10:31:06 +02:00
_socketSemaphoreHandlers . Wait ( ) ;
2021-08-26 08:40:36 +02:00
try
{
2021-12-13 14:43:04 +01:00
if ( _eventHandlers . ContainsKey ( eventString ) )
{
_eventHandlers . Remove ( eventString ) ;
}
_eventHandlers . Add ( eventString , o = >
2021-09-01 18:28:44 +02:00
{
try
{
fn ( o . GetValue < T > ( 0 ) ) ;
}
2021-12-13 14:43:04 +01:00
catch ( Exception E )
2021-09-01 18:28:44 +02:00
{
2021-09-01 18:29:20 +02:00
LogError ( E , "Error running handler for event {0}" , eventString ) ;
2021-09-01 18:28:44 +02:00
}
} ) ;
2021-12-13 14:43:04 +01:00
_socket . On ( eventString , _eventHandlers [ eventString ] ) ;
}
finally
{
_socketSemaphoreHandlers . Release ( ) ;
}
}
private static void RehookHandlers ( SocketIO newSocket )
{
_socketSemaphoreHandlers . Wait ( ) ;
try
{
foreach ( var kv in _eventHandlers )
{
newSocket . On ( kv . Key , kv . Value ) ;
}
2021-08-26 08:40:36 +02:00
}
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 ( ) ;
2022-07-28 11:51:18 +02:00
foreach ( var obj in args )
2021-08-18 10:24:12 +02:00
{
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-09-02 20:37:22 +02:00
internal static void Log ( string formatString , params object [ ] args )
2017-10-14 17:58:16 +02:00
{
2021-09-02 20:37:22 +02:00
if ( Logger is object )
{
Logger . LogInformation ( formatString , args ) ;
}
else
2017-10-14 17:58:16 +02:00
{
2021-09-02 20:37:22 +02:00
Console . WriteLine ( formatString , args ) ;
}
}
internal static void LogError ( Exception E , string formatString , params object [ ] args )
2017-10-14 17:58:16 +02:00
{
2021-09-02 20:37:22 +02:00
if ( Logger is object )
2017-10-14 17:58:16 +02:00
{
2021-09-02 20:37:22 +02:00
Logger . LogError ( E , formatString , args ) ;
}
else
{
Console . WriteLine ( formatString , args ) ;
Console . WriteLine ( E . ToString ( ) ) ;
}
}
2021-12-13 14:43:04 +01:00
private static Thread _backgroundMonitorThread ;
2021-09-02 20:37:22 +02:00
private static void EnsureSocketTaskIsCreated ( )
{
if ( _socket is null )
{
2022-07-28 11:51:18 +02:00
if ( string . IsNullOrWhiteSpace ( AuthKey ) )
2017-10-23 21:24:05 +02:00
{
2021-12-23 09:24:29 +01:00
throw new Exception ( "You must call Electron.ReadAuth() first thing on your main entry point." ) ;
}
2021-07-12 19:50:39 +02:00
2021-09-02 20:37:22 +02:00
if ( HybridSupport . IsElectronActive )
2017-10-23 21:24:05 +02:00
{
2021-09-02 20:37:22 +02:00
lock ( _syncRoot )
2017-10-23 21:24:05 +02:00
{
2022-04-11 13:59:42 +02:00
if ( _socket is null )
2017-11-04 00:16:14 +01:00
{
2022-04-11 13:59:42 +02:00
if ( 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 ( )
{
2022-04-11 13:59:42 +02:00
EIO = 4 ,
Reconnection = true ,
ReconnectionAttempts = int . MaxValue ,
ReconnectionDelay = 500 ,
ReconnectionDelayMax = 2000 ,
RandomizationFactor = 0.5 ,
ConnectionTimeout = TimeSpan . FromSeconds ( 10 ) ,
Transport = SocketIOClient . Transport . TransportProtocol . WebSocket
2021-07-12 19:50:39 +02:00
} ) ;
2022-04-11 13:59:42 +02:00
socket . JsonSerializer = new CamelCaseNewtonsoftJsonSerializer ( ) ;
2021-07-12 19:50:39 +02:00
2021-12-15 10:44:59 +01:00
_connectedSocketEvent . Reset ( ) ;
2021-07-12 19:50:39 +02:00
socket . OnConnected + = ( _ , __ ) = >
{
2022-04-11 13:59:42 +02:00
Task . Run ( async ( ) = >
{
await socket . EmitAsync ( "auth" , AuthKey ) ;
_connectedSocketEvent . Set ( ) ;
Log ( "ElectronNET socket {1} connected on port {0}!" , BridgeSettings . SocketPort , socket . Id ) ;
} ) ;
2021-07-12 19:50:39 +02:00
} ;
2022-04-11 13:59:42 +02:00
socket . OnReconnectAttempt + = ( _ , __ ) = >
{
_connectedSocketEvent . Reset ( ) ;
Log ( "ElectronNET socket {1} is trying to reconnect on port {0}..." , BridgeSettings . SocketPort , socket . Id ) ;
} ;
2022-01-02 22:46:53 -05:00
2022-04-11 13:59:42 +02:00
socket . OnReconnectError + = ( _ , ex ) = >
{
_connectedSocketEvent . Reset ( ) ;
Log ( "ElectronNET socket {1} failed to connect {0}" , ex , socket . Id ) ;
} ;
2022-01-02 22:46:53 -05:00
2022-07-14 16:56:50 +02:00
socket . OnReconnectFailed + = ( _ , ex ) = >
{
_connectedSocketEvent . Reset ( ) ;
Log ( "ElectronNET socket {1} failed to reconnect {0}" , ex , socket . Id ) ;
} ;
2022-01-02 22:46:53 -05:00
2022-04-11 13:59:42 +02:00
socket . OnReconnected + = ( _ , __ ) = >
{
_connectedSocketEvent . Set ( ) ;
Log ( "ElectronNET socket {1} reconnected on port {0}..." , BridgeSettings . SocketPort , socket . Id ) ;
} ;
2022-01-02 22:46:53 -05:00
2022-04-11 13:59:42 +02:00
socket . OnDisconnected + = ( _ , reason ) = >
{
_connectedSocketEvent . Reset ( ) ;
Log ( "ElectronNET socket {2} disconnected with reason {0}, trying to reconnect on port {1}!" , reason , BridgeSettings . SocketPort , socket . Id ) ;
} ;
2022-01-02 22:46:53 -05:00
2022-04-11 13:59:42 +02:00
socket . OnError + = ( _ , msg ) = >
{
2022-07-14 16:56:50 +02:00
//_connectedSocketEvent.Reset();
Log ( "ElectronNET socket {1} error: {0}..." , msg , socket . Id ) ;
2022-04-11 13:59:42 +02:00
} ;
2022-01-02 22:46:53 -05:00
2022-04-11 13:59:42 +02:00
_socket = socket ;
2022-01-02 23:11:01 -05:00
2022-04-11 13:59:42 +02:00
Task . Run ( async ( ) = >
2021-11-05 11:01:32 +01:00
{
2022-04-11 13:59:42 +02:00
try
2021-11-05 11:01:32 +01:00
{
2022-04-11 13:59:42 +02:00
await socket . ConnectAsync ( ) ;
2021-11-05 11:01:32 +01:00
}
2022-04-11 13:59:42 +02:00
catch ( Exception e )
{
Console . WriteLine ( e . ToString ( ) ) ;
2021-09-02 20:37:22 +02:00
2022-04-11 13:59:42 +02:00
if ( ! App . TryRaiseOnSocketConnectFail ( ) )
{
Environment . Exit ( 0xDEAD ) ;
}
}
} ) ;
2022-01-02 23:11:01 -05:00
2022-04-11 13:59:42 +02:00
RehookHandlers ( socket ) ;
}
else
{
throw new Exception ( "Missing Socket Port" ) ;
}
2017-11-04 00:16:14 +01:00
}
2022-01-02 23:11:01 -05:00
}
2017-10-23 21:24:05 +02:00
}
2021-09-02 20:37:22 +02:00
else
{
throw new Exception ( "Missing Socket Port" ) ;
}
2022-01-02 22:46:53 -05:00
}
}
2021-08-26 09:55:14 +02:00
internal static ILogger < App > Logger { private get ; set ; }
2022-07-14 16:56:50 +02:00
internal static string AuthKey { get ; set ; } = null ;
2022-01-02 22:46:53 -05:00
2021-08-23 11:22:37 +02:00
private class CamelCaseNewtonsoftJsonSerializer : NewtonsoftJsonSerializer
{
2021-11-16 14:02:17 +01:00
public CamelCaseNewtonsoftJsonSerializer ( ) : base ( )
2022-01-02 22:46:53 -05:00
{
2022-03-25 10:30:51 +01:00
OptionsProvider = ( ) = > new JsonSerializerSettings ( )
2022-01-02 22:46:53 -05:00
{
2021-08-23 11:57:42 +02:00
ContractResolver = new CamelCasePropertyNamesContractResolver ( ) ,
NullValueHandling = NullValueHandling . Ignore ,
2021-08-23 11:22:37 +02:00
} ;
2022-01-02 22:46:53 -05:00
}
}
2017-10-14 17:58:16 +02:00
}
2022-07-28 11:51:18 +02:00
}