Skip to content

Commit 5ff8ac4

Browse files
committed
Runtime app config (aka boot config) selection can now be asynchronous
- changed SFSDKAppConfigRuntimeSelectorBlock to take a callback parameter - renamed runtimeSelectedAppConfig to appConfigForLoginHost and it now takes a callback parameter - moved the call to appConfigForLoginHost in SFUserAccountManager to the async part of authenticateWithRequest (leaving the building of the auth request unchanged from before this PR) - updated tests / sample app
1 parent 0fc755c commit 5ff8ac4

File tree

7 files changed

+103
-142
lines changed

7 files changed

+103
-142
lines changed

libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,15 @@ NS_SWIFT_NAME(SalesforceManager)
320320
- (id <SFBiometricAuthenticationManager>)biometricAuthenticationManager;
321321

322322
/**
323-
* Returns app config (aka boot config) at runtime based on login host
323+
* Asynchronously retrieves the app config (aka boot config) for the specified login host.
324+
*
325+
* If an appConfigRuntimeSelectorBlock is set, it will be invoked to select the appropriate config.
326+
* If the block is not set or returns nil, the default appConfig will be returned.
324327
*
325328
* @param loginHost The selected login host
329+
* @param callback The callback invoked with the selected app config
326330
*/
327-
- (SFSDKAppConfig*)runtimeSelectedAppConfig:(nullable NSString *)loginHost NS_SWIFT_NAME(runtimeSelectedBootConfig( loginHost:));
331+
- (void)appConfigForLoginHost:(nullable NSString *)loginHost callback:(nonnull void (^)(SFSDKAppConfig * _Nullable))callback NS_SWIFT_NAME(bootConfig(forLoginHost:callback:));
328332

329333
/**
330334
* Creates the NativeLoginManager instance.

libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -914,13 +914,16 @@ - (void)biometricAuthenticationFlowDidComplete:(NSNotification *)notification {
914914
return [SFScreenLockManagerInternal shared];
915915
}
916916

917-
#pragma mark - Dynamic Boot Config
917+
#pragma mark - Runtime App Config (aka Bootconfig) Override
918918

919-
- (SFSDKAppConfig*) runtimeSelectedAppConfig:(nullable NSString *)loginHost {
919+
- (void) appConfigForLoginHost:(nullable NSString *)loginHost callback:(nonnull void (^)(SFSDKAppConfig * _Nullable))callback {
920920
if (self.appConfigRuntimeSelectorBlock) {
921-
return self.appConfigRuntimeSelectorBlock(loginHost);
921+
self.appConfigRuntimeSelectorBlock(loginHost, ^(SFSDKAppConfig *config) {
922+
// Fall back to default appConfig if the selector block returns nil
923+
callback(config ?: self.appConfig);
924+
});
922925
} else {
923-
return nil;
926+
callback(self.appConfig);
924927
}
925928
}
926929

libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -534,9 +534,7 @@ - (BOOL)authenticateWithCompletion:(SFUserAccountManagerSuccessCallbackBlock)com
534534
if (self.nativeLoginEnabled && !self.shouldFallbackToWebAuthentication) {
535535
request = [self nativeLoginAuthRequest];
536536
} else {
537-
// NB: Will be nil if application did not provide a appConfigRuntimeSelectorBlock
538-
SFSDKAppConfig* appConfig = [[SalesforceSDKManager sharedManager] runtimeSelectedAppConfig:loginHost];
539-
request = [self authRequestWithLoginHost:loginHost appConfig:appConfig];
537+
request = [self defaultAuthRequestWithLoginHost:loginHost];
540538
}
541539

542540
if (scene) {
@@ -554,17 +552,15 @@ - (BOOL)authenticateWithCompletion:(SFUserAccountManagerSuccessCallbackBlock)com
554552
codeVerifier:codeVerifier];
555553
}
556554

557-
-(SFSDKAuthRequest *)authRequestWithLoginHost:(nullable NSString *)loginHost
558-
appConfig:(nullable SFSDKAppConfig *)appConfig
559-
{
555+
-(SFSDKAuthRequest *)defaultAuthRequestWithLoginHost:(nullable NSString *)loginHost {
560556
SFSDKAuthRequest *request = [[SFSDKAuthRequest alloc] init];
561557
request.loginHost = loginHost != nil ? loginHost : self.loginHost;
562558
request.additionalOAuthParameterKeys = self.additionalOAuthParameterKeys;
563559
request.loginViewControllerConfig = self.loginViewControllerConfig;
564560
request.brandLoginPath = self.brandLoginPath;
565-
request.oauthClientId = appConfig != nil ? appConfig.remoteAccessConsumerKey : self.oauthClientId;
566-
request.oauthCompletionUrl = appConfig != nil ? appConfig.oauthRedirectURI : self.oauthCompletionUrl;
567-
request.scopes = appConfig != nil ? appConfig.oauthScopes : self.scopes;
561+
request.oauthClientId = self.oauthClientId;
562+
request.oauthCompletionUrl = self.oauthCompletionUrl;
563+
request.scopes = self.scopes;
568564
request.retryLoginAfterFailure = self.retryLoginAfterFailure;
569565
request.useBrowserAuth = self.useBrowserAuth;
570566
request.spAppLoginFlowSelectionAction = self.idpLoginFlowSelectionAction;
@@ -574,7 +570,7 @@ -(SFSDKAuthRequest *)authRequestWithLoginHost:(nullable NSString *)loginHost
574570
}
575571

576572
-(SFSDKAuthRequest *)defaultAuthRequest {
577-
return [self authRequestWithLoginHost:nil appConfig:nil];
573+
return [self defaultAuthRequestWithLoginHost:nil];
578574
}
579575

580576
-(SFSDKAuthRequest *)nativeLoginAuthRequest {
@@ -617,9 +613,16 @@ - (BOOL)authenticateWithRequest:(SFSDKAuthRequest *)request
617613

618614
dispatch_async(dispatch_get_main_queue(), ^{
619615
[SFSDKWebViewStateManager removeSessionForcefullyWithCompletionHandler:^{
620-
[authSession.oauthCoordinator authenticateWithCredentials:authSession.credentials];
616+
// Get app config for the login host. If appConfigRuntimeSelectorBlock is set,
617+
// it will be invoked to select the appropriate config. Otherwise, returns the default appConfig.
618+
[[SalesforceSDKManager sharedManager] appConfigForLoginHost:request.loginHost callback:^(SFSDKAppConfig* appConfig) {
619+
authSession.credentials.clientId = appConfig.remoteAccessConsumerKey;
620+
authSession.credentials.redirectUri = appConfig.oauthRedirectURI;
621+
authSession.credentials.scopes = [appConfig.oauthScopes allObjects];
622+
[authSession.oauthCoordinator authenticateWithCredentials:authSession.credentials];
623+
}];
621624
}];
622-
625+
623626
});
624627
return self.authSessions[sceneId].isAuthenticating;
625628
}

libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Util/SalesforceSDKCoreDefines.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ typedef UIViewController<SFSDKUserSelectionView>*_Nonnull (^SFIDPUserSelectionBl
5757

5858
/**
5959
Block to select an app config at runtime based on the login host.
60+
The block takes a login host and a callback. The callback should be invoked with the selected app config.
6061
*/
61-
typedef SFSDKAppConfig* _Nullable (^SFSDKAppConfigRuntimeSelectorBlock)(NSString * _Nonnull loginHost) NS_SWIFT_NAME(BootConfigRuntimeSelector);
62+
typedef void (^SFSDKAppConfigRuntimeSelectorBlock)(NSString * _Nonnull loginHost, void (^_Nonnull callback)(SFSDKAppConfig * _Nullable)) NS_SWIFT_NAME(BootConfigRuntimeSelector);
6263

6364
NS_ASSUME_NONNULL_END

libs/SalesforceSDKCore/SalesforceSDKCoreTests/SFUserAccountManagerTests.m

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -664,96 +664,4 @@ - (void)testUserAccountEncoding {
664664
XCTAssertEqual(userIn.accessRestrictions, userOut.accessRestrictions, @"accessRestrictions mismatch");
665665
}
666666

667-
#pragma mark - authRequestWithLoginHost Tests
668-
669-
- (void)testAuthRequestWithLoginHostDefaults {
670-
// Setup default values
671-
NSString *expectedLoginHost = @"https://login.salesforce.com";
672-
NSString *expectedClientId = @"testClientId";
673-
NSString *expectedRedirectUri = @"testapp://oauth/done";
674-
NSSet *expectedScopes = [NSSet setWithObjects:@"api", @"web", @"refresh_token", nil];
675-
676-
self.uam.loginHost = expectedLoginHost;
677-
self.uam.oauthClientId = expectedClientId;
678-
self.uam.oauthCompletionUrl = expectedRedirectUri;
679-
self.uam.scopes = expectedScopes;
680-
681-
// Call with nil loginHost and nil appConfig (should use defaults)
682-
SFSDKAuthRequest *request = [self.uam authRequestWithLoginHost:nil appConfig:nil];
683-
684-
XCTAssertNotNil(request, @"Auth request should not be nil");
685-
XCTAssertEqualObjects(request.loginHost, expectedLoginHost, @"Login host should match default");
686-
XCTAssertEqualObjects(request.oauthClientId, expectedClientId, @"Client ID should match default");
687-
XCTAssertEqualObjects(request.oauthCompletionUrl, expectedRedirectUri, @"Redirect URI should match default");
688-
XCTAssertEqualObjects(request.scopes, expectedScopes, @"Scopes should match default");
689-
}
690-
691-
- (void)testAuthRequestWithLoginHostCustomHost {
692-
// Setup default values
693-
NSString *defaultLoginHost = @"https://login.salesforce.com";
694-
NSString *customLoginHost = @"https://test.salesforce.com";
695-
NSString *expectedClientId = @"testClientId";
696-
697-
self.uam.loginHost = defaultLoginHost;
698-
self.uam.oauthClientId = expectedClientId;
699-
700-
// Call with custom loginHost
701-
SFSDKAuthRequest *request = [self.uam authRequestWithLoginHost:customLoginHost appConfig:nil];
702-
703-
XCTAssertNotNil(request, @"Auth request should not be nil");
704-
XCTAssertEqualObjects(request.loginHost, customLoginHost, @"Login host should be custom value, not default");
705-
XCTAssertEqualObjects(request.oauthClientId, expectedClientId, @"Client ID should match default");
706-
}
707-
708-
- (void)testAuthRequestWithLoginHostWithAppConfig {
709-
// Setup default values
710-
NSString *defaultLoginHost = @"https://login.salesforce.com";
711-
NSString *defaultClientId = @"defaultClientId";
712-
NSString *defaultRedirectUri = @"defaultapp://oauth/done";
713-
NSSet *defaultScopes = [NSSet setWithObjects:@"api", @"web", nil];
714-
715-
self.uam.loginHost = defaultLoginHost;
716-
self.uam.oauthClientId = defaultClientId;
717-
self.uam.oauthCompletionUrl = defaultRedirectUri;
718-
self.uam.scopes = defaultScopes;
719-
720-
// Create app config with different values
721-
NSString *appConfigClientId = @"appConfigClientId";
722-
NSString *appConfigRedirectUri = @"appconfig://oauth/done";
723-
NSSet *appConfigScopes = [NSSet setWithObjects:@"id", @"api", @"refresh_token", nil];
724-
725-
NSDictionary *configDict = @{
726-
@"remoteAccessConsumerKey": appConfigClientId,
727-
@"oauthRedirectURI": appConfigRedirectUri,
728-
@"oauthScopes": [appConfigScopes allObjects],
729-
@"shouldAuthenticate": @YES
730-
};
731-
SFSDKAppConfig *appConfig = [[SFSDKAppConfig alloc] initWithDict:configDict];
732-
733-
// Call with appConfig
734-
SFSDKAuthRequest *request = [self.uam authRequestWithLoginHost:nil appConfig:appConfig];
735-
736-
XCTAssertNotNil(request, @"Auth request should not be nil");
737-
XCTAssertEqualObjects(request.loginHost, defaultLoginHost, @"Login host should match default");
738-
XCTAssertEqualObjects(request.oauthClientId, appConfigClientId, @"Client ID should come from appConfig");
739-
XCTAssertEqualObjects(request.oauthCompletionUrl, appConfigRedirectUri, @"Redirect URI should come from appConfig");
740-
XCTAssertEqualObjects(request.scopes, appConfigScopes, @"Scopes should come from appConfig");
741-
}
742-
743-
- (void)testDefaultAuthRequestUsesAuthRequestWithLoginHost {
744-
// Setup default values
745-
NSString *expectedLoginHost = @"https://login.salesforce.com";
746-
NSString *expectedClientId = @"testClientId";
747-
748-
self.uam.loginHost = expectedLoginHost;
749-
self.uam.oauthClientId = expectedClientId;
750-
751-
// defaultAuthRequest should call authRequestWithLoginHost:nil appConfig:nil
752-
SFSDKAuthRequest *request = [self.uam defaultAuthRequest];
753-
754-
XCTAssertNotNil(request, @"Default auth request should not be nil");
755-
XCTAssertEqualObjects(request.loginHost, expectedLoginHost, @"Login host should match default");
756-
XCTAssertEqualObjects(request.oauthClientId, expectedClientId, @"Client ID should match default");
757-
}
758-
759667
@end

libs/SalesforceSDKCore/SalesforceSDKCoreTests/SalesforceSDKManagerTests.m

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -487,20 +487,40 @@ - (void)compareAppNames:(NSString *)expectedAppName
487487

488488
#pragma mark - Runtime Selected App Config Tests
489489

490-
- (void)testRuntimeSelectedAppConfigReturnsNilWhenBlockNotSet {
490+
- (void)verifyAppConfigForLoginHost:(NSString *)loginHost
491+
description:(NSString *)description
492+
assertions:(void (^)(SFSDKAppConfig *config))assertions {
493+
XCTestExpectation *expectation = [self expectationWithDescription:description];
494+
[[SalesforceSDKManager sharedManager] appConfigForLoginHost:loginHost callback:^(SFSDKAppConfig *config) {
495+
assertions(config);
496+
[expectation fulfill];
497+
}];
498+
[self waitForExpectations:@[expectation] timeout:1.0];
499+
}
500+
501+
- (void)testAppConfigForLoginHostReturnsDefaultWhenBlockNotSet {
491502
// Clear any existing block
492503
[SalesforceSDKManager sharedManager].appConfigRuntimeSelectorBlock = nil;
493504

494-
// Call with nil loginHost
495-
SFSDKAppConfig *config = [[SalesforceSDKManager sharedManager] runtimeSelectedAppConfig:nil];
496-
XCTAssertNil(config, @"Should return nil when no selector block is set");
505+
// Get the default app config for comparison
506+
SFSDKAppConfig *defaultConfig = [SalesforceSDKManager sharedManager].appConfig;
507+
508+
// Test with nil loginHost - should return default config
509+
[self verifyAppConfigForLoginHost:nil
510+
description:@"Callback should be called with default config"
511+
assertions:^(SFSDKAppConfig *config) {
512+
XCTAssertEqual(config, defaultConfig, @"Should return default appConfig when no selector block is set");
513+
}];
497514

498-
// Call with a loginHost
499-
config = [[SalesforceSDKManager sharedManager] runtimeSelectedAppConfig:@"https://test.salesforce.com"];
500-
XCTAssertNil(config, @"Should return nil when no selector block is set, regardless of loginHost");
515+
// Test with a loginHost - should still return default config
516+
[self verifyAppConfigForLoginHost:@"https://test.salesforce.com"
517+
description:@"Callback should be called with default config"
518+
assertions:^(SFSDKAppConfig *config) {
519+
XCTAssertEqual(config, defaultConfig, @"Should return default appConfig when no selector block is set, regardless of loginHost");
520+
}];
501521
}
502522

503-
- (void)testRuntimeSelectedAppConfigWithDifferentLoginHosts {
523+
- (void)testAppConfigForLoginHostWithDifferentLoginHosts {
504524
NSString *loginHost1 = @"https://login.salesforce.com";
505525
NSString *loginHost2 = @"https://test.salesforce.com";
506526

@@ -518,43 +538,65 @@ - (void)testRuntimeSelectedAppConfigWithDifferentLoginHosts {
518538
};
519539
SFSDKAppConfig *config2 = [[SFSDKAppConfig alloc] initWithDict:config2Dict];
520540

541+
// Get the default app config for comparison
542+
SFSDKAppConfig *defaultConfig = [SalesforceSDKManager sharedManager].appConfig;
543+
521544
// Set the selector block to return different configs based on loginHost
522-
[SalesforceSDKManager sharedManager].appConfigRuntimeSelectorBlock = ^SFSDKAppConfig *(NSString *loginHost) {
545+
[SalesforceSDKManager sharedManager].appConfigRuntimeSelectorBlock = ^(NSString *loginHost, void (^callback)(SFSDKAppConfig *)) {
523546
if ([loginHost isEqualToString:loginHost1]) {
524-
return config1;
547+
callback(config1);
525548
} else if ([loginHost isEqualToString:loginHost2]) {
526-
return config2;
549+
callback(config2);
550+
} else {
551+
callback(nil);
527552
}
528-
return nil;
529553
};
530554

531555
// Test first loginHost
532-
SFSDKAppConfig *result1 = [[SalesforceSDKManager sharedManager] runtimeSelectedAppConfig:loginHost1];
533-
XCTAssertNotNil(result1, @"Should return config for loginHost1");
534-
XCTAssertEqual(result1, config1, @"Should return config1 for loginHost1");
535-
XCTAssertEqualObjects(result1.remoteAccessConsumerKey, @"clientId1", @"Should have correct client ID for config1");
556+
[self verifyAppConfigForLoginHost:loginHost1
557+
description:@"First callback should be called"
558+
assertions:^(SFSDKAppConfig *result1) {
559+
XCTAssertNotNil(result1, @"Should return config for loginHost1");
560+
XCTAssertEqual(result1, config1, @"Should return config1 for loginHost1");
561+
XCTAssertEqualObjects(result1.remoteAccessConsumerKey, @"clientId1", @"Should have correct client ID for config1");
562+
}];
536563

537564
// Test second loginHost
538-
SFSDKAppConfig *result2 = [[SalesforceSDKManager sharedManager] runtimeSelectedAppConfig:loginHost2];
539-
XCTAssertNotNil(result2, @"Should return config for loginHost2");
540-
XCTAssertEqual(result2, config2, @"Should return config2 for loginHost2");
541-
XCTAssertEqualObjects(result2.remoteAccessConsumerKey, @"clientId2", @"Should have correct client ID for config2");
565+
[self verifyAppConfigForLoginHost:loginHost2
566+
description:@"Second callback should be called"
567+
assertions:^(SFSDKAppConfig *result2) {
568+
XCTAssertNotNil(result2, @"Should return config for loginHost2");
569+
XCTAssertEqual(result2, config2, @"Should return config2 for loginHost2");
570+
XCTAssertEqualObjects(result2.remoteAccessConsumerKey, @"clientId2", @"Should have correct client ID for config2");
571+
}];
572+
573+
// Test with nil loginHost - should return default config
574+
[self verifyAppConfigForLoginHost:nil
575+
description:@"Callback should be called with default config"
576+
assertions:^(SFSDKAppConfig *config) {
577+
XCTAssertEqual(config, defaultConfig, @"Should return default appConfig when nil loginHost is passed");
578+
}];
542579
}
543580

544-
- (void)testRuntimeSelectedAppConfigBlockReturnsNil {
581+
- (void)testAppConfigForLoginHostReturnsDefaultWhenBlockReturnsNil {
545582
__block BOOL blockWasCalled = NO;
546583

547-
// Set the selector block to return nil
548-
[SalesforceSDKManager sharedManager].appConfigRuntimeSelectorBlock = ^SFSDKAppConfig *(NSString *loginHost) {
584+
// Get the default app config for comparison
585+
SFSDKAppConfig *defaultConfig = [SalesforceSDKManager sharedManager].appConfig;
586+
587+
// Set the selector block to return nil via callback
588+
[SalesforceSDKManager sharedManager].appConfigRuntimeSelectorBlock = ^(NSString *loginHost, void (^callback)(SFSDKAppConfig *)) {
549589
blockWasCalled = YES;
550-
return nil;
590+
callback(nil);
551591
};
552592

553-
// Call the method
554-
SFSDKAppConfig *config = [[SalesforceSDKManager sharedManager] runtimeSelectedAppConfig:@"https://test.salesforce.com"];
555-
556-
XCTAssertTrue(blockWasCalled, @"Block should have been called");
557-
XCTAssertNil(config, @"Should return nil when block returns nil");
593+
// Call the method - should fall back to default config even though block returns nil
594+
[self verifyAppConfigForLoginHost:@"https://test.salesforce.com"
595+
description:@"Callback should be called"
596+
assertions:^(SFSDKAppConfig *config) {
597+
XCTAssertTrue(blockWasCalled, @"Block should have been called");
598+
XCTAssertEqual(config, defaultConfig, @"Should return default appConfig when block returns nil");
599+
}];
558600
}
559601

560602
@end

native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/ConfigPickerViewController.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ struct ConfigPickerView: View {
160160
private func handleDynamicBootconfig() {
161161
isLoading = true
162162

163-
SalesforceManager.shared.bootConfigRuntimeSelector = { _ in
163+
SalesforceManager.shared.bootConfigRuntimeSelector = { _, callback in
164164
// Create dynamic BootConfig from user-entered values
165165
// Parse scopes from space-separated string
166166
let scopesArray = self.dynamicScopes
@@ -179,7 +179,7 @@ struct ConfigPickerView: View {
179179
configDict["oauthScopes"] = scopesArray
180180
}
181181

182-
return BootConfig(configDict)
182+
callback(BootConfig(configDict))
183183
}
184184

185185
// Proceed with login

0 commit comments

Comments
 (0)