/* * Copyright (c) 2026 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using Microsoft.Windows.AppLifecycle; using ProtonVPN.Client.Common.Dispatching; using ProtonVPN.Client.Core.Services.Activation; using ProtonVPN.Client.Logic.Auth.Contracts; using ProtonVPN.Client.Logic.Auth.Contracts.Enums; using ProtonVPN.Client.Logic.Auth.Contracts.Models; using ProtonVPN.Client.Logic.Servers.Cache; using ProtonVPN.Client.Logic.Services.Contracts; using ProtonVPN.Client.Logic.Updates.Contracts; using ProtonVPN.Client.Logic.Users.Contracts; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.Client.Settings.Contracts.Enums; using ProtonVPN.Client.Settings.Contracts.Initializers; using ProtonVPN.Client.Settings.Contracts.Migrations; using ProtonVPN.Common.Core.Extensions; using ProtonVPN.IssueReporting.Static; using ProtonVPN.Logging.Contracts; using ProtonVPN.Logging.Contracts.Events.AppLogs; using ProtonVPN.StatisticalEvents.Contracts; using Windows.ApplicationModel.Activation; using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs; namespace ProtonVPN.Client.Services.Bootstrapping; public class Bootstrapper : IBootstrapper { private readonly IClientInstallsReporter _clientInstallsReporter; private readonly IProcessCommunicationStarter _processCommunicationStarter; private readonly ISettingsRestorer _settingsRestorer; private readonly IServiceManager _serviceManager; private readonly IUserAuthenticator _userAuthenticator; private readonly IServersCache _serversCache; private readonly IUpdatesManager _updatesManager; private readonly IGlobalSettingsMigrator _globalSettingsMigrator; private readonly ISettings _settings; private readonly ISessionSettings _sessionSettings; private readonly ILogger _logger; private readonly ISystemConfigurationInitializer _systemConfigurationInitializer; private readonly IMainWindowActivator _mainWindowActivator; private readonly IVpnPlanUpdater _vpnPlanUpdater; private readonly IUIThreadDispatcher _uiThreadDispatcher; private bool _isOpenOnDesktopRequested; public Bootstrapper( IClientInstallsReporter clientInstallsReporter, IProcessCommunicationStarter processCommunicationStarter, ISettingsRestorer settingsRestorer, IServiceManager serviceManager, IUserAuthenticator userAuthenticator, IServersCache serversCache, IUpdatesManager updatesManager, IGlobalSettingsMigrator settingsMigrator, ISettings settings, ISessionSettings sessionSettings, ILogger logger, ISystemConfigurationInitializer systemConfigurationInitializer, IMainWindowActivator mainWindowActivator, IVpnPlanUpdater vpnPlanUpdater, IUIThreadDispatcher uiThreadDispatcher) { _clientInstallsReporter = clientInstallsReporter; _processCommunicationStarter = processCommunicationStarter; _settingsRestorer = settingsRestorer; _serviceManager = serviceManager; _userAuthenticator = userAuthenticator; _serversCache = serversCache; _updatesManager = updatesManager; _globalSettingsMigrator = settingsMigrator; _settings = settings; _sessionSettings = sessionSettings; _logger = logger; _systemConfigurationInitializer = systemConfigurationInitializer; _mainWindowActivator = mainWindowActivator; _vpnPlanUpdater = vpnPlanUpdater; _uiThreadDispatcher = uiThreadDispatcher; } public async Task StartAsync(LaunchActivatedEventArgs args) { try { IssueReportingInitializer.SetEnabled(_settings.IsShareCrashReportsEnabled); AppInstance.GetCurrent().Activated += OnCurrentAppInstanceActivated; _systemConfigurationInitializer.Initialize(); HandleCommandLineArguments(); _globalSettingsMigrator.Migrate(); HandleMainWindow(); await StartServiceAndLogInAsync(); } catch (Exception e) { _logger.Error("Error occured during the app start up process.", e); } } private async Task StartServiceAndLogInAsync() { Task autoLoginTask = _userAuthenticator.AutoLoginUserAsync(isAppStartup: true); await Task.WhenAll( StartServiceAsync(), autoLoginTask); AuthResult autoLoginResult = await autoLoginTask; bool isNoVpnAccess = autoLoginResult.Failure && autoLoginResult.Value == AuthError.NoVpnAccess; bool isLoggedInWithoutServers = _userAuthenticator.IsLoggedIn && _serversCache.HasNoServers(); if (isNoVpnAccess || isLoggedInWithoutServers) { _mainWindowActivator.Activate(); } } private void OnCurrentAppInstanceActivated(object? sender, AppActivationArguments e) { _uiThreadDispatcher.TryEnqueue(() => { switch (e.Kind) { case ExtendedActivationKind.Protocol: HandleProtocolActivationArguments(e.Data as ProtocolActivatedEventArgs); break; case ExtendedActivationKind.StartupTask: _logger.Info($"Handle startup activation - App is already started, do nothing"); break; default: _logger.Info($"Handle {e.Kind} activation - Activate window"); _mainWindowActivator.Activate(); break; } }); } private void HandleProtocolActivationArguments(ProtocolActivatedEventArgs? args) { _logger.Info("Handle protocol activation - Activate window and refresh vpn plan"); // TODO: Investigate why protocol activation arguments are always null _mainWindowActivator.Activate(); _vpnPlanUpdater.ForceUpdateAsync(); } private void HandleCommandLineArguments() { string[] args = Environment.GetCommandLineArgs(); for (int i = 0; i < args.Length; i++) { string arg = args[i]; if (arg.EqualsIgnoringCase("-Language")) { int languageArgumentIndex = i + 1; if (languageArgumentIndex < args.Length) { _settings.Language = args[languageArgumentIndex]; i++; } } else if (arg.EqualsIgnoringCase("-RestoreDefaultSettings")) { _settingsRestorer.Restore(); } else if (arg.EqualsIgnoringCase("-DisableAutoUpdate")) { _settings.AreAutomaticUpdatesEnabled = false; } else if (arg.EqualsIgnoringCase("-ExitAppOnClose")) { _mainWindowActivator.DisableHandleClosedEvent(); } else if (arg.EqualsIgnoringCase("-username") || arg.EqualsIgnoringCase("-u")) { int usernameIndex = i + 1; if (usernameIndex < args.Length) { _sessionSettings.Username = args[usernameIndex]; i++; } } else if (arg.EqualsIgnoringCase("-password") || arg.EqualsIgnoringCase("-p")) { int passwordIndex = i + 1; if (passwordIndex < args.Length) { _sessionSettings.Password = args[passwordIndex]; i++; } } else if (arg.EqualsIgnoringCase("-ResetLogicals")) { _settings.LogicalsLastModifiedDate = DefaultSettings.LogicalsLastModifiedDate; } else if (arg.EqualsIgnoringCase("-AllowEfficiencyMode")) { _settings.IsEfficiencyModeAllowed = true; } else if (arg.EqualsIgnoringCase("-OpenOnDesktop")) { _isOpenOnDesktopRequested = true; } else if (arg.EqualsIgnoringCase("-SkipOnboarding")) { _settings.SkipOnboarding = true; } } HandleProtonInstallerArguments(args); } private void HandleProtonInstallerArguments(string[] args) { bool isCleanInstall = false; bool isMailInstalled = false; bool isDriveInstalled = false; bool isPassInstalled = false; for (int i = 0; i < args.Length; i++) { string arg = args[i]; if (arg.EqualsIgnoringCase("-CleanInstall")) { isCleanInstall = true; } else if (arg.EqualsIgnoringCase("-MailInstalled")) { isMailInstalled = true; } else if (arg.EqualsIgnoringCase("-DriveInstalled")) { isDriveInstalled = true; } else if (arg.EqualsIgnoringCase("-PassInstalled")) { isPassInstalled = true; } } if (isCleanInstall) { _clientInstallsReporter.Report( isMailInstalled: isMailInstalled, isDriveInstalled: isDriveInstalled, isPassInstalled: isPassInstalled); } } private void HandleMainWindow() { bool hasAuthenticatedSessionData = _userAuthenticator.HasAuthenticatedSessionData(); bool isAutoLaunchEnabled = _settings.IsAutoLaunchEnabled; bool isAutoLaunchModeOpenOnDesktop = _settings.AutoLaunchMode == AutoLaunchMode.OpenOnDesktop; _logger.Info($"Handle main window start conditions - HasAuthenticatedSessionData: {hasAuthenticatedSessionData}, " + $"IsAutoLaunchEnabled: {isAutoLaunchEnabled}, IsAutoLaunchModeOpenOnDesktop: {isAutoLaunchModeOpenOnDesktop}, " + $"IsOpenOnDesktopRequested: {_isOpenOnDesktopRequested}"); if (!hasAuthenticatedSessionData || !isAutoLaunchEnabled || isAutoLaunchModeOpenOnDesktop || _isOpenOnDesktopRequested) { _mainWindowActivator.Activate(); } } private async Task StartServiceAsync() { try { await _serviceManager.StartAsync(); } catch { } try { _processCommunicationStarter.Start(); } catch { } _updatesManager.Initialize(); } }