Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions libs/SalesforceSDKCore/SalesforceSDKCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
009D77521CA4A92200D5183A /* SFPushNotificationManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 009D77511CA4A92200D5183A /* SFPushNotificationManagerTests.m */; };
010A9B551CC1A131002AF4D3 /* SFCryptoStreamTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 010A9B511CC1A131002AF4D3 /* SFCryptoStreamTestUtils.h */; };
010A9B591CC1A147002AF4D3 /* SFCryptoStreamTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 010A9B521CC1A131002AF4D3 /* SFCryptoStreamTestUtils.m */; };
092F3EB14B5A4FFEA77B21BC /* SFSDKOAuth2RefreshInstanceUrlTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3168ED77C1354C30BA9DB766 /* SFSDKOAuth2RefreshInstanceUrlTests.m */; };
1A31073F5F374B9EB1162F2E /* SFOAuthCoordinatorLightningURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399A11508BCB47F490DFB724 /* SFOAuthCoordinatorLightningURLTests.swift */; };
230834842DF7838200C7CBF7 /* URLSessionTask+RetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 230834832DF7837400C7CBF7 /* URLSessionTask+RetryPolicy.swift */; };
230834862DF8938D00C7CBF7 /* URLSessionTask+RetryPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 230834852DF8938D00C7CBF7 /* URLSessionTask+RetryPolicyTests.swift */; };
Expand Down Expand Up @@ -95,7 +96,6 @@
4F9E05322DD6A08000548985 /* SFSDKOAuthTokenEndpointResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F9E052C2DD6A06F00548985 /* SFSDKOAuthTokenEndpointResponseTests.m */; };
4F9E05342DD7BE1500548985 /* SFOAuthCredentialsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F9E05332DD7BE0A00548985 /* SFOAuthCredentialsTests.m */; };
4FA1B2C32F0E000000000001 /* LoginForAdminTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA1B2C32F0E000000000002 /* LoginForAdminTests.swift */; };
092F3EB14B5A4FFEA77B21BC /* SFSDKOAuth2RefreshInstanceUrlTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3168ED77C1354C30BA9DB766 /* SFSDKOAuth2RefreshInstanceUrlTests.m */; };
4FAUTHFLOW001234567890ABC /* AuthFlowTypesViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FAUTHFLOW112345678901ABC /* AuthFlowTypesViewTests.swift */; };
4FBOOTCP001234567890ABCD /* LoginOptionsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FBOOTCP112345678901ABCD /* LoginOptionsViewControllerTests.swift */; };
4FDEVINFO001234567890ABCD /* DevInfoViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDEVINFO112345678901ABCD /* DevInfoViewControllerTests.swift */; };
Expand Down Expand Up @@ -153,6 +153,9 @@
69848CB82364035300893E57 /* SFSDKEncryptedPushNotificationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 69848CB72364035300893E57 /* SFSDKEncryptedPushNotificationTests.m */; };
69848CBD2364063E00893E57 /* SFSDKPushNotificationDataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 69848CBC2364063E00893E57 /* SFSDKPushNotificationDataProvider.m */; };
6990CBC029F5AF56004A5F8D /* SFSDKIDPAuthCodeLoginRequestCommandTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6990CBBF29F5AF56004A5F8D /* SFSDKIDPAuthCodeLoginRequestCommandTest.m */; };
699202FD2FEF11D70032ECD7 /* SFSDKTokenRefreshCoordinatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 699202FC2FEF11D70032ECD7 /* SFSDKTokenRefreshCoordinatorTests.m */; };
699203052FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = 699203042FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.m */; };
699203062FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = 699203032FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.h */; };
69B36F492D30C34F00EFF84A /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B36F482D30C34F00EFF84A /* ColorExtension.swift */; };
69BDD7E926F16C9000C26D77 /* SFSDKSalesforceSDKUpgradeManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 69BDD7E226F16C9000C26D77 /* SFSDKSalesforceSDKUpgradeManager.h */; };
69BDD7EA26F16C9000C26D77 /* SFSDKSalesforceSDKUpgradeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 69BDD7E826F16C9000C26D77 /* SFSDKSalesforceSDKUpgradeManager.m */; };
Expand Down Expand Up @@ -572,6 +575,7 @@
23EED8902E2ACF3100646B10 /* MockNavigationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNavigationAction.swift; sourceTree = "<group>"; };
23F200AB2E551C890091C5F5 /* ActionTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ActionTypeTests.swift; path = SalesforceSDKCoreTests/ActionTypeTests.swift; sourceTree = SOURCE_ROOT; };
23F200AD2E551C890091C5F5 /* BootconfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BootconfigTests.swift; path = SalesforceSDKCoreTests/BootconfigTests.swift; sourceTree = SOURCE_ROOT; };
3168ED77C1354C30BA9DB766 /* SFSDKOAuth2RefreshInstanceUrlTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SFSDKOAuth2RefreshInstanceUrlTests.m; path = ../SalesforceSDKCoreTests/SFSDKOAuth2RefreshInstanceUrlTests.m; sourceTree = "<group>"; };
399A11508BCB47F490DFB724 /* SFOAuthCoordinatorLightningURLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SFOAuthCoordinatorLightningURLTests.swift; path = ../SalesforceSDKCoreTests/SFOAuthCoordinatorLightningURLTests.swift; sourceTree = "<group>"; };
444B95CF1E83251900908C61 /* UIColor+SFColorsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIColor+SFColorsTests.m"; path = "SalesforceSDKCoreTests/UIColor+SFColorsTests.m"; sourceTree = SOURCE_ROOT; };
4F06AF5D1C49A16A00F70798 /* NSURL+SFStringUtilsTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSURL+SFStringUtilsTests.h"; path = "SalesforceSDKCoreTests/NSURL+SFStringUtilsTests.h"; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -685,7 +689,6 @@
4F9E052C2DD6A06F00548985 /* SFSDKOAuthTokenEndpointResponseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SFSDKOAuthTokenEndpointResponseTests.m; path = ../SalesforceSDKCoreTests/SFSDKOAuthTokenEndpointResponseTests.m; sourceTree = "<group>"; };
4F9E05332DD7BE0A00548985 /* SFOAuthCredentialsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SFOAuthCredentialsTests.m; path = ../SalesforceSDKCoreTests/SFOAuthCredentialsTests.m; sourceTree = "<group>"; };
4FA1B2C32F0E000000000002 /* LoginForAdminTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginForAdminTests.swift; sourceTree = "<group>"; };
3168ED77C1354C30BA9DB766 /* SFSDKOAuth2RefreshInstanceUrlTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SFSDKOAuth2RefreshInstanceUrlTests.m; path = ../SalesforceSDKCoreTests/SFSDKOAuth2RefreshInstanceUrlTests.m; sourceTree = "<group>"; };
4FAUTHFLOW112345678901ABC /* AuthFlowTypesViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthFlowTypesViewTests.swift; path = SalesforceSDKCoreTests/AuthFlowTypesViewTests.swift; sourceTree = SOURCE_ROOT; };
4FBOOTCP112345678901ABCD /* LoginOptionsViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginOptionsViewControllerTests.swift; path = SalesforceSDKCoreTests/LoginOptionsViewControllerTests.swift; sourceTree = SOURCE_ROOT; };
4FDEVINFO112345678901ABCD /* DevInfoViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DevInfoViewControllerTests.swift; path = SalesforceSDKCoreTests/DevInfoViewControllerTests.swift; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -759,6 +762,9 @@
69848CBB2364063E00893E57 /* SFSDKPushNotificationDataProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SFSDKPushNotificationDataProvider.h; path = SalesforceSDKCoreTests/SFSDKPushNotificationDataProvider.h; sourceTree = SOURCE_ROOT; };
69848CBC2364063E00893E57 /* SFSDKPushNotificationDataProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SFSDKPushNotificationDataProvider.m; path = SalesforceSDKCoreTests/SFSDKPushNotificationDataProvider.m; sourceTree = SOURCE_ROOT; };
6990CBBF29F5AF56004A5F8D /* SFSDKIDPAuthCodeLoginRequestCommandTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SFSDKIDPAuthCodeLoginRequestCommandTest.m; path = SalesforceSDKCoreTests/SFSDKIDPAuthCodeLoginRequestCommandTest.m; sourceTree = SOURCE_ROOT; };
699202FC2FEF11D70032ECD7 /* SFSDKTokenRefreshCoordinatorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SFSDKTokenRefreshCoordinatorTests.m; sourceTree = "<group>"; };
699203032FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SFSDKTokenRefreshCoordinator.h; sourceTree = "<group>"; };
699203042FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SFSDKTokenRefreshCoordinator.m; sourceTree = "<group>"; };
69B2B4F2261EA6F500AB86C2 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk/System/Library/Frameworks/CoreTelephony.framework; sourceTree = DEVELOPER_DIR; };
69B36F482D30C34F00EFF84A /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
69BDD7E226F16C9000C26D77 /* SFSDKSalesforceSDKUpgradeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SFSDKSalesforceSDKUpgradeManager.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1034,6 +1040,7 @@
4F7EB3F61BFFC84700768720 /* SalesforceSDKCoreTests */ = {
isa = PBXGroup;
children = (
699202FC2FEF11D70032ECD7 /* SFSDKTokenRefreshCoordinatorTests.m */,
AA9154472F59E3C900A0A41C /* SFRestAPIDataTaskRaceTests.m */,
23F200AB2E551C890091C5F5 /* ActionTypeTests.swift */,
4FAUTHFLOW112345678901ABC /* AuthFlowTypesViewTests.swift */,
Expand Down Expand Up @@ -1192,6 +1199,8 @@
4F96FCC41BFD32130022F021 /* OAuth */ = {
isa = PBXGroup;
children = (
699203032FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.h */,
699203042FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.m */,
4F5A494F2E98711600C89DDD /* ScopeParser.swift */,
23D96B6E2E145AC20004B06A /* DomainDiscoveryCoordinator.swift */,
4F8A3B002CEC202F00ECDC76 /* JwtAccessToken.swift */,
Expand Down Expand Up @@ -1928,6 +1937,7 @@
CE4CE3421C0E5252009F6029 /* SFOAuthCoordinator+Internal.h in Headers */,
CED452BC1D808D0C009266EB /* SFRestAPI+QueryBuilder.h in Headers */,
CE4CE3301C0E523B009F6029 /* SFSDKAppConfig.h in Headers */,
699203062FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.h in Headers */,
CE675A371E0B2CC6002DBF5A /* SFSDKSoslReturningBuilder.h in Headers */,
4F06AF861C49A16A00F70798 /* SFTestSDKManagerFlow.h in Headers */,
CE4CE3691C0E526A009F6029 /* SFDefaultUserManagementViewController+Internal.h in Headers */,
Expand Down Expand Up @@ -2230,6 +2240,7 @@
FDD7D7A1232039D000F5FB2D /* SFUserAccountPhotoTests.m in Sources */,
444B95D01E83251900908C61 /* UIColor+SFColorsTests.m in Sources */,
009D77521CA4A92200D5183A /* SFPushNotificationManagerTests.m in Sources */,
699202FD2FEF11D70032ECD7 /* SFSDKTokenRefreshCoordinatorTests.m in Sources */,
696E91472AD0A14A00205884 /* BiometricAuthenticationManagerTests.swift in Sources */,
B7E8A2AF1E77062E007C0D92 /* SFUserAccountManagerPersisterTests.m in Sources */,
23EDDF022DE0F9EF0024AD39 /* URLRequest+RestRequestTests.swift in Sources */,
Expand Down Expand Up @@ -2318,6 +2329,7 @@
CE4CE3261C0E523B009F6029 /* UIScreen+SFAdditions.m in Sources */,
B7C273F01F7EEFB700CE539D /* SFSDKIDPConstants.m in Sources */,
D3D675DB2D39EF01008E468E /* ChatGenerationsRequestBody.swift in Sources */,
699203052FEF146F0032ECD7 /* SFSDKTokenRefreshCoordinator.m in Sources */,
D3D675DC2D39EF01008E468E /* EmbeddingsRequestBody.swift in Sources */,
D3D675DD2D39EF01008E468E /* ChatGenerationsResponseBody.swift in Sources */,
D3D675DE2D39EF01008E468E /* GenerationsResponseBody.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ extension UserAccountManager: UserAccountManaging {
}
}

/// Kick off the login process for jwt token with credentials previously configured.
/// Refresh the session for the given credentials.
/// - Parameters:
/// - credentials: the OAuthCredentials object
/// - credentials: The credentials to refresh.
/// - completionBlock: completion block to invoke with a success tuple (UserAccount, AuthInfo) or UserAccountManagerError for failure wrapped in a Result type.
public func refresh(credentials: OAuthCredentials, _ completionBlock: @escaping (Result<(UserAccount, AuthInfo), UserAccountManagerError>) -> Void) -> Bool {
return __refreshCredentials(credentials, completion: { (authInfo, userAccount) in
Expand All @@ -83,6 +83,18 @@ extension UserAccountManager: UserAccountManaging {
}
}

/// Refresh the session for the given credentials.
/// - Parameter credentials: The credentials to refresh.
/// - Returns: A tuple of the updated `UserAccount` and `AuthInfo` on success.
/// - Throws: `UserAccountManagerError.refreshFailed` if the refresh fails.
public func refresh(credentials: OAuthCredentials) async throws -> (UserAccount, AuthInfo) {
Comment thread
wmathurin marked this conversation as resolved.
try await withCheckedThrowingContinuation { continuation in
_ = self.refresh(credentials: credentials) { result in
continuation.resume(with: result.mapError { $0 })
}
}
}

/// Switch to a new user. Kicks off the login flow. Once complete switches to a new user on success else does not change the current user.
/// - Parameter completionBlock: completion block to invoke with a UserAccount on success or UserAccountManagerError on failure wrapped in a Result type.
public func switchToNewUserAccount(_ completionBlock: @escaping (Result<UserAccount, UserAccountManagerError>) -> Void) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
#import "SFSDKAuthSession.h"
#import "SFOAuthInfo.h"
@class SFIdentityData;
@class SFOAuthSessionRefresher;
NS_ASSUME_NONNULL_BEGIN
/**
* Internal interface for the SFIdentityCoordinator.
Expand All @@ -44,10 +43,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, strong, nullable) NSURLSession *session;

/**
* The OAuth sesssion refresher to use if the identity request fails with expired credentials.
*/
@property (nonatomic, strong, nullable) SFOAuthSessionRefresher *oauthSessionRefresher;

/**
* Dictionary mapping error codes to their respective types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
#import <SalesforceSDKCommon/SFJsonUtils.h>
#import "SFIdentityCoordinator+Internal.h"
#import "SFOAuthCredentials.h"
#import "SFOAuthSessionRefresher.h"
#import "SFSDKTokenRefreshCoordinator.h"
#import "SFUserAccountManager.h"
#import "SFNetwork.h"
#import "SFSDKAuthSession.h"
Expand Down Expand Up @@ -66,7 +66,6 @@ @implementation SFIdentityCoordinator
@synthesize timeout = _timeout;
@synthesize retrievingData = _retrievingData;
@synthesize session = _session;
@synthesize oauthSessionRefresher = _oauthSessionRefresher;

#pragma mark - init / dealloc

Expand Down Expand Up @@ -171,8 +170,7 @@ - (void)sendRequest
if (statusCode == 401 || statusCode == 403) {
// The session timed out. Identity service tends to send 403s for session timeouts. Try to refresh.
[SFSDKCoreLogger i:[self class] format:@"%@: Identity request failed due to expired credentials. Attempting to refresh credentials.", NSStringFromSelector(_cmd)];
strongSelf.oauthSessionRefresher = [[SFOAuthSessionRefresher alloc] initWithCredentials:strongSelf.credentials];
[strongSelf.oauthSessionRefresher refreshSessionWithCompletion:^(SFOAuthCredentials *updatedCredentials) {
[[SFSDKTokenRefreshCoordinator sharedInstance] refreshSessionForCredentials:strongSelf.credentials completion:^(SFOAuthCredentials *updatedCredentials) {
Comment thread
wmathurin marked this conversation as resolved.
[SFSDKCoreLogger d:[strongSelf class] format:@"%@: Credentials refresh successful. Replaying original identity request.", NSStringFromSelector(_cmd)];
strongSelf.credentials = updatedCredentials;
dispatch_async(dispatch_get_main_queue(), ^{
Expand Down Expand Up @@ -221,15 +219,13 @@ - (void)dealloc
self.session = nil;
self.credentials = nil;
self.idData = nil;
self.oauthSessionRefresher = nil;
}

- (void)cleanupData
{
[SFNetwork removeSharedInstanceForIdentifier:self.networkIdentifier];
self.networkIdentifier = nil;
self.session = nil;
self.oauthSessionRefresher = nil;
self.retrievingData = NO;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#import "SalesforceSDKConstants.h"

SFSDK_USE_DEPRECATED_BEGIN
#import "SFOAuthSessionRefresher.h"
#import "SFOAuthCoordinator.h"

Expand All @@ -32,3 +35,5 @@
@property (nonatomic, copy) void (^errorBlock)(NSError *);

@end

SFSDK_USE_DEPRECATED_END
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/

#import <Foundation/Foundation.h>
#import <SalesforceSDKCore/SalesforceSDKConstants.h>

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -37,20 +38,23 @@ typedef NS_ENUM(NSUInteger, SFOAuthSessionRefreshErrorCode) {

/** This class refreshes stale OAuth sessions, if possible.
*/
SFSDK_DEPRECATED(14.0, 15.0, "SFOAuthSessionRefresher bypasses the centralized token refresh coordinator and will be removed from the public API.");
@interface SFOAuthSessionRefresher : NSObject

/**
* Initializes the object with the given credentials.
* @param credentials The OAuth credentials used to refresh the session.
*/
- (instancetype)initWithCredentials:(nullable SFOAuthCredentials *)credentials NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCredentials:(nullable SFOAuthCredentials *)credentials NS_DESIGNATED_INITIALIZER
SFSDK_DEPRECATED(14.0, 15.0, "Use UserAccountManager.refresh(credentials:) instead.");

/**
* Refreshes the expired session, with the given completion and error handler blocks.
* @param completionBlock Called once the session has been refreshed.
* @param errorBlock Called if there was an error refreshing the session.
*/
- (void)refreshSessionWithCompletion:(void (^) (SFOAuthCredentials *))completionBlock error:(void (^) (NSError *))errorBlock;
- (void)refreshSessionWithCompletion:(void (^) (SFOAuthCredentials *))completionBlock error:(void (^) (NSError *))errorBlock
SFSDK_DEPRECATED(14.0, 15.0, "Use UserAccountManager.refresh(credentials:) instead.");

@end

Expand Down
Loading
Loading