2025-03-11 01:11:20 -05:00
using LANCommander.SDK.Models ;
2024-08-21 20:19:04 -05:00
using LANCommander.SDK.PowerShell.Cmdlets ;
2024-10-04 23:37:49 -05:00
using LANCommander.SDK.Services ;
2025-06-22 00:00:58 +02:00
using LANCommander.SDK.Extensions ;
using LANCommander.SDK.Exceptions ;
2023-11-09 19:40:38 -06:00
using Microsoft.Extensions.Logging ;
2025-06-22 00:00:58 +02:00
using Newtonsoft.Json ;
2023-01-04 20:31:02 -06:00
using RestSharp ;
2024-08-05 18:02:01 -05:00
using RestSharp.Interceptors ;
2024-05-13 18:41:00 -05:00
using Semver ;
2023-01-04 20:31:02 -06:00
using System ;
using System.Collections.Generic ;
2023-01-06 22:12:03 -06:00
using System.ComponentModel ;
using System.IO ;
2023-01-04 20:31:02 -06:00
using System.Linq ;
2023-01-05 01:07:17 -06:00
using System.Net ;
2025-03-15 22:02:26 -05:00
using System.Net.Http ;
2023-01-15 20:45:37 -06:00
using System.Net.NetworkInformation ;
2024-05-13 18:41:00 -05:00
using System.Reflection ;
2025-03-18 20:30:04 -05:00
using System.ServiceModel.Channels ;
2023-01-04 20:31:02 -06:00
using System.Threading.Tasks ;
2025-08-28 19:57:23 -05:00
using LANCommander.SDK.Rpc ;
2023-01-04 20:31:02 -06:00
2023-11-09 19:40:38 -06:00
namespace LANCommander.SDK
2023-01-04 20:31:02 -06:00
{
2023-11-09 19:40:38 -06:00
public class Client
2023-01-04 20:31:02 -06:00
{
2023-11-10 20:53:48 -06:00
private readonly ILogger Logger ;
2023-08-21 18:44:20 -05:00
2023-11-17 20:47:11 -06:00
private RestClient ApiClient ;
2023-11-16 01:44:35 -06:00
private AuthToken Token ;
2024-01-23 18:01:12 -06:00
private bool Connected = false ;
2025-03-16 21:04:24 -05:00
private bool IgnoreVersion = false ;
2024-01-23 18:01:12 -06:00
2024-02-11 17:57:38 -06:00
public Uri BaseUrl ;
2024-01-02 02:34:58 -06:00
public string DefaultInstallDirectory ;
2023-01-04 20:31:02 -06:00
2024-01-02 02:34:58 -06:00
public readonly GameService Games ;
2024-10-31 01:54:34 -05:00
public readonly LibraryService Library ;
2024-11-11 19:54:02 -06:00
public readonly DepotService Depot ;
2024-01-02 02:34:58 -06:00
public readonly SaveService Saves ;
public readonly RedistributableService Redistributables ;
2024-08-05 20:29:12 -05:00
public readonly ScriptService Scripts ;
2024-01-02 02:34:58 -06:00
public readonly ProfileService Profile ;
2024-02-20 17:58:49 -06:00
public readonly MediaService Media ;
2024-07-25 19:21:52 -05:00
public readonly LauncherService Launcher ;
2024-08-11 14:48:00 -05:00
public readonly IssueService Issues ;
2024-09-06 00:30:10 -05:00
public readonly LobbyService Lobbies ;
2024-10-01 17:56:06 -05:00
public readonly ServerService Servers ;
2025-01-25 12:45:33 -06:00
public readonly PlaySessionService PlaySessions ;
2025-03-16 21:04:24 -05:00
public readonly TagService Tags ;
2025-04-23 21:23:33 -05:00
public readonly BeaconService Beacon ;
2025-08-20 20:53:48 -05:00
public readonly ChatService Chat ;
2025-08-28 19:57:23 -05:00
public readonly RpcClient RPC ;
2024-01-02 02:34:58 -06:00
2024-01-02 20:46:28 -06:00
private Settings _Settings { get ; set ; }
public Settings Settings
{
get
{
if ( _Settings = = null )
_Settings = GetSettings ( ) ;
return _Settings ;
}
}
2025-01-18 20:25:39 -06:00
public EventHandler < Exception > OnError ;
2025-01-31 00:34:37 -06:00
public delegate void OnInstallProgressUpdateHandler ( InstallProgress e ) ;
public event OnInstallProgressUpdateHandler OnInstallProgressUpdate ;
2025-01-18 20:25:39 -06:00
2024-01-02 02:34:58 -06:00
public Client ( string baseUrl , string defaultInstallDirectory )
2023-01-04 20:31:02 -06:00
{
2024-01-02 02:34:58 -06:00
DefaultInstallDirectory = defaultInstallDirectory ;
Games = new GameService ( this , DefaultInstallDirectory ) ;
2024-10-31 01:54:34 -05:00
Library = new LibraryService ( this ) ;
2024-11-11 19:54:02 -06:00
Depot = new DepotService ( this ) ;
2024-01-02 02:34:58 -06:00
Saves = new SaveService ( this ) ;
Redistributables = new RedistributableService ( this ) ;
2024-08-05 20:29:12 -05:00
Scripts = new ScriptService ( this ) ;
2024-01-02 02:34:58 -06:00
Profile = new ProfileService ( this ) ;
2024-02-20 17:58:49 -06:00
Media = new MediaService ( this ) ;
2024-07-25 19:21:52 -05:00
Launcher = new LauncherService ( this ) ;
2024-08-11 14:48:00 -05:00
Issues = new IssueService ( this ) ;
2024-09-06 00:30:10 -05:00
Lobbies = new LobbyService ( this ) ;
2024-10-01 17:56:06 -05:00
Servers = new ServerService ( this ) ;
2025-01-25 12:45:33 -06:00
PlaySessions = new PlaySessionService ( this ) ;
2025-03-16 21:04:24 -05:00
Tags = new TagService ( this ) ;
2025-04-23 21:23:33 -05:00
Beacon = new BeaconService ( this ) ;
2025-08-20 20:53:48 -05:00
Chat = new ChatService ( this ) ;
2023-11-16 01:44:35 -06:00
2024-08-21 20:19:04 -05:00
BaseCmdlet . Client = this ;
2025-03-12 01:10:38 -05:00
try
{
2025-06-10 02:08:14 +02:00
ConfigureServerAddress ( baseUrl ) ;
2025-03-12 01:10:38 -05:00
}
catch
{
}
2023-01-04 20:31:02 -06:00
}
2024-01-02 02:34:58 -06:00
public Client ( string baseUrl , string defaultInstallDirectory , ILogger logger )
2023-11-10 20:53:48 -06:00
{
2025-03-12 01:10:38 -05:00
try
{
2025-06-10 02:08:14 +02:00
ConfigureServerAddress ( baseUrl ) ;
2025-03-12 01:10:38 -05:00
}
catch
{
}
2024-02-17 13:25:56 -06:00
2024-01-02 02:34:58 -06:00
DefaultInstallDirectory = defaultInstallDirectory ;
Games = new GameService ( this , DefaultInstallDirectory , logger ) ;
2024-10-31 01:54:34 -05:00
Library = new LibraryService ( this , logger ) ;
2024-11-11 19:54:02 -06:00
Depot = new DepotService ( this , logger ) ;
2024-01-02 02:34:58 -06:00
Saves = new SaveService ( this , logger ) ;
Redistributables = new RedistributableService ( this , logger ) ;
2024-08-05 20:29:12 -05:00
Scripts = new ScriptService ( this , logger ) ;
2024-01-02 02:34:58 -06:00
Profile = new ProfileService ( this , logger ) ;
2024-02-20 17:58:49 -06:00
Media = new MediaService ( this , logger ) ;
2024-07-25 19:21:52 -05:00
Launcher = new LauncherService ( this ) ;
2024-08-11 14:48:00 -05:00
Issues = new IssueService ( this ) ;
2024-09-06 00:30:10 -05:00
Lobbies = new LobbyService ( this , logger ) ;
2024-10-01 17:56:06 -05:00
Servers = new ServerService ( this , logger ) ;
2025-01-25 12:45:33 -06:00
PlaySessions = new PlaySessionService ( this , logger ) ;
2025-03-16 21:04:24 -05:00
Tags = new TagService ( this , logger ) ;
2025-04-23 21:23:33 -05:00
Beacon = new BeaconService ( this , logger ) ;
2025-08-20 20:53:48 -05:00
Chat = new ChatService ( this ) ;
2025-08-28 19:57:23 -05:00
RPC = new RpcClient ( this ) ;
2023-11-16 01:44:35 -06:00
2024-08-21 20:19:04 -05:00
BaseCmdlet . Client = this ;
2023-11-10 20:53:48 -06:00
Logger = logger ;
}
2025-03-15 22:02:26 -05:00
// Constructor for tests
internal Client ( HttpClient httpClient , string defaultInstallDirectory )
{
ApiClient = new RestClient ( httpClient ) ;
DefaultInstallDirectory = defaultInstallDirectory ;
Games = new GameService ( this , DefaultInstallDirectory ) ;
Library = new LibraryService ( this ) ;
Depot = new DepotService ( this ) ;
Saves = new SaveService ( this ) ;
Redistributables = new RedistributableService ( this ) ;
Scripts = new ScriptService ( this ) ;
Profile = new ProfileService ( this ) ;
Media = new MediaService ( this ) ;
Launcher = new LauncherService ( this ) ;
Issues = new IssueService ( this ) ;
Lobbies = new LobbyService ( this ) ;
Servers = new ServerService ( this ) ;
PlaySessions = new PlaySessionService ( this ) ;
2025-03-16 21:04:24 -05:00
Tags = new TagService ( this ) ;
2025-08-28 19:57:23 -05:00
RPC = new RpcClient ( this ) ;
2025-03-16 21:04:24 -05:00
IgnoreVersion = true ;
2025-03-15 22:02:26 -05:00
BaseCmdlet . Client = this ;
}
2025-06-10 02:08:14 +02:00
public void ConfigureServerAddress ( string baseUrl )
{
if ( ! String . IsNullOrWhiteSpace ( baseUrl ) )
{
var urisToTry = baseUrl . SuggestValidUris ( ) ;
foreach ( var uri in urisToTry )
{
Logger ? . LogInformation ( "Attempting to configure server at {ServerAddress}" , uri . ToString ( ) ) ;
try
{
ApiClient = new RestClient ( uri ) ;
BaseUrl = uri ;
// Successful! Found our service
Logger ? . LogInformation ( "Using server address {ServerAddress}" , uri . ToString ( ) ) ;
return ;
}
catch
{
Logger ? . LogError ( "Could not configure server at {ServerAddress}" , uri . ToString ( ) ) ;
}
}
throw new Exception ( "Could not configure a server at that address" ) ;
}
}
2025-03-11 01:11:20 -05:00
public async Task ChangeServerAddressAsync ( string baseUrl )
2024-05-19 17:54:56 -05:00
{
if ( ! String . IsNullOrWhiteSpace ( baseUrl ) )
{
2025-03-10 21:44:51 -05:00
var urisToTry = baseUrl . SuggestValidUris ( ) ;
2024-09-17 00:19:23 -05:00
2025-06-10 03:34:59 +02:00
// if url is fully qualified, limit specific urls
2025-06-17 00:24:02 +02:00
if ( Uri . TryCreate ( baseUrl , UriKind . RelativeOrAbsolute , out var baseUri ) )
2025-06-10 03:34:59 +02:00
{
2025-06-17 00:24:02 +02:00
var hasPort = baseUrl . Replace ( Uri . SchemeDelimiter , "" ) . Contains ( ':' ) ;
if ( hasPort )
{
urisToTry = urisToTry . Take ( baseUri . IsAbsoluteUri ? 1 : 2 ) ;
}
2025-06-10 03:34:59 +02:00
}
2025-03-10 21:44:51 -05:00
foreach ( var uri in urisToTry )
2024-08-04 20:47:19 -05:00
{
2025-03-10 21:44:51 -05:00
Logger ? . LogInformation ( "Attempting to find server at {ServerAddress}" , uri . ToString ( ) ) ;
try
{
ApiClient = new RestClient ( uri ) ;
2025-03-11 01:11:20 -05:00
if ( await PingAsync ( ) )
2025-03-10 21:44:51 -05:00
{
BaseUrl = uri ;
// Successful! Found our service
Logger ? . LogInformation ( "Using server address {ServerAddress}" , uri . ToString ( ) ) ;
2025-08-28 19:57:23 -05:00
await RPC . ConnectAsync ( ) ;
2025-03-10 21:44:51 -05:00
return ;
}
}
catch
{
Logger ? . LogError ( "Did not find server at {ServerAddress}" , uri . ToString ( ) ) ;
}
}
2025-03-11 01:11:20 -05:00
throw new Exception ( "Could not find a server at that address" ) ;
2024-05-19 17:54:56 -05:00
}
}
2025-06-10 01:49:32 +02:00
public bool IsConfigured ( )
{
return ApiClient ! = null & & ! string . IsNullOrWhiteSpace ( BaseUrl ? . ToString ( ) ) ;
}
2024-01-23 18:01:12 -06:00
public bool IsConnected ( )
{
return Connected ;
}
2024-05-13 18:41:00 -05:00
public static SemVersion GetCurrentVersion ( )
{
return SemVersion . FromVersion ( Assembly . GetExecutingAssembly ( ) . GetName ( ) . Version ) ;
}
2024-07-25 19:21:52 -05:00
internal T PostRequest < T > ( string route , object body , bool ignoreVersion = false )
2023-01-05 01:07:17 -06:00
{
2025-01-18 20:25:39 -06:00
try
{
if ( Token = = null )
return default ;
2024-01-18 23:22:46 -06:00
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( route )
. AddJsonBody ( body )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2023-01-05 01:07:17 -06:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-13 18:41:00 -05:00
2025-01-18 20:25:39 -06:00
var response = ApiClient . Post < T > ( request ) ;
2023-01-05 01:07:17 -06:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2023-01-05 01:07:17 -06:00
}
2024-07-25 19:21:52 -05:00
internal T PostRequest < T > ( string route , bool ignoreVersion = false )
2023-11-17 02:28:46 -06:00
{
2025-01-18 20:25:39 -06:00
try
{
if ( Token = = null )
return default ;
2024-01-18 23:22:46 -06:00
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( route )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2023-11-17 02:28:46 -06:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-13 18:41:00 -05:00
2025-01-18 20:25:39 -06:00
var response = ApiClient . Post < T > ( request ) ;
2023-11-17 02:28:46 -06:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2023-11-17 02:28:46 -06:00
}
2024-07-25 19:21:52 -05:00
internal async Task < T > PostRequestAsync < T > ( string route , object body , bool ignoreVersion = false )
2024-05-19 18:06:58 -05:00
{
2025-01-18 20:25:39 -06:00
try
{
if ( Token = = null )
return default ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( route )
. AddJsonBody ( body )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-05-19 18:06:58 -05:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
var response = await ApiClient . PostAsync < T > ( request ) ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2024-05-19 18:06:58 -05:00
}
2024-07-25 19:21:52 -05:00
internal async Task < T > PostRequestAsync < T > ( string route , bool ignoreVersion = false )
2024-05-19 18:06:58 -05:00
{
2025-01-18 20:25:39 -06:00
try
{
if ( Token = = null )
return default ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( route )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-05-19 18:06:58 -05:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
var response = await ApiClient . PostAsync < T > ( request ) ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2024-05-19 18:06:58 -05:00
}
2024-11-12 23:28:05 -06:00
internal async Task < T > PutRequestAsync < T > ( string route , object body , bool ignoreVersion = false )
{
2025-01-18 20:25:39 -06:00
try
{
if ( Token = = null )
return default ;
2024-11-12 23:28:05 -06:00
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( route )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-11-12 23:28:05 -06:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-11-12 23:28:05 -06:00
2025-01-18 20:25:39 -06:00
var response = await ApiClient . PutAsync < T > ( request ) ;
2024-11-12 23:28:05 -06:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2024-11-12 23:28:05 -06:00
}
2024-07-25 19:21:52 -05:00
internal T GetRequest < T > ( string route , bool ignoreVersion = false )
2023-01-06 02:39:00 -06:00
{
2025-01-18 20:25:39 -06:00
try
{
if ( Token = = null )
return default ;
2024-01-18 23:22:46 -06:00
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( route )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2023-01-06 02:39:00 -06:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-13 18:41:00 -05:00
2025-01-18 20:25:39 -06:00
var response = ApiClient . Get < T > ( request ) ;
2023-01-06 02:39:00 -06:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2023-01-06 02:39:00 -06:00
}
2024-07-25 19:21:52 -05:00
internal async Task < T > GetRequestAsync < T > ( string route , bool ignoreVersion = false )
2024-05-19 18:06:58 -05:00
{
2025-01-18 20:25:39 -06:00
try
{
if ( Token = = null )
return default ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( route )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-05-19 18:06:58 -05:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
var response = await ApiClient . GetAsync < T > ( request ) ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2024-05-19 18:06:58 -05:00
}
2024-10-23 18:17:01 -05:00
internal async Task < T > DeleteRequestAsync < T > ( string route , bool ignoreVersion = false )
{
2025-01-18 20:25:39 -06:00
try
{
if ( Token = = null )
return default ;
2024-10-23 18:17:01 -05:00
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( route )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-10-23 18:17:01 -05:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-10-23 18:17:01 -05:00
2025-01-18 20:25:39 -06:00
var response = await ApiClient . DeleteAsync < T > ( request ) ;
2024-10-23 18:17:01 -05:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2024-10-23 18:17:01 -05:00
}
2024-05-19 18:06:58 -05:00
internal async Task < string > DownloadRequestAsync ( string route , Action < DownloadProgressChangedEventArgs > progressHandler , Action < AsyncCompletedEventArgs > completeHandler )
2023-01-06 22:12:03 -06:00
{
2025-01-18 20:25:39 -06:00
try
{
route = route . TrimStart ( '/' ) ;
2023-01-14 16:11:28 -06:00
2025-01-18 20:25:39 -06:00
var client = new WebClient ( ) ;
var tempFile = Path . GetTempFileName ( ) ;
2023-01-06 22:12:03 -06:00
2025-01-18 20:25:39 -06:00
client . Headers . Add ( "Authorization" , $"Bearer {Token.AccessToken}" ) ;
client . Headers . Add ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
client . DownloadProgressChanged + = ( s , e ) = > progressHandler ( e ) ;
client . DownloadFileCompleted + = ( s , e ) = > completeHandler ( e ) ;
2023-01-06 22:12:03 -06:00
2025-01-18 20:25:39 -06:00
try
{
await client . DownloadFileTaskAsync ( new Uri ( BaseUrl , route ) , tempFile ) ;
}
catch ( Exception ex )
{
Logger ? . LogError ( ex , "An unknown error occurred while downloading from the server at route {Route}" , route ) ;
if ( File . Exists ( tempFile ) )
File . Delete ( tempFile ) ;
tempFile = String . Empty ;
}
return tempFile ;
2023-12-27 17:22:36 -06:00
}
catch ( Exception ex )
{
2025-01-18 20:25:39 -06:00
OnError ? . Invoke ( this , ex ) ;
return null ;
2023-12-27 17:22:36 -06:00
}
2023-01-06 22:12:03 -06:00
}
2024-05-19 18:06:58 -05:00
internal async Task < string > DownloadRequestAsync ( string route , string destination )
2024-02-20 17:58:49 -06:00
{
2025-01-18 20:25:39 -06:00
try
{
route = route . TrimStart ( '/' ) ;
2024-02-20 17:58:49 -06:00
2025-01-18 20:25:39 -06:00
var client = new WebClient ( ) ;
2024-02-20 17:58:49 -06:00
2025-01-18 20:25:39 -06:00
client . Headers . Add ( "Authorization" , $"Bearer {Token.AccessToken}" ) ;
client . Headers . Add ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-02-20 17:58:49 -06:00
2025-01-18 20:25:39 -06:00
try
{
await client . DownloadFileTaskAsync ( new Uri ( BaseUrl , route ) , destination ) ;
}
catch ( Exception ex )
{
Logger ? . LogError ( ex , "An unknown error occurred while downloading from the server at route {Route}" ,
route ) ;
if ( File . Exists ( destination ) )
File . Delete ( destination ) ;
destination = String . Empty ;
}
return destination ;
2024-02-20 17:58:49 -06:00
}
catch ( Exception ex )
{
2025-01-18 20:25:39 -06:00
OnError ? . Invoke ( this , ex ) ;
return null ;
2024-02-20 17:58:49 -06:00
}
}
2024-01-02 02:34:58 -06:00
internal TrackableStream StreamRequest ( string route )
2023-04-07 20:08:03 -05:00
{
route = route . TrimStart ( '/' ) ;
var client = new WebClient ( ) ;
client . Headers . Add ( "Authorization" , $"Bearer {Token.AccessToken}" ) ;
2024-07-26 00:54:24 -05:00
client . Headers . Add ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2023-04-07 20:08:03 -05:00
2024-02-11 17:57:38 -06:00
var ws = client . OpenRead ( new Uri ( BaseUrl , route ) ) ;
2023-04-07 20:08:03 -05:00
return new TrackableStream ( ws , true , Convert . ToInt64 ( client . ResponseHeaders [ "Content-Length" ] ) ) ;
}
2024-07-25 19:21:52 -05:00
internal T UploadRequest < T > ( string route , string fileName , byte [ ] data , bool ignoreVersion = false )
2024-01-02 02:34:58 -06:00
{
2025-01-18 20:25:39 -06:00
try
{
var request = new RestRequest ( route , Method . Post )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-01-02 02:34:58 -06:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-13 18:41:00 -05:00
2025-01-18 20:25:39 -06:00
request . AddFile ( fileName , data , fileName ) ;
2024-01-02 02:34:58 -06:00
2025-01-18 20:25:39 -06:00
var response = ApiClient . Post < T > ( request ) ;
2024-01-02 02:34:58 -06:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2024-01-02 02:34:58 -06:00
}
2025-03-18 20:30:04 -05:00
internal async Task < T > UploadRequestAsync < T > ( string route , Stream stream , bool ignoreVersion = false )
{
try
{
var request = new RestRequest ( route , Method . Post )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
if ( ! ignoreVersion & & ! IgnoreVersion )
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
request . AddFile ( "File" , ( ) = > stream , "File" ) ;
var response = await ApiClient . PostAsync < T > ( request ) ;
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
}
2024-07-25 19:21:52 -05:00
internal async Task < T > UploadRequestAsync < T > ( string route , string fileName , byte [ ] data , bool ignoreVersion = false )
2024-05-19 18:06:58 -05:00
{
2025-01-18 20:25:39 -06:00
try
{
var request = new RestRequest ( route , Method . Post )
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-05-19 18:06:58 -05:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
request . AddFile ( fileName , data , fileName ) ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
var response = await ApiClient . PostAsync < T > ( request ) ;
2024-05-19 18:06:58 -05:00
2025-01-18 20:25:39 -06:00
return response ;
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
return default ;
}
2024-05-19 18:06:58 -05:00
}
2024-08-12 19:33:26 -05:00
internal async Task < Guid > ChunkedUploadRequestAsync ( string fileName , Stream stream , bool ignoreVersion = false )
{
2025-01-18 20:25:39 -06:00
try
2024-08-12 19:33:26 -05:00
{
2025-01-18 20:25:39 -06:00
var maxChunkSize = 1024 * 1024 * 50 ;
var initResponse = await PostRequestAsync < UploadInitResponse > ( "/Upload/Init" , ignoreVersion ) ;
2024-08-12 19:33:26 -05:00
2025-01-18 20:25:39 -06:00
var buffer = new byte [ maxChunkSize ] ;
2024-08-12 19:33:26 -05:00
2025-01-18 20:25:39 -06:00
while ( stream . Position < stream . Length )
2024-08-12 19:33:26 -05:00
{
2025-01-18 20:25:39 -06:00
var chunkRequest = new UploadChunkRequest ( ) ;
2024-08-12 19:33:26 -05:00
2025-01-18 20:25:39 -06:00
chunkRequest . Start = stream . Position ;
2024-08-12 19:33:26 -05:00
2025-01-18 20:25:39 -06:00
if ( stream . Position + maxChunkSize > stream . Length )
{
var bytes = stream . Length - stream . Position ;
2024-08-12 19:33:26 -05:00
2025-01-18 20:25:39 -06:00
buffer = new byte [ bytes ] ;
2024-07-25 21:26:18 -05:00
2025-01-18 20:25:39 -06:00
await stream . ReadAsync ( buffer , 0 , ( int ) ( stream . Length - stream . Position ) ) ;
}
else
await stream . ReadAsync ( buffer , 0 , maxChunkSize ) ;
2024-07-25 21:26:18 -05:00
2025-01-18 20:25:39 -06:00
chunkRequest . End = stream . Position ;
chunkRequest . Total = stream . Length ;
chunkRequest . File = buffer ;
chunkRequest . Key = initResponse . Key ;
2024-07-25 21:26:18 -05:00
2025-01-18 20:25:39 -06:00
await PostRequestAsync < object > ( "/Upload/Chunk" , chunkRequest , ignoreVersion ) ;
}
2024-07-25 21:26:18 -05:00
2025-01-18 20:25:39 -06:00
return initResponse . Key ;
}
catch ( Exception ex )
2024-09-17 00:19:23 -05:00
{
2025-01-18 20:25:39 -06:00
OnError ? . Invoke ( this , ex ) ;
return default ;
2024-09-17 00:19:23 -05:00
}
2025-01-18 20:25:39 -06:00
}
2023-01-05 01:07:17 -06:00
2025-01-18 20:25:39 -06:00
public async Task < AuthToken > AuthenticateAsync ( string username , string password , bool ignoreVersion = false )
{
try
2023-01-05 18:35:58 -06:00
{
2025-01-18 20:25:39 -06:00
var request = new RestRequest ( "/api/Auth/Login" , Method . Post ) ;
2023-11-09 19:40:38 -06:00
2025-01-18 20:25:39 -06:00
request . AddJsonBody ( new AuthRequest ( )
{
UserName = username ,
Password = password
} ) ;
2024-01-02 02:34:58 -06:00
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2025-01-18 20:25:39 -06:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-01-23 18:01:12 -06:00
2025-01-18 20:25:39 -06:00
var response = await ApiClient . ExecuteAsync < AuthToken > ( request ) ;
2023-01-05 18:35:58 -06:00
2025-06-22 00:00:58 +02:00
ErrorResponse errorResponse = null ;
if ( response . ResponseStatus = = ResponseStatus . Error | | response . ErrorException ! = null )
2025-01-18 20:25:39 -06:00
{
2025-06-22 00:00:58 +02:00
string message = response . ErrorMessage ? ? response ? . ErrorException . Message ;
Logger ? . LogError ( response . ErrorException , "Authentication failed for user {UserName}: {Message}" , username , message ) ;
errorResponse = ParseErrorResponse ( response ) ;
2025-01-18 20:25:39 -06:00
}
switch ( response . StatusCode )
{
case HttpStatusCode . OK :
var token = new AuthToken
{
AccessToken = response . Data . AccessToken ,
RefreshToken = response . Data . RefreshToken ,
Expiration = response . Data . Expiration
} ;
UseToken ( token ) ;
Connected = true ;
return token ;
case HttpStatusCode . Forbidden :
case HttpStatusCode . BadRequest :
case HttpStatusCode . Unauthorized :
Connected = false ;
Logger ? . LogError ( "Authentication failed for user {UserName}: invalid username or password" , username ) ;
2025-06-22 00:00:58 +02:00
throw new AuthFailedException ( AuthFailedException . AuthenticationErrorCode . InvalidCredentials , "Invalid username or password" , errorData : errorResponse , innerException : response . ErrorException ) ;
2025-01-18 20:25:39 -06:00
default :
Connected = false ;
Logger ? . LogError ( "Authentication failed for user {UserName}: could not communicate with the server" , username ) ;
2025-06-22 00:00:58 +02:00
throw new WebException ( "Could not communicate with the server" , innerException : response . ErrorException ) ;
2025-01-18 20:25:39 -06:00
}
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
2025-06-22 00:00:58 +02:00
throw ;
2023-01-05 18:35:58 -06:00
}
2023-01-05 01:07:17 -06:00
}
2025-01-18 20:25:39 -06:00
public void Disconnect ( )
{
Connected = false ;
}
2024-01-23 18:01:12 -06:00
public async Task LogoutAsync ( )
{
2024-08-04 20:47:19 -05:00
await ApiClient . ExecuteAsync ( new RestRequest ( "/api/Auth/Logout" , Method . Post ) ) ;
2024-01-23 18:01:12 -06:00
Connected = false ;
Token = null ;
}
2025-01-05 21:09:57 -06:00
public async Task < AuthToken > RegisterAsync ( string username , string password , string passwordConfirmation )
2023-03-14 02:31:42 -05:00
{
2025-06-22 00:00:58 +02:00
try
2023-03-14 02:31:42 -05:00
{
2025-06-22 00:00:58 +02:00
var request = new RestRequest ( "/api/Auth/Register" , Method . Post ) ;
request . AddJsonBody ( new AuthRequest ( )
{
UserName = username ,
Password = password
} ) ;
2023-03-14 02:31:42 -05:00
2025-06-22 00:00:58 +02:00
var response = await ApiClient . ExecuteAsync < AuthResponse > ( request ) ;
2024-10-24 19:38:31 -05:00
2025-06-22 00:00:58 +02:00
ErrorResponse errorResponse = null ;
if ( response . ResponseStatus = = ResponseStatus . Error | | response . ErrorException ! = null )
2024-10-24 19:44:20 -05:00
{
2025-06-22 00:00:58 +02:00
string message = response . ErrorMessage ? ? response ? . ErrorException . Message ;
Logger ? . LogError ( response . ErrorException , "Registration failed for user {UserName}: {Message}" , username , message ) ;
errorResponse = ParseErrorResponse ( response ) ;
2024-10-24 19:44:20 -05:00
}
2024-10-24 19:38:31 -05:00
2025-06-22 00:00:58 +02:00
switch ( response . StatusCode )
{
case HttpStatusCode . OK :
Token = new AuthToken
{
AccessToken = response . Data . AccessToken ,
RefreshToken = response . Data . RefreshToken ,
Expiration = response . Data . Expiration
} ;
2023-11-10 01:32:30 -06:00
2025-06-22 00:00:58 +02:00
Connected = true ;
2024-01-23 18:01:12 -06:00
2025-06-22 00:00:58 +02:00
return Token ;
2023-03-14 02:31:42 -05:00
2025-06-22 00:00:58 +02:00
case HttpStatusCode . BadRequest :
case HttpStatusCode . Forbidden :
case HttpStatusCode . Unauthorized :
Connected = false ;
throw new RegisterFailedException ( response . Data . Message , errorData : errorResponse , innerException : response . ErrorException ) ;
default :
Connected = false ;
Logger ? . LogError ( "Registering failed for user {UserName}: could not communicate with the server" , username ) ;
throw new WebException ( "Could not communicate with the server" , innerException : response . ErrorException ) ;
}
}
catch ( Exception ex )
{
OnError ? . Invoke ( this , ex ) ;
2023-03-14 02:31:42 -05:00
2025-06-22 00:00:58 +02:00
throw ;
2023-03-14 02:31:42 -05:00
}
}
2025-01-03 22:52:30 -06:00
public async Task < IEnumerable < AuthenticationProvider > > GetAuthenticationProvidersAsync ( )
2025-01-03 00:55:22 -06:00
{
2025-01-21 00:17:55 -06:00
var request = new RestRequest ( "/api/Auth/AuthenticationProviders" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
var response = await ApiClient . GetAsync < IEnumerable < AuthenticationProvider > > ( request ) ;
return response ;
2025-01-03 00:55:22 -06:00
}
public string GetAuthenticationProviderLoginUrl ( string provider )
{
return $"{BaseUrl}api/Auth/Login?Provider={provider}" ;
}
2025-03-10 21:44:51 -05:00
public bool Ping ( )
{
try
{
var guid = Guid . NewGuid ( ) . ToString ( ) ;
var request = new RestRequest ( "/api/Ping" , Method . Head ) ;
request . AddHeader ( "X-Ping" , guid ) ;
var response = ApiClient . Execute ( request ) ;
return response . StatusCode = = HttpStatusCode . OK & & response . GetHeaderValue ( "X-Pong" ) = = guid . FastReverse ( ) ;
}
catch
{
return false ;
}
}
2023-03-14 02:31:42 -05:00
public async Task < bool > PingAsync ( )
{
2025-03-10 21:44:51 -05:00
try
{
var guid = Guid . NewGuid ( ) . ToString ( ) ;
var request = new RestRequest ( "/api/Ping" , Method . Head ) ;
request . AddHeader ( "X-Ping" , guid ) ;
2023-03-14 02:31:42 -05:00
2025-06-15 23:28:00 +02:00
// specify timeout for ping response
request . Timeout = TimeSpan . FromSeconds ( 4 ) ;
2025-03-10 21:44:51 -05:00
var response = await ApiClient . ExecuteAsync ( request ) ;
return response . StatusCode = = HttpStatusCode . OK & & response . GetHeaderValue ( "X-Pong" ) = = guid . FastReverse ( ) ;
}
catch
{
return false ;
}
2023-03-14 02:31:42 -05:00
}
2023-11-09 19:40:38 -06:00
public bool ValidateToken ( )
{
return ValidateToken ( Token ) ;
}
2024-07-25 19:21:52 -05:00
public bool ValidateToken ( AuthToken token , bool ignoreVersion = false )
2023-01-05 01:07:17 -06:00
{
2023-11-10 20:53:28 -06:00
Logger ? . LogTrace ( "Validating token..." ) ;
2023-08-21 18:44:20 -05:00
2023-03-17 02:07:25 -05:00
if ( token = = null )
2023-08-21 18:44:20 -05:00
{
2023-11-10 20:53:28 -06:00
Logger ? . LogTrace ( "Token is null!" ) ;
2023-03-17 02:07:25 -05:00
return false ;
2023-08-21 18:44:20 -05:00
}
2023-03-17 02:07:25 -05:00
2023-01-05 01:07:17 -06:00
var request = new RestRequest ( "/api/Auth/Validate" )
2024-07-26 00:54:24 -05:00
. AddHeader ( "Authorization" , $"Bearer {Token.AccessToken}" )
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2023-01-05 01:07:17 -06:00
2025-06-15 23:28:00 +02:00
// specify timeout for auth response
request . Timeout = TimeSpan . FromSeconds ( 8 ) ;
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2024-08-05 18:02:01 -05:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-13 18:41:00 -05:00
2023-03-17 02:07:25 -05:00
if ( String . IsNullOrEmpty ( token . AccessToken ) | | String . IsNullOrEmpty ( token . RefreshToken ) )
2023-08-21 18:44:20 -05:00
{
2023-11-10 20:53:28 -06:00
Logger ? . LogTrace ( "Token is empty!" ) ;
2023-03-17 02:07:25 -05:00
return false ;
2023-08-21 18:44:20 -05:00
}
2023-03-17 02:07:25 -05:00
2024-06-18 07:41:53 -05:00
try
{
var response = ApiClient . Post ( request ) ;
2023-01-05 01:07:17 -06:00
2024-06-18 07:41:53 -05:00
var valid = response . StatusCode = = HttpStatusCode . OK ;
2023-08-21 18:44:20 -05:00
2024-06-18 07:41:53 -05:00
if ( valid )
Logger ? . LogTrace ( "Token is valid!" ) ;
else
Logger ? . LogTrace ( "Token is invalid!" ) ;
2023-08-21 18:44:20 -05:00
2024-06-18 07:41:53 -05:00
Connected = valid ;
2024-01-23 18:01:12 -06:00
2024-06-18 07:41:53 -05:00
return response . StatusCode = = HttpStatusCode . OK ;
}
catch ( Exception ex )
{
Logger ? . LogTrace ( ex , "Token could not be retrieved" ) ;
return false ;
}
2023-01-05 01:07:17 -06:00
}
2024-07-17 20:58:51 -05:00
public async Task < bool > ValidateTokenAsync ( )
{
return await ValidateTokenAsync ( Token ) ;
}
2024-07-25 19:21:52 -05:00
public async Task < bool > ValidateTokenAsync ( AuthToken token , bool ignoreVersion = false )
2024-05-19 18:06:58 -05:00
{
Logger ? . LogTrace ( "Validating token..." ) ;
if ( token = = null )
{
Logger ? . LogTrace ( "Token is null!" ) ;
return false ;
}
var request = new RestRequest ( "/api/Auth/Validate" )
2025-02-28 22:48:02 -06:00
. AddHeader ( "Authorization" , $"Bearer {token.AccessToken}" )
2024-07-26 00:54:24 -05:00
. AddHeader ( "X-API-Version" , GetCurrentVersion ( ) . ToString ( ) ) ;
2024-05-19 18:06:58 -05:00
2025-06-15 23:28:00 +02:00
// specify timeout for auth response
request . Timeout = TimeSpan . FromSeconds ( 8 ) ;
2025-03-16 21:04:24 -05:00
if ( ! ignoreVersion & & ! IgnoreVersion )
2024-08-05 18:02:01 -05:00
request . Interceptors = new List < Interceptor > ( ) { new VersionInterceptor ( ) } ;
2024-05-19 18:06:58 -05:00
if ( String . IsNullOrEmpty ( token . AccessToken ) | | String . IsNullOrEmpty ( token . RefreshToken ) )
{
Logger ? . LogTrace ( "Token is empty!" ) ;
return false ;
}
try
{
var response = await ApiClient . PostAsync < object > ( request ) ;
Logger ? . LogTrace ( "Token is valid!" ) ;
Connected = true ;
}
2024-07-15 17:26:24 -05:00
catch ( Exception ex )
2024-05-19 18:06:58 -05:00
{
2024-07-15 17:26:24 -05:00
Logger ? . LogTrace ( ex , "Could not validate token" ) ;
2024-05-19 18:06:58 -05:00
Connected = false ;
}
return Connected ;
}
2023-11-09 19:40:38 -06:00
public void UseToken ( AuthToken token )
{
Token = token ;
}
2024-01-02 20:46:28 -06:00
public string GetServerAddress ( )
{
2024-02-11 17:57:38 -06:00
return BaseUrl . ToString ( ) ;
2024-01-02 20:46:28 -06:00
}
public Settings GetSettings ( )
{
return GetRequest < Settings > ( $"/api/Settings" ) ;
}
2024-01-02 02:34:58 -06:00
internal string GetMacAddress ( )
2023-01-15 20:45:37 -06:00
{
return NetworkInterface . GetAllNetworkInterfaces ( )
. Where ( nic = > nic . OperationalStatus = = OperationalStatus . Up & & nic . NetworkInterfaceType ! = NetworkInterfaceType . Loopback )
. Select ( nic = > nic . GetPhysicalAddress ( ) . ToString ( ) )
. FirstOrDefault ( ) ;
}
2024-01-02 02:34:58 -06:00
internal string GetIpAddress ( )
2023-01-15 20:45:37 -06:00
{
return Dns . GetHostEntry ( Dns . GetHostName ( ) ) . AddressList [ 0 ] . ToString ( ) ;
}
2024-08-26 20:18:54 -05:00
internal async Task < string > GetIPXRelayHostAsync ( )
{
var host = Settings . IPXRelayHost ;
if ( String . IsNullOrWhiteSpace ( host ) )
{
var serverAddress = new Uri ( GetServerAddress ( ) ) ;
host = serverAddress . DnsSafeHost ;
}
var entry = await Dns . GetHostEntryAsync ( host ) ;
if ( entry . AddressList . Length > 0 )
host = entry . AddressList . First ( ) . ToString ( ) ;
return host ;
}
2025-06-22 00:00:58 +02:00
internal ErrorResponse ParseErrorResponse ( RestResponse response , bool defaultToGenericResponse = false )
{
ErrorResponse errorResponse = null ;
// Try to deserialize the error response.
try
{
errorResponse = JsonConvert . DeserializeObject < ErrorResponse > ( response . Content ) ;
return errorResponse ;
}
catch ( Exception deserializationEx )
{
// Log error and create a fallback message if deserialization fails.
if ( defaultToGenericResponse )
{
Logger ? . LogError ( deserializationEx , "Error deserializing error response for route {Route}" , response . Request ) ;
errorResponse = new ErrorResponse
{
Message = "Could not process the server response."
} ;
}
}
return errorResponse ;
}
2023-01-04 20:31:02 -06:00
}
}