diff --git a/packages/firebase_app_check/firebase_app_check/android/build.gradle b/packages/firebase_app_check/firebase_app_check/android/build.gradle index d4fdbc15fca9..c154343bf696 100644 --- a/packages/firebase_app_check/firebase_app_check/android/build.gradle +++ b/packages/firebase_app_check/firebase_app_check/android/build.gradle @@ -7,6 +7,7 @@ apply from: file("local-config.gradle") buildscript { ext.kotlin_version = "2.0.0" repositories { + mavenLocal() google() mavenCentral() } @@ -14,6 +15,7 @@ buildscript { rootProject.allprojects { repositories { + mavenLocal() google() mavenCentral() } @@ -78,6 +80,7 @@ android { implementation platform("com.google.firebase:firebase-bom:${getRootProjectExtOrCoreProperty("FirebaseSDKVersion", firebaseCoreProject)}") implementation 'com.google.firebase:firebase-appcheck-debug' implementation 'com.google.firebase:firebase-appcheck-playintegrity' + implementation 'com.google.firebase:firebase-appcheck-recaptcha:16.0.0-beta01' implementation 'androidx.annotation:annotation:1.7.0' } } diff --git a/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/FirebaseAppCheckPlugin.kt b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/FirebaseAppCheckPlugin.kt index cdf1ca77a6ea..81ecc937febd 100644 --- a/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/FirebaseAppCheckPlugin.kt +++ b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/FirebaseAppCheckPlugin.kt @@ -11,6 +11,7 @@ import com.google.firebase.FirebaseApp import com.google.firebase.appcheck.FirebaseAppCheck import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory +import com.google.firebase.appcheck.recaptcha.RecaptchaAppCheckProviderFactory import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding import io.flutter.plugin.common.BinaryMessenger @@ -62,6 +63,11 @@ class FirebaseAppCheckPlugin : FlutterFirebasePlugin, FlutterPlugin, FirebaseApp firebaseAppCheck.installAppCheckProviderFactory( DebugAppCheckProviderFactory.getInstance()) } + "recaptcha" -> { + firebaseAppCheck.installAppCheckProviderFactory( + RecaptchaAppCheckProviderFactory.getInstance() + ) + } else -> { firebaseAppCheck.installAppCheckProviderFactory( PlayIntegrityAppCheckProviderFactory.getInstance()) diff --git a/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/GeneratedAndroidFirebaseAppCheck.g.kt b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/GeneratedAndroidFirebaseAppCheck.g.kt index 5e3ea8faca0c..97f6635352ad 100644 --- a/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/GeneratedAndroidFirebaseAppCheck.g.kt +++ b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/GeneratedAndroidFirebaseAppCheck.g.kt @@ -1,7 +1,7 @@ // Copyright 2025, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -10,11 +10,12 @@ package io.flutter.plugins.firebase.appcheck import android.util.Log import io.flutter.plugin.common.BasicMessageChannel import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec import io.flutter.plugin.common.StandardMessageCodec import java.io.ByteArrayOutputStream import java.nio.ByteBuffer - private object GeneratedAndroidFirebaseAppCheckPigeonUtils { fun wrapResult(result: Any?): List { @@ -23,82 +24,61 @@ private object GeneratedAndroidFirebaseAppCheckPigeonUtils { fun wrapError(exception: Throwable): List { return if (exception is FlutterError) { - listOf(exception.code, exception.message, exception.details) + listOf( + exception.code, + exception.message, + exception.details + ) } else { listOf( - exception.javaClass.simpleName, - exception.toString(), - "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) } } } /** * Error class for passing custom error details to Flutter via a thrown PlatformException. - * * @property code The error code. * @property message The error message. * @property details The error details. Must be a datatype supported by the api codec. */ -class FlutterError( - val code: String, - override val message: String? = null, - val details: Any? = null -) : Throwable() - +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : RuntimeException() private open class GeneratedAndroidFirebaseAppCheckPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { - return super.readValueOfType(type, buffer) + return super.readValueOfType(type, buffer) } - - override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { super.writeValue(stream, value) } } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface FirebaseAppCheckHostApi { - fun activate( - appName: String, - androidProvider: String?, - appleProvider: String?, - debugToken: String?, - callback: (Result) -> Unit - ) - + fun activate(appName: String, androidProvider: String?, appleProvider: String?, debugToken: String?, callback: (Result) -> Unit) fun getToken(appName: String, forceRefresh: Boolean, callback: (Result) -> Unit) - - fun setTokenAutoRefreshEnabled( - appName: String, - isTokenAutoRefreshEnabled: Boolean, - callback: (Result) -> Unit - ) - + fun setTokenAutoRefreshEnabled(appName: String, isTokenAutoRefreshEnabled: Boolean, callback: (Result) -> Unit) fun registerTokenListener(appName: String, callback: (Result) -> Unit) - fun getLimitedUseAppCheckToken(appName: String, callback: (Result) -> Unit) companion object { /** The codec used by FirebaseAppCheckHostApi. */ - val codec: MessageCodec by lazy { GeneratedAndroidFirebaseAppCheckPigeonCodec() } - /** - * Sets up an instance of `FirebaseAppCheckHostApi` to handle messages through the - * `binaryMessenger`. - */ + val codec: MessageCodec by lazy { + GeneratedAndroidFirebaseAppCheckPigeonCodec() + } + /** Sets up an instance of `FirebaseAppCheckHostApi` to handle messages through the `binaryMessenger`. */ @JvmOverloads - fun setUp( - binaryMessenger: BinaryMessenger, - api: FirebaseAppCheckHostApi?, - messageChannelSuffix: String = "" - ) { - val separatedMessageChannelSuffix = - if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + fun setUp(binaryMessenger: BinaryMessenger, api: FirebaseAppCheckHostApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List @@ -106,8 +86,7 @@ interface FirebaseAppCheckHostApi { val androidProviderArg = args[1] as String? val appleProviderArg = args[2] as String? val debugTokenArg = args[3] as String? - api.activate(appNameArg, androidProviderArg, appleProviderArg, debugTokenArg) { - result: Result -> + api.activate(appNameArg, androidProviderArg, appleProviderArg, debugTokenArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapError(error)) @@ -121,11 +100,7 @@ interface FirebaseAppCheckHostApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getToken$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getToken$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List @@ -146,18 +121,13 @@ interface FirebaseAppCheckHostApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.setTokenAutoRefreshEnabled$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.setTokenAutoRefreshEnabled$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val appNameArg = args[0] as String val isTokenAutoRefreshEnabledArg = args[1] as Boolean - api.setTokenAutoRefreshEnabled(appNameArg, isTokenAutoRefreshEnabledArg) { - result: Result -> + api.setTokenAutoRefreshEnabled(appNameArg, isTokenAutoRefreshEnabledArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapError(error)) @@ -171,11 +141,7 @@ interface FirebaseAppCheckHostApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.registerTokenListener$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.registerTokenListener$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List @@ -195,11 +161,7 @@ interface FirebaseAppCheckHostApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getLimitedUseAppCheckToken$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getLimitedUseAppCheckToken$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List diff --git a/packages/firebase_app_check/firebase_app_check/example/android/build.gradle b/packages/firebase_app_check/firebase_app_check/example/android/build.gradle index d2ffbffa4cd2..c63004eaacea 100644 --- a/packages/firebase_app_check/firebase_app_check/example/android/build.gradle +++ b/packages/firebase_app_check/firebase_app_check/example/android/build.gradle @@ -1,5 +1,6 @@ allprojects { repositories { + mavenLocal() google() mavenCentral() } diff --git a/packages/firebase_app_check/firebase_app_check/example/android/settings.gradle b/packages/firebase_app_check/firebase_app_check/example/android/settings.gradle index 30463c1cf2f2..4fb566e9929e 100644 --- a/packages/firebase_app_check/firebase_app_check/example/android/settings.gradle +++ b/packages/firebase_app_check/firebase_app_check/example/android/settings.gradle @@ -22,7 +22,7 @@ plugins { // START: FlutterFire Configuration id "com.google.gms.google-services" version "4.3.15" apply false // END: FlutterFire Configuration - id "org.jetbrains.kotlin.android" version "1.9.22" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false } include ":app" diff --git a/packages/firebase_app_check/firebase_app_check/example/lib/main.dart b/packages/firebase_app_check/firebase_app_check/example/lib/main.dart index 8f26c6439a83..e400ad08fbeb 100644 --- a/packages/firebase_app_check/firebase_app_check/example/lib/main.dart +++ b/packages/firebase_app_check/firebase_app_check/example/lib/main.dart @@ -79,13 +79,21 @@ class _FirebaseAppCheck extends State { final appCheck = FirebaseAppCheck.instance; String _message = ''; String _eventToken = 'not yet'; + late final TextEditingController _webSiteKeyController; @override void initState() { + _webSiteKeyController = TextEditingController(text: kWebRecaptchaSiteKey); appCheck.onTokenChange.listen(setEventToken); super.initState(); } + @override + void dispose() { + _webSiteKeyController.dispose(); + super.dispose(); + } + void setMessage(String message) { setState(() { _message = message; @@ -101,16 +109,25 @@ class _FirebaseAppCheck extends State { Future _activate({ AndroidAppCheckProvider? android, AppleAppCheckProvider? apple, + String? webProviderType, WindowsAppCheckProvider? windows, }) async { try { + dynamic providerWeb; + if (webProviderType == 'enterprise') { + providerWeb = ReCaptchaEnterpriseProvider(_webSiteKeyController.text); + } else if (webProviderType == 'v3') { + providerWeb = ReCaptchaV3Provider(_webSiteKeyController.text); + } + await appCheck.activate( providerAndroid: android ?? const AndroidPlayIntegrityProvider(), providerApple: apple ?? const AppleDeviceCheckProvider(), - providerWeb: ReCaptchaV3Provider(kWebRecaptchaSiteKey), + providerWeb: providerWeb, providerWindows: windows ?? const WindowsDebugProvider(), ); final providerName = windows?.runtimeType.toString() ?? + webProviderType ?? apple?.runtimeType.toString() ?? android?.runtimeType.toString() ?? 'default'; @@ -136,40 +153,103 @@ class _FirebaseAppCheck extends State { style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), - ElevatedButton( - onPressed: () => _activate( - android: const AndroidDebugProvider(), - apple: const AppleDebugProvider(), - windows: WindowsDebugProvider( - debugToken: - kWindowsDebugToken.isNotEmpty ? kWindowsDebugToken : null, + if (kIsWeb) ...[ + ElevatedButton( + onPressed: () => _activate( + webProviderType: 'v3', ), + child: const Text('activate(Web reCAPTCHA v3)'), ), - child: const Text('activate(Debug)'), - ), - ElevatedButton( - onPressed: () => _activate( - android: const AndroidPlayIntegrityProvider(), - apple: const AppleDeviceCheckProvider(), + const SizedBox(height: 8), + TextField( + controller: _webSiteKeyController, + decoration: const InputDecoration( + labelText: 'Web reCAPTCHA Site Key', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 8), + ElevatedButton( + onPressed: () => _activate( + webProviderType: 'enterprise', + ), + child: const Text('activate(Web reCAPTCHA Enterprise)'), + ), + ], + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) ...[ + ElevatedButton( + onPressed: () => _activate( + android: const AndroidDebugProvider(), + ), + child: const Text('activate(Android Debug)'), + ), + ElevatedButton( + onPressed: () => _activate( + android: const AndroidPlayIntegrityProvider(), + ), + child: const Text('activate(Android Play Integrity)'), + ), + + const SizedBox(height: 8), + ElevatedButton( + onPressed: () => _activate( + android: const AndroidReCaptchaProvider(), + ), + child: const Text('activate(Android reCAPTCHA)'), + ), + ], + if (!kIsWeb && + (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS)) ...[ + ElevatedButton( + onPressed: () => _activate( + apple: const AppleDebugProvider(), + ), + child: const Text('activate(Apple Debug)'), + ), + ElevatedButton( + onPressed: () => _activate( + apple: const AppleDeviceCheckProvider(), + ), + child: const Text('activate(Apple DeviceCheck)'), ), - child: const Text('activate(PlayIntegrity / DeviceCheck)'), - ), - if (!kIsWeb) ElevatedButton( onPressed: () => _activate( apple: const AppleAppAttestProvider(), ), - child: const Text('activate(AppAttest)'), + child: const Text('activate(Apple AppAttest)'), ), - if (!kIsWeb) ElevatedButton( onPressed: () => _activate( apple: const AppleAppAttestWithDeviceCheckFallbackProvider(), ), child: const Text( - 'activate(AppAttest + DeviceCheck fallback)', + 'activate(Apple AppAttest + DeviceCheck fallback)', + ), + ), + if (defaultTargetPlatform == TargetPlatform.iOS) ...[ + + const SizedBox(height: 8), + ElevatedButton( + onPressed: () => _activate( + apple: const AppleReCaptchaProvider(), + ), + child: const Text('activate(Apple reCAPTCHA)'), + ), + ], + ], + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.windows) ...[ + ElevatedButton( + onPressed: () => _activate( + windows: WindowsDebugProvider( + debugToken: kWindowsDebugToken.isNotEmpty + ? kWindowsDebugToken + : null, + ), ), + child: const Text('activate(Windows Debug)'), ), + ], const SizedBox(height: 16), const Text( 'Actions', @@ -226,7 +306,7 @@ class _FirebaseAppCheck extends State { child: const Text('Test Firestore with App Check'), ), const SizedBox(height: 20), - Text( + SelectableText( _message, style: const TextStyle( color: Color.fromRGBO(47, 79, 79, 1), @@ -234,7 +314,7 @@ class _FirebaseAppCheck extends State { ), ), const SizedBox(height: 20), - Text( + SelectableText( 'Token from onTokenChange: $_eventToken', style: const TextStyle( color: Color.fromRGBO(128, 0, 128, 1), diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift index 57e0accedf53..ac93fc9c338f 100644 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift +++ b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift @@ -1,7 +1,7 @@ // Copyright 2025, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -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?] { @@ -52,13 +53,13 @@ private func wrapError(_ error: Any) -> [Any?] { } return [ "\(error)", - "\(type(of: error))", + "\(Swift.type(of: error))", "Stacktrace: \(Thread.callStackSymbols)", ] } private func isNullish(_ value: Any?) -> Bool { - value is NSNull || value == nil + return value is NSNull || value == nil } private func nilOrValue(_ value: Any?) -> T? { @@ -66,76 +67,52 @@ private func nilOrValue(_ value: Any?) -> T? { return value as! T? } -private class FirebaseAppCheckMessagesPigeonCodecReader: FlutterStandardReader {} -private class FirebaseAppCheckMessagesPigeonCodecWriter: FlutterStandardWriter {} +private class FirebaseAppCheckMessagesPigeonCodecReader: FlutterStandardReader { +} + +private class FirebaseAppCheckMessagesPigeonCodecWriter: FlutterStandardWriter { +} 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() - ) + static let shared = FirebaseAppCheckMessagesPigeonCodec(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) - func getToken( - appName: String, forceRefresh: Bool, - completion: @escaping (Result) -> Void) - func setTokenAutoRefreshEnabled( - appName: String, isTokenAutoRefreshEnabled: Bool, - completion: @escaping (Result) -> Void) + func activate(appName: String, androidProvider: String?, appleProvider: String?, debugToken: String?, completion: @escaping (Result) -> Void) + func getToken(appName: String, forceRefresh: Bool, 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) + func getLimitedUseAppCheckToken(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 func setUp( - binaryMessenger: FlutterBinaryMessenger, api: FirebaseAppCheckHostApi?, - messageChannelSuffix: String = "" - ) { + 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 = "") { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - let activateChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate\(channelSuffix)", - binaryMessenger: binaryMessenger, - codec: codec - ) - if let api { + let activateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate\(channelSuffix)", 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]) - api.activate( - appName: appNameArg, - androidProvider: androidProviderArg, - appleProvider: appleProviderArg, - debugToken: debugTokenArg - ) { result in + api.activate(appName: appNameArg, androidProvider: androidProviderArg, appleProvider: appleProviderArg, debugToken: debugTokenArg) { result in switch result { case .success: reply(wrapResult(nil)) @@ -147,13 +124,8 @@ class FirebaseAppCheckHostApiSetup { } else { activateChannel.setMessageHandler(nil) } - let getTokenChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getToken\(channelSuffix)", - binaryMessenger: binaryMessenger, - codec: codec - ) - if let api { + let getTokenChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getToken\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { getTokenChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appNameArg = args[0] as! String @@ -170,21 +142,13 @@ class FirebaseAppCheckHostApiSetup { } else { getTokenChannel.setMessageHandler(nil) } - let setTokenAutoRefreshEnabledChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.setTokenAutoRefreshEnabled\(channelSuffix)", - binaryMessenger: binaryMessenger, - codec: codec - ) - if let api { + let setTokenAutoRefreshEnabledChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.setTokenAutoRefreshEnabled\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { setTokenAutoRefreshEnabledChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appNameArg = args[0] as! String let isTokenAutoRefreshEnabledArg = args[1] as! Bool - api.setTokenAutoRefreshEnabled( - appName: appNameArg, - isTokenAutoRefreshEnabled: isTokenAutoRefreshEnabledArg - ) { result in + api.setTokenAutoRefreshEnabled(appName: appNameArg, isTokenAutoRefreshEnabled: isTokenAutoRefreshEnabledArg) { result in switch result { case .success: reply(wrapResult(nil)) @@ -196,13 +160,8 @@ class FirebaseAppCheckHostApiSetup { } else { setTokenAutoRefreshEnabledChannel.setMessageHandler(nil) } - let registerTokenListenerChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.registerTokenListener\(channelSuffix)", - binaryMessenger: binaryMessenger, - codec: codec - ) - if let api { + let registerTokenListenerChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.registerTokenListener\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { registerTokenListenerChannel.setMessageHandler { message, reply in let args = message as! [Any?] let appNameArg = args[0] as! String @@ -218,13 +177,8 @@ class FirebaseAppCheckHostApiSetup { } else { registerTokenListenerChannel.setMessageHandler(nil) } - let getLimitedUseAppCheckTokenChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getLimitedUseAppCheckToken\(channelSuffix)", - binaryMessenger: binaryMessenger, - codec: codec - ) - if let api { + let getLimitedUseAppCheckTokenChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getLimitedUseAppCheckToken\(channelSuffix)", 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/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift index c737dcd75a22..631681d5ebd3 100644 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift +++ b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift @@ -79,7 +79,11 @@ public class FirebaseAppCheckPlugin: NSObject, FlutterPlugin, } let provider = appleProvider ?? "deviceCheck" - providerFactory?.configure(app: app, providerName: provider, debugToken: debugToken) + providerFactory?.configure( + app: app, + providerName: provider, + debugToken: debugToken + ) completion(.success(())) } @@ -192,7 +196,7 @@ public class FirebaseAppCheckPlugin: NSObject, FlutterPlugin, channel.setStreamHandler(nil) } for (_, handler) in streamHandlers { - handler.onCancel(withArguments: nil) + _ = handler.onCancel(withArguments: nil) } eventChannels.removeAll() streamHandlers.removeAll() @@ -277,24 +281,40 @@ class FlutterAppCheckProviderFactory: NSObject, AppCheckProviderFactory { if providers[app.name] == nil { let wrapper = AppCheckProviderWrapper() // Default to deviceCheck. activate() will reconfigure with the correct provider. - wrapper.configure(app: app, providerName: "deviceCheck", debugToken: nil) + wrapper.configure( + app: app, + providerName: "deviceCheck", + debugToken: nil + ) providers[app.name] = wrapper } return providers[app.name] } - func configure(app: FirebaseApp, providerName: String, debugToken: String?) { + func configure( + app: FirebaseApp, + providerName: String, + debugToken: String? + ) { if providers[app.name] == nil { providers[app.name] = AppCheckProviderWrapper() } - providers[app.name]?.configure(app: app, providerName: providerName, debugToken: debugToken) + providers[app.name]?.configure( + app: app, + providerName: providerName, + debugToken: debugToken + ) } } class AppCheckProviderWrapper: NSObject, AppCheckProvider { private var delegateProvider: (any AppCheckProvider)? - func configure(app: FirebaseApp, providerName: String, debugToken: String?) { + func configure( + app: FirebaseApp, + providerName: String, + debugToken: String? + ) { switch providerName { case "debug": if let debugToken { @@ -316,6 +336,15 @@ class AppCheckProviderWrapper: NSObject, AppCheckProvider { } else { delegateProvider = DeviceCheckProvider(app: app) } + case "recaptcha": + #if os(iOS) + delegateProvider = RecaptchaProvider(app: app) + if delegateProvider == nil { + print("Firebase App Check: failed to initialize RecaptchaProvider. Ensure site key is in GoogleService-Info.plist.") + } + #else + print("Firebase App Check: reCAPTCHA is only supported on iOS.") + #endif default: // deviceCheck delegateProvider = DeviceCheckProvider(app: app) diff --git a/packages/firebase_app_check/firebase_app_check/lib/firebase_app_check.dart b/packages/firebase_app_check/firebase_app_check/lib/firebase_app_check.dart index cd569468c12c..88dcb0f16064 100644 --- a/packages/firebase_app_check/firebase_app_check/lib/firebase_app_check.dart +++ b/packages/firebase_app_check/firebase_app_check/lib/firebase_app_check.dart @@ -13,12 +13,14 @@ export 'package:firebase_app_check_platform_interface/firebase_app_check_platfor AndroidAppCheckProvider, AndroidDebugProvider, AndroidPlayIntegrityProvider, + AndroidReCaptchaProvider, AppleProvider, AppleAppCheckProvider, AppleDebugProvider, AppleDeviceCheckProvider, AppleAppAttestProvider, AppleAppAttestWithDeviceCheckFallbackProvider, + AppleReCaptchaProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider, WebDebugProvider, diff --git a/packages/firebase_app_check/firebase_app_check/windows/messages.g.cpp b/packages/firebase_app_check/firebase_app_check/windows/messages.g.cpp index 0343b73ea815..0da3e3c1ded5 100644 --- a/packages/firebase_app_check/firebase_app_check/windows/messages.g.cpp +++ b/packages/firebase_app_check/firebase_app_check/windows/messages.g.cpp @@ -1,7 +1,7 @@ // Copyright 2025, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -13,16 +13,18 @@ #include #include +#include +#include #include #include #include namespace firebase_app_check_windows { -using flutter::BasicMessageChannel; -using flutter::CustomEncodableValue; -using flutter::EncodableList; -using flutter::EncodableMap; -using flutter::EncodableValue; +using ::flutter::BasicMessageChannel; +using ::flutter::CustomEncodableValue; +using ::flutter::EncodableList; +using ::flutter::EncodableMap; +using ::flutter::EncodableValue; FlutterError CreateConnectionError(const std::string channel_name) { return FlutterError( @@ -31,34 +33,242 @@ FlutterError CreateConnectionError(const std::string channel_name) { EncodableValue("")); } +namespace { +template +bool PigeonInternalDeepEquals(const T& a, const T& b); + +bool PigeonInternalDeepEquals(const double& a, const double& b); + +template +bool PigeonInternalDeepEquals(const std::vector& a, const std::vector& b); + +template +bool PigeonInternalDeepEquals(const std::map& a, const std::map& b); + +template +bool PigeonInternalDeepEquals(const std::optional& a, + const std::optional& b); + +template +bool PigeonInternalDeepEquals(const std::unique_ptr& a, + const std::unique_ptr& b); + +bool PigeonInternalDeepEquals(const ::flutter::EncodableValue& a, + const ::flutter::EncodableValue& b); + +template +bool PigeonInternalDeepEquals(const T& a, const T& b) { + return a == b; +} + +template +bool PigeonInternalDeepEquals(const std::vector& a, + const std::vector& b) { + if (a.size() != b.size()) { + return false; + } + for (size_t i = 0; i < a.size(); ++i) { + if (!PigeonInternalDeepEquals(a[i], b[i])) { + return false; + } + } + return true; +} + +template +bool PigeonInternalDeepEquals(const std::map& a, + const std::map& b) { + if (a.size() != b.size()) { + return false; + } + for (const auto& kv : a) { + bool found = false; + for (const auto& b_kv : b) { + if (PigeonInternalDeepEquals(kv.first, b_kv.first)) { + if (PigeonInternalDeepEquals(kv.second, b_kv.second)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; +} + +bool PigeonInternalDeepEquals(const double& a, const double& b) { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (a == b) || (std::isnan(a) && std::isnan(b)); +} + +template +bool PigeonInternalDeepEquals(const std::optional& a, + const std::optional& b) { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + return PigeonInternalDeepEquals(*a, *b); +} + +template +bool PigeonInternalDeepEquals(const std::unique_ptr& a, + const std::unique_ptr& b) { + if (a.get() == b.get()) { + return true; + } + if (!a || !b) { + return false; + } + return PigeonInternalDeepEquals(*a, *b); +} + +bool PigeonInternalDeepEquals(const ::flutter::EncodableValue& a, + const ::flutter::EncodableValue& b) { + if (a.index() != b.index()) { + return false; + } + if (const double* da = std::get_if(&a)) { + return PigeonInternalDeepEquals(*da, std::get(b)); + } else if (const ::flutter::EncodableList* la = + std::get_if<::flutter::EncodableList>(&a)) { + return PigeonInternalDeepEquals(*la, std::get<::flutter::EncodableList>(b)); + } else if (const ::flutter::EncodableMap* ma = + std::get_if<::flutter::EncodableMap>(&a)) { + return PigeonInternalDeepEquals(*ma, std::get<::flutter::EncodableMap>(b)); + } + return a == b; +} + +template +size_t PigeonInternalDeepHash(const T& v); + +size_t PigeonInternalDeepHash(const double& v); + +template +size_t PigeonInternalDeepHash(const std::vector& v); + +template +size_t PigeonInternalDeepHash(const std::map& v); + +template +size_t PigeonInternalDeepHash(const std::optional& v); + +template +size_t PigeonInternalDeepHash(const std::unique_ptr& v); + +size_t PigeonInternalDeepHash(const ::flutter::EncodableValue& v); + +template +size_t PigeonInternalDeepHash(const T& v) { + return std::hash()(v); +} + +template +size_t PigeonInternalDeepHash(const std::vector& v) { + size_t result = 1; + for (const auto& item : v) { + result = result * 31 + PigeonInternalDeepHash(item); + } + return result; +} + +template +size_t PigeonInternalDeepHash(const std::map& v) { + size_t result = 0; + for (const auto& kv : v) { + result += ((PigeonInternalDeepHash(kv.first) * 31) ^ + PigeonInternalDeepHash(kv.second)); + } + return result; +} + +size_t PigeonInternalDeepHash(const double& v) { + if (std::isnan(v)) { + // Normalize NaN to a consistent hash. + return std::hash()(std::numeric_limits::quiet_NaN()); + } + if (v == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return std::hash()(0.0); + } + return std::hash()(v); +} + +template +size_t PigeonInternalDeepHash(const std::optional& v) { + return v ? PigeonInternalDeepHash(*v) : 0; +} + +template +size_t PigeonInternalDeepHash(const std::unique_ptr& v) { + return v ? PigeonInternalDeepHash(*v) : 0; +} + +size_t PigeonInternalDeepHash(const ::flutter::EncodableValue& v) { + size_t result = v.index(); + if (const double* dv = std::get_if(&v)) { + result = result * 31 + PigeonInternalDeepHash(*dv); + } else if (const ::flutter::EncodableList* lv = + std::get_if<::flutter::EncodableList>(&v)) { + result = result * 31 + PigeonInternalDeepHash(*lv); + } else if (const ::flutter::EncodableMap* mv = + std::get_if<::flutter::EncodableMap>(&v)) { + result = result * 31 + PigeonInternalDeepHash(*mv); + } else { + std::visit( + [&result](const auto& val) { + using T = std::decay_t; + if constexpr (!std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v) { + result = result * 31 + PigeonInternalDeepHash(val); + } + }, + v); + } + return result; +} + +} // namespace + PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {} EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( - uint8_t type, flutter::ByteStreamReader* stream) const { - return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); + uint8_t type, ::flutter::ByteStreamReader* stream) const { + return ::flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } void PigeonInternalCodecSerializer::WriteValue( - const EncodableValue& value, flutter::ByteStreamWriter* stream) const { - flutter::StandardCodecSerializer::WriteValue(value, stream); + const EncodableValue& value, ::flutter::ByteStreamWriter* stream) const { + ::flutter::StandardCodecSerializer::WriteValue(value, stream); } /// The codec used by FirebaseAppCheckHostApi. -const flutter::StandardMessageCodec& FirebaseAppCheckHostApi::GetCodec() { - return flutter::StandardMessageCodec::GetInstance( +const ::flutter::StandardMessageCodec& FirebaseAppCheckHostApi::GetCodec() { + return ::flutter::StandardMessageCodec::GetInstance( &PigeonInternalCodecSerializer::GetInstance()); } // Sets up an instance of `FirebaseAppCheckHostApi` to handle messages through // the `binary_messenger`. -void FirebaseAppCheckHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, - FirebaseAppCheckHostApi* api) { +void FirebaseAppCheckHostApi::SetUp( + ::flutter::BinaryMessenger* binary_messenger, + FirebaseAppCheckHostApi* api) { FirebaseAppCheckHostApi::SetUp(binary_messenger, api, ""); } -void FirebaseAppCheckHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, - FirebaseAppCheckHostApi* api, - const std::string& message_channel_suffix) { +void FirebaseAppCheckHostApi::SetUp( + ::flutter::BinaryMessenger* binary_messenger, FirebaseAppCheckHostApi* api, + const std::string& message_channel_suffix) { const std::string prepended_suffix = message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix @@ -73,7 +283,7 @@ void FirebaseAppCheckHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, if (api != nullptr) { channel.SetMessageHandler( [api](const EncodableValue& message, - const flutter::MessageReply& reply) { + const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_app_name_arg = args.at(0); @@ -121,7 +331,7 @@ void FirebaseAppCheckHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, if (api != nullptr) { channel.SetMessageHandler( [api](const EncodableValue& message, - const flutter::MessageReply& reply) { + const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_app_name_arg = args.at(0); @@ -173,7 +383,7 @@ void FirebaseAppCheckHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, if (api != nullptr) { channel.SetMessageHandler( [api](const EncodableValue& message, - const flutter::MessageReply& reply) { + const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_app_name_arg = args.at(0); @@ -221,7 +431,7 @@ void FirebaseAppCheckHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, if (api != nullptr) { channel.SetMessageHandler( [api](const EncodableValue& message, - const flutter::MessageReply& reply) { + const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_app_name_arg = args.at(0); @@ -260,7 +470,7 @@ void FirebaseAppCheckHostApi::SetUp(flutter::BinaryMessenger* binary_messenger, if (api != nullptr) { channel.SetMessageHandler( [api](const EncodableValue& message, - const flutter::MessageReply& reply) { + const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); const auto& encodable_app_name_arg = args.at(0); diff --git a/packages/firebase_app_check/firebase_app_check/windows/messages.g.h b/packages/firebase_app_check/firebase_app_check/windows/messages.g.h index c56d71862604..50ae482963dc 100644 --- a/packages/firebase_app_check/firebase_app_check/windows/messages.g.h +++ b/packages/firebase_app_check/firebase_app_check/windows/messages.g.h @@ -1,7 +1,7 @@ // Copyright 2025, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_MESSAGES_G_H_ @@ -25,17 +25,17 @@ class FlutterError { explicit FlutterError(const std::string& code, const std::string& message) : code_(code), message_(message) {} explicit FlutterError(const std::string& code, const std::string& message, - const flutter::EncodableValue& details) + const ::flutter::EncodableValue& details) : code_(code), message_(message), details_(details) {} const std::string& code() const { return code_; } const std::string& message() const { return message_; } - const flutter::EncodableValue& details() const { return details_; } + const ::flutter::EncodableValue& details() const { return details_; } private: std::string code_; std::string message_; - flutter::EncodableValue details_; + ::flutter::EncodableValue details_; }; template @@ -58,7 +58,8 @@ class ErrorOr { std::variant v_; }; -class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { +class PigeonInternalCodecSerializer + : public ::flutter::StandardCodecSerializer { public: PigeonInternalCodecSerializer(); inline static PigeonInternalCodecSerializer& GetInstance() { @@ -66,12 +67,12 @@ class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { return sInstance; } - void WriteValue(const flutter::EncodableValue& value, - flutter::ByteStreamWriter* stream) const override; + void WriteValue(const ::flutter::EncodableValue& value, + ::flutter::ByteStreamWriter* stream) const override; protected: - flutter::EncodableValue ReadValueOfType( - uint8_t type, flutter::ByteStreamReader* stream) const override; + ::flutter::EncodableValue ReadValueOfType( + uint8_t type, ::flutter::ByteStreamReader* stream) const override; }; // Generated interface from Pigeon that represents a handler of messages from @@ -100,16 +101,16 @@ class FirebaseAppCheckHostApi { std::function reply)> result) = 0; // The codec used by FirebaseAppCheckHostApi. - static const flutter::StandardMessageCodec& GetCodec(); + static const ::flutter::StandardMessageCodec& GetCodec(); // Sets up an instance of `FirebaseAppCheckHostApi` to handle messages through // the `binary_messenger`. - static void SetUp(flutter::BinaryMessenger* binary_messenger, + static void SetUp(::flutter::BinaryMessenger* binary_messenger, FirebaseAppCheckHostApi* api); - static void SetUp(flutter::BinaryMessenger* binary_messenger, + static void SetUp(::flutter::BinaryMessenger* binary_messenger, FirebaseAppCheckHostApi* api, const std::string& message_channel_suffix); - static flutter::EncodableValue WrapError(std::string_view error_message); - static flutter::EncodableValue WrapError(const FlutterError& error); + static ::flutter::EncodableValue WrapError(std::string_view error_message); + static ::flutter::EncodableValue WrapError(const FlutterError& error); protected: FirebaseAppCheckHostApi() = default; diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/android_providers.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/android_providers.dart index c827b3184022..47640028475a 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/android_providers.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/android_providers.dart @@ -32,3 +32,10 @@ class AndroidDebugProvider extends AndroidAppCheckProvider { class AndroidPlayIntegrityProvider extends AndroidAppCheckProvider { const AndroidPlayIntegrityProvider() : super('playIntegrity'); } + +/// reCAPTCHA provider for Android. +/// +/// The site key is retrieved automatically from google-services.json. +class AndroidReCaptchaProvider extends AndroidAppCheckProvider { + const AndroidReCaptchaProvider() : super('recaptcha'); +} diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/apple_providers.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/apple_providers.dart index 5e6af1ce2359..e768fca29477 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/apple_providers.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/apple_providers.dart @@ -54,3 +54,10 @@ class AppleAppAttestWithDeviceCheckFallbackProvider const AppleAppAttestWithDeviceCheckFallbackProvider() : super('appAttestWithDeviceCheckFallback'); } + +/// reCAPTCHA provider for Apple platforms. +/// +/// The site key is retrieved automatically from GoogleService-Info.plist. +class AppleReCaptchaProvider extends AppleAppCheckProvider { + const AppleReCaptchaProvider() : super('recaptcha'); +} diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/pigeon/messages.pigeon.dart index 8916928599ce..ca154bea1029 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -1,21 +1,40 @@ // Copyright 2025, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'dart:typed_data' show Float64List, Int32List, Int64List; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); +Object? _extractReplyValueOrThrow( + List? replyList, + String channelName, { + required bool isNullValid, +}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; } class _PigeonCodec extends StandardMessageCodec { @@ -56,143 +75,101 @@ class FirebaseAppCheckHostApi { Future activate(String appName, String? androidProvider, String? appleProvider, String? debugToken) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel .send([appName, androidProvider, appleProvider, debugToken]); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future getToken(String appName, bool forceRefresh) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getToken$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send([appName, forceRefresh]); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as String?); - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + return pigeonVar_replyValue as String?; } Future setTokenAutoRefreshEnabled( String appName, bool isTokenAutoRefreshEnabled) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.setTokenAutoRefreshEnabled$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send([appName, isTokenAutoRefreshEnabled]); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future registerTokenListener(String appName) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.registerTokenListener$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send([appName]); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as String?)!; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as String; } Future getLimitedUseAppCheckToken(String appName) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getLimitedUseAppCheckToken$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send([appName]); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as String?)!; - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as String; } } diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/web_providers.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/web_providers.dart index 55d5ab576c7e..5127a277cf0d 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/web_providers.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/web_providers.dart @@ -31,3 +31,8 @@ class WebDebugProvider extends WebProvider { /// The debug token for this provider. final String? debugToken; } + +/// reCAPTCHA Enterprise provider for Web that does not use the enterprise name and does not take in a siteKey. +class WebReCaptchaProvider extends WebProvider { + WebReCaptchaProvider() : super(''); +} diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/test/method_channel_tests/method_channel_firebase_app_check_test.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/test/method_channel_tests/method_channel_firebase_app_check_test.dart index 0839886ba264..7c60c253ff3d 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/test/method_channel_tests/method_channel_firebase_app_check_test.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/test/method_channel_tests/method_channel_firebase_app_check_test.dart @@ -7,6 +7,7 @@ import 'package:firebase_app_check_platform_interface/src/pigeon/messages.pigeon import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/services.dart'; import '../mock.dart'; @@ -121,5 +122,60 @@ void main() { expect(calls.single[3], 'android-debug-token'); }); }); + + group('activate() with Recaptcha', () { + test('passes recaptcha on Android', () async { + final appCheck = MethodChannelFirebaseAppCheck(app: Firebase.app()); + + final List log = []; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler( + 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate', + (message) async { + final list = const StandardMessageCodec().decodeMessage(message) + as List; + log.add(list); + return const StandardMessageCodec() + .encodeMessage([null]); // Return success + }, + ); + + await appCheck.activate( + providerAndroid: const AndroidReCaptchaProvider(), + ); + + expect(log.length, 1); + expect(log[0][0], '[DEFAULT]'); // appName + expect(log[0][1], 'recaptcha'); // androidProvider + }); + + test('passes recaptcha on iOS', () async { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + final appCheck = MethodChannelFirebaseAppCheck(app: Firebase.app()); + + final List log = []; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler( + 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate', + (message) async { + final list = const StandardMessageCodec().decodeMessage(message) + as List; + log.add(list); + return const StandardMessageCodec() + .encodeMessage([null]); // Return success + }, + ); + + await appCheck.activate( + providerApple: const AppleReCaptchaProvider(), + ); + + expect(log.length, 1); + expect(log[0][0], '[DEFAULT]'); // appName + expect(log[0][2], 'recaptcha'); // appleProvider + }); + }); }); } diff --git a/packages/firebase_app_check/firebase_app_check_web/lib/firebase_app_check_web.dart b/packages/firebase_app_check/firebase_app_check_web/lib/firebase_app_check_web.dart index cfb4a24e9ce0..f367e16ca340 100644 --- a/packages/firebase_app_check/firebase_app_check_web/lib/firebase_app_check_web.dart +++ b/packages/firebase_app_check/firebase_app_check_web/lib/firebase_app_check_web.dart @@ -23,6 +23,7 @@ class FirebaseAppCheckWeb extends FirebaseAppCheckPlatform { static const recaptchaTypeV3 = 'recaptcha-v3'; static const recaptchaTypeEnterprise = 'enterprise'; static const recaptchaTypeDebug = 'debug'; + static const recaptchaTypeWebRecaptcha = 'web-recaptcha'; static Map> _tokenChangesListeners = {}; /// Stub initializer to allow the [registerWith] to create an instance without @@ -63,6 +64,8 @@ class FirebaseAppCheckWeb extends FirebaseAppCheckPlatform { final debugToken = recaptchaSiteKey?.isNotEmpty ?? false ? recaptchaSiteKey : null; provider = WebDebugProvider(debugToken: debugToken); + } else if (recaptchaType == recaptchaTypeWebRecaptcha) { + provider = WebReCaptchaProvider(); } else if (recaptchaSiteKey != null) { if (recaptchaType == recaptchaTypeV3) { provider = ReCaptchaV3Provider(recaptchaSiteKey); @@ -143,6 +146,8 @@ class FirebaseAppCheckWeb extends FirebaseAppCheckPlatform { recaptchaType = recaptchaTypeV3; } else if (webProvider is ReCaptchaEnterpriseProvider) { recaptchaType = recaptchaTypeEnterprise; + } else if (webProvider is WebReCaptchaProvider) { + recaptchaType = recaptchaTypeWebRecaptcha; } else { throw Exception('Invalid web provider: $webProvider'); } diff --git a/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check.dart b/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check.dart index c35a5d1d8d16..a3d2b896e96b 100644 --- a/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check.dart +++ b/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check.dart @@ -16,9 +16,11 @@ export 'app_check_interop.dart'; /// Given an AppJSImp, return the AppCheck instance. AppCheck? getAppCheckInstance([App? app, WebProvider? provider]) { - late app_check_interop.ReCaptchaProvider jsProvider; + app_check_interop.ReCaptchaProvider? jsProvider; - if (provider is WebDebugProvider) { + if (provider == null || provider is WebReCaptchaProvider) { + jsProvider = null; + } else if (provider is WebDebugProvider) { // Set the debug token global before initializing App Check. // The Firebase JS SDK reads this and creates a DebugProvider internally. if (provider.debugToken != null) { diff --git a/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check_interop.dart b/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check_interop.dart index a72b2cf0c2a4..1afb3a69b181 100644 --- a/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check_interop.dart +++ b/packages/firebase_app_check/firebase_app_check_web/lib/src/interop/app_check_interop.dart @@ -74,14 +74,14 @@ extension type AppCheckTokenResultJsImpl._(JSObject _) implements JSObject { class AppCheckOptions { external factory AppCheckOptions({ JSBoolean? isTokenAutoRefreshEnabled, - ReCaptchaProvider provider, + ReCaptchaProvider? provider, }); } extension AppCheckOptionsJsImplX on AppCheckOptions { external JSBoolean? get isTokenAutoRefreshEnabled; - external ReCaptchaProvider get provider; + external ReCaptchaProvider? get provider; } extension type AppCheckJsImpl._(JSObject _) implements JSObject { diff --git a/packages/firebase_app_check/firebase_app_check_web/test/firebase_app_check_web_test.dart b/packages/firebase_app_check/firebase_app_check_web/test/firebase_app_check_web_test.dart index dd91f9bde80a..e220e74fadce 100644 --- a/packages/firebase_app_check/firebase_app_check_web/test/firebase_app_check_web_test.dart +++ b/packages/firebase_app_check/firebase_app_check_web/test/firebase_app_check_web_test.dart @@ -58,6 +58,20 @@ void main() { verifyNoMoreInteractions(appCheck); }); + test('activate with WebReCaptchaProvider', () async { + final provider = WebReCaptchaProvider(); + await appCheck.activate( + webProvider: provider, + ); + verify( + appCheck.activate( + webProvider: provider, + ), + ); + verifyNoMoreInteractions(appCheck); + }); + + test('getToken', () async { await appCheck.getToken(true); verify(appCheck.getToken(true));