From 764895de496b5857f9ce6c59a44bf9c139ff71f6 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 4 Jan 2022 14:16:53 -0700 Subject: [PATCH] Can login to IPS4 sites, and can log in via Google --- Wabbajack.App.Wpf/App.xaml.cs | 7 +- Wabbajack.App.Wpf/LibCefHelpers/Init.cs | 30 ++---- .../LoginManagers/LoversLabLoginManager.cs | 64 +++++++++++++ .../LoginManagers/NexusLoginManager.cs | 6 -- .../LoginManagers/VectorPlexusLoginManager.cs | 64 +++++++++++++ Wabbajack.App.Wpf/Messages/ALoginMessage.cs | 34 +++++++ Wabbajack.App.Wpf/Messages/LoversLabLogin.cs | 15 +++ Wabbajack.App.Wpf/Messages/NexusLogin.cs | 24 +---- .../Messages/VectorPlexusLogin.cs | 15 +++ Wabbajack.App.Wpf/Models/CefService.cs | 12 ++- .../UserIntervention/LoversLabLoginHandler.cs | 16 ++++ .../UserIntervention/NexusLoginHandler.cs | 18 ++-- .../UserIntervention/OAuth2LoginHandler.cs | 95 +++++++++++++++++++ .../VectorPlexusLoginHandler.cs | 15 +++ .../WebUserInterventionBase.cs | 14 ++- Wabbajack.App.Wpf/View Models/MainWindowVM.cs | 73 ++++++-------- .../View Models/Settings/SettingsVM.cs | 2 + Wabbajack.App.Wpf/View Models/WebBrowserVM.cs | 2 +- .../WebAutomation/CefSharpWrapper.cs | 54 ++++++++++- Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs | 14 +-- .../WebAutomation/WebAutomation.cs | 14 +-- 21 files changed, 451 insertions(+), 137 deletions(-) create mode 100644 Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs create mode 100644 Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs create mode 100644 Wabbajack.App.Wpf/Messages/ALoginMessage.cs create mode 100644 Wabbajack.App.Wpf/Messages/LoversLabLogin.cs create mode 100644 Wabbajack.App.Wpf/Messages/VectorPlexusLogin.cs create mode 100644 Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs create mode 100644 Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs create mode 100644 Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs diff --git a/Wabbajack.App.Wpf/App.xaml.cs b/Wabbajack.App.Wpf/App.xaml.cs index 136c8170..9c59733d 100644 --- a/Wabbajack.App.Wpf/App.xaml.cs +++ b/Wabbajack.App.Wpf/App.xaml.cs @@ -66,11 +66,16 @@ namespace Wabbajack services.AddTransient(); services.AddTransient(); - + + // Login Handlers + services.AddTransient(); services.AddTransient(); + services.AddTransient(); // Login Managers + services.AddAllSingleton(); services.AddAllSingleton(); + services.AddAllSingleton(); return services; } diff --git a/Wabbajack.App.Wpf/LibCefHelpers/Init.cs b/Wabbajack.App.Wpf/LibCefHelpers/Init.cs index 36a4e434..0744bea3 100644 --- a/Wabbajack.App.Wpf/LibCefHelpers/Init.cs +++ b/Wabbajack.App.Wpf/LibCefHelpers/Init.cs @@ -37,12 +37,12 @@ namespace Wabbajack.LibCefHelpers return container; } - public static async Task GetCookies(string domainEnding = "") + public static async Task GetCookies(string domainEnding = "") { var manager = Cef.GetGlobalCookieManager(); var visitor = new CookieVisitor(); if (!manager.VisitAllCookies(visitor)) - return new Cookie[0]; + return Array.Empty(); var cc = await visitor.Task; return (await visitor.Task).Where(c => c.Domain.EndsWith(domainEnding)).ToArray(); @@ -50,10 +50,10 @@ namespace Wabbajack.LibCefHelpers private class CookieVisitor : ICookieVisitor { - TaskCompletionSource> _source = new TaskCompletionSource>(); - public Task> Task => _source.Task; + TaskCompletionSource> _source = new(); + public Task> Task => _source.Task; - public List Cookies { get; } = new List(); + public List Cookies { get; } = new (); public void Dispose() { _source.SetResult(Cookies); @@ -61,7 +61,7 @@ namespace Wabbajack.LibCefHelpers public bool Visit(CefSharp.Cookie cookie, int count, int total, ref bool deleteCookie) { - Cookies.Add(new Cookie + Cookies.Add(new DTOs.Logins.Cookie { Name = cookie.Name, Value = cookie.Value, @@ -74,16 +74,6 @@ namespace Wabbajack.LibCefHelpers return true; } } - - [JsonName("HttpCookie")] - public class Cookie - { - public string Name { get; set; } = string.Empty; - public string Value { get; set; } = string.Empty; - public string Domain { get; set; } = string.Empty; - public string Path { get; set; } = string.Empty; - } - public static void ClearCookies() { var manager = Cef.GetGlobalCookieManager(); @@ -91,7 +81,7 @@ namespace Wabbajack.LibCefHelpers manager.VisitAllCookies(visitor); } - public static async Task DeleteCookiesWhere(Func filter) + public static async Task DeleteCookiesWhere(Func filter) { var manager = Cef.GetGlobalCookieManager(); var visitor = new CookieDeleter(filter); @@ -101,9 +91,9 @@ namespace Wabbajack.LibCefHelpers class CookieDeleter : ICookieVisitor { - private Func? _filter; + private Func? _filter; - public CookieDeleter(Func? filter = null) + public CookieDeleter(Func? filter = null) { _filter = filter; } @@ -119,7 +109,7 @@ namespace Wabbajack.LibCefHelpers } else { - var conv = new Helpers.Cookie + var conv = new DTOs.Logins.Cookie { Name = cookie.Name, Domain = cookie.Domain, Value = cookie.Value, Path = cookie.Path }; diff --git a/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs new file mode 100644 index 00000000..36345225 --- /dev/null +++ b/Wabbajack.App.Wpf/LoginManagers/LoversLabLoginManager.cs @@ -0,0 +1,64 @@ +using System; +using System.Drawing; +using System.Reactive.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.Extensions.Logging; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using Wabbajack.Common; +using Wabbajack.DTOs.Interventions; +using Wabbajack.DTOs.Logins; +using Wabbajack.Messages; +using Wabbajack.Networking.Http.Interfaces; + +namespace Wabbajack.LoginManagers; + +public class LoversLabLoginManager : ViewModel, INeedsLogin +{ + private readonly ILogger _logger; + private readonly ITokenProvider _token; + private readonly IUserInterventionHandler _handler; + + public string SiteName { get; } = "Lovers Lab"; + public ICommand TriggerLogin { get; set; } + public ICommand ClearLogin { get; set; } + + public ImageSource Icon { get; set; } + + [Reactive] + public bool HaveLogin { get; set; } + + public LoversLabLoginManager(ILogger logger, ITokenProvider token) + { + _logger = logger; + _token = token; + RefreshTokenState(); + + ClearLogin = ReactiveCommand.CreateFromTask(async () => + { + _logger.LogInformation("Deleting Login information for {SiteName}", SiteName); + await _token.Delete(); + RefreshTokenState(); + }, this.WhenAnyValue(v => v.HaveLogin)); + + Icon = BitmapFrame.Create( + typeof(NexusLoginManager).Assembly.GetManifestResourceStream("Wabbajack.LoginManagers.Icons.nexus.png")!); + + TriggerLogin = ReactiveCommand.CreateFromTask(async () => + { + _logger.LogInformation("Logging into {SiteName}", SiteName); + await LoversLabLogin.Send(); + RefreshTokenState(); + }, this.WhenAnyValue(v => v.HaveLogin).Select(v => !v)); + } + + private void RefreshTokenState() + { + HaveLogin = _token.HaveToken(); + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs index 3def6f85..ce71833d 100644 --- a/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs +++ b/Wabbajack.App.Wpf/LoginManagers/NexusLoginManager.cs @@ -1,16 +1,10 @@ -using System; -using System.Drawing; using System.Reactive.Linq; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using Microsoft.Extensions.Logging; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using Wabbajack.Common; using Wabbajack.DTOs.Interventions; using Wabbajack.DTOs.Logins; using Wabbajack.Messages; diff --git a/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs b/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs new file mode 100644 index 00000000..464fce87 --- /dev/null +++ b/Wabbajack.App.Wpf/LoginManagers/VectorPlexusLoginManager.cs @@ -0,0 +1,64 @@ +using System; +using System.Drawing; +using System.Reactive.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.Extensions.Logging; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using Wabbajack.Common; +using Wabbajack.DTOs.Interventions; +using Wabbajack.DTOs.Logins; +using Wabbajack.Messages; +using Wabbajack.Networking.Http.Interfaces; + +namespace Wabbajack.LoginManagers; + +public class VectorPlexusLoginManager : ViewModel, INeedsLogin +{ + private readonly ILogger _logger; + private readonly ITokenProvider _token; + private readonly IUserInterventionHandler _handler; + + public string SiteName { get; } = "Vector Plexus"; + public ICommand TriggerLogin { get; set; } + public ICommand ClearLogin { get; set; } + + public ImageSource Icon { get; set; } + + [Reactive] + public bool HaveLogin { get; set; } + + public VectorPlexusLoginManager(ILogger logger, ITokenProvider token) + { + _logger = logger; + _token = token; + RefreshTokenState(); + + ClearLogin = ReactiveCommand.CreateFromTask(async () => + { + _logger.LogInformation("Deleting Login information for {SiteName}", SiteName); + await _token.Delete(); + RefreshTokenState(); + }, this.WhenAnyValue(v => v.HaveLogin)); + + Icon = BitmapFrame.Create( + typeof(NexusLoginManager).Assembly.GetManifestResourceStream("Wabbajack.LoginManagers.Icons.nexus.png")!); + + TriggerLogin = ReactiveCommand.CreateFromTask(async () => + { + _logger.LogInformation("Logging into {SiteName}", SiteName); + await VectorPlexusLogin.Send(); + RefreshTokenState(); + }, this.WhenAnyValue(v => v.HaveLogin).Select(v => !v)); + } + + private void RefreshTokenState() + { + HaveLogin = _token.HaveToken(); + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/Messages/ALoginMessage.cs b/Wabbajack.App.Wpf/Messages/ALoginMessage.cs new file mode 100644 index 00000000..921cf97b --- /dev/null +++ b/Wabbajack.App.Wpf/Messages/ALoginMessage.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using ReactiveUI; +using Wabbajack.DTOs.Interventions; + +namespace Wabbajack.Messages; + +public class ALoginMessage : IUserIntervention +{ + private readonly CancellationTokenSource _source; + public TaskCompletionSource CompletionSource { get; } + public CancellationToken Token => _source.Token; + public void SetException(Exception exception) + { + CompletionSource.SetException(exception); + _source.Cancel(); + } + + public ALoginMessage() + { + CompletionSource = new TaskCompletionSource(); + _source = new CancellationTokenSource(); + + } + + public void Cancel() + { + _source.Cancel(); + CompletionSource.TrySetCanceled(); + } + + public bool Handled => CompletionSource.Task.IsCompleted; +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/Messages/LoversLabLogin.cs b/Wabbajack.App.Wpf/Messages/LoversLabLogin.cs new file mode 100644 index 00000000..586252eb --- /dev/null +++ b/Wabbajack.App.Wpf/Messages/LoversLabLogin.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using ReactiveUI; + +namespace Wabbajack.Messages; + +public class LoversLabLogin : ALoginMessage +{ + + public static Task Send() + { + var msg = new LoversLabLogin(); + MessageBus.Current.SendMessage(msg); + return msg.CompletionSource.Task; + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/Messages/NexusLogin.cs b/Wabbajack.App.Wpf/Messages/NexusLogin.cs index 69c37a56..6330407d 100644 --- a/Wabbajack.App.Wpf/Messages/NexusLogin.cs +++ b/Wabbajack.App.Wpf/Messages/NexusLogin.cs @@ -7,36 +7,16 @@ using Wabbajack.Networking.Http.Interfaces; namespace Wabbajack.Messages; -public class NexusLogin : IUserIntervention +public class NexusLogin : ALoginMessage { - private readonly CancellationTokenSource _source; - public TaskCompletionSource CompletionSource { get; } - public CancellationToken Token => _source.Token; - public void SetException(Exception exception) - { - CompletionSource.SetException(exception); - _source.Cancel(); - } - public NexusLogin() { - CompletionSource = new TaskCompletionSource(); - _source = new CancellationTokenSource(); - } - + public static Task Send() { var msg = new NexusLogin(); MessageBus.Current.SendMessage(msg); return msg.CompletionSource.Task; } - - public void Cancel() - { - _source.Cancel(); - CompletionSource.TrySetCanceled(); - } - - public bool Handled => CompletionSource.Task.IsCompleted; } \ No newline at end of file diff --git a/Wabbajack.App.Wpf/Messages/VectorPlexusLogin.cs b/Wabbajack.App.Wpf/Messages/VectorPlexusLogin.cs new file mode 100644 index 00000000..752fa595 --- /dev/null +++ b/Wabbajack.App.Wpf/Messages/VectorPlexusLogin.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using ReactiveUI; + +namespace Wabbajack.Messages; + +public class VectorPlexusLogin : ALoginMessage +{ + + public static Task Send() + { + var msg = new VectorPlexusLogin(); + MessageBus.Current.SendMessage(msg); + return msg.CompletionSource.Task; + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/Models/CefService.cs b/Wabbajack.App.Wpf/Models/CefService.cs index f8a91f52..3328d169 100644 --- a/Wabbajack.App.Wpf/Models/CefService.cs +++ b/Wabbajack.App.Wpf/Models/CefService.cs @@ -1,5 +1,5 @@ using System; -using System.Threading.Tasks; +using System.Reactive.Subjects; using CefSharp; using CefSharp.Wpf; using Microsoft.Extensions.Logging; @@ -10,7 +10,9 @@ public class CefService { private readonly ILogger _logger; private bool Inited { get; set; } = false; - + + private readonly Subject _schemeStream = new(); + public IObservable SchemeStream => _schemeStream; public Func? SchemeHandler { get; set; } public CefService(ILogger logger) @@ -31,7 +33,7 @@ public class CefService var settings = new CefSettings { CachePath = Consts.CefCacheLocation.ToString(), - JavascriptFlags = "--noexpose_wasm" + UserAgent = "Wabbajack In-App Browser" }; settings.RegisterScheme(new CefCustomScheme() { @@ -60,9 +62,9 @@ public class CefService public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) { _logger.LogInformation("Scheme handler Got: {Scheme} : {Url}", schemeName, request.Url); - if (_service.SchemeHandler != null && schemeName == "wabbajack") + if (schemeName == "wabbajack") { - return _service.SchemeHandler!(browser, frame, schemeName, request); + _service._schemeStream.OnNext(request.Url); } return new ResourceHandler(); } diff --git a/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs new file mode 100644 index 00000000..6b000c08 --- /dev/null +++ b/Wabbajack.App.Wpf/UserIntervention/LoversLabLoginHandler.cs @@ -0,0 +1,16 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Wabbajack.Models; +using Wabbajack.Networking.Http.Interfaces; + +namespace Wabbajack.UserIntervention; + +public class LoversLabLoginHandler : OAuth2LoginHandler +{ + public LoversLabLoginHandler(ILogger logger, HttpClient client, ITokenProvider tokenProvider, + WebBrowserVM browser, CefService service) + : base(logger, client, tokenProvider, browser, service) + { + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs index 342732bb..bbba578f 100644 --- a/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs +++ b/Wabbajack.App.Wpf/UserIntervention/NexusLoginHandler.cs @@ -6,20 +6,22 @@ using ReactiveUI; using Wabbajack.DTOs.Logins; using Wabbajack.LibCefHelpers; using Wabbajack.Messages; +using Wabbajack.Models; using Wabbajack.Networking.Http.Interfaces; using Wabbajack.Services.OSIntegrated.TokenProviders; namespace Wabbajack.UserIntervention; -public class NexusLoginHandler : WebUserInterventionBase +public class NexusLoginHandler : WebUserInterventionBase { private readonly ITokenProvider _provider; - public NexusLoginHandler(ILogger logger, WebBrowserVM browserVM, ITokenProvider provider) : base(logger, browserVM) + public NexusLoginHandler(ILogger logger, WebBrowserVM browserVM, ITokenProvider provider, CefService service) + : base(logger, browserVM, service) { _provider = provider; } - public async Task Begin() + public override async Task Begin() { try { @@ -29,7 +31,7 @@ public class NexusLoginHandler : WebUserInterventionBase await NavigateTo(new Uri("https://users.nexusmods.com/auth/continue?client_id=nexus&redirect_uri=https://www.nexusmods.com/oauth/callback&response_type=code&referrer=//www.nexusmods.com")); - Helpers.Cookie[] cookies = {}; + Cookie[] cookies = {}; while (true) { cookies = await Driver.GetCookies("nexusmods.com"); @@ -89,13 +91,7 @@ public class NexusLoginHandler : WebUserInterventionBase await _provider.SetToken(new NexusApiState() { ApiKey = key, - Cookies = cookies.Select(c => new Cookie() - { - Domain = c.Domain, - Name = c.Name, - Path = c.Path, - Value = c.Value - }).ToArray() + Cookies = cookies }); ((NexusLogin)Message).CompletionSource.SetResult(); diff --git a/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs new file mode 100644 index 00000000..97e66c4f --- /dev/null +++ b/Wabbajack.App.Wpf/UserIntervention/OAuth2LoginHandler.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using Microsoft.Extensions.Logging; +using ReactiveUI; +using Wabbajack.DTOs.Interventions; +using Wabbajack.DTOs.Logins; +using Wabbajack.Messages; +using Wabbajack.Models; +using Wabbajack.Networking.Http.Interfaces; +using Wabbajack.Services.OSIntegrated; + +namespace Wabbajack.UserIntervention; + +public abstract class OAuth2LoginHandler : WebUserInterventionBase + where TIntervention : IUserIntervention + where TLoginType : OAuth2LoginState, new() +{ + private readonly HttpClient _httpClient; + private readonly ITokenProvider _tokenProvider; + + public OAuth2LoginHandler(ILogger logger, HttpClient httpClient, + ITokenProvider tokenProvider, WebBrowserVM browserVM, CefService service) : base(logger, browserVM, service) + { + _httpClient = httpClient; + _tokenProvider = tokenProvider; + } + + public override async Task Begin() + { + Messages.NavigateTo.Send(Browser); + var tlogin = new TLoginType(); + + await Driver.WaitForInitialized(); + + using var handler = Driver.WithSchemeHandler(uri => uri.Scheme == "wabbajack"); + + UpdateStatus($"Please log in and allow Wabbajack to access your {tlogin.SiteName} account"); + + var scopes = string.Join(" ", tlogin.Scopes); + var state = Guid.NewGuid().ToString(); + + await NavigateTo(new Uri(tlogin.AuthorizationEndpoint + $"?response_type=code&client_id={tlogin.ClientID}&state={state}&scope={scopes}")); + + var uri = await handler.Task.WaitAsync(Message.Token); + + var cookies = await Driver.GetCookies(tlogin.AuthorizationEndpoint.Host); + + var parsed = HttpUtility.ParseQueryString(uri.Query); + if (parsed.Get("state") != state) + { + Logger.LogCritical("Bad OAuth state, this shouldn't happen"); + throw new Exception("Bad OAuth State"); + } + + if (parsed.Get("code") == null) + { + Logger.LogCritical("Bad code result from OAuth"); + throw new Exception("Bad code result from OAuth"); + } + + var authCode = parsed.Get("code"); + + var formData = new KeyValuePair[] + { + new("grant_type", "authorization_code"), + new("code", authCode), + new("client_id", tlogin.ClientID) + }; + + var msg = new HttpRequestMessage(); + msg.Method = HttpMethod.Post; + msg.RequestUri = tlogin.TokenEndpoint; + msg.Headers.Add("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"); + msg.Headers.Add("Cookie", string.Join(";", cookies.Select(c => $"{c.Name}={c.Value}"))); + msg.Content = new FormUrlEncodedContent(formData.ToList()); + + using var response = await _httpClient.SendAsync(msg, Message.Token); + var data = await response.Content.ReadFromJsonAsync(cancellationToken: Message.Token); + + await _tokenProvider.SetToken(new TLoginType + { + Cookies = cookies, + ResultState = data! + }); + + Messages.NavigateTo.Send(PrevPane); + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs b/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs new file mode 100644 index 00000000..c1edadc6 --- /dev/null +++ b/Wabbajack.App.Wpf/UserIntervention/VectorPlexusLoginHandler.cs @@ -0,0 +1,15 @@ +using System.Net.Http; +using Microsoft.Extensions.Logging; +using Wabbajack.Models; +using Wabbajack.Networking.Http.Interfaces; + +namespace Wabbajack.UserIntervention; + +public class VectorPlexusLoginHandler : OAuth2LoginHandler +{ + public VectorPlexusLoginHandler(ILogger logger, HttpClient client, ITokenProvider tokenProvider, + WebBrowserVM browser, CefService service) + : base(logger, client, tokenProvider, browser, service) + { + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/UserIntervention/WebUserInterventionBase.cs b/Wabbajack.App.Wpf/UserIntervention/WebUserInterventionBase.cs index 4c6a73f8..2e29a1c6 100644 --- a/Wabbajack.App.Wpf/UserIntervention/WebUserInterventionBase.cs +++ b/Wabbajack.App.Wpf/UserIntervention/WebUserInterventionBase.cs @@ -4,26 +4,28 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Wabbajack.DTOs.Interventions; using Wabbajack.Interventions; +using Wabbajack.Models; using Wabbajack.WebAutomation; namespace Wabbajack.UserIntervention; -public class WebUserInterventionBase +public abstract class WebUserInterventionBase +where T : IUserIntervention { protected readonly WebBrowserVM Browser; protected readonly ILogger Logger; - protected IUserIntervention Message; + protected T Message; protected ViewModel PrevPane; protected IWebDriver Driver; - public WebUserInterventionBase(ILogger logger, WebBrowserVM browser) + protected WebUserInterventionBase(ILogger logger, WebBrowserVM browser, CefService service) { Logger = logger; Browser = browser; - Driver = new CefSharpWrapper(logger, browser.Browser); + Driver = new CefSharpWrapper(logger, browser.Browser, service); } - public void Configure(ViewModel prevPane, IUserIntervention message) + public void Configure(ViewModel prevPane, T message) { Message = message; PrevPane = prevPane; @@ -39,4 +41,6 @@ public class WebUserInterventionBase await Driver.NavigateTo(uri, Message.Token); } + public abstract Task Begin(); + } \ No newline at end of file diff --git a/Wabbajack.App.Wpf/View Models/MainWindowVM.cs b/Wabbajack.App.Wpf/View Models/MainWindowVM.cs index 421ed5f8..fcf2ebfe 100644 --- a/Wabbajack.App.Wpf/View Models/MainWindowVM.cs +++ b/Wabbajack.App.Wpf/View Models/MainWindowVM.cs @@ -101,57 +101,22 @@ namespace Wabbajack .DisposeWith(CompositeDisposable); MessageBus.Current.Listen() - .Subscribe(m => HandleNexusLogin(m)) + .Subscribe(HandleLogin) + .DisposeWith(CompositeDisposable); + + MessageBus.Current.Listen() + .Subscribe(HandleLogin) + .DisposeWith(CompositeDisposable); + + MessageBus.Current.Listen() + .Subscribe(HandleLogin) .DisposeWith(CompositeDisposable); _resourceMonitor.Updates .Select(r => string.Join(", ", r.Where(r => r.Throughput > 0) .Select(s => $"{s.Name} - {s.Throughput.ToFileSizeString()}/sec"))) .BindToStrict(this, view => view.ResourceStatus); - - // Set up logging - /* TODO - Utils.LogMessages - .ObserveOn(RxApp.TaskpoolScheduler) - .ToObservableChangeSet() - .Buffer(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler) - .Where(l => l.Count > 0) - .FlattenBufferResult() - .ObserveOnGuiThread() - .Bind(Log) - .Subscribe() - .DisposeWith(CompositeDisposable); - - Utils.LogMessages - .Where(a => a is IUserIntervention or CriticalFailureIntervention) - .ObserveOnGuiThread() - .SelectTask(async msg => - { - try - { - await UserInterventionHandlers.Handle(msg); - } - catch (Exception ex) - when (ex.GetType() != typeof(TaskCanceledException)) - { - _logger.LogError(ex, "Error while handling user intervention of type {Type}",msg?.GetType()); - try - { - if (msg is IUserIntervention {Handled: false} intervention) - { - intervention.Cancel(); - } - } - catch (Exception cancelEx) - { - _logger.LogError(cancelEx, "Error while cancelling user intervention of type {Type}",msg?.GetType()); - } - } - }) - .Subscribe() - .DisposeWith(CompositeDisposable); - */ if (IsStartingFromModlist(out var path)) { @@ -192,15 +157,30 @@ namespace Wabbajack private void HandleNavigateTo(ViewModel objViewModel) { + ActivePane = objViewModel; } - private void HandleNexusLogin(NexusLogin nexusLogin) + private void HandleLogin(NexusLogin nexusLogin) { var handler = _serviceProvider.GetRequiredService(); handler.Configure(ActivePane, nexusLogin); handler.Begin().FireAndForget(); } + + private void HandleLogin(LoversLabLogin loversLabLogin) + { + var handler = _serviceProvider.GetRequiredService(); + handler.Configure(ActivePane, loversLabLogin); + handler.Begin().FireAndForget(); + } + + private void HandleLogin(VectorPlexusLogin vectorPlexusLogin) + { + var handler = _serviceProvider.GetRequiredService(); + handler.Configure(ActivePane, vectorPlexusLogin); + handler.Begin().FireAndForget(); + } private void HandleNavigateBack(NavigateBack navigateBack) { @@ -210,6 +190,9 @@ namespace Wabbajack private void HandleNavigateTo(NavigateToGlobal.ScreenType s) { + if (s is NavigateToGlobal.ScreenType.Settings) + PreviousPanes.Add(ActivePane); + ActivePane = s switch { NavigateToGlobal.ScreenType.ModeSelectionView => ModeSelectionVM, diff --git a/Wabbajack.App.Wpf/View Models/Settings/SettingsVM.cs b/Wabbajack.App.Wpf/View Models/Settings/SettingsVM.cs index e09f5c09..aff16add 100644 --- a/Wabbajack.App.Wpf/View Models/Settings/SettingsVM.cs +++ b/Wabbajack.App.Wpf/View Models/Settings/SettingsVM.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging; using ReactiveUI; using Wabbajack; using Wabbajack.LoginManagers; +using Wabbajack.Messages; using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Services.OSIntegrated.TokenProviders; using Wabbajack.View_Models.Settings; @@ -39,6 +40,7 @@ namespace Wabbajack provider.GetRequiredService()!, provider.GetRequiredService()!, this); Filters = mainWindowVM.Settings.Filters; OpenTerminalCommand = ReactiveCommand.CreateFromTask(OpenTerminal); + BackCommand = ReactiveCommand.Create(NavigateBack.Send); } private async Task OpenTerminal() diff --git a/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs b/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs index 2a1d371e..fd009ff5 100644 --- a/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs +++ b/Wabbajack.App.Wpf/View Models/WebBrowserVM.cs @@ -48,7 +48,7 @@ namespace Wabbajack BackCommand = ReactiveCommand.Create(NavigateBack.Send); Browser = cefService.CreateBrowser(); - Driver = new CefSharpWrapper(_logger, Browser); + Driver = new CefSharpWrapper(_logger, Browser, cefService); } diff --git a/Wabbajack.App.Wpf/WebAutomation/CefSharpWrapper.cs b/Wabbajack.App.Wpf/WebAutomation/CefSharpWrapper.cs index cff52bc4..fe47ea82 100644 --- a/Wabbajack.App.Wpf/WebAutomation/CefSharpWrapper.cs +++ b/Wabbajack.App.Wpf/WebAutomation/CefSharpWrapper.cs @@ -3,21 +3,27 @@ using System.Threading; using System.Threading.Tasks; using CefSharp; using Microsoft.Extensions.Logging; -using Microsoft.VisualBasic.CompilerServices; using Wabbajack.LibCefHelpers; +using Wabbajack.Models; using Wabbajack.Networking.Http; using Wabbajack.Paths; +using Cookie = Wabbajack.DTOs.Logins.Cookie; namespace Wabbajack.WebAutomation { public class CefSharpWrapper : IWebDriver { private readonly IWebBrowser _browser; + + private static readonly Random RetryRandom = new Random(); + private readonly ILogger _logger; + private readonly CefService _service; public Action? DownloadHandler { get; set; } - public CefSharpWrapper(ILogger logger, IWebBrowser browser) + public CefSharpWrapper(ILogger logger, IWebBrowser browser, CefService service) { _logger = logger; _browser = browser; + _service = service; _browser.DownloadHandler = new DownloadHandler(this); _browser.LifeSpanHandler = new PopupBlocker(this); @@ -56,8 +62,7 @@ namespace Wabbajack.WebAutomation ("

400 Bad Request

", 400), ("We could not locate the item you are trying to view.", 404), }; - private static readonly Random RetryRandom = new Random(); - private readonly ILogger _logger; + public async Task NavigateToAndDownload(Uri uri, AbsolutePath dest, bool quickMode = false, CancellationToken? token = null) { @@ -115,7 +120,7 @@ namespace Wabbajack.WebAutomation return (string)result.Result; } - public Task GetCookies(string domainPrefix) + public Task GetCookies(string domainPrefix) { return Helpers.GetCookies(domainPrefix); } @@ -127,6 +132,45 @@ namespace Wabbajack.WebAutomation while (!_browser.IsBrowserInitialized) await Task.Delay(100); } + + + public ISchemeHandler WithSchemeHandler(Predicate predicate) + { + return new SchemeHandler(predicate, _service); + } + + private class SchemeHandler : ISchemeHandler + { + private readonly TaskCompletionSource _tcs; + private readonly IDisposable _disposable; + + public SchemeHandler(Predicate predicate, CefService service) + { + _tcs = new TaskCompletionSource(); + _disposable = service.SchemeStream.Subscribe(s => + { + if (Uri.TryCreate(s, UriKind.Absolute, out var result) && predicate(result)) + { + _tcs.TrySetResult(result); + } + }); + } + + public void Dispose() + { + + _tcs.TrySetCanceled(); + _disposable.Dispose(); + } + + public Task Task => _tcs.Task; + } + + + public async Task RegisterSchemeCallback() + { + var frame = _browser.GetFocusedFrame(); + } public string Location => _browser.Address; diff --git a/Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs b/Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs index a7c2cae6..94ad17e3 100644 --- a/Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs +++ b/Wabbajack.App.Wpf/WebAutomation/IWebDriver.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Wabbajack.LibCefHelpers; +using Wabbajack.DTOs.Logins; namespace Wabbajack.WebAutomation { @@ -13,8 +9,14 @@ namespace Wabbajack.WebAutomation { Task NavigateTo(Uri uri, CancellationToken? token = null); Task EvaluateJavaScript(string text); - Task GetCookies(string domainPrefix); + Task GetCookies(string domainPrefix); public Action? DownloadHandler { get; set; } public Task WaitForInitialized(); + ISchemeHandler WithSchemeHandler(Predicate wabbajack); + } + + public interface ISchemeHandler : IDisposable + { + public Task Task { get; } } } diff --git a/Wabbajack.App.Wpf/WebAutomation/WebAutomation.cs b/Wabbajack.App.Wpf/WebAutomation/WebAutomation.cs index abf049f9..a3baa417 100644 --- a/Wabbajack.App.Wpf/WebAutomation/WebAutomation.cs +++ b/Wabbajack.App.Wpf/WebAutomation/WebAutomation.cs @@ -9,6 +9,7 @@ using HtmlAgilityPack; using Microsoft.Extensions.Logging; using Wabbajack.Common; using Wabbajack.LibCefHelpers; +using Wabbajack.Models; using Wabbajack.Paths; using Wabbajack.Paths.IO; @@ -19,19 +20,12 @@ namespace Wabbajack.WebAutomation private readonly IWebBrowser _browser; private readonly CefSharpWrapper _driver; - public Driver(ILogger logger) + public Driver(ILogger logger, CefService service) { _browser = new ChromiumWebBrowser(); - _driver = new CefSharpWrapper(logger, _browser); + _driver = new CefSharpWrapper(logger, _browser, service); } - public static async Task Create(ILogger logger) - { - var driver = new Driver(logger); - await driver._driver.WaitForInitialized(); - return driver; - } - public async Task NavigateTo(Uri uri, CancellationToken? token = null) { try @@ -118,7 +112,7 @@ namespace Wabbajack.WebAutomation Helpers.ClearCookies(); } - public async Task DeleteCookiesWhere(Func filter) + public async Task DeleteCookiesWhere(Func filter) { await Helpers.DeleteCookiesWhere(filter); }