From eb31072850656c3539448837564daff8fd6d0de4 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Mon, 14 Sep 2026 15:00:09 +0500 Subject: [PATCH 1/3] firebase_app_check_tvos: regenerate Pigeon for 0.4.7 and refresh stale example comments --- .../cloud_functions_tvos/example/pubspec.yaml | 5 +- .../firebase_ai_tvos/example/pubspec.yaml | 4 +- .../example/pubspec.yaml | 6 +- packages/firebase_app_check_tvos/CHANGELOG.md | 7 + .../example/pubspec.yaml | 8 +- packages/firebase_app_check_tvos/pubspec.yaml | 7 +- .../Classes/FirebaseAppCheckMessages.g.swift | 250 +++++++++++++++--- .../tvos/Classes/FirebaseAppCheckPlugin.swift | 57 +++- .../tvos/firebase_app_check_tvos.podspec | 2 +- .../example/pubspec.yaml | 4 +- .../example/pubspec.yaml | 4 +- .../example/pubspec.yaml | 4 +- 12 files changed, 282 insertions(+), 76 deletions(-) diff --git a/packages/cloud_functions_tvos/example/pubspec.yaml b/packages/cloud_functions_tvos/example/pubspec.yaml index aac83c3..4ba6201 100755 --- a/packages/cloud_functions_tvos/example/pubspec.yaml +++ b/packages/cloud_functions_tvos/example/pubspec.yaml @@ -7,10 +7,7 @@ environment: flutter: '>=3.27.0' dependencies: - # Pinned exact to the 4.11.x train (firebase_core_platform_interface 7.1.0) - # that firebase_core_tvos ships on — 6.3.6 / firebase_core 4.13.0 drift to - # platform_interface 8.1.0 (14-field FirebaseOptions) and mismatch the native - # Pigeon, crashing at launch under Dart options: init. + # Exact pins: firebase_core and firebase_core_tvos must move together. cloud_functions: 6.3.3 cloud_functions_tvos: path: ../ diff --git a/packages/firebase_ai_tvos/example/pubspec.yaml b/packages/firebase_ai_tvos/example/pubspec.yaml index ef3ec83..22e531a 100755 --- a/packages/firebase_ai_tvos/example/pubspec.yaml +++ b/packages/firebase_ai_tvos/example/pubspec.yaml @@ -7,9 +7,7 @@ environment: flutter: '>=3.27.0' dependencies: - # Pinned to the 4.11.x train (firebase_core_platform_interface 7.1.0) that the - # Firebase _tvos family ships on; newer firebase_core drifts to pi 8.1 and - # mismatches firebase_core_tvos's native Pigeon. + # Exact pins: firebase_core and firebase_core_tvos must move together. firebase_ai: 3.13.1 firebase_ai_tvos: path: ../ diff --git a/packages/firebase_analytics_tvos/example/pubspec.yaml b/packages/firebase_analytics_tvos/example/pubspec.yaml index fbcc4b3..8ab3bef 100755 --- a/packages/firebase_analytics_tvos/example/pubspec.yaml +++ b/packages/firebase_analytics_tvos/example/pubspec.yaml @@ -7,11 +7,7 @@ environment: flutter: '>=3.27.0' dependencies: - # Pinned to the train the published _tvos packages were built from - # (firebase_core 4.11.0 -> firebase_core_platform_interface 7.1.0, whose - # FirebaseOptions has 15 fields incl. recaptchaSiteKey, matching the native - # Pigeon in firebase_core_tvos). No patch, no plist needed: Dart sends 15 - # fields, native reads 15. This is the same train flutter_pca will bump to. + # Exact pins: firebase_core and firebase_core_tvos must move together. firebase_core: 4.11.0 firebase_analytics: 12.4.3 firebase_analytics_tvos: diff --git a/packages/firebase_app_check_tvos/CHANGELOG.md b/packages/firebase_app_check_tvos/CHANGELOG.md index f4c89b4..f90676f 100644 --- a/packages/firebase_app_check_tvos/CHANGELOG.md +++ b/packages/firebase_app_check_tvos/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.0.2 + +* Regenerated the Pigeon messages against `firebase_app_check` 0.4.7: `activate()` + takes a 5th `recaptchaSiteKey` argument and the `getTokenResult` channel is + implemented. +* Requires `firebase_app_check` `>=0.4.7 <0.4.8`. + ## 0.0.1 * Initial tvOS (Apple TV) implementation of `firebase_app_check`, ported from diff --git a/packages/firebase_app_check_tvos/example/pubspec.yaml b/packages/firebase_app_check_tvos/example/pubspec.yaml index 140f77e..41b3b68 100644 --- a/packages/firebase_app_check_tvos/example/pubspec.yaml +++ b/packages/firebase_app_check_tvos/example/pubspec.yaml @@ -7,11 +7,9 @@ environment: flutter: '>=3.27.0' dependencies: - # Pinned to the train the published _tvos packages ship on (firebase_core - # 4.11.x -> firebase_core_platform_interface 7.1.0). Keeps the native Pigeon - # ABI in sync with firebase_core_tvos. - firebase_core: 4.11.0 - firebase_app_check: 0.4.5 + # Exact pins: firebase_core and firebase_core_tvos must move together. + firebase_core: 4.14.0 + firebase_app_check: 0.4.7 firebase_app_check_tvos: path: ../ firebase_core_tvos: ^0.0.1 diff --git a/packages/firebase_app_check_tvos/pubspec.yaml b/packages/firebase_app_check_tvos/pubspec.yaml index b71543b..6669104 100644 --- a/packages/firebase_app_check_tvos/pubspec.yaml +++ b/packages/firebase_app_check_tvos/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_app_check_tvos description: >- tvOS (Apple TV) implementation of the firebase_app_check Flutter plugin, providing Firebase App Check on Apple TV (App Attest / DeviceCheck). -version: 0.0.1 +version: 0.0.2 homepage: https://fluttertv.dev repository: https://github.com/fluttertv/plugins/tree/main/packages/firebase_app_check_tvos issue_tracker: https://github.com/fluttertv/plugins/issues @@ -16,10 +16,7 @@ environment: dependencies: flutter: sdk: flutter - # Pinned below 0.4.6: that release moves the platform-interface pigeon - # contract (new getTokenResult channel; activate() gains a 5th - # recaptchaSiteKey arg) which the generated Swift here does not cover. - firebase_app_check: ">=0.4.5 <0.4.6" + firebase_app_check: ">=0.4.7 <0.4.8" # Transitive Dart dependency so the app's dependency graph includes # firebase_core_tvos — plugin discovery (tvos_plugins.dart) only adds a # package's native pod to the generated Podfile if it's reachable here, diff --git a/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckMessages.g.swift b/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckMessages.g.swift index abbf5f8..fb88bdf 100644 --- a/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckMessages.g.swift +++ b/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckMessages.g.swift @@ -27,12 +27,13 @@ final class PigeonError: Error { } var localizedDescription: String { - "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" } } private func wrapResult(_ result: Any?) -> [Any?] { - [result] + return [result] } private func wrapError(_ error: Any) -> [Any?] { @@ -58,7 +59,7 @@ private func wrapError(_ error: Any) -> [Any?] { } private func isNullish(_ value: Any?) -> Bool { - value is NSNull || value == nil + return value is NSNull || value == nil } private func nilOrValue(_ value: Any?) -> T? { @@ -66,52 +67,210 @@ private func nilOrValue(_ value: Any?) -> T? { return value as! T? } -private class FirebaseAppCheckMessagesPigeonCodecReader: FlutterStandardReader {} +private func doubleEqualsFirebaseAppCheckMessages(_ lhs: Double, _ rhs: Double) -> Bool { + return (lhs.isNaN && rhs.isNaN) || lhs == rhs +} + +private func doubleHashFirebaseAppCheckMessages(_ value: Double, _ hasher: inout Hasher) { + if value.isNaN { + hasher.combine(0x7FF8_0000_0000_0000) + } else { + // Normalize -0.0 to 0.0 + hasher.combine(value == 0 ? 0 : value) + } +} + +func deepEqualsFirebaseAppCheckMessages(_ lhs: Any?, _ rhs: Any?) -> Bool { + let cleanLhs = nilOrValue(lhs) as Any? + let cleanRhs = nilOrValue(rhs) as Any? + switch (cleanLhs, cleanRhs) { + case (nil, nil): + return true + + case (nil, _), (_, nil): + return false + + case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs: + return true + + case is (Void, Void): + return true + + case (let lhsArray, let rhsArray) as ([Any?], [Any?]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !deepEqualsFirebaseAppCheckMessages(element, rhsArray[index]) { + return false + } + } + return true + + case (let lhsArray, let rhsArray) as ([Double], [Double]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !doubleEqualsFirebaseAppCheckMessages(element, rhsArray[index]) { + return false + } + } + return true + + case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard lhsDictionary.count == rhsDictionary.count else { return false } + for (lhsKey, lhsValue) in lhsDictionary { + var found = false + for (rhsKey, rhsValue) in rhsDictionary { + if deepEqualsFirebaseAppCheckMessages(lhsKey, rhsKey) { + if deepEqualsFirebaseAppCheckMessages(lhsValue, rhsValue) { + found = true + break + } else { + return false + } + } + } + if !found { return false } + } + return true -private class FirebaseAppCheckMessagesPigeonCodecWriter: FlutterStandardWriter {} + case (let lhs as Double, let rhs as Double): + return doubleEqualsFirebaseAppCheckMessages(lhs, rhs) + + case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable): + return lhsHashable == rhsHashable + + default: + return false + } +} + +func deepHashFirebaseAppCheckMessages(value: Any?, hasher: inout Hasher) { + let cleanValue = nilOrValue(value) as Any? + if let cleanValue = cleanValue { + if let doubleValue = cleanValue as? Double { + doubleHashFirebaseAppCheckMessages(doubleValue, &hasher) + } else if let valueList = cleanValue as? [Any?] { + for item in valueList { + deepHashFirebaseAppCheckMessages(value: item, hasher: &hasher) + } + } else if let valueList = cleanValue as? [Double] { + for item in valueList { + doubleHashFirebaseAppCheckMessages(item, &hasher) + } + } else if let valueDict = cleanValue as? [AnyHashable: Any?] { + var result = 0 + for (key, value) in valueDict { + var entryKeyHasher = Hasher() + deepHashFirebaseAppCheckMessages(value: key, hasher: &entryKeyHasher) + var entryValueHasher = Hasher() + deepHashFirebaseAppCheckMessages(value: value, hasher: &entryValueHasher) + result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize()) + } + hasher.combine(result) + } else if let hashableValue = cleanValue as? AnyHashable { + hasher.combine(hashableValue) + } else { + hasher.combine(String(describing: cleanValue)) + } + } else { + hasher.combine(0) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct InternalAppCheckTokenResult: Hashable { + var token: String + var expirationTimestamp: Int64? = nil + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> InternalAppCheckTokenResult? { + let token = pigeonVar_list[0] as! String + let expirationTimestamp: Int64? = nilOrValue(pigeonVar_list[1]) + + return InternalAppCheckTokenResult( + token: token, + expirationTimestamp: expirationTimestamp + ) + } + func toList() -> [Any?] { + return [ + token, + expirationTimestamp, + ] + } + static func == (lhs: InternalAppCheckTokenResult, rhs: InternalAppCheckTokenResult) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsFirebaseAppCheckMessages(lhs.token, rhs.token) + && deepEqualsFirebaseAppCheckMessages(lhs.expirationTimestamp, rhs.expirationTimestamp) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("InternalAppCheckTokenResult") + deepHashFirebaseAppCheckMessages(value: token, hasher: &hasher) + deepHashFirebaseAppCheckMessages(value: expirationTimestamp, hasher: &hasher) + } +} + +private class FirebaseAppCheckMessagesPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + return InternalAppCheckTokenResult.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class FirebaseAppCheckMessagesPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? InternalAppCheckTokenResult { + super.writeByte(129) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} private class FirebaseAppCheckMessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { override func reader(with data: Data) -> FlutterStandardReader { - FirebaseAppCheckMessagesPigeonCodecReader(data: data) + return FirebaseAppCheckMessagesPigeonCodecReader(data: data) } override func writer(with data: NSMutableData) -> FlutterStandardWriter { - FirebaseAppCheckMessagesPigeonCodecWriter(data: data) + return FirebaseAppCheckMessagesPigeonCodecWriter(data: data) } } class FirebaseAppCheckMessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { static let shared = FirebaseAppCheckMessagesPigeonCodec( - readerWriter: FirebaseAppCheckMessagesPigeonCodecReaderWriter() - ) + readerWriter: FirebaseAppCheckMessagesPigeonCodecReaderWriter()) } /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol FirebaseAppCheckHostApi { func activate( - appName: String, androidProvider: String?, appleProvider: String?, - debugToken: String?, - completion: @escaping (Result) -> Void) + appName: String, androidProvider: String?, appleProvider: String?, debugToken: String?, + recaptchaSiteKey: String?, completion: @escaping (Result) -> Void) func getToken( + appName: String, forceRefresh: Bool, completion: @escaping (Result) -> Void) + func getTokenResult( appName: String, forceRefresh: Bool, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void) func setTokenAutoRefreshEnabled( appName: String, isTokenAutoRefreshEnabled: Bool, completion: @escaping (Result) -> Void) func registerTokenListener(appName: String, completion: @escaping (Result) -> Void) func getLimitedUseAppCheckToken( - appName: String, - completion: @escaping (Result) -> Void) + appName: String, completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class FirebaseAppCheckHostApiSetup { - static var codec: FlutterStandardMessageCodec { - FirebaseAppCheckMessagesPigeonCodec.shared - } - - /// Sets up an instance of `FirebaseAppCheckHostApi` to handle messages through the - /// `binaryMessenger`. + static var codec: FlutterStandardMessageCodec { FirebaseAppCheckMessagesPigeonCodec.shared } + /// Sets up an instance of `FirebaseAppCheckHostApi` to handle messages through the `binaryMessenger`. static func setUp( binaryMessenger: FlutterBinaryMessenger, api: FirebaseAppCheckHostApi?, messageChannelSuffix: String = "" @@ -120,18 +279,18 @@ class FirebaseAppCheckHostApiSetup { let activateChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec - ) - if let api { + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { activateChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appNameArg = args[0] as! String let androidProviderArg: String? = nilOrValue(args[1]) let appleProviderArg: String? = nilOrValue(args[2]) let debugTokenArg: String? = nilOrValue(args[3]) + let recaptchaSiteKeyArg: String? = nilOrValue(args[4]) api.activate( appName: appNameArg, androidProvider: androidProviderArg, appleProvider: appleProviderArg, - debugToken: debugTokenArg + debugToken: debugTokenArg, recaptchaSiteKey: recaptchaSiteKeyArg ) { result in switch result { case .success: @@ -147,9 +306,8 @@ class FirebaseAppCheckHostApiSetup { let getTokenChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getToken\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec - ) - if let api { + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { getTokenChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appNameArg = args[0] as! String @@ -166,12 +324,32 @@ class FirebaseAppCheckHostApiSetup { } else { getTokenChannel.setMessageHandler(nil) } + let getTokenResultChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getTokenResult\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getTokenResultChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let appNameArg = args[0] as! String + let forceRefreshArg = args[1] as! Bool + api.getTokenResult(appName: appNameArg, forceRefresh: forceRefreshArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getTokenResultChannel.setMessageHandler(nil) + } let setTokenAutoRefreshEnabledChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.setTokenAutoRefreshEnabled\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec - ) - if let api { + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { setTokenAutoRefreshEnabledChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appNameArg = args[0] as! String @@ -193,9 +371,8 @@ class FirebaseAppCheckHostApiSetup { let registerTokenListenerChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.registerTokenListener\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec - ) - if let api { + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { registerTokenListenerChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appNameArg = args[0] as! String @@ -214,9 +391,8 @@ class FirebaseAppCheckHostApiSetup { let getLimitedUseAppCheckTokenChannel = FlutterBasicMessageChannel( name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getLimitedUseAppCheckToken\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec - ) - if let api { + binaryMessenger: binaryMessenger, codec: codec) + if let api = api { getLimitedUseAppCheckTokenChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appNameArg = args[0] as! String diff --git a/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift b/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift index 312ace8..0e47d33 100644 --- a/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift +++ b/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift @@ -61,7 +61,7 @@ public class FirebaseAppCheckPlugin: NSObject, FlutterPlugin, func activate( appName: String, androidProvider: String?, appleProvider: String?, - debugToken: String?, + debugToken: String?, recaptchaSiteKey: String?, completion: @escaping (Result) -> Void ) { guard let app = FLTFirebasePlugin.firebaseAppNamed(appName) else { @@ -79,7 +79,8 @@ public class FirebaseAppCheckPlugin: NSObject, FlutterPlugin, providerFactory?.configure( app: app, providerName: provider, - debugToken: debugToken + debugToken: debugToken, + recaptchaSiteKey: recaptchaSiteKey ) completion(.success(())) @@ -111,6 +112,41 @@ public class FirebaseAppCheckPlugin: NSObject, FlutterPlugin, } } + func getTokenResult( + appName: String, forceRefresh: Bool, + completion: @escaping (Result) -> Void + ) { + guard let app = FLTFirebasePlugin.firebaseAppNamed(appName), + let appCheck = AppCheck.appCheck(app: app) + else { + completion( + .failure( + FlutterError( + code: "unknown", message: "App Check not available for app: \(appName)", details: nil + ) + ) + ) + return + } + + appCheck.token(forcingRefresh: forceRefresh) { token, error in + if let error { + completion(.failure(self.createFlutterError(error))) + } else { + completion( + .success( + token.map { + InternalAppCheckTokenResult( + token: $0.token, + expirationTimestamp: Int64($0.expirationDate.timeIntervalSince1970 * 1000) + ) + } + ) + ) + } + } + } + func setTokenAutoRefreshEnabled( appName: String, isTokenAutoRefreshEnabled: Bool, completion: @escaping (Result) -> Void @@ -291,7 +327,8 @@ class FlutterAppCheckProviderFactory: NSObject, AppCheckProviderFactory { func configure( app: FirebaseApp, providerName: String, - debugToken: String? + debugToken: String?, + recaptchaSiteKey: String? ) { if providers[app.name] == nil { providers[app.name] = AppCheckProviderWrapper() @@ -299,7 +336,8 @@ class FlutterAppCheckProviderFactory: NSObject, AppCheckProviderFactory { providers[app.name]?.configure( app: app, providerName: providerName, - debugToken: debugToken + debugToken: debugToken, + recaptchaSiteKey: recaptchaSiteKey ) } } @@ -310,7 +348,8 @@ class AppCheckProviderWrapper: NSObject, AppCheckProvider { func configure( app: FirebaseApp, providerName: String, - debugToken: String? + debugToken: String?, + recaptchaSiteKey: String? ) { switch providerName { case "debug": @@ -340,10 +379,14 @@ class AppCheckProviderWrapper: NSObject, AppCheckProvider { // provider unconfigured so getToken surfaces an explicit error instead of // failing silently. #if os(iOS) - delegateProvider = RecaptchaProvider(app: app) + if let recaptchaSiteKey { + delegateProvider = RecaptchaProvider(app: app, siteKey: recaptchaSiteKey) + } else { + delegateProvider = nil + } if delegateProvider == nil { print( - "Firebase App Check: failed to initialize RecaptchaProvider. Ensure site key is in GoogleService-Info.plist." + "Firebase App Check: failed to initialize RecaptchaProvider. Ensure site key is provided." ) } #else diff --git a/packages/firebase_app_check_tvos/tvos/firebase_app_check_tvos.podspec b/packages/firebase_app_check_tvos/tvos/firebase_app_check_tvos.podspec index 128b62a..57aff69 100644 --- a/packages/firebase_app_check_tvos/tvos/firebase_app_check_tvos.podspec +++ b/packages/firebase_app_check_tvos/tvos/firebase_app_check_tvos.podspec @@ -6,7 +6,7 @@ # Pod::Spec.new do |s| s.name = 'firebase_app_check_tvos' - s.version = '0.0.1' + s.version = '0.0.2' s.summary = 'tvOS implementation of firebase_app_check.' s.description = <<-DESC tvOS implementation of firebase_app_check, the federated platform diff --git a/packages/firebase_crashlytics_tvos/example/pubspec.yaml b/packages/firebase_crashlytics_tvos/example/pubspec.yaml index 8efa575..b90fc95 100755 --- a/packages/firebase_crashlytics_tvos/example/pubspec.yaml +++ b/packages/firebase_crashlytics_tvos/example/pubspec.yaml @@ -7,9 +7,7 @@ environment: flutter: '>=3.27.0' dependencies: - # Pinned to the train the published _tvos packages ship on (firebase_core - # 4.11.x -> firebase_core_platform_interface 7.1.0). Keeps the native Pigeon - # ABI in sync with firebase_core_tvos. + # Exact pins: firebase_core and firebase_core_tvos must move together. firebase_core: 4.11.0 firebase_crashlytics: 5.2.4 firebase_crashlytics_tvos: diff --git a/packages/firebase_performance_tvos/example/pubspec.yaml b/packages/firebase_performance_tvos/example/pubspec.yaml index 9cbd8fa..471dd80 100755 --- a/packages/firebase_performance_tvos/example/pubspec.yaml +++ b/packages/firebase_performance_tvos/example/pubspec.yaml @@ -7,9 +7,7 @@ environment: flutter: '>=3.27.0' dependencies: - # Pinned to the train the published _tvos packages ship on (firebase_core - # 4.11.x -> firebase_core_platform_interface 7.1.0). Keeps the native Pigeon - # ABI in sync with firebase_core_tvos. + # Exact pins: firebase_core and firebase_core_tvos must move together. firebase_core: 4.11.0 firebase_performance: 0.11.4+3 firebase_performance_tvos: diff --git a/packages/firebase_remote_config_tvos/example/pubspec.yaml b/packages/firebase_remote_config_tvos/example/pubspec.yaml index 589fa53..50622b2 100644 --- a/packages/firebase_remote_config_tvos/example/pubspec.yaml +++ b/packages/firebase_remote_config_tvos/example/pubspec.yaml @@ -7,9 +7,7 @@ environment: flutter: '>=3.27.0' dependencies: - # Pinned to the train the published _tvos packages ship on (firebase_core - # 4.11.x -> firebase_core_platform_interface 7.1.0). Keeps the native Pigeon - # ABI in sync with firebase_core_tvos. + # Exact pins: firebase_core and firebase_core_tvos must move together. firebase_core: 4.11.0 firebase_remote_config: 6.5.3 firebase_remote_config_tvos: From 0f664926226caf4917dee9d0e32890e6ae1524c8 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Mon, 14 Sep 2026 16:50:06 +0500 Subject: [PATCH 2/3] fix(firebase_app_check_tvos): pass recaptchaSiteKey from the provider factory --- .../tvos/Classes/FirebaseAppCheckPlugin.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift b/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift index 0e47d33..bc9636a 100644 --- a/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift +++ b/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift @@ -317,7 +317,8 @@ class FlutterAppCheckProviderFactory: NSObject, AppCheckProviderFactory { wrapper.configure( app: app, providerName: "deviceCheck", - debugToken: nil + debugToken: nil, + recaptchaSiteKey: nil ) providers[app.name] = wrapper } From 6e8e20b7ea874321163d7ed70d844302b0817387 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Fri, 18 Sep 2026 03:05:04 +0500 Subject: [PATCH 3/3] firebase_app_check_tvos: App Attest fallback fix, 0.4.7 docs/pins re-sync --- .../cloud_functions_tvos/example/pubspec.yaml | 3 +- .../firebase_ai_tvos/example/pubspec.yaml | 3 +- .../example/pubspec.yaml | 3 +- packages/firebase_app_check_tvos/CHANGELOG.md | 6 +- .../firebase_app_check_tvos/PORTING_REPORT.md | 63 ++++++++++++++++--- packages/firebase_app_check_tvos/README.md | 14 ++--- .../example/lib/main.dart | 28 +++++++-- .../example/pubspec.yaml | 5 +- packages/firebase_app_check_tvos/pubspec.yaml | 8 ++- .../tvos/Classes/Constants.swift | 2 +- .../tvos/Classes/FirebaseAppCheckPlugin.swift | 14 +++-- .../example/pubspec.yaml | 3 +- .../example/pubspec.yaml | 3 +- .../example/pubspec.yaml | 3 +- 14 files changed, 123 insertions(+), 35 deletions(-) diff --git a/packages/cloud_functions_tvos/example/pubspec.yaml b/packages/cloud_functions_tvos/example/pubspec.yaml index 4ba6201..4b27367 100755 --- a/packages/cloud_functions_tvos/example/pubspec.yaml +++ b/packages/cloud_functions_tvos/example/pubspec.yaml @@ -7,7 +7,8 @@ environment: flutter: '>=3.27.0' dependencies: - # Exact pins: firebase_core and firebase_core_tvos must move together. + # firebase_core is pinned exact; its train picks the matching firebase_core_tvos + # (4.11.x -> pi 7.1.0 -> 0.0.2); floating it breaks the native Pigeon ABI. cloud_functions: 6.3.3 cloud_functions_tvos: path: ../ diff --git a/packages/firebase_ai_tvos/example/pubspec.yaml b/packages/firebase_ai_tvos/example/pubspec.yaml index 22e531a..793e031 100755 --- a/packages/firebase_ai_tvos/example/pubspec.yaml +++ b/packages/firebase_ai_tvos/example/pubspec.yaml @@ -7,7 +7,8 @@ environment: flutter: '>=3.27.0' dependencies: - # Exact pins: firebase_core and firebase_core_tvos must move together. + # firebase_core is pinned exact; its train picks the matching firebase_core_tvos + # (4.11.x -> pi 7.1.0 -> 0.0.2); floating it breaks the native Pigeon ABI. firebase_ai: 3.13.1 firebase_ai_tvos: path: ../ diff --git a/packages/firebase_analytics_tvos/example/pubspec.yaml b/packages/firebase_analytics_tvos/example/pubspec.yaml index 8ab3bef..dbfe347 100755 --- a/packages/firebase_analytics_tvos/example/pubspec.yaml +++ b/packages/firebase_analytics_tvos/example/pubspec.yaml @@ -7,7 +7,8 @@ environment: flutter: '>=3.27.0' dependencies: - # Exact pins: firebase_core and firebase_core_tvos must move together. + # firebase_core is pinned exact; its train picks the matching firebase_core_tvos + # (4.11.x -> pi 7.1.0 -> 0.0.2); floating it breaks the native Pigeon ABI. firebase_core: 4.11.0 firebase_analytics: 12.4.3 firebase_analytics_tvos: diff --git a/packages/firebase_app_check_tvos/CHANGELOG.md b/packages/firebase_app_check_tvos/CHANGELOG.md index f90676f..5b366d1 100644 --- a/packages/firebase_app_check_tvos/CHANGELOG.md +++ b/packages/firebase_app_check_tvos/CHANGELOG.md @@ -3,7 +3,11 @@ * Regenerated the Pigeon messages against `firebase_app_check` 0.4.7: `activate()` takes a 5th `recaptchaSiteKey` argument and the `getTokenResult` channel is implemented. -* Requires `firebase_app_check` `>=0.4.7 <0.4.8`. +* Fixed `appAttestWithDeviceCheckFallback` never falling back: it now checks + `DCAppAttestService.shared.isSupported` rather than the OS version alone, + mirroring upstream's fix in 0.4.7 (flutterfire#18568). +* Requires `firebase_app_check` `>=0.4.7 <0.4.8` and `firebase_core_tvos` + `^0.0.3` (the `firebase_core_platform_interface` 8.1.1 train). ## 0.0.1 diff --git a/packages/firebase_app_check_tvos/PORTING_REPORT.md b/packages/firebase_app_check_tvos/PORTING_REPORT.md index 66425cd..2ee094b 100644 --- a/packages/firebase_app_check_tvos/PORTING_REPORT.md +++ b/packages/firebase_app_check_tvos/PORTING_REPORT.md @@ -2,7 +2,8 @@ Generated by `flutter-tvos plugin port` on 2026-08-14. -Source: `firebase_app_check` 0.4.5 (Swift). Base platform: ios. +Source: `firebase_app_check` 0.4.7 (Swift). Base platform: ios. +Originally ported from 0.4.5; re-synced to 0.4.7 for 0.0.2 (see addendum). Output: `./firebase_app_check_tvos` ## Summary @@ -49,18 +50,25 @@ firebase_core_shared` repointed to a plain `import firebase_core_tvos`. - **reCAPTCHA triage (the one real tvOS-incompatible API):** `RecaptchaProvider` is unavailable in the tvOS Firebase SDK, so the `case "recaptcha":` branch was narrowed from `#if (os(iOS) || os(tvOS))` to `#if os(iOS)`. The `case` is - **kept** (so Pigeon dispatch stays intact); on tvOS, requesting reCAPTCHA falls - back to the default DeviceCheck provider. tvOS callers should use DeviceCheck / - App Attest. + **kept** (so Pigeon dispatch stays intact); on tvOS `delegateProvider` is left + nil, so `getToken` fails with `Provider not configured` rather than silently + attesting as something else. tvOS callers should use DeviceCheck / App Attest. - **Deleted the generated `tvos/Package.swift`** (route via the podspec). - **`lib/firebase_app_check_tvos.dart`:** one-line re-export of `package:firebase_app_check/firebase_app_check.dart`. Copied Dart (`lib/src/`) removed. -**Version alignment:** matches `firebase_app_check 0.4.5` on the -`firebase_core_platform_interface` **7.1.0** train (`firebase_core 4.11.x`) — the -same train as `firebase_core_tvos`. 0.4.5+1 is core 4.12 and 0.4.6 is core 4.13 -(off-train), so 0.4.5 is the correct latest on this train. +**Version alignment (0.0.2):** matches `firebase_app_check 0.4.7` on the +`firebase_core_platform_interface` **8.1.1** train (`firebase_core 4.14.x`), +which is the train `firebase_core_tvos` 0.0.3 ships on — hence the +`firebase_core_tvos: ^0.0.3` floor here. 0.0.2 is excluded by the solver +(it declares pi `>=7.1.0 <8.1.0`) and 0.0.1 declares no pi constraint at all, so +without that floor pub could legally select a core pod whose native Pigeon +expects the pre-8.1 `FirebaseOptions` payload — a crash at launch rather than a +resolution error. + +Superseded (0.0.1): matched `firebase_app_check 0.4.5` on the pi **7.1.0** train +(`firebase_core 4.11.x`), when 0.4.6 was off-train. **Verified (runtime):** on the tvOS simulator (26.2, Apple TV 4K) against a live Firebase project — native `[FirebaseAppCheck] 12.15.0` initialized, the Debug @@ -78,3 +86,42 @@ pending. `Unknown=0 … Unsupported=4`), so on tvOS `Unsupported` (App Attest missing on older hardware) reaches Dart as `unknown`. Left verbatim to keep rebases cheap — the physical-hardware pass should expect `unknown` where `unsupported` is meant. + +## Addendum: re-sync to 0.4.7 (2026-09-17, release 0.0.2) + +- **`FirebaseAppCheckMessages.g.swift`** regenerated from `firebase_app_check` + 0.4.7 — byte-identical to upstream's apart from the `#if os(iOS)` → + `#if (os(iOS) || os(tvOS))` guard on line 9. Covers the two 0.4.6 contract + changes: `activate` gains a 5th `recaptchaSiteKey` argument, and + `getTokenResult` exists. +- **`FirebaseAppCheckPlugin.swift`:** `getTokenResult` and the `recaptchaSiteKey` + plumbing taken verbatim from 0.4.7; `appAttestWithDeviceCheckFallback` now + mirrors upstream's fix for flutterfire#18568 (check + `DCAppAttestService.shared.isSupported` rather than OS version alone, so the + provider actually falls back), with `import DeviceCheck` restored and the + porter's `tvOS 15.0` availability kept. +- **`Constants.swift`:** `versionNumber` → `"0.4.7"`, matching upstream. +- **Constraints:** `firebase_app_check ">=0.4.7 <0.4.8"`, + `firebase_core_tvos ^0.0.3`. + +**Verified on the simulator for 0.0.2** (tvOS 26.5, Apple TV 4K 3rd gen, native +`Firebase/AppCheck 12.15.0`): + +| Probe | Result | +| -------------------------------- | -------------------------------------------------------------- | +| `activate(Debug)` | ok — 5-argument payload accepted | +| `getToken` | reaches `…/exchangeDebugToken`, HTTP 403 (debug token not registered for this app) | +| `getLimitedUseToken` | same endpoint, same 403 — i.e. the channel dispatches | +| `activate(DeviceCheck)` | ok; `getToken` → "DeviceCheckProvider is not supported…" | +| `activate(AppAttestFallback)` | ok; `getToken` → "**DeviceCheckProvider** is not supported…" | + +The last row is the App Attest fallback fix, observed rather than inferred. The +simulator reports tvOS 26.5, so the old `#available(tvOS 15.0)` check passed and +selected App Attest — a control build with the pre-fix block reports +"**AppAttestProvider** is not supported…" on the same call. Naming DeviceCheck +means `DCAppAttestService.shared.isSupported` returned false and the provider +actually fell back. + +What the simulator still cannot show: a successfully *issued* token (needs a +debug token registered in the Firebase console) and real DeviceCheck / App +Attest attestation (needs a physical Apple TV). diff --git a/packages/firebase_app_check_tvos/README.md b/packages/firebase_app_check_tvos/README.md index 2b54bff..fbf0302 100644 --- a/packages/firebase_app_check_tvos/README.md +++ b/packages/firebase_app_check_tvos/README.md @@ -3,7 +3,7 @@ The tvOS implementation of [`firebase_app_check`](https://pub.dev/packages/firebase_app_check). > Ported with [`flutter-tvos plugin port`](https://github.com/fluttertv/flutter-tvos) -> from `firebase_app_check` 0.4.5, then finished + verified by hand. See +> from `firebase_app_check` 0.4.7, then finished + verified by hand. See > `PORTING_REPORT.md`. ## Usage @@ -13,10 +13,10 @@ automatically. Apps that already use `firebase_app_check` and target tvOS add: ```yaml dependencies: - firebase_app_check: ^0.4.5 - firebase_app_check_tvos: ^0.0.1 - firebase_core: ^4.11.0 - firebase_core_tvos: ^0.0.1 # tvOS core (this package depends on it) + firebase_app_check: ">=0.4.7 <0.4.8" # closed window: 0.4.8 may move the Pigeon contract + firebase_app_check_tvos: ^0.0.2 + firebase_core: ^4.14.0 + firebase_core_tvos: ^0.0.3 # tvOS core (this package depends on it) ``` Use the Apple providers that exist on tvOS 15+ — **DeviceCheck** or **App Attest**: @@ -41,7 +41,7 @@ await FirebaseAppCheck.instance.activate( | **DeviceCheck** (`AppleDeviceCheckProvider`) | ✅ (tvOS 15+) | | **App Attest** (`AppleAppAttestProvider`, …WithDeviceCheckFallback) | ✅ (tvOS 15+) | | **Debug** (`AppleDebugProvider`) | ✅ (use on the simulator) | -| **reCAPTCHA** (`AppleReCaptchaProvider`) | ❌ **not available** — `RecaptchaProvider` is iOS-only in the Firebase Apple SDK; not an Apple-TV provider. Use a DeviceCheck / App Attest provider on tvOS; if reCAPTCHA is requested, the tvOS build falls back to the default DeviceCheck provider rather than reCAPTCHA. | +| **reCAPTCHA** (`AppleReCaptchaProvider`) | ❌ **not available** — `RecaptchaProvider` is iOS-only in the Firebase Apple SDK; not an Apple-TV provider. Use a DeviceCheck / App Attest provider on tvOS; if reCAPTCHA is requested, the provider is left unconfigured and `getToken` fails with `Provider not configured`. | DeviceCheck / App Attest attestation runs on **real Apple TV hardware** (not the simulator — use the Debug provider there). @@ -51,7 +51,7 @@ simulator — use the Debug provider there). | Platform | Implemented | Verified | | --- | --- | --- | | Apple TV (`appletvos`) | yes | ⏳ pending physical Apple TV pass (DeviceCheck runs only on real hardware) | -| Apple TV simulator (`appletvsimulator`) | yes | ✅ verified — native `FirebaseAppCheck 12.15.0` initializes on tvOS, the Debug provider issues a token, and `getToken` round-trips to the App Check backend (`exchangeDebugToken`) via the Pigeon channel | +| Apple TV simulator (`appletvsimulator`) | yes | ✅ re-verified for 0.0.2 on tvOS 26.5 (Apple TV 4K, 3rd gen) — all three `activate()` calls cross the 5-argument Pigeon payload, and `getToken` / `getLimitedUseToken` both reach `exchangeDebugToken` on the App Check backend and return typed errors to Dart. Token *issuance* still needs a debug token registered in the console; attestation itself needs real hardware | ## License diff --git a/packages/firebase_app_check_tvos/example/lib/main.dart b/packages/firebase_app_check_tvos/example/lib/main.dart index 9905ed0..38232d7 100644 --- a/packages/firebase_app_check_tvos/example/lib/main.dart +++ b/packages/firebase_app_check_tvos/example/lib/main.dart @@ -39,6 +39,11 @@ class _AppCheckHomePageState extends State { String _status = 'Ready.'; String _token = '(none yet)'; + /// A token is ~700 chars of JWT; show enough to tell a real one from a stub. + static String _describe(String? token) => token == null + ? '(null)' + : '${token.substring(0, token.length.clamp(0, 24))}… (${token.length} chars)'; + Future _run(String label, Future Function() action) async { try { await action(); @@ -76,17 +81,30 @@ class _AppCheckHomePageState extends State { }), child: const Text('activate (DeviceCheck)'), ), + ElevatedButton( + onPressed: () => _run('activate (Debug)', () async { + // The only provider that can mint a token on the simulator: + // App Attest and DeviceCheck both need real hardware. + await _appCheck.activate( + providerApple: const AppleDebugProvider(), + ); + }), + child: const Text('activate (Debug)'), + ), ElevatedButton( onPressed: () => _run('getToken', () async { final token = await _appCheck.getToken(); - setState(() { - _token = token == null - ? '(null)' - : '${token.substring(0, token.length.clamp(0, 24))}… (${token.length} chars)'; - }); + setState(() => _token = _describe(token)); }), child: const Text('get App Check token'), ), + ElevatedButton( + onPressed: () => _run('getLimitedUseToken', () async { + final token = await _appCheck.getLimitedUseToken(); + setState(() => _token = _describe(token)); + }), + child: const Text('get limited-use token'), + ), ], ), ], diff --git a/packages/firebase_app_check_tvos/example/pubspec.yaml b/packages/firebase_app_check_tvos/example/pubspec.yaml index 41b3b68..c657b2b 100644 --- a/packages/firebase_app_check_tvos/example/pubspec.yaml +++ b/packages/firebase_app_check_tvos/example/pubspec.yaml @@ -7,12 +7,13 @@ environment: flutter: '>=3.27.0' dependencies: - # Exact pins: firebase_core and firebase_core_tvos must move together. + # firebase_core is pinned exact and firebase_core_tvos floored to match: + # 0.4.7 needs pi ^8.1.1 (the 4.14.x train), which only core_tvos 0.0.3 accepts. firebase_core: 4.14.0 firebase_app_check: 0.4.7 firebase_app_check_tvos: path: ../ - firebase_core_tvos: ^0.0.1 + firebase_core_tvos: ^0.0.3 flutter: sdk: flutter diff --git a/packages/firebase_app_check_tvos/pubspec.yaml b/packages/firebase_app_check_tvos/pubspec.yaml index 6669104..acba218 100644 --- a/packages/firebase_app_check_tvos/pubspec.yaml +++ b/packages/firebase_app_check_tvos/pubspec.yaml @@ -22,7 +22,13 @@ dependencies: # package's native pod to the generated Podfile if it's reachable here, # which the podspec's `s.dependency 'firebase_core_tvos'` then resolves # instead of failing over to upstream's iOS-only firebase_core pod. - firebase_core_tvos: ^0.0.1 + # + # Floored at 0.0.3, not ^0.0.1: firebase_app_check 0.4.7 requires + # firebase_core_platform_interface ^8.1.1, which 0.0.2 excludes by its own + # constraint but 0.0.1 does not constrain at all. Without this floor pub can + # legally pick 0.0.1, whose native Pigeon expects the pre-8.1 FirebaseOptions + # payload — a crash at launch instead of a solver error. + firebase_core_tvos: ^0.0.3 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/packages/firebase_app_check_tvos/tvos/Classes/Constants.swift b/packages/firebase_app_check_tvos/tvos/Classes/Constants.swift index 28a2ec5..189b1dc 100644 --- a/packages/firebase_app_check_tvos/tvos/Classes/Constants.swift +++ b/packages/firebase_app_check_tvos/tvos/Classes/Constants.swift @@ -3,4 +3,4 @@ // found in the LICENSE file. /// Auto-generated file. Do not edit. -public let versionNumber = "0.4.5" +public let versionNumber = "0.4.7" diff --git a/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift b/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift index bc9636a..ce99bdb 100644 --- a/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift +++ b/packages/firebase_app_check_tvos/tvos/Classes/FirebaseAppCheckPlugin.swift @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import DeviceCheck import FirebaseAppCheck import FirebaseCore @@ -368,11 +369,16 @@ class AppCheckProviderWrapper: NSObject, AppCheckProvider { delegateProvider = AppCheckDebugProvider(app: app) } case "appAttestWithDeviceCheckFallback": - if #available(iOS 14.0, tvOS 15.0, macOS 14.0, *) { - delegateProvider = AppAttestProvider(app: app) - } else { - delegateProvider = DeviceCheckProvider(app: app) + // A new enough OS is not sufficient: the device must also support App + // Attest, which is false on the tvOS simulator. The SDK only reports that + // when a token is first requested, so check the same capability it checks + // — otherwise this provider never actually falls back. Typed as the + // protocol to keep the availability-annotated type inside `#available`. + var appAttestProvider: (any AppCheckProvider)? + if #available(iOS 14.0, tvOS 15.0, macOS 14.0, *), DCAppAttestService.shared.isSupported { + appAttestProvider = AppAttestProvider(app: app) } + delegateProvider = appAttestProvider ?? DeviceCheckProvider(app: app) case "recaptcha": // `RecaptchaProvider` is unavailable in the tvOS Firebase SDK (reCAPTCHA // App Check is not a tvOS provider — tvOS uses DeviceCheck / App Attest). diff --git a/packages/firebase_crashlytics_tvos/example/pubspec.yaml b/packages/firebase_crashlytics_tvos/example/pubspec.yaml index b90fc95..e4a9bf2 100755 --- a/packages/firebase_crashlytics_tvos/example/pubspec.yaml +++ b/packages/firebase_crashlytics_tvos/example/pubspec.yaml @@ -7,7 +7,8 @@ environment: flutter: '>=3.27.0' dependencies: - # Exact pins: firebase_core and firebase_core_tvos must move together. + # firebase_core is pinned exact; its train picks the matching firebase_core_tvos + # (4.11.x -> pi 7.1.0 -> 0.0.2); floating it breaks the native Pigeon ABI. firebase_core: 4.11.0 firebase_crashlytics: 5.2.4 firebase_crashlytics_tvos: diff --git a/packages/firebase_performance_tvos/example/pubspec.yaml b/packages/firebase_performance_tvos/example/pubspec.yaml index 471dd80..b81ef30 100755 --- a/packages/firebase_performance_tvos/example/pubspec.yaml +++ b/packages/firebase_performance_tvos/example/pubspec.yaml @@ -7,7 +7,8 @@ environment: flutter: '>=3.27.0' dependencies: - # Exact pins: firebase_core and firebase_core_tvos must move together. + # firebase_core is pinned exact; its train picks the matching firebase_core_tvos + # (4.11.x -> pi 7.1.0 -> 0.0.2); floating it breaks the native Pigeon ABI. firebase_core: 4.11.0 firebase_performance: 0.11.4+3 firebase_performance_tvos: diff --git a/packages/firebase_remote_config_tvos/example/pubspec.yaml b/packages/firebase_remote_config_tvos/example/pubspec.yaml index 50622b2..7ef3f36 100644 --- a/packages/firebase_remote_config_tvos/example/pubspec.yaml +++ b/packages/firebase_remote_config_tvos/example/pubspec.yaml @@ -7,7 +7,8 @@ environment: flutter: '>=3.27.0' dependencies: - # Exact pins: firebase_core and firebase_core_tvos must move together. + # firebase_core is pinned exact; its train picks the matching firebase_core_tvos + # (4.11.x -> pi 7.1.0 -> 0.0.2); floating it breaks the native Pigeon ABI. firebase_core: 4.11.0 firebase_remote_config: 6.5.3 firebase_remote_config_tvos: