From beae3116127b45c0242c2e12edb7cb4319feeb2e Mon Sep 17 00:00:00 2001 From: Brianna Birman Date: Tue, 7 Oct 2025 17:25:45 -0700 Subject: [PATCH 1/4] Add settings menu to login screen --- .../Classes/Common/WebViewStateManager.swift | 9 +++ .../Classes/Login/SFLoginViewController.h | 6 ++ .../Classes/Login/SFLoginViewController.m | 62 ++++++++++++++----- .../UserAccount/SFUserAccountManager.m | 21 ++++++- .../en.lproj/Localizable.strings | 4 ++ 5 files changed, 87 insertions(+), 15 deletions(-) diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/WebViewStateManager.swift b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/WebViewStateManager.swift index 6867eeb3b4..ca7b725850 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/WebViewStateManager.swift +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/WebViewStateManager.swift @@ -38,6 +38,7 @@ public class SFSDKWebViewStateManager: NSObject { @objc @MainActor + @available(*, deprecated, renamed: "resetSessionCookie", message: "Deprecated in Salesforce Mobile SDK 13.2 and will be removed in Salesforce Mobile SDK 14.0. Use resetSessionCookie instead.") public static func removeSession() { if sessionCookieManagementDisabled { SFSDKCoreLogger.d(SFSDKWebViewStateManager.self, message: "[\(Self.self) removeSession]: Cookie Management disabled. Will do nothing.") @@ -62,6 +63,14 @@ public class SFSDKWebViewStateManager: NSObject { } } + @objc + @MainActor + public static func clearCache() async { + let dataStore = WKWebsiteDataStore.default() + let websiteDataTypes: Set = [WKWebsiteDataTypeFetchCache, WKWebsiteDataTypeMemoryCache, WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeOfflineWebApplicationCache] + await dataStore.removeData(ofTypes: websiteDataTypes, modifiedSince: Date.distantPast) + } + @objc @MainActor public static func removeSessionForcefully() async { diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.h b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.h index a279f638cc..4d8feed872 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.h +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.h @@ -48,6 +48,12 @@ NS_SWIFT_NAME(SalesforceLoginViewControllerDelegate) */ - (void)loginViewController:(nonnull SFLoginViewController *)loginViewController didChangeLoginHost:(nonnull SFSDKLoginHost *)newLoginHost; +- (void)loginViewControllerDidClearCache:(nonnull SFLoginViewController *)loginViewController; + +- (void)loginViewControllerDidClearCookies:(nonnull SFLoginViewController *)loginViewController; + +- (void)loginViewControllerDidReload:(nonnull SFLoginViewController *)loginViewController; + @end /** The Salesforce login screen view. diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.m index 87998d5641..72fd4e0e08 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.m @@ -200,20 +200,10 @@ - (void)setupNavigationBar { if (self.showNavbar) { self.navBar = self.navigationController.navigationBar; self.navBar.topItem.titleView = [self createTitleItem]; - // Hides the gear icon if there are no hosts to switch to. - SFManagedPreferences *managedPreferences = [SFManagedPreferences sharedPreferences]; - if (managedPreferences.onlyShowAuthorizedHosts && managedPreferences.loginHosts.count == 0) { - self.config.showSettingsIcon = NO; - } + if(self.showSettingsIcon) { // Setup right bar button. UIBarButtonItem *button = [self createSettingsButton]; - if (!button.target){ - [button setTarget:self]; - } - if (!button.action){ - [button setAction:@selector(showLoginHost:)]; - } self.navBar.topItem.rightBarButtonItem = button; } [self styleNavigationBar:self.navBar]; @@ -265,9 +255,53 @@ - (UIBarButtonItem *)createBackButton { - (UIBarButtonItem *)createSettingsButton { UIImage *image = [[SFSDKResourceUtils imageNamed:@"login-window-gear"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:@selector(showLoginHost:)]; - settingsButton.accessibilityLabel = [SFSDKResourceUtils localizedString:@"LOGIN_CHOOSE_SERVER"]; - settingsButton.accessibilityIdentifier = @"choose connection button"; + + NSMutableArray *menuActions = [[NSMutableArray alloc] initWithCapacity:3]; + + // Don't show the change server option if there are no hosts to switch to. + SFManagedPreferences *managedPreferences = [SFManagedPreferences sharedPreferences]; + if (!managedPreferences.onlyShowAuthorizedHosts || managedPreferences.loginHosts.count != 0) { + [menuActions addObject:[UIAction actionWithTitle:[SFSDKResourceUtils localizedString:@"LOGIN_CHOOSE_SERVER"] + image:nil + identifier:nil + handler:^(__kindof UIAction* _Nonnull action) { + [self showLoginHost:self]; + }]]; + } + + + [menuActions addObject:[UIAction actionWithTitle:[SFSDKResourceUtils localizedString:@"LOGIN_CLEAR_COOKIES"] + image:nil + identifier:nil + handler:^(__kindof UIAction* _Nonnull action) { + if ([self.delegate respondsToSelector:@selector(loginViewControllerDidClearCookies:)]) { + [self.delegate loginViewControllerDidClearCookies:self]; + } + }]]; + + [menuActions addObject:[UIAction actionWithTitle:[SFSDKResourceUtils localizedString:@"LOGIN_CLEAR_CACHE"] + image:nil + identifier:nil + handler:^(__kindof UIAction* _Nonnull action) { + if ([self.delegate respondsToSelector:@selector(loginViewControllerDidClearCache:)]) { + [self.delegate loginViewControllerDidClearCache:self]; + } + }]]; + + [menuActions addObject:[UIAction actionWithTitle:[SFSDKResourceUtils localizedString:@"LOGIN_RELOAD"] + image:nil + identifier:nil + handler:^(__kindof UIAction* _Nonnull action) { + if ([self.delegate respondsToSelector:@selector(loginViewControllerDidReload:)]) { + [self.delegate loginViewControllerDidReload:self]; + } + }]]; + + UIMenu *menu = [UIMenu menuWithTitle:@"" // No title + children:menuActions]; + UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithImage:image menu:menu]; + settingsButton.accessibilityLabel = [SFSDKResourceUtils localizedString:@"LOGIN_SETTINGS_BUTTON"]; + settingsButton.accessibilityIdentifier = @"settings"; return settingsButton; } diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m index beb8aaceaa..6b8b94b345 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m @@ -730,7 +730,7 @@ - (void)postPushUnregistration:(SFUserAccount *)user logoutReason:(SFLogoutReaso [self setCurrentUserInternal:nil]; } - [SFSDKWebViewStateManager removeSession]; + [SFSDKWebViewStateManager resetSessionCookie]; //restore these id's inorder to enable post logout cleanup of components // TODO: Revisit the userInfo data structure of kSFNotificationUserDidLogout in 7.0. @@ -1044,6 +1044,25 @@ - (void)loginViewController:(SFLoginViewController *)loginViewController didChan [[NSNotificationCenter defaultCenter] postNotification:loginHostChangedNotification]; NSString *sceneId = loginViewController.view.window.windowScene.session.persistentIdentifier; self.authSessions[sceneId].oauthRequest.loginHost = newLoginHost.host; + [self restartAuthenticationForViewController:loginViewController]; +} + +- (void)loginViewControllerDidClearCache:(SFLoginViewController *)loginViewController { + [SFSDKWebViewStateManager clearCacheWithCompletionHandler:^{}]; + [self restartAuthenticationForViewController:loginViewController]; +} + +- (void)loginViewControllerDidClearCookies:(SFLoginViewController *)loginViewController { + [SFSDKWebViewStateManager resetSessionCookie]; + [self restartAuthenticationForViewController:loginViewController]; +} + +- (void)loginViewControllerDidReload:(SFLoginViewController *)loginViewController { + [self restartAuthenticationForViewController:loginViewController]; +} + +- (void)restartAuthenticationForViewController:(SFLoginViewController *)loginViewController { + NSString *sceneId = loginViewController.view.window.windowScene.session.persistentIdentifier; [self restartAuthentication:self.authSessions[sceneId]]; } diff --git a/shared/resources/SalesforceSDKResources.bundle/en.lproj/Localizable.strings b/shared/resources/SalesforceSDKResources.bundle/en.lproj/Localizable.strings index c2d73e02b2..66c4e64e3e 100644 --- a/shared/resources/SalesforceSDKResources.bundle/en.lproj/Localizable.strings +++ b/shared/resources/SalesforceSDKResources.bundle/en.lproj/Localizable.strings @@ -66,6 +66,10 @@ "LOGIN_SERVER_URL_PLACEHOLDER" = "Example: login.salesforce.com"; "LOGIN_SERVER_NAME_PLACEHOLDER" = "Optional"; "DONE_BUTTON" = "Done"; +"LOGIN_SETTINGS_BUTTON" = "Settings"; +"LOGIN_CLEAR_COOKIES" = "Clear Cookies"; +"LOGIN_CLEAR_CACHE" = "Clear Cache"; +"LOGIN_RELOAD" = "Reload"; // Account Switcher "ACCOUNT_SWITCHER_TITLE" = "Manage Accounts"; From aeb46ee6dfba231eb0bbff27e96b0fe3eec380a9 Mon Sep 17 00:00:00 2001 From: Brianna Birman Date: Thu, 9 Oct 2025 12:14:01 -0700 Subject: [PATCH 2/4] Update title string to 'Change Server' --- .../SalesforceSDKResources.bundle/en.lproj/Localizable.strings | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/resources/SalesforceSDKResources.bundle/en.lproj/Localizable.strings b/shared/resources/SalesforceSDKResources.bundle/en.lproj/Localizable.strings index 66c4e64e3e..8ed89f12db 100644 --- a/shared/resources/SalesforceSDKResources.bundle/en.lproj/Localizable.strings +++ b/shared/resources/SalesforceSDKResources.bundle/en.lproj/Localizable.strings @@ -59,7 +59,7 @@ "LOGIN_SERVER_PRODUCTION" = "Production"; "LOGIN_SERVER_SANDBOX" = "Sandbox"; "LOGIN_SERVER_WELCOME" = "Welcome"; -"LOGIN_CHOOSE_SERVER" = "Choose Connection"; +"LOGIN_CHOOSE_SERVER" = "Change Server"; "LOGIN_ADD_SERVER" = "Add Connection"; "LOGIN_SERVER_URL" = "Host"; "LOGIN_SERVER_NAME" = "Label"; @@ -67,6 +67,7 @@ "LOGIN_SERVER_NAME_PLACEHOLDER" = "Optional"; "DONE_BUTTON" = "Done"; "LOGIN_SETTINGS_BUTTON" = "Settings"; + "LOGIN_CLEAR_COOKIES" = "Clear Cookies"; "LOGIN_CLEAR_CACHE" = "Clear Cache"; "LOGIN_RELOAD" = "Reload"; From 650e8fcef623c080459675d679ed8e46e751e753 Mon Sep 17 00:00:00 2001 From: Brianna Birman Date: Thu, 9 Oct 2025 14:28:40 -0700 Subject: [PATCH 3/4] Clear more record types and clear cookies forcefully --- .../Classes/Common/WebViewStateManager.swift | 12 ++++++++++-- .../Classes/UserAccount/SFUserAccountManager.m | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/WebViewStateManager.swift b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/WebViewStateManager.swift index ca7b725850..c34b34d9f7 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/WebViewStateManager.swift +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/WebViewStateManager.swift @@ -67,8 +67,16 @@ public class SFSDKWebViewStateManager: NSObject { @MainActor public static func clearCache() async { let dataStore = WKWebsiteDataStore.default() - let websiteDataTypes: Set = [WKWebsiteDataTypeFetchCache, WKWebsiteDataTypeMemoryCache, WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeOfflineWebApplicationCache] - await dataStore.removeData(ofTypes: websiteDataTypes, modifiedSince: Date.distantPast) + let nonCookieDataTypes: Set = [WKWebsiteDataTypeDiskCache, + WKWebsiteDataTypeMemoryCache, + WKWebsiteDataTypeFetchCache, + WKWebsiteDataTypeLocalStorage, + WKWebsiteDataTypeSessionStorage, + WKWebsiteDataTypeIndexedDBDatabases, + WKWebsiteDataTypeWebSQLDatabases, + WKWebsiteDataTypeOfflineWebApplicationCache, + WKWebsiteDataTypeServiceWorkerRegistrations] + await dataStore.removeData(ofTypes: nonCookieDataTypes, modifiedSince: Date.distantPast) } @objc diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m index 6b8b94b345..79883ba703 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m @@ -1053,7 +1053,7 @@ - (void)loginViewControllerDidClearCache:(SFLoginViewController *)loginViewContr } - (void)loginViewControllerDidClearCookies:(SFLoginViewController *)loginViewController { - [SFSDKWebViewStateManager resetSessionCookie]; + [SFSDKWebViewStateManager removeSessionForcefullyWithCompletionHandler:^{}]; [self restartAuthenticationForViewController:loginViewController]; } From 1649d0db2e0d90102dbc20b0dca78e32e721c647 Mon Sep 17 00:00:00 2001 From: Brianna Birman Date: Wed, 15 Oct 2025 23:25:21 -0700 Subject: [PATCH 4/4] Add showServerPicker property --- .../Classes/Login/SFLoginViewController.h | 5 ++++- .../Classes/Login/SFLoginViewController.m | 14 +++++++++++++- .../Classes/Login/SFSDKLoginViewControllerConfig.h | 9 ++++++--- .../Classes/Login/SFSDKLoginViewControllerConfig.m | 1 + 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.h b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.h index 4d8feed872..032b65eca8 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.h +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.h @@ -91,9 +91,12 @@ NS_SWIFT_NAME(SalesforceLoginViewController) /** Specify visibility of nav bar. This property will be used to hide/show the nav bar*/ @property (nonatomic) BOOL showNavbar NS_SWIFT_NAME(showsNavigationBar); -/** Specifiy the visibility of the settings icon. This property will be used to hide/show the settings icon*/ +/** Specify the visibility of the settings icon. This property will be used to hide/show the settings icon*/ @property (nonatomic) BOOL showSettingsIcon NS_SWIFT_NAME(showsSettingsIcon); +/// Specify the visibility of the server picker option in the settings menu. +@property (nonatomic) BOOL showServerPicker NS_SWIFT_NAME(showsServerPicker); + /** Specify all display properties in a config. All the above properties are backed by a config object */ @property (nonatomic, strong, nonnull) SFSDKLoginViewControllerConfig *config; diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.m index 72fd4e0e08..ff08857525 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFLoginViewController.m @@ -184,6 +184,14 @@ - (void)setShowSettingsIcon:(BOOL)showSettingsIcon { self.config.showSettingsIcon = showSettingsIcon; } +- (BOOL)showServerPicker { + return self.config.showServerPicker; +} + +- (void)setShowServerPicker:(BOOL)showServerPicker { + self.config.showServerPicker = showServerPicker; +} + - (SFSDKLoginViewControllerConfig *)config { return _config; } @@ -260,7 +268,11 @@ - (UIBarButtonItem *)createSettingsButton { // Don't show the change server option if there are no hosts to switch to. SFManagedPreferences *managedPreferences = [SFManagedPreferences sharedPreferences]; - if (!managedPreferences.onlyShowAuthorizedHosts || managedPreferences.loginHosts.count != 0) { + if (managedPreferences.onlyShowAuthorizedHosts && managedPreferences.loginHosts.count == 0) { + self.showServerPicker = NO; + } + + if (self.showServerPicker) { [menuActions addObject:[UIAction actionWithTitle:[SFSDKResourceUtils localizedString:@"LOGIN_CHOOSE_SERVER"] image:nil identifier:nil diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFSDKLoginViewControllerConfig.h b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFSDKLoginViewControllerConfig.h index 524613b142..e13cccf498 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFSDKLoginViewControllerConfig.h +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFSDKLoginViewControllerConfig.h @@ -43,14 +43,17 @@ NS_SWIFT_NAME(SalesforceLoginViewControllerConfig) /** Specify visibility of nav bar. This property will be used to hide/show the nav bar*/ @property (nonatomic) BOOL showNavbar NS_SWIFT_NAME(showsNavigationBar); -/** Specifiy the visibility of the settings icon. This property will be used to hide/show the settings icon*/ +/** Specify the visibility of the settings icon. This property will be used to hide/show the settings icon*/ @property (nonatomic) BOOL showSettingsIcon NS_SWIFT_NAME(showsSettingsIcon); -/** Specifiy the visibility of the back icon. This property value can be changed by changing the value of shouldAuthenticate in bootconfig or by subclasssing SFLoginViewController. +/// Specify the visibility of the server picker option in the settings menu. +@property (nonatomic) BOOL showServerPicker NS_SWIFT_NAME(showsServerPicker); + +/** Specify the visibility of the back icon. This property value can be changed by changing the value of shouldAuthenticate in bootconfig or by subclasssing SFLoginViewController. */ @property (nonatomic,readonly) BOOL shouldDisplayBackButton NS_SWIFT_NAME(showsBackButton); -/** Specifiy a delegate for LoginViewController. */ +/** Specify a delegate for LoginViewController. */ @property (nonatomic, weak, nullable) id delegate; @property (nonatomic, copy, nullable) SFLoginViewControllerCreationBlock loginViewControllerCreationBlock; diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFSDKLoginViewControllerConfig.m b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFSDKLoginViewControllerConfig.m index 70e6bca3ab..bae0d826ec 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFSDKLoginViewControllerConfig.m +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/SFSDKLoginViewControllerConfig.m @@ -45,6 +45,7 @@ - (instancetype)init { self.navBarFont = nil; _showNavbar = YES; _showSettingsIcon = YES; + _showServerPicker = YES; } return self; }