From f15632f2d0e30f05ac4faf7cc6ee6dcbe785c4ec Mon Sep 17 00:00:00 2001 From: achiez Date: Sun, 25 Jan 2026 18:48:11 +0200 Subject: [PATCH] Refactored MAAC error/status handling and UI feedback - Introduced centralized MAACRequestHandler for error tracking, retries, and status escalation (Ok/Warning/Error) per account - PortableMaClient now uses new Status property (PortableMaClientStatus) instead of IsError; error history tracked in PortableMaClientErrorData - All MAAC requests now routed through MAACRequestHandler, providing unified error handling and retry logic - MultiAccountAutoConfirmer now filters accounts by Status.StatusType == Ok - Updated MainWindow UI: account name color reflects status (Success/Warning/Error); added "Unattach proxy" to context menu - Removed obsolete PortableMaClientStatusToColorConverter and related bindings - Settings: removed IgnorePatchTuesdayErrors, added MaacErrorThreshold and MaacRetryInterval for error escalation control - Refactored SessionHandler: split into logic, API, and helpers; improved exception propagation and session management - AdmissionHelper methods now handle null sessions and always transfer community cookies if session is missing - Mafile export: always preserves SteamId, even if session data is not exported - ViewModel: RemoveProxy command now parameterized and only enabled if Mafile has a proxy - Enabled concurrent log writing in NLog config - Minor code cleanups and improved exception signatures --- src/NebulaAuth/Converters/Converters.xaml | 1 - .../PortableMaClientStatusToColorConverter.cs | 25 --- src/NebulaAuth/MainWindow.xaml | 48 +++-- .../Model/MAAC/MAACRequestHandler.cs | 166 ++++++++++++++++++ .../Model/MAAC/MultiAccountAutoConfirmer.cs | 9 +- src/NebulaAuth/Model/MAAC/PortableMaClient.cs | 117 +++--------- .../Model/MAAC/PortableMaClientErrorData.cs | 38 ++++ .../Model/MAAC/PortableMaClientStatus.cs | 26 +++ src/NebulaAuth/Model/MAAC/Result.cs | 29 +++ src/NebulaAuth/Model/MaClient.cs | 12 +- .../Model/MafileExport/MafileExporter.cs | 9 +- src/NebulaAuth/Model/Mafiles/Storage.cs | 1 - .../{ => SessionHandler}/SessionHandler.cs | 75 ++------ .../SessionHandler_API.cs | 2 - .../SessionHandler/SessionHandler_Helpers.cs | 42 +++++ src/NebulaAuth/Model/Settings.cs | 4 +- src/NebulaAuth/NLog.config | 6 +- src/NebulaAuth/NebulaAuth.csproj | 2 +- src/NebulaAuth/View/SettingsView.xaml | 8 - src/NebulaAuth/ViewModel/MainVM.cs | 2 + src/NebulaAuth/ViewModel/MainVM_Proxy.cs | 19 +- src/NebulaAuth/ViewModel/Other/SettingsVM.cs | 6 - src/NebulaAuth/localization.loc.json | 16 ++ .../Authentication/AdmissionHelper.cs | 23 ++- .../Authorization/SessionInvalidException.cs | 2 +- ... => SessionPermanentlyExpiredException.cs} | 2 +- 26 files changed, 452 insertions(+), 238 deletions(-) delete mode 100644 src/NebulaAuth/Converters/PortableMaClientStatusToColorConverter.cs create mode 100644 src/NebulaAuth/Model/MAAC/MAACRequestHandler.cs create mode 100644 src/NebulaAuth/Model/MAAC/PortableMaClientErrorData.cs create mode 100644 src/NebulaAuth/Model/MAAC/PortableMaClientStatus.cs create mode 100644 src/NebulaAuth/Model/MAAC/Result.cs rename src/NebulaAuth/Model/{ => SessionHandler}/SessionHandler.cs (66%) rename src/NebulaAuth/Model/{ => SessionHandler}/SessionHandler_API.cs (90%) create mode 100644 src/NebulaAuth/Model/SessionHandler/SessionHandler_Helpers.cs rename src/SteamLibForked/Exceptions/Authorization/{SessionExpiredException.cs => SessionPermanentlyExpiredException.cs} (85%) diff --git a/src/NebulaAuth/Converters/Converters.xaml b/src/NebulaAuth/Converters/Converters.xaml index d99c88e..97888f1 100644 --- a/src/NebulaAuth/Converters/Converters.xaml +++ b/src/NebulaAuth/Converters/Converters.xaml @@ -12,7 +12,6 @@ - diff --git a/src/NebulaAuth/Converters/PortableMaClientStatusToColorConverter.cs b/src/NebulaAuth/Converters/PortableMaClientStatusToColorConverter.cs deleted file mode 100644 index 89acb45..0000000 --- a/src/NebulaAuth/Converters/PortableMaClientStatusToColorConverter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; -using System.Windows.Media; - -namespace NebulaAuth.Converters; - -public class PortableMaClientStatusToColorConverter : IValueConverter -{ - public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is false) - { - return new SolidColorBrush(Color.FromRgb(187, 224, 139)); - } - - - return new SolidColorBrush(Color.FromRgb(224, 139, 139)); - } - - public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotSupportedException(); - } -} \ No newline at end of file diff --git a/src/NebulaAuth/MainWindow.xaml b/src/NebulaAuth/MainWindow.xaml index 40f25d5..ab49375 100644 --- a/src/NebulaAuth/MainWindow.xaml +++ b/src/NebulaAuth/MainWindow.xaml @@ -294,18 +294,42 @@ - - - - + + + + + - - - + @@ -393,7 +417,9 @@ - + diff --git a/src/NebulaAuth/Model/MAAC/MAACRequestHandler.cs b/src/NebulaAuth/Model/MAAC/MAACRequestHandler.cs new file mode 100644 index 0000000..4ab9e9b --- /dev/null +++ b/src/NebulaAuth/Model/MAAC/MAACRequestHandler.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using NebulaAuth.Core; +using NebulaAuth.Model.Entities; +using SteamLib.Exceptions.Authorization; + +namespace NebulaAuth.Model.MAAC; + +public static class MAACRequestHandler +{ + private const string LOC_PATH = "MAAC"; + private static readonly ConcurrentDictionary _errors = new(); + + public static void Register(Mafile mafile) + { + _errors[mafile] = new PortableMaClientErrorData(); + } + + public static void Unregister(Mafile mafile) + { + _errors.TryRemove(mafile, out _); + } + + public static PortableMaClientStatus ClearErrors(Mafile mafile) + { + if (_errors.TryGetValue(mafile, out var data)) + { + data.Clear(); + } + + return PortableMaClientStatus.Ok(); + } + + /// + /// Handles a MAAC request with progressive error handling strategy: retries and status management based on error + /// history. + /// + /// + /// + /// + /// + public static async Task HandleRequest(PortableMaClient client, Func> func) + { + var result = await client.DoRequest(func, client.Mafile.PreviouslyHadNoErrors()); + if (result.IsSuccess) + { + InformRequestSuccessful(client.Mafile); + return result.Data; + } + + if (client.Mafile.PreviouslyHadNoErrors()) + { + client.SetStatus(PortableMaClientStatus.Warning(GetPortableMaClientStatus("SessionError"))); + } + else if (client.Mafile.LastErrorWasAtLeast(Settings.Instance.MaacRetryInterval)) + { + Shell.Logger.Info("Retrying MAAC request for {name} MAAC account after previous error.", + client.Mafile.AccountName); + result = await client.DoRequest(func); + if (result.IsSuccess) + { + InformRequestSuccessful(client.Mafile); + return result.Data; + } + } + + if (client.Mafile.ErrorPersistedFor(Settings.Instance.MaacErrorThreshold)) + { + client.SetStatus(PortableMaClientStatus.Error(GetPortableMaClientStatus("SessionError"))); + } + + AddError(client.Mafile, result.Exception!); + throw result.Exception!; + } + + + private static async Task> DoRequest(this PortableMaClient client, Func> req, + bool withSessionHandler = true) + { + try + { + return withSessionHandler + ? Result.Success(await SessionHandler.Handle(req, client.Mafile, client.Chp(), + GetTimerPrefix(client.Mafile))) + : Result.Success(await req()); + } + catch (SessionInvalidException ex) + { + Shell.Logger.Warn("SessionInvalidException caught in MAAC request handler."); + return Result.Error(ex); + } + } + + private static void InformRequestSuccessful(Mafile mafile) + { + if (_errors.TryGetValue(mafile, out var data)) + { + Shell.Logger.Info("MAAC request for {name} MAAC account succeeded, clearing error history.", + mafile.AccountName); + data.Clear(); + } + } + + private static void AddError(Mafile mafile, Exception ex) + { + if (_errors.TryGetValue(mafile, out var data)) + { + Shell.Logger.Info("Registering error for {name} MAAC account: {ex}", mafile.AccountName, ex.Message); + data.AddEntry(ex); + } + } + + private static bool ErrorPersistedFor(this Mafile mafile, TimeSpan span) + { + if (_errors.TryGetValue(mafile, out var data)) + { + var oldestError = data.GetOldestErrorTime(); + if (oldestError.HasValue && DateTime.UtcNow - oldestError.Value > span) + { + return true; + } + } + + return false; + } + + + private static bool PreviouslyHadNoErrors(this Mafile mafile) + { + if (_errors.TryGetValue(mafile, out var data)) + { + return data.NoErrors; + } + + return true; + } + + private static bool LastErrorWasAtLeast(this Mafile mafile, TimeSpan timeAgo) + { + if (!_errors.TryGetValue(mafile, out var data)) return false; + var latestError = data.GetTimeFromLastError(); + if (latestError.HasValue && latestError.Value > timeAgo) + { + return true; + } + + return false; + } + + + private static string GetTimerPrefix(Mafile mafile) + { + return GetLocalization("TimerPrefix") + mafile.AccountName + ": "; + } + + private static string GetLocalization(string key) + { + return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key); + } + + private static string GetPortableMaClientStatus(string key) + { + return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key); + } +} \ No newline at end of file diff --git a/src/NebulaAuth/Model/MAAC/MultiAccountAutoConfirmer.cs b/src/NebulaAuth/Model/MAAC/MultiAccountAutoConfirmer.cs index 8d3714b..d01be16 100644 --- a/src/NebulaAuth/Model/MAAC/MultiAccountAutoConfirmer.cs +++ b/src/NebulaAuth/Model/MAAC/MultiAccountAutoConfirmer.cs @@ -59,7 +59,8 @@ public static class MultiAccountAutoConfirmer private static async Task TimerConfirmInternal() { var clients = Lock.ReadLock(() => Clients.ToArray()); - var enabledClients = clients.Where(x => x.LinkedClient is {IsError: false}).ToArray(); + var enabledClients = clients.Where(x => x.LinkedClient is {Status.StatusType: PortableMaClientStatusType.Ok}) + .ToArray(); enabledClients = DistributeEvenly(enabledClients).ToArray(); var confirmed = 0; await Task.Run(async () => @@ -69,7 +70,8 @@ public static class MultiAccountAutoConfirmer var conf = 0; try { - conf = await client.LinkedClient!.Confirm(); + conf = await MAACRequestHandler.HandleRequest(client.LinkedClient!, + () => client.LinkedClient!.Confirm()); } catch (ObjectDisposedException) { @@ -114,7 +116,6 @@ public static class MultiAccountAutoConfirmer } } - public static bool TryAddToConfirm(Mafile mafile) { return Lock.WriteLock(() => @@ -122,6 +123,7 @@ public static class MultiAccountAutoConfirmer if (Clients.Contains(mafile)) return false; mafile.LinkedClient = new PortableMaClient(mafile); Clients.Add(mafile); + MAACRequestHandler.Register(mafile); return true; }); } @@ -134,6 +136,7 @@ public static class MultiAccountAutoConfirmer Clients.Remove(mafile); mafile.LinkedClient?.Dispose(); mafile.LinkedClient = null; + MAACRequestHandler.Unregister(mafile); }); } diff --git a/src/NebulaAuth/Model/MAAC/PortableMaClient.cs b/src/NebulaAuth/Model/MAAC/PortableMaClient.cs index 0de4ec7..1b8ec3b 100644 --- a/src/NebulaAuth/Model/MAAC/PortableMaClient.cs +++ b/src/NebulaAuth/Model/MAAC/PortableMaClient.cs @@ -11,11 +11,9 @@ using AchiesUtilities.Web.Proxy; using CommunityToolkit.Mvvm.ComponentModel; using NebulaAuth.Core; using NebulaAuth.Model.Entities; -using NebulaAuth.Utility; using SteamLib.Api.Mobile; using SteamLib.Api.Trade; using SteamLib.Authentication; -using SteamLib.Exceptions.Authorization; using SteamLib.SteamMobile.Confirmations; using SteamLib.Web; using SteamLibForked.Abstractions; @@ -34,7 +32,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable [ObservableProperty] private bool _autoConfirmMarket; [ObservableProperty] private bool _autoConfirmTrades; - [ObservableProperty] private bool _isError; + [ObservableProperty] private PortableMaClientStatus _status = PortableMaClientStatus.Ok(); public PortableMaClient(Mafile mafile) { @@ -46,6 +44,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable ClientHandler = pair.Handler; UpdateCookies(mafile.SessionData); Mafile.PropertyChanged += Mafile_PropertyChanged; + SetStatus(PortableMaClientStatus.Error("123123")); } private void Mafile_PropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -56,37 +55,21 @@ public partial class PortableMaClient : ObservableObject, IDisposable } } - private void UpdateCookies(IMobileSessionData? sessionData) { - Application.Current.Dispatcher.Invoke(() => IsError = sessionData == null); + var newStatus = MAACRequestHandler.ClearErrors(Mafile); + if (!newStatus.Equals(Status)) + SetStatus(newStatus); + ClientHandler.CookieContainer.ClearAllCookies(); - if (sessionData != null) - { - ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(sessionData); - } - else - { - ClientHandler.CookieContainer.ClearSteamCookies(); - ClientHandler.CookieContainer.AddMinimalMobileCookies(); - AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer); - } + ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(sessionData); } public async Task Confirm() { Proxy.SetData(Mafile.Proxy?.Data ?? MaClient.DefaultProxy); - List conf; - try - { - conf = (await HandleTimerRequest(GetConfirmations)).ToList(); - } - catch (ApplicationException ex) - { - Shell.Logger.Warn(ex, "Timer {accountName}: Error GetConf in timer.", Mafile.AccountName); - return 0; - } + var conf = (await GetConfirmations()).ToList(); var toConfirm = new List(); if (AutoConfirmMarket) @@ -103,20 +86,12 @@ public partial class PortableMaClient : ObservableObject, IDisposable } if (toConfirm.Count == 0) return 0; - try - { - Shell.Logger.Debug("Timer {accountName}: Sending confirmations. Count: {count}", Mafile.AccountName, - toConfirm.Count); - var success = await HandleTimerRequest(() => SendConfirmations(toConfirm)); - //TODO: handle success == false - Shell.Logger.Debug("Timer {accountName}: Confirmation sent: {confirmResult}", Mafile.AccountName, success); - return toConfirm.Count; - } - catch (ApplicationException ex) - { - Shell.Logger.Warn(ex, "Timer {accountName}: MultiConf error in Timer.", Mafile.AccountName); - return 0; - } + Shell.Logger.Debug("Timer {accountName}: Sending confirmations. Count: {count}", Mafile.AccountName, + toConfirm.Count); + var success = await SendConfirmations(toConfirm); + //TODO: handle success == false + Shell.Logger.Debug("Timer {accountName}: Confirmation sent: {confirmResult}", Mafile.AccountName, success); + return toConfirm.Count; } @@ -148,58 +123,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable Mafile.SessionData!.SteamId, Mafile, true, _cts.Token); } - private async Task HandleTimerRequest(Func> func) - { - Exception innerException; - try - { - return await SessionHandler.Handle(func, Mafile, Chp(), GetTimerPrefix()); - } - catch (OperationCanceledException ex) - { - innerException = ex; //Ignored - } - catch (SessionInvalidException ex) - { - if (IgnoreTimerErrors()) - { - Shell.Logger.Warn( - "Timer {accountName}: Session error while requesting in timer. Ignored due to IgnorePatchTuesdayErrors setting", - Mafile.AccountName); - } - else - { - Shell.Logger.Warn("Timer {accountName}: Session error while requesting in timer. Timer disabled", - Mafile.AccountName); - SetError(); - } - - innerException = ex; - } - catch (Exception ex) when (ExceptionHandler.Handle(ex, GetTimerPrefix())) - { - innerException = ex; - } - - throw new ApplicationException("Swallowed", innerException); - } - - - private bool IgnoreTimerErrors() - { - if (!Settings.Instance.IgnorePatchTuesdayErrors) return false; - - var pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); - var pstNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, pstZone); - - var startTime = pstNow.Date.AddHours(15).AddMinutes(55); // 15:55 PST //22:55 GMT //00:55 GMT+2 - var endTime = pstNow.Date.AddHours(17).AddMinutes(15); // 17:15 PST //00:15 GMT //02:15 GMT+2 - - return pstNow.DayOfWeek == DayOfWeek.Tuesday && pstNow >= startTime && pstNow <= endTime; - } - - - private HttpClientHandlerPair Chp() + public HttpClientHandlerPair Chp() { return new HttpClientHandlerPair(Client, ClientHandler); } @@ -214,11 +138,14 @@ public partial class PortableMaClient : ObservableObject, IDisposable return GetLocalization("TimerPrefix") + Mafile.AccountName + ": "; } - public void SetError() + public void SetStatus(PortableMaClientStatus status) { - Application.Current.Dispatcher.Invoke(() => IsError = true); - Shell.Logger.Warn("Timer {accountName}: disabled due to error.", Mafile.AccountName); - SnackbarController.SendSnackbar(GetTimerPrefix() + GetLocalization("TimerSessionError")); + Application.Current.Dispatcher.Invoke(() => Status = status); + if (status.StatusType == PortableMaClientStatusType.Error) + { + Shell.Logger.Warn("Timer {accountName}: disabled due to error.", Mafile.AccountName); + SnackbarController.SendSnackbar(GetTimerPrefix() + status.Message); + } } public void Dispose() diff --git a/src/NebulaAuth/Model/MAAC/PortableMaClientErrorData.cs b/src/NebulaAuth/Model/MAAC/PortableMaClientErrorData.cs new file mode 100644 index 0000000..9235ed5 --- /dev/null +++ b/src/NebulaAuth/Model/MAAC/PortableMaClientErrorData.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NebulaAuth.Model.MAAC; + +public sealed class PortableMaClientErrorData +{ + public SortedDictionary Values { get; } = new(); + public bool NoErrors => Values.Count == 0; + private readonly object _lock = new(); + + + public DateTime? GetOldestErrorTime() + { + if (Values.Count == 0) return null; + return Values.Keys.First(); + } + + public void AddEntry(Exception ex) + { + lock (_lock) + Values[DateTime.UtcNow] = ex; + } + + public void Clear() + { + lock (_lock) + Values.Clear(); + } + + public TimeSpan? GetTimeFromLastError() + { + if (Values.Count == 0) return null; + var lastEntry = Values.Keys.Last(); + return DateTime.UtcNow - lastEntry; + } +} \ No newline at end of file diff --git a/src/NebulaAuth/Model/MAAC/PortableMaClientStatus.cs b/src/NebulaAuth/Model/MAAC/PortableMaClientStatus.cs new file mode 100644 index 0000000..2cd95b6 --- /dev/null +++ b/src/NebulaAuth/Model/MAAC/PortableMaClientStatus.cs @@ -0,0 +1,26 @@ +namespace NebulaAuth.Model.MAAC; + +public record PortableMaClientStatus(PortableMaClientStatusType StatusType, string? Message = null) +{ + public static PortableMaClientStatus Ok() + { + return new PortableMaClientStatus(PortableMaClientStatusType.Ok); + } + + public static PortableMaClientStatus Warning(string? message) + { + return new PortableMaClientStatus(PortableMaClientStatusType.Warning, message); + } + + public static PortableMaClientStatus Error(string? message) + { + return new PortableMaClientStatus(PortableMaClientStatusType.Error, message); + } +} + +public enum PortableMaClientStatusType +{ + Ok, + Warning, + Error +} \ No newline at end of file diff --git a/src/NebulaAuth/Model/MAAC/Result.cs b/src/NebulaAuth/Model/MAAC/Result.cs new file mode 100644 index 0000000..d7af708 --- /dev/null +++ b/src/NebulaAuth/Model/MAAC/Result.cs @@ -0,0 +1,29 @@ +using System; + +namespace NebulaAuth.Model.MAAC; + +public struct Result +{ + public bool IsSuccess { get; } + public T Data => !IsSuccess ? throw new InvalidOperationException("No data available") : _data!; + public Exception? Exception { get; } + + private readonly T? _data; + + private Result(bool success, T? data, Exception? exception) + { + IsSuccess = success; + _data = data; + Exception = exception; + } + + public static Result Success(T data) + { + return new Result(true, data, null); + } + + public static Result Error(Exception ex) + { + return new Result(false, default, ex); + } +} \ No newline at end of file diff --git a/src/NebulaAuth/Model/MaClient.cs b/src/NebulaAuth/Model/MaClient.cs index 3be9ca6..115f5e3 100644 --- a/src/NebulaAuth/Model/MaClient.cs +++ b/src/NebulaAuth/Model/MaClient.cs @@ -38,17 +38,7 @@ public static class MaClient { ClientHandler.CookieContainer.ClearAllCookies(); if (account == null) return; - if (account.SessionData != null) - { - ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(account.SessionData); - } - else - { - ClientHandler.CookieContainer.ClearSteamCookies(); - ClientHandler.CookieContainer.AddMinimalMobileCookies(); - AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer); - } - + ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(account.SessionData); Proxy.SetData(account.Proxy?.Data); } diff --git a/src/NebulaAuth/Model/MafileExport/MafileExporter.cs b/src/NebulaAuth/Model/MafileExport/MafileExporter.cs index 141cd38..9774193 100644 --- a/src/NebulaAuth/Model/MafileExport/MafileExporter.cs +++ b/src/NebulaAuth/Model/MafileExport/MafileExporter.cs @@ -6,6 +6,7 @@ using AchiesUtilities.Extensions; using NebulaAuth.Core; using NebulaAuth.Model.Entities; using NebulaAuth.Model.Mafiles; +using SteamLibForked.Models.Session; using SteamLibForked.Models.SteamIds; namespace NebulaAuth.Model.MafileExport; @@ -71,6 +72,12 @@ public static class MafileExporter private static async Task ExportMafile(string path, MafileExportTemplate template, Mafile mafile) { + // We preserve SteamId even if other info is not included + var session = template.IncludeSessionData + ? mafile.SessionData + : new MobileSessionData(null!, mafile.SteamId, default, null, tokens: null); + + var serializeMaf = new Mafile { SharedSecret = template.IncludeSharedSecret ? mafile.SharedSecret : null!, @@ -78,7 +85,7 @@ public static class MafileExporter DeviceId = template.IncludeIdentitySecret ? mafile.DeviceId : null!, RevocationCode = template.IncludeRCode ? mafile.RevocationCode : null!, AccountName = mafile.AccountName, - SessionData = template.IncludeSessionData ? mafile.SessionData : null!, + SessionData = session, SteamId = mafile.SteamId, ServerTime = template.IncludeOtherInfo ? mafile.ServerTime : 0, SerialNumber = template.IncludeOtherInfo ? mafile.SerialNumber : 0, diff --git a/src/NebulaAuth/Model/Mafiles/Storage.cs b/src/NebulaAuth/Model/Mafiles/Storage.cs index f11f93f..77c66dd 100644 --- a/src/NebulaAuth/Model/Mafiles/Storage.cs +++ b/src/NebulaAuth/Model/Mafiles/Storage.cs @@ -67,7 +67,6 @@ public static class Storage }, token); MaFiles = new ObservableCollection(localList.OrderBy(m => m.AccountName)); - } /// diff --git a/src/NebulaAuth/Model/SessionHandler.cs b/src/NebulaAuth/Model/SessionHandler/SessionHandler.cs similarity index 66% rename from src/NebulaAuth/Model/SessionHandler.cs rename to src/NebulaAuth/Model/SessionHandler/SessionHandler.cs index 7e3a0e3..b1c9979 100644 --- a/src/NebulaAuth/Model/SessionHandler.cs +++ b/src/NebulaAuth/Model/SessionHandler/SessionHandler.cs @@ -34,37 +34,28 @@ public static partial class SessionHandler string? snackbarPrefix = null) { using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler"); - var mobileTokenExpired = MobileTokenExpired(mafile); - var refreshTokenExpired = RefreshTokenExpired(mafile); - var password = GetPassword(mafile); + Exception? currentException = null; - if (!mobileTokenExpired) + if (!mafile.MobileTokenExpired()) { try { return await func(); } catch (SessionInvalidException ex) - when (!refreshTokenExpired || password != null) { - if (ex is SessionPermanentlyExpiredException) - { - Shell.Logger.Debug(ex, "RefreshToken on mafile {name} {steamid} is expired", mafile.AccountName, - mafile.SessionData?.SteamId); - refreshTokenExpired = true; - } - else - { - Shell.Logger.Debug(ex, "MobileToken on mafile {name} {steamid} is expired", mafile.AccountName, - mafile.SessionData?.SteamId); - } + Shell.Logger.Warn(ex, "Session on mafile {name} {steamid} is invalid when mobile token is not expired", + mafile.AccountName, mafile.SessionData?.SteamId); + currentException = ex; } } - - //State: mobileToken invalid/expired, refreshToken maybe not expired - if (!refreshTokenExpired) + if (!mafile.RefreshTokenExpired()) { + Shell.Logger.Info("Trying to refresh session on mafile {name} {steamid} using refresh token", + mafile.AccountName, + mafile.SessionData?.SteamId); + var refreshed = await RefreshInternal(chp, mafile); if (refreshed) { @@ -77,19 +68,14 @@ public static partial class SessionHandler } catch (SessionInvalidException ex) { - Shell.Logger.Info(ex, "MobileToken on {name} {steamid} was refreshed but after it, error occured", + Shell.Logger.Warn(ex, "MobileToken on {name} {steamid} was refreshed but after it, error occured", mafile.AccountName, mafile.SessionData?.SteamId); - if (password == null) - throw; + currentException = ex; } } } - Shell.Logger.Debug("Session on mafile {name} {steamid} is invalid/expired", mafile.AccountName, - mafile.SessionData?.SteamId); - - //State: mobileToken invalid/expired, refreshToken invalid/expired - if (password != null) + if (mafile.HasPassword(out var password)) { var logged = await LoginAgainInternal(chp, mafile, password, true); if (logged) @@ -100,41 +86,10 @@ public static partial class SessionHandler } } - //Nothing to do more, everything is expired - throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG); + throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG, + currentException); } - - private static bool MobileTokenExpired(Mafile mafile) - { - var mobileToken = mafile.SessionData?.GetMobileToken(); - return mobileToken == null || mobileToken.Value.IsExpired; - } - - private static bool RefreshTokenExpired(Mafile mafile) - { - var refreshToken = mafile.SessionData?.RefreshToken; - return refreshToken == null || refreshToken.Value.IsExpired; - } - - private static string? GetPassword(Mafile mafile) - { - try - { - if (PHandler.IsPasswordSet && !string.IsNullOrWhiteSpace(mafile.Password)) - { - return PHandler.Decrypt(mafile.Password); - } - } - catch - { - // ignored - } - - return null; - } - - private static async Task RefreshInternal(HttpClientHandlerPair chp, Mafile mafile) { try diff --git a/src/NebulaAuth/Model/SessionHandler_API.cs b/src/NebulaAuth/Model/SessionHandler/SessionHandler_API.cs similarity index 90% rename from src/NebulaAuth/Model/SessionHandler_API.cs rename to src/NebulaAuth/Model/SessionHandler/SessionHandler_API.cs index 0b38909..b644462 100644 --- a/src/NebulaAuth/Model/SessionHandler_API.cs +++ b/src/NebulaAuth/Model/SessionHandler/SessionHandler_API.cs @@ -28,7 +28,6 @@ public partial class SessionHandler //API mafile.SessionData.SetMobileToken(newToken); //Trigger PropertyChanged event for PortableMaClient handling session updated from MaClient - //RETHINK: it makes double operation when session handled from PortableMaClient (more often scenario) which is unwanted behaviour mafile.SetSessionData(mafile.SessionData); await Storage.UpdateMafileAsync(mafile); chp.Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData); @@ -50,7 +49,6 @@ public partial class SessionHandler //API AdmissionHelper.TransferCommunityCookies(chp.Handler.CookieContainer); //Triggers PropertyChanged event for PortableMaClient handling session updated from MaClient - //RETHINK: it makes double operation when session handled from PortableMaClient (more often scenario) which is unwanted behaviour mafile.SetSessionData((MobileSessionData) result); if (PHandler.IsPasswordSet) mafile.Password = savePassword ? PHandler.Encrypt(password) : null; diff --git a/src/NebulaAuth/Model/SessionHandler/SessionHandler_Helpers.cs b/src/NebulaAuth/Model/SessionHandler/SessionHandler_Helpers.cs new file mode 100644 index 0000000..448472e --- /dev/null +++ b/src/NebulaAuth/Model/SessionHandler/SessionHandler_Helpers.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.CodeAnalysis; +using NebulaAuth.Model.Entities; + +namespace NebulaAuth.Model; + +public partial class SessionHandler //Helpers +{ + private static bool MobileTokenExpired(this Mafile mafile) + { + var mobileToken = mafile.SessionData?.GetMobileToken(); + return mobileToken == null || mobileToken.Value.IsExpired; + } + + private static bool RefreshTokenExpired(this Mafile mafile) + { + var refreshToken = mafile.SessionData?.RefreshToken; + return refreshToken == null || refreshToken.Value.IsExpired; + } + + private static bool HasPassword(this Mafile mafile, [NotNullWhen(true)] out string? plainPassword) + { + plainPassword = GetPassword(mafile); + return !string.IsNullOrWhiteSpace(plainPassword); + } + + private static string? GetPassword(Mafile mafile) + { + try + { + if (PHandler.IsPasswordSet && !string.IsNullOrWhiteSpace(mafile.Password)) + { + return PHandler.Decrypt(mafile.Password); + } + } + catch + { + // ignored + } + + return null; + } +} \ No newline at end of file diff --git a/src/NebulaAuth/Model/Settings.cs b/src/NebulaAuth/Model/Settings.cs index e4d3963..afa052b 100644 --- a/src/NebulaAuth/Model/Settings.cs +++ b/src/NebulaAuth/Model/Settings.cs @@ -94,7 +94,6 @@ public partial class Settings : ObservableObject [ObservableProperty] private bool _legacyMode = true; [ObservableProperty] private bool _allowAutoUpdate; [ObservableProperty] private bool _useAccountNameAsMafileName; - [ObservableProperty] private bool _ignorePatchTuesdayErrors; [ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default; [ObservableProperty] private double _leftOpacity = 0.4; @@ -108,6 +107,9 @@ public partial class Settings : ObservableObject [ObservableProperty] private bool _proxyManagerDisplayProtocol; [ObservableProperty] private bool _proxyManagerDisplayCredentials; + [ObservableProperty] private TimeSpan _maacErrorThreshold = TimeSpan.FromHours(3); + [ObservableProperty] private TimeSpan _maacRetryInterval = TimeSpan.FromMinutes(15); + #endregion } diff --git a/src/NebulaAuth/NLog.config b/src/NebulaAuth/NLog.config index 259810c..d35ee8f 100644 --- a/src/NebulaAuth/NLog.config +++ b/src/NebulaAuth/NLog.config @@ -5,7 +5,11 @@ throwExceptions="true"> - + diff --git a/src/NebulaAuth/NebulaAuth.csproj b/src/NebulaAuth/NebulaAuth.csproj index 6237b34..e626474 100644 --- a/src/NebulaAuth/NebulaAuth.csproj +++ b/src/NebulaAuth/NebulaAuth.csproj @@ -10,7 +10,7 @@ en;ru;ua Theme\lock.ico 7.0 - 1.8.0 + 1.8.1 true diff --git a/src/NebulaAuth/View/SettingsView.xaml b/src/NebulaAuth/View/SettingsView.xaml index 44c0f15..b0015e6 100644 --- a/src/NebulaAuth/View/SettingsView.xaml +++ b/src/NebulaAuth/View/SettingsView.xaml @@ -63,14 +63,6 @@ Content="{Tr SettingsDialog.LegacyMafileMode}" ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}" /> - - -