Skip to content

Commit 71e0d67

Browse files
[Auth] Add Swift 6 conformance to FirebaseAuth/Sources/Swift/SystemService/ directory (#14839)
Co-authored-by: Morgan Chen <morganchen12@gmail.com>
1 parent 1deb75c commit 71e0d67

File tree

8 files changed

+73
-47
lines changed

8 files changed

+73
-47
lines changed

FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
#if !os(macOS)
1616
import Foundation
1717

18+
// TODO(ncooke3): I believe this could be made a struct now.
19+
1820
/// A data structure for an APNs token.
19-
class AuthAPNSToken {
21+
final class AuthAPNSToken: Sendable {
2022
let data: Data
2123
let type: AuthAPNSTokenType
2224

@@ -30,13 +32,13 @@
3032
}
3133

3234
/// The uppercase hexadecimal string form of the APNs token data.
33-
lazy var string: String = {
35+
var string: String {
3436
let byteArray = [UInt8](data)
3537
var s = ""
3638
for byte in byteArray {
3739
s.append(String(format: "%02X", byte))
3840
}
3941
return s
40-
}()
42+
}
4143
}
4244
#endif

FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,38 +24,41 @@
2424

2525
// Protocol to help with unit tests.
2626
protocol AuthAPNSTokenApplication {
27-
func registerForRemoteNotifications()
27+
@MainActor func registerForRemoteNotifications()
2828
}
2929

3030
extension UIApplication: AuthAPNSTokenApplication {}
3131

3232
/// A class to manage APNs token in memory.
3333
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
34-
class AuthAPNSTokenManager {
34+
class AuthAPNSTokenManager: @unchecked Sendable /* TODO: sendable */ {
3535
/// The timeout for registering for remote notification.
3636
///
3737
/// Only tests should access this property.
38-
var timeout: TimeInterval = 5
38+
let timeout: TimeInterval
3939

4040
/// Initializes the instance.
4141
/// - Parameter application: The `UIApplication` to request the token from.
4242
/// - Returns: The initialized instance.
43-
init(withApplication application: AuthAPNSTokenApplication) {
43+
init(withApplication application: sending AuthAPNSTokenApplication, timeout: TimeInterval = 5) {
4444
self.application = application
45+
self.timeout = timeout
4546
}
4647

4748
/// Attempts to get the APNs token.
4849
/// - Parameter callback: The block to be called either immediately or in future, either when a
4950
/// token becomes available, or when timeout occurs, whichever happens earlier.
5051
///
5152
/// This function is internal to make visible for tests.
52-
func getTokenInternal(callback: @escaping (Result<AuthAPNSToken, Error>) -> Void) {
53+
func getTokenInternal(callback: @escaping @Sendable (Result<AuthAPNSToken, Error>) -> Void) {
5354
if let token = tokenStore {
5455
callback(.success(token))
5556
return
5657
}
5758
if pendingCallbacks.count > 0 {
5859
pendingCallbacks.append(callback)
60+
// TODO(ncooke3): This is likely a bug in that the async wrapper method
61+
// cannot make forward progress.
5962
return
6063
}
6164
pendingCallbacks = [callback]

FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenType.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
///
2020
/// This enum is available on iOS, macOS Catalyst, tvOS, and watchOS only.
2121

22-
@objc(FIRAuthAPNSTokenType) public enum AuthAPNSTokenType: Int {
22+
@objc(FIRAuthAPNSTokenType) public enum AuthAPNSTokenType: Int, Sendable {
2323
/// Unknown token type.
2424
///
2525
/// The actual token type will be detected from the provisioning profile in the app's bundle.

FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import Foundation
1616

1717
/// A class represents a credential that proves the identity of the app.
1818
@objc(FIRAuthAppCredential) // objc Needed for decoding old versions
19-
class AuthAppCredential: NSObject, NSSecureCoding {
19+
final class AuthAppCredential: NSObject, NSSecureCoding, Sendable {
2020
/// The server acknowledgement of receiving client's claim of identity.
21-
var receipt: String
21+
let receipt: String
2222

2323
/// The secret that the client received from server via a trusted channel, if ever.
24-
var secret: String?
24+
let secret: String?
2525

2626
/// Initializes the instance.
2727
/// - Parameter receipt: The server acknowledgement of receiving client's claim of identity.

FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import Foundation
15+
import FirebaseCoreInternal
1616

1717
private let kFiveMinutes = 5 * 60.0
1818

@@ -114,12 +114,17 @@ actor SecureTokenServiceInternal {
114114
/// A class represents a credential that proves the identity of the app.
115115
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
116116
@objc(FIRSecureTokenService) // objc Needed for decoding old versions
117-
class SecureTokenService: NSObject, NSSecureCoding {
117+
final class SecureTokenService: NSObject, NSSecureCoding, Sendable {
118118
/// Internal actor to enforce serialization
119119
private let internalService: SecureTokenServiceInternal
120120

121121
/// The configuration for making requests to server.
122-
var requestConfiguration: AuthRequestConfiguration?
122+
var requestConfiguration: AuthRequestConfiguration? {
123+
get { _requestConfiguration.withLock { $0 } }
124+
set { _requestConfiguration.withLock { $0 = newValue } }
125+
}
126+
127+
let _requestConfiguration: FIRAllocatedUnfairLock<AuthRequestConfiguration?>
123128

124129
/// The cached access token.
125130
///
@@ -130,20 +135,29 @@ class SecureTokenService: NSObject, NSSecureCoding {
130135
/// - Note: The atomic wrapper can be removed when the SDK is fully
131136
/// synchronized with structured concurrency.
132137
var accessToken: String {
133-
get { accessTokenLock.withLock { _accessToken } }
134-
set { accessTokenLock.withLock { _accessToken = newValue } }
138+
get { _accessToken.withLock { $0 } }
139+
set { _accessToken.withLock { $0 = newValue } }
135140
}
136141

137-
private var _accessToken: String
138-
private let accessTokenLock = NSLock()
142+
private let _accessToken: FIRAllocatedUnfairLock<String>
139143

140144
/// The refresh token for the user, or `nil` if the user has yet completed sign-in flow.
141145
///
142146
/// This property needs to be set manually after the instance is decoded from archive.
143-
var refreshToken: String?
147+
var refreshToken: String? {
148+
get { _refreshToken.withLock { $0 } }
149+
set { _refreshToken.withLock { $0 = newValue } }
150+
}
151+
152+
private let _refreshToken: FIRAllocatedUnfairLock<String?>
144153

145154
/// The expiration date of the cached access token.
146-
var accessTokenExpirationDate: Date?
155+
var accessTokenExpirationDate: Date? {
156+
get { _accessTokenExpirationDate.withLock { $0 } }
157+
set { _accessTokenExpirationDate.withLock { $0 = newValue } }
158+
}
159+
160+
private let _accessTokenExpirationDate: FIRAllocatedUnfairLock<Date?>
147161

148162
/// Creates a `SecureTokenService` with access and refresh tokens.
149163
/// - Parameter requestConfiguration: The configuration for making requests to server.
@@ -155,10 +169,10 @@ class SecureTokenService: NSObject, NSSecureCoding {
155169
accessTokenExpirationDate: Date?,
156170
refreshToken: String) {
157171
internalService = SecureTokenServiceInternal()
158-
self.requestConfiguration = requestConfiguration
159-
_accessToken = accessToken
160-
self.accessTokenExpirationDate = accessTokenExpirationDate
161-
self.refreshToken = refreshToken
172+
_requestConfiguration = FIRAllocatedUnfairLock(initialState: requestConfiguration)
173+
_accessToken = FIRAllocatedUnfairLock(initialState: accessToken)
174+
_accessTokenExpirationDate = FIRAllocatedUnfairLock(initialState: accessTokenExpirationDate)
175+
_refreshToken = FIRAllocatedUnfairLock(initialState: refreshToken)
162176
}
163177

164178
/// Fetch a fresh ephemeral access token for the ID associated with this instance. The token

FirebaseAuth/Tests/Unit/AuthAPNSTokenManagerTests.swift

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import XCTest
1818

1919
@testable import FirebaseAuth
20+
import FirebaseCoreInternal
2021

2122
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
2223
class AuthAPNSTokenManagerTests: XCTestCase {
@@ -61,10 +62,10 @@
6162
func testCallback() throws {
6263
let expectation = self.expectation(description: #function)
6364
XCTAssertFalse(fakeApplication!.registerCalled)
64-
var firstCallbackCalled = false
65+
let firstCallbackCalled = FIRAllocatedUnfairLock(initialState: false)
6566
let manager = try XCTUnwrap(manager)
6667
manager.getTokenInternal { result in
67-
firstCallbackCalled = true
68+
firstCallbackCalled.withLock { $0 = true }
6869
switch result {
6970
case let .success(token):
7071
XCTAssertEqual(token.data, self.data)
@@ -73,12 +74,12 @@
7374
XCTFail("Unexpected error: \(error)")
7475
}
7576
}
76-
XCTAssertFalse(firstCallbackCalled)
77+
XCTAssertFalse(firstCallbackCalled.value())
7778

7879
// Add second callback, which is yet to be called either.
79-
var secondCallbackCalled = false
80+
let secondCallbackCalled = FIRAllocatedUnfairLock(initialState: false)
8081
manager.getTokenInternal { result in
81-
secondCallbackCalled = true
82+
secondCallbackCalled.withLock { $0 = true }
8283
switch result {
8384
case let .success(token):
8485
XCTAssertEqual(token.data, self.data)
@@ -87,25 +88,25 @@
8788
XCTFail("Unexpected error: \(error)")
8889
}
8990
}
90-
XCTAssertFalse(secondCallbackCalled)
91+
XCTAssertFalse(secondCallbackCalled.value())
9192

9293
// Setting nil token shouldn't trigger either callbacks.
9394
manager.token = nil
94-
XCTAssertFalse(firstCallbackCalled)
95-
XCTAssertFalse(secondCallbackCalled)
95+
XCTAssertFalse(firstCallbackCalled.value())
96+
XCTAssertFalse(secondCallbackCalled.value())
9697
XCTAssertNil(manager.token)
9798

9899
// Setting a real token should trigger both callbacks.
99100
manager.token = AuthAPNSToken(withData: data!, type: .sandbox)
100-
XCTAssertTrue(firstCallbackCalled)
101-
XCTAssertTrue(secondCallbackCalled)
101+
XCTAssertTrue(firstCallbackCalled.value())
102+
XCTAssertTrue(secondCallbackCalled.value())
102103
XCTAssertEqual(manager.token?.data, data)
103104
XCTAssertEqual(manager.token?.type, .sandbox)
104105

105106
// Add third callback, which should be called back immediately.
106-
var thirdCallbackCalled = false
107+
let thirdCallbackCalled = FIRAllocatedUnfairLock(initialState: false)
107108
manager.getTokenInternal { result in
108-
thirdCallbackCalled = true
109+
thirdCallbackCalled.withLock { $0 = true }
109110
switch result {
110111
case let .success(token):
111112
XCTAssertEqual(token.data, self.data)
@@ -114,7 +115,7 @@
114115
XCTFail("Unexpected error: \(error)")
115116
}
116117
}
117-
XCTAssertTrue(thirdCallbackCalled)
118+
XCTAssertTrue(thirdCallbackCalled.value())
118119

119120
// In the main thread, Verify the that the fake `registerForRemoteNotifications` was called.
120121
DispatchQueue.main.async {
@@ -129,9 +130,12 @@
129130
*/
130131
func testTimeout() throws {
131132
// Set up timeout.
133+
manager = AuthAPNSTokenManager(
134+
withApplication: fakeApplication!,
135+
timeout: kRegistrationTimeout
136+
)
132137
let manager = try XCTUnwrap(manager)
133138
XCTAssertGreaterThan(try XCTUnwrap(manager.timeout), 0)
134-
manager.timeout = kRegistrationTimeout
135139

136140
// Add callback to time out.
137141
let expectation = self.expectation(description: #function)
@@ -166,23 +170,26 @@
166170
*/
167171
func testCancel() throws {
168172
// Set up timeout.
173+
manager = AuthAPNSTokenManager(
174+
withApplication: fakeApplication!,
175+
timeout: kRegistrationTimeout
176+
)
169177
let manager = try XCTUnwrap(manager)
170178
XCTAssertGreaterThan(try XCTUnwrap(manager.timeout), 0)
171-
manager.timeout = kRegistrationTimeout
172179

173180
// Add callback to cancel.
174-
var callbackCalled = false
181+
let callbackCalled = FIRAllocatedUnfairLock(initialState: false)
175182
manager.getTokenInternal { result in
176183
switch result {
177184
case let .success(token):
178185
XCTFail("Unexpected success: \(token)")
179186
case let .failure(error):
180187
XCTAssertEqual(error as NSError, self.error as NSError)
181188
}
182-
XCTAssertFalse(callbackCalled) // verify callback is not called twice
183-
callbackCalled = true
189+
XCTAssertFalse(callbackCalled.value()) // verify callback is not called twice
190+
callbackCalled.withLock { $0 = true }
184191
}
185-
XCTAssertFalse(callbackCalled)
192+
XCTAssertFalse(callbackCalled.value())
186193

187194
// Call cancel.
188195
manager.cancel(withError: error)

FirebaseAuth/Tests/Unit/AuthTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,7 +2291,7 @@ class AuthTests: RPCBaseTests {
22912291

22922292
#if os(iOS)
22932293
func testAppDidRegisterForRemoteNotifications_APNSTokenUpdated() {
2294-
class FakeAuthTokenManager: AuthAPNSTokenManager {
2294+
class FakeAuthTokenManager: AuthAPNSTokenManager, @unchecked Sendable {
22952295
override var token: AuthAPNSToken? {
22962296
get {
22972297
return tokenStore
@@ -2310,7 +2310,7 @@ class AuthTests: RPCBaseTests {
23102310
}
23112311

23122312
func testAppDidFailToRegisterForRemoteNotifications_TokenManagerCancels() {
2313-
class FakeAuthTokenManager: AuthAPNSTokenManager {
2313+
class FakeAuthTokenManager: AuthAPNSTokenManager, @unchecked Sendable {
23142314
var cancelled = false
23152315
override func cancel(withError error: Error) {
23162316
cancelled = true

FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@
923923
}
924924
}
925925

926-
class FakeTokenManager: AuthAPNSTokenManager {
926+
class FakeTokenManager: AuthAPNSTokenManager, @unchecked Sendable {
927927
override func getTokenInternal(callback: @escaping (Result<AuthAPNSToken, Error>) -> Void) {
928928
let error = NSError(domain: "dummy domain", code: AuthErrorCode.missingAppToken.rawValue)
929929
callback(.failure(error))

0 commit comments

Comments
 (0)