diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index 82d6f9fd8..c49bc0758 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -502,11 +502,6 @@ namespace Bit.Droid.Services public async Task SetScreenCaptureAllowedAsync() { - if (CoreHelpers.ForceScreenCaptureEnabled()) - { - return; - } - var activity = CrossCurrentActivity.Current?.Activity; if (await _stateService.GetScreenCaptureAllowedAsync()) { diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index ec49f9c42..2845401bf 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -806,11 +806,6 @@ namespace Bit.App.Pages public async Task SetScreenCaptureAllowedAsync() { - if (CoreHelpers.ForceScreenCaptureEnabled()) - { - return; - } - try { if (!_screenCaptureAllowed diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index ecfba14d8..43a3d1107 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -73,8 +73,8 @@ namespace Bit.Core.Abstractions Task SetInvalidUnlockAttemptsAsync(int? value, string userId = null); Task GetLastBuildAsync(); Task SetLastBuildAsync(string value); - Task GetDisableFaviconAsync(string userId = null); - Task SetDisableFaviconAsync(bool? value, string userId = null); + Task GetDisableFaviconAsync(); + Task SetDisableFaviconAsync(bool? value); Task GetDisableAutoTotpCopyAsync(string userId = null); Task SetDisableAutoTotpCopyAsync(bool? value, string userId = null); Task GetInlineAutofillEnabledAsync(string userId = null); @@ -109,10 +109,10 @@ namespace Bit.Core.Abstractions Task SetRememberedEmailAsync(string value); Task GetRememberedOrgIdentifierAsync(); Task SetRememberedOrgIdentifierAsync(string value); - Task GetThemeAsync(string userId = null); - Task SetThemeAsync(string value, string userId = null); - Task GetAutoDarkThemeAsync(string userId = null); - Task SetAutoDarkThemeAsync(string value, string userId = null); + Task GetThemeAsync(); + Task SetThemeAsync(string value); + Task GetAutoDarkThemeAsync(); + Task SetAutoDarkThemeAsync(string value); Task GetAddSitePromptShownAsync(string userId = null); Task SetAddSitePromptShownAsync(bool? value, string userId = null); Task GetPushInitialPromptShownAsync(); diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index b520cf108..c67a67537 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -3,6 +3,7 @@ public static class Constants { public const int MaxAccounts = 5; + public const int VaultTimeoutDefault = 15; public const string AndroidAppProtocol = "androidapp://"; public const string iOSAppProtocol = "iosapp://"; public const string DefaultUsernameGenerated = "-"; @@ -29,6 +30,9 @@ public static string RememberedEmailKey = "rememberedEmail"; public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier"; public static string PasswordlessLoginNotificationKey = "passwordlessLoginNotificationKey"; + public const string ThemeKey = "theme"; + public const string AutoDarkThemeKey = "autoDarkTheme"; + public const string DisableFaviconKey = "disableFavicon"; public const string PasswordlessNotificationId = "26072022"; public const string AndroidNotificationChannelId = "general_notification_channel"; public const string iOSNotificationCategoryId = "dismissableCategory"; @@ -65,6 +69,8 @@ iOSShareExtensionClearCiphersCacheKey }; + public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}"; + public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}"; public static string CiphersKey(string userId) => $"ciphers_{userId}"; public static string FoldersKey(string userId) => $"folders_{userId}"; public static string CollectionsKey(string userId) => $"collections_{userId}"; @@ -89,10 +95,7 @@ public static string AutofillBlacklistedUrisKey(string userId) => $"autofillBlacklistedUris_{userId}"; public static string ClearClipboardKey(string userId) => $"clearClipboard_{userId}"; public static string SyncOnRefreshKey(string userId) => $"syncOnRefresh_{userId}"; - public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}"; public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}"; - public static string ThemeKey(string userId) => $"theme_{userId}"; - public static string AutoDarkThemeKey(string userId) => $"autoDarkTheme_{userId}"; public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}"; public static string PreviousPageKey(string userId) => $"previousPage_{userId}"; public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}"; @@ -107,5 +110,6 @@ public static string PushLastRegistrationDateKey(string userId) => $"pushLastRegistrationDate_{userId}"; public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}"; public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}"; + public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}"; } } diff --git a/src/Core/Models/Domain/Account.cs b/src/Core/Models/Domain/Account.cs index b110a9563..c9014ec9e 100644 --- a/src/Core/Models/Domain/Account.cs +++ b/src/Core/Models/Domain/Account.cs @@ -1,4 +1,5 @@ -using Bit.Core.Enums; +using System; +using Bit.Core.Enums; using Bit.Core.Models.Data; namespace Bit.Core.Models.Domain @@ -104,9 +105,12 @@ namespace Bit.Core.Models.Domain } public EnvironmentUrlData EnvironmentUrls; + [Obsolete("Feb 10 2023: VaultTimeout has been deprecated in favor of stored prefs to retain value after logout. It remains here to allow for migration during app upgrade.")] public int? VaultTimeout; + [Obsolete("Feb 10 2023: VaultTimeoutAction has been deprecated in favor of stored prefs to retain value after logout. It remains here to allow for migration during app upgrade.")] public VaultTimeoutAction? VaultTimeoutAction; - public bool ScreenCaptureAllowed; + [Obsolete("Feb 10 2023: ScreenCaptureAllowed has been deprecated in favor of stored prefs to retain value after logout. It remains here to allow for migration during app upgrade.")] + public bool? ScreenCaptureAllowed; } public class AccountVolatileData diff --git a/src/Core/Services/StateMigrationService.cs b/src/Core/Services/StateMigrationService.cs index 52a33005a..31bc62c40 100644 --- a/src/Core/Services/StateMigrationService.cs +++ b/src/Core/Services/StateMigrationService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; @@ -11,7 +12,7 @@ namespace Bit.Core.Services { public class StateMigrationService : IStateMigrationService { - private const int StateVersion = 3; + private const int StateVersion = 4; private readonly IStorageService _preferencesStorageService; private readonly IStorageService _liteDbStorageService; @@ -54,6 +55,9 @@ namespace Bit.Core.Services goto case 2; case 2: await MigrateFrom2To3Async(); + goto case 3; + case 3: + await MigrateFrom3To4Async(); break; } } @@ -220,9 +224,9 @@ namespace Bit.Core.Services await GetValueAsync>(Storage.LiteDb, V2Keys.AutofillBlacklistedUrisKey); await SetValueAsync(Storage.LiteDb, Constants.AutofillBlacklistedUrisKey(userId), autofillBlacklistedUris); var disableFavicon = await GetValueAsync(Storage.Prefs, V2Keys.DisableFaviconKey); - await SetValueAsync(Storage.LiteDb, Constants.DisableFaviconKey(userId), disableFavicon); + await SetValueAsync(Storage.LiteDb, V3Keys.DisableFaviconKey(userId), disableFavicon); var theme = await GetValueAsync(Storage.Prefs, V2Keys.ThemeKey); - await SetValueAsync(Storage.LiteDb, Constants.ThemeKey(userId), theme); + await SetValueAsync(Storage.LiteDb, V3Keys.ThemeKey(userId), theme); var clearClipboard = await GetValueAsync(Storage.Prefs, V2Keys.ClearClipboardKey); await SetValueAsync(Storage.LiteDb, Constants.ClearClipboardKey(userId), clearClipboard); var previousPage = await GetValueAsync(Storage.LiteDb, V2Keys.PreviousPageKey); @@ -314,6 +318,86 @@ namespace Bit.Core.Services await RemoveValueAsync(Storage.LiteDb, V2Keys.Keys_PassGenHistory); } + // v3 to v4 Migration + + private class V3Keys + { + internal static string ThemeKey(string userId) => $"theme_{userId}"; + internal static string AutoDarkThemeKey(string userId) => $"autoDarkTheme_{userId}"; + internal static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}"; + } + + private async Task MigrateFrom3To4Async() + { + var state = await GetValueAsync(Storage.LiteDb, Constants.StateKey); + if (state?.Accounts is null) + { + // Update stored version + await SetLastStateVersionAsync(4); + return; + } + + string firstUserId = null; + + // move values from state to standalone values in LiteDB + foreach (var account in state.Accounts.Where(a => a.Value?.Profile?.UserId != null)) + { + var userId = account.Value.Profile.UserId; + if (firstUserId == null) + { + firstUserId = userId; + } + var vaultTimeout = account.Value.Settings?.VaultTimeout; + await SetValueAsync(Storage.LiteDb, V4Keys.VaultTimeoutKey(userId), vaultTimeout); + + var vaultTimeoutAction = account.Value.Settings?.VaultTimeoutAction; + await SetValueAsync(Storage.LiteDb, V4Keys.VaultTimeoutActionKey(userId), vaultTimeoutAction); + + var screenCaptureAllowed = account.Value.Settings?.ScreenCaptureAllowed; + await SetValueAsync(Storage.LiteDb, V4Keys.ScreenCaptureAllowedKey(userId), screenCaptureAllowed); + } + + // use values from first userId to apply globals + if (firstUserId != null) + { + var theme = await GetValueAsync(Storage.LiteDb, V3Keys.ThemeKey(firstUserId)); + await SetValueAsync(Storage.LiteDb, V4Keys.ThemeKey, theme); + + var autoDarkTheme = await GetValueAsync(Storage.LiteDb, V3Keys.AutoDarkThemeKey(firstUserId)); + await SetValueAsync(Storage.LiteDb, V4Keys.AutoDarkThemeKey, autoDarkTheme); + + var disableFavicon = await GetValueAsync(Storage.LiteDb, V3Keys.DisableFaviconKey(firstUserId)); + await SetValueAsync(Storage.LiteDb, V4Keys.DisableFaviconKey, disableFavicon); + } + + // Update stored version + await SetLastStateVersionAsync(4); + + // Remove old data + foreach (var account in state.Accounts) + { + var userId = account.Value?.Profile?.UserId; + if (userId != null) + { + await RemoveValueAsync(Storage.LiteDb, V3Keys.ThemeKey(userId)); + await RemoveValueAsync(Storage.LiteDb, V3Keys.AutoDarkThemeKey(userId)); + await RemoveValueAsync(Storage.LiteDb, V3Keys.DisableFaviconKey(userId)); + } + } + + // Removal of old state data will happen organically as state is rebuilt in app + } + + private class V4Keys + { + internal static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}"; + internal static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}"; + internal static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}"; + internal const string ThemeKey = "theme"; + internal const string AutoDarkThemeKey = "autoDarkTheme"; + internal const string DisableFaviconKey = "disableFavicon"; + } + // Helpers private async Task GetLastStateVersionAsync() diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index dce4c980b..75a903cc9 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -539,55 +539,50 @@ namespace Bit.Core.Services public async Task GetVaultTimeoutAsync(string userId = null) { - return (await GetAccountAsync( - ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) - ))?.Settings?.VaultTimeout; + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.VaultTimeoutKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? Constants.VaultTimeoutDefault; } public async Task SetVaultTimeoutAsync(int? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); - var account = await GetAccountAsync(reconciledOptions); - account.Settings.VaultTimeout = value; - await SaveAccountAsync(account, reconciledOptions); + var key = Constants.VaultTimeoutKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); } public async Task GetVaultTimeoutActionAsync(string userId = null) { - return (await GetAccountAsync( - ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) - ))?.Settings?.VaultTimeoutAction; + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.VaultTimeoutActionKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? VaultTimeoutAction.Lock; } public async Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); - var account = await GetAccountAsync(reconciledOptions); - account.Settings.VaultTimeoutAction = value; - await SaveAccountAsync(account, reconciledOptions); + var key = Constants.VaultTimeoutActionKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); } public async Task GetScreenCaptureAllowedAsync(string userId = null) { - if (CoreHelpers.ForceScreenCaptureEnabled()) - { - return true; - } - - return (await GetAccountAsync( - ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) - ))?.Settings?.ScreenCaptureAllowed ?? false; + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultStorageOptionsAsync()); + var key = Constants.ScreenCaptureAllowedKey(reconciledOptions.UserId); + return await GetValueAsync(key, reconciledOptions) ?? CoreHelpers.InDebugMode(); } public async Task SetScreenCaptureAllowedAsync(bool value, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); - var account = await GetAccountAsync(reconciledOptions); - account.Settings.ScreenCaptureAllowed = value; - await SaveAccountAsync(account, reconciledOptions); + var key = Constants.ScreenCaptureAllowedKey(reconciledOptions.UserId); + await SetValueAsync(key, value, reconciledOptions); } public async Task GetLastFileCacheClearAsync() @@ -650,23 +645,18 @@ namespace Bit.Core.Services await SetValueAsync(key, value, options); } - public async Task GetDisableFaviconAsync(string userId = null) + public async Task GetDisableFaviconAsync() { - var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, - await GetDefaultStorageOptionsAsync()); - var key = Constants.DisableFaviconKey(reconciledOptions.UserId); - return await GetValueAsync(key, reconciledOptions); + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.DisableFaviconKey; + return await GetValueAsync(key, options); } - public async Task SetDisableFaviconAsync(bool? value, string userId = null) + public async Task SetDisableFaviconAsync(bool? value) { - var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, - await GetDefaultStorageOptionsAsync()); - var key = Constants.DisableFaviconKey(reconciledOptions.UserId); - await SetValueAsync(key, value, reconciledOptions); - - // TODO remove this to restore per-account DisableFavicon support - SetValueGloballyAsync(Constants.DisableFaviconKey, value, reconciledOptions).FireAndForget(); + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.DisableFaviconKey; + await SetValueAsync(key, value, options); } public async Task GetDisableAutoTotpCopyAsync(string userId = null) @@ -937,42 +927,32 @@ namespace Bit.Core.Services await SetValueAsync(key, value, options); } - public async Task GetThemeAsync(string userId = null) + public async Task GetThemeAsync() { - var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, - await GetDefaultStorageOptionsAsync()); - var key = Constants.ThemeKey(reconciledOptions.UserId); - return await GetValueAsync(key, reconciledOptions); + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.ThemeKey; + return await GetValueAsync(key, options); } - public async Task SetThemeAsync(string value, string userId = null) + public async Task SetThemeAsync(string value) { - var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, - await GetDefaultStorageOptionsAsync()); - var key = Constants.ThemeKey(reconciledOptions.UserId); - await SetValueAsync(key, value, reconciledOptions); - - // TODO remove this to restore per-account Theme support - SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget(); + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.ThemeKey; + await SetValueAsync(key, value, options); } - public async Task GetAutoDarkThemeAsync(string userId = null) + public async Task GetAutoDarkThemeAsync() { - var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, - await GetDefaultStorageOptionsAsync()); - var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId); - return await GetValueAsync(key, reconciledOptions); + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.AutoDarkThemeKey; + return await GetValueAsync(key, options); } - public async Task SetAutoDarkThemeAsync(string value, string userId = null) + public async Task SetAutoDarkThemeAsync(string value) { - var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, - await GetDefaultStorageOptionsAsync()); - var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId); - await SetValueAsync(key, value, reconciledOptions); - - // TODO remove this to restore per-account Theme support - SetValueGloballyAsync(Constants.AutoDarkThemeKey, value, reconciledOptions).FireAndForget(); + var options = await GetDefaultStorageOptionsAsync(); + var key = Constants.AutoDarkThemeKey; + await SetValueAsync(key, value, options); } public async Task GetAddSitePromptShownAsync(string userId = null) @@ -1333,30 +1313,6 @@ namespace Bit.Core.Services await GetStorageService(options).SaveAsync(key, value); } - private async Task SetValueGloballyAsync(Func keyPrefix, T value, StorageOptions options) - { - if (value == null) - { - // don't remove values globally - return; - } - await CheckStateAsync(); - if (_state?.Accounts == null) - { - return; - } - // userId from options was already applied, skip those - var userIdToSkip = options.UserId; - foreach (var account in _state.Accounts) - { - var uid = account.Value?.Profile?.UserId; - if (uid != null && uid != userIdToSkip) - { - await SetValueAsync(keyPrefix(uid), value, options); - } - } - } - private IStorageService GetStorageService(StorageOptions options) { return options.UseSecureStorage.GetValueOrDefault(false) ? _secureStorageService : _storageService; @@ -1489,7 +1445,6 @@ namespace Bit.Core.Services } // Non-state storage - await SetBiometricUnlockAsync(null, userId); await SetProtectedPinAsync(null, userId); await SetPinProtectedAsync(null, userId); await SetKeyEncryptedAsync(null, userId); @@ -1511,37 +1466,11 @@ namespace Bit.Core.Services await SetEncryptedPasswordGenerationHistoryAsync(null, userId); await SetEncryptedSendsAsync(null, userId); await SetSettingsAsync(null, userId); - await SetApprovePasswordlessLoginsAsync(null, userId); - - if (userInitiated) - { - // user initiated logout (not vault timeout or scaffolding new account) so remove remaining settings - await SetAutofillBlacklistedUrisAsync(null, userId); - await SetDisableFaviconAsync(null, userId); - await SetDisableAutoTotpCopyAsync(null, userId); - await SetInlineAutofillEnabledAsync(null, userId); - await SetAutofillDisableSavePromptAsync(null, userId); - await SetDefaultUriMatchAsync(null, userId); - await SetNeverDomainsAsync(null, userId); - await SetClearClipboardAsync(null, userId); - await SetPasswordRepromptAutofillAsync(null, userId); - await SetPasswordVerifiedAutofillAsync(null, userId); - await SetSyncOnRefreshAsync(null, userId); - await SetThemeAsync(null, userId); - await SetAutoDarkThemeAsync(null, userId); - await SetAddSitePromptShownAsync(null, userId); - await SetPasswordGenerationOptionsAsync(null, userId); - await SetApprovePasswordlessLoginsAsync(null, userId); - await SetUsernameGenerationOptionsAsync(null, userId); - } } private async Task ScaffoldNewAccountAsync(Account account) { await CheckStateAsync(); - var currentTheme = await GetThemeAsync(); - var currentAutoDarkTheme = await GetAutoDarkThemeAsync(); - var currentDisableFavicons = await GetDisableFaviconAsync(); account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync(); @@ -1551,28 +1480,6 @@ namespace Bit.Core.Services { state.Accounts = new Dictionary(); } - if (state.Accounts.ContainsKey(account.Profile.UserId)) - { - // Run cleanup pass on existing account before proceeding - await RemoveAccountAsync(account.Profile.UserId, false); - var existingAccount = state.Accounts[account.Profile.UserId]; - account.Settings.VaultTimeout = existingAccount.Settings.VaultTimeout; - account.Settings.VaultTimeoutAction = existingAccount.Settings.VaultTimeoutAction; - account.Settings.ScreenCaptureAllowed = existingAccount.Settings.ScreenCaptureAllowed; - } - - // New account defaults - if (account.Settings.VaultTimeout == null) - { - account.Settings.VaultTimeout = 15; - } - if (account.Settings.VaultTimeoutAction == null) - { - account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock; - } - await SetThemeAsync(currentTheme, account.Profile.UserId); - await SetAutoDarkThemeAsync(currentAutoDarkTheme, account.Profile.UserId); - await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId); state.Accounts[account.Profile.UserId] = account; await SaveStateToStorageAsync(state); diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index 0ae2c559c..8c63374e3 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -37,25 +37,6 @@ namespace Bit.Core.Utilities #endif } - /// - /// Returns whether to force enabling the screen capture. - /// On Debug it will allow screen capture by default but this method - /// makes it easier to test the change on enabling/disabling the feature - /// on debug. - /// - /// - /// To test enabling/disabling in DEBUG, just return false in the #if condition - /// and that's it. - /// - public static bool ForceScreenCaptureEnabled() - { -#if DEBUG - return true; -#else - return false; -#endif - } - public static string GetHostname(string uriString) { var uri = GetUri(uriString);