Skip to content

Commit 9903f31

Browse files
committed
AuthNotificationManager from continuation to AsyncStream
1 parent acc4bbf commit 9903f31

File tree

3 files changed

+89
-63
lines changed

3 files changed

+89
-63
lines changed

FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift

Lines changed: 40 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@
5656
/// Only tests should access this property.
5757
var immediateCallbackForTestFaking: (() -> Bool)?
5858

59-
/// All pending callbacks while a check is being performed.
60-
private var pendingCallbacks: [(Bool) -> Void]?
59+
private let condition: AuthCondition
6160

6261
/// Initializes the instance.
6362
/// - Parameter application: The application.
@@ -69,56 +68,53 @@
6968
self.application = application
7069
self.appCredentialManager = appCredentialManager
7170
timeout = kProbingTimeout
71+
condition = AuthCondition()
7272
}
7373

74-
/// Checks whether or not remote notifications are being forwarded to this class.
75-
/// - Parameter callback: The block to be called either immediately or in future once a result
76-
/// is available.
77-
func checkNotificationForwardingInternal(withCallback callback: @escaping (Bool) -> Void) {
78-
if pendingCallbacks != nil {
79-
pendingCallbacks?.append(callback)
80-
return
74+
private actor PendingCount {
75+
private var count = 0
76+
func increment() -> Int {
77+
count = count + 1
78+
return count
8179
}
80+
}
81+
82+
private let pendingCount = PendingCount()
83+
84+
/// Checks whether or not remote notifications are being forwarded to this class.
85+
func checkNotificationForwarding() async -> Bool {
8286
if let getValueFunc = immediateCallbackForTestFaking {
83-
callback(getValueFunc())
84-
return
87+
return getValueFunc()
8588
}
8689
if hasCheckedNotificationForwarding {
87-
callback(isNotificationBeingForwarded)
88-
return
90+
return isNotificationBeingForwarded
8991
}
90-
hasCheckedNotificationForwarding = true
91-
pendingCallbacks = [callback]
92-
93-
DispatchQueue.main.async {
94-
let proberNotification = [self.kNotificationDataKey: [self.kNotificationProberKey:
95-
"This fake notification should be forwarded to Firebase Auth."]]
96-
if let delegate = self.application.delegate,
97-
delegate
98-
.responds(to: #selector(UIApplicationDelegate
99-
.application(_:didReceiveRemoteNotification:fetchCompletionHandler:))) {
100-
delegate.application?(self.application,
101-
didReceiveRemoteNotification: proberNotification) { _ in
92+
if await pendingCount.increment() == 1 {
93+
DispatchQueue.main.async {
94+
let proberNotification = [self.kNotificationDataKey: [self.kNotificationProberKey:
95+
"This fake notification should be forwarded to Firebase Auth."]]
96+
if let delegate = self.application.delegate,
97+
delegate
98+
.responds(to: #selector(UIApplicationDelegate
99+
.application(_:didReceiveRemoteNotification:fetchCompletionHandler:))) {
100+
delegate.application?(self.application,
101+
didReceiveRemoteNotification: proberNotification) { _ in
102+
}
103+
} else {
104+
AuthLog.logWarning(
105+
code: "I-AUT000015",
106+
message: "The UIApplicationDelegate must handle " +
107+
"remote notification for phone number authentication to work."
108+
)
109+
}
110+
kAuthGlobalWorkQueue.asyncAfter(deadline: .now() + .seconds(Int(self.timeout))) {
111+
self.condition.signal()
102112
}
103-
} else {
104-
AuthLog.logWarning(
105-
code: "I-AUT000015",
106-
message: "The UIApplicationDelegate must handle " +
107-
"remote notification for phone number authentication to work."
108-
)
109-
}
110-
kAuthGlobalWorkQueue.asyncAfter(deadline: .now() + .seconds(Int(self.timeout))) {
111-
self.callback()
112-
}
113-
}
114-
}
115-
116-
func checkNotificationForwarding() async -> Bool {
117-
return await withUnsafeContinuation { continuation in
118-
checkNotificationForwardingInternal { value in
119-
continuation.resume(returning: value)
120113
}
121114
}
115+
await condition.wait()
116+
hasCheckedNotificationForwarding = true
117+
return isNotificationBeingForwarded
122118
}
123119

124120
/// Attempts to handle the remote notification.
@@ -140,12 +136,12 @@
140136
return false
141137
}
142138
if dictionary[kNotificationProberKey] != nil {
143-
if pendingCallbacks == nil {
139+
if hasCheckedNotificationForwarding {
144140
// The prober notification probably comes from another instance, so pass it along.
145141
return false
146142
}
147143
isNotificationBeingForwarded = true
148-
callback()
144+
condition.signal()
149145
return true
150146
}
151147
guard let receipt = dictionary[kNotificationReceiptKey] as? String,
@@ -154,17 +150,5 @@
154150
}
155151
return appCredentialManager.canFinishVerification(withReceipt: receipt, secret: secret)
156152
}
157-
158-
// MARK: Internal methods
159-
160-
private func callback() {
161-
guard let pendingCallbacks else {
162-
return
163-
}
164-
self.pendingCallbacks = nil
165-
for callback in pendingCallbacks {
166-
callback(isNotificationBeingForwarded)
167-
}
168-
}
169153
}
170154
#endif
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
/// Utility struct to make the execution of one task dependent upon a signal from another task.
18+
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
19+
struct AuthCondition {
20+
private let waiter: () async -> Void
21+
private let stream: AsyncStream<Void>.Continuation
22+
23+
init() {
24+
let (stream, continuation) = AsyncStream<Void>.makeStream()
25+
waiter = {
26+
for await _ in stream {}
27+
}
28+
self.stream = continuation
29+
}
30+
31+
// Signal to unblock the waiter.
32+
func signal() {
33+
stream.finish()
34+
}
35+
36+
/// Wait for the condition.
37+
func wait() async {
38+
await waiter()
39+
}
40+
}

FirebaseAuth/Tests/Unit/AuthNotificationManagerTests.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
/** @property notificationManager
3434
@brief The notification manager to forward.
3535
*/
36-
private var notificationManager: AuthNotificationManager?
36+
private var notificationManager: AuthNotificationManager!
3737

3838
/** @var modernDelegate
3939
@brief The modern fake UIApplicationDelegate for testing.
@@ -75,7 +75,8 @@
7575
private func verify(forwarding: Bool, delegate: FakeForwardingDelegate) throws {
7676
delegate.forwardsNotification = forwarding
7777
let expectation = self.expectation(description: "callback")
78-
notificationManager?.checkNotificationForwardingInternal { forwarded in
78+
Task {
79+
let forwarded = await notificationManager.checkNotificationForwarding()
7980
XCTAssertEqual(forwarded, forwarding)
8081
expectation.fulfill()
8182
}
@@ -93,12 +94,13 @@
9394
let delegate = try XCTUnwrap(modernDelegate)
9495
try verify(forwarding: false, delegate: delegate)
9596
modernDelegate?.notificationReceived = false
96-
var calledBack = false
97-
notificationManager?.checkNotificationForwardingInternal { isNotificationBeingForwarded in
97+
let expectation = self.expectation(description: "callback")
98+
Task {
99+
let isNotificationBeingForwarded = await notificationManager.checkNotificationForwarding()
98100
XCTAssertFalse(isNotificationBeingForwarded)
99-
calledBack = true
101+
expectation.fulfill()
100102
}
101-
XCTAssertTrue(calledBack)
103+
waitForExpectations(timeout: 5)
102104
XCTAssertFalse(delegate.notificationReceived)
103105
}
104106

@@ -136,7 +138,7 @@
136138
.canHandle(notification: ["com.google.firebase.auth": ["secret": kSecret]]))
137139
// Probing notification does not belong to this instance.
138140
XCTAssertFalse(manager
139-
.canHandle(notification: ["com.google.firebase.auth": ["warning": "asdf"]]))
141+
.canHandle(notification: ["com.google.firebase.auth": ["error": "asdf"]]))
140142
}
141143

142144
private class FakeApplication: UIApplication {}

0 commit comments

Comments
 (0)