mirror of
https://github.com/block/buzz.git
synced 2026-11-01 23:11:58 +00:00
## Summary Opening an invite from Safari can deliver the login request while iOS still considers Buzz inactive. `ASWebAuthenticationSession.start()` then returns false, showing a connection error until the user retries. Wait for app activation before starting the retained session, with a ten-second timeout and cancellation cleanup. Extract the existing browser-auth bridge from the size-limited AppDelegate and add one native journey test through its Flutter method channel. URL validation, callback fencing, and normal active-app login retain their existing behavior. Temporary diagnostic logging and generated dependency changes are excluded. ### Related issue No duplicate found in the open handoff/authentication search. Related invite-consent work: #8601; this fixes the subsequent iOS authentication presentation race. ### Testing - Focused `RunnerTests/BrowserAuthBridgeTests` passed on an iPhone 18 Pro / iOS 27 simulator. Covers inactive handoff, cancellation before activation, retry, duplicate activation notifications, and immediate active-app start. - A local build reproduced the original inactive-app `start=false` failure; the fixed build opened authentication on the first background-to-app attempt. The same fixed build subsequently completed an existing-account staging flow. - Mutation check passed in isolated source, Git metadata, and build outputs: clean control passed; bypassing the production activation wait failed the inactive-handoff assertion; restored source passed again, with its SHA-256 verified against the committed file. - Self-review found no blocking issue. CI and final review remain pending. Mandatory pre-commit and pre-push hooks passed, including mobile analysis and the hook-run Flutter suite. No full local suite was manually invoked. - Human acceptance: from Safari, tap **Accept invite in Buzz** and confirm sign-in opens on the first attempt without **Try again**. Final acceptance of this PR remains pending. The separately reported callback failure may have involved production rather than staging. That cause is unconfirmed and is outside this change. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
66 lines
2.9 KiB
Swift
66 lines
2.9 KiB
Swift
import Flutter
|
|
import UIKit
|
|
import XCTest
|
|
@testable import Buzz
|
|
|
|
final class BrowserAuthBridgeTests: XCTestCase {
|
|
@MainActor
|
|
func testInviteHandoffWaitsForActivationAndCancelledAttemptCannotReopen() {
|
|
let messenger = BrowserAuthTestMessenger()
|
|
var active = false
|
|
var starts = 0
|
|
let bridge = BuzzBrowserAuthBridge(messenger: messenger, isActive: { active },
|
|
startSession: { _ in starts += 1; return true })
|
|
defer { withExtendedLifetime(bridge) {} }
|
|
let arguments = ["url": "https://app.builderlabstaging.xyz/api/goose/v1/auth/login"]
|
|
var results: [String] = []
|
|
messenger.invoke("start", arguments: arguments) {
|
|
results.append(($0 as? FlutterError)?.code ?? "success")
|
|
}
|
|
XCTAssertEqual(starts, 0, "An incoming invite must not present auth while inactive")
|
|
XCTAssertTrue(results.isEmpty)
|
|
messenger.invoke("cancel", arguments: [:]) { _ in }
|
|
XCTAssertEqual(results, ["cancelled"])
|
|
active = true
|
|
NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
XCTAssertEqual(starts, 0, "Cancelled handoffs must not reopen sign-in")
|
|
|
|
active = false
|
|
messenger.invoke("start", arguments: arguments) {
|
|
results.append(($0 as? FlutterError)?.code ?? "success")
|
|
}
|
|
XCTAssertEqual(starts, 0)
|
|
active = true
|
|
NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
XCTAssertEqual(starts, 1, "Activation must present the original request without a retry")
|
|
NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
XCTAssertEqual(starts, 1)
|
|
XCTAssertEqual(results, ["cancelled"])
|
|
messenger.invoke("cancel", arguments: [:]) { _ in }
|
|
XCTAssertEqual(results, ["cancelled", "cancelled"])
|
|
messenger.invoke("start", arguments: arguments) { _ in }
|
|
XCTAssertEqual(starts, 2, "Ordinary active-app login should start immediately")
|
|
messenger.invoke("cancel", arguments: [:]) { _ in }
|
|
}
|
|
}
|
|
|
|
private final class BrowserAuthTestMessenger: NSObject, FlutterBinaryMessenger {
|
|
private var handler: FlutterBinaryMessageHandler?
|
|
func send(onChannel channel: String, message: Data?) {}
|
|
func send(onChannel channel: String, message: Data?, binaryReply callback: FlutterBinaryReply?) {
|
|
callback?(nil)
|
|
}
|
|
func setMessageHandlerOnChannel(_ channel: String,
|
|
binaryMessageHandler handler: FlutterBinaryMessageHandler?) -> FlutterBinaryMessengerConnection {
|
|
self.handler = handler
|
|
return 1
|
|
}
|
|
func cleanUpConnection(_ connection: FlutterBinaryMessengerConnection) { handler = nil }
|
|
func invoke(_ method: String, arguments: [String: String], reply: @escaping (Any?) -> Void) {
|
|
let codec = FlutterStandardMethodCodec.sharedInstance()
|
|
handler?(codec.encode(FlutterMethodCall(methodName: method, arguments: arguments))) {
|
|
reply($0.flatMap { codec.decodeEnvelope($0) })
|
|
}
|
|
}
|
|
}
|