diff --git a/melos.yaml b/melos.yaml index 38eab295bfa1..2f403ee4d305 100644 --- a/melos.yaml +++ b/melos.yaml @@ -21,7 +21,9 @@ command: dart run scripts/generate_dataconnect_version.dart && \ dart run scripts/generate_versions_web.dart && \ dart run scripts/generate_versions_spm.dart && \ - git add packages/firebase_data_connect/firebase_data_connect/lib/src/dataconnect_version.dart && git add packages/*/*_web/lib/src/*_version.dart && git add packages/*/*/ios/*/Package.swift packages/*/*/macos/*/Package.swift + git add packages/firebase_data_connect/firebase_data_connect/lib/src/dataconnect_version.dart && git add packages/*/*_web/lib/src/*_version.dart && git add packages/*/*/ios/*/Package.swift packages/*/*/macos/*/Package.swift && git add packages/*/*/ios/*/Sources/*/Constants.swift + post: | + dart run scripts/generate_tag_spm_firebase_core.dart bootstrap: # It seems so that running "pub get" in parallel has some issues (like 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 099e28aab3c1..45c2fa4d6345 100644 --- a/packages/firebase_app_check/firebase_app_check/android/build.gradle +++ b/packages/firebase_app_check/firebase_app_check/android/build.gradle @@ -5,14 +5,11 @@ apply plugin: 'com.android.library' apply from: file("local-config.gradle") buildscript { + ext.kotlin_version = "1.8.22" repositories { google() mavenCentral() } - - dependencies { - classpath 'com.android.tools.build:gradle:8.3.0' - } } rootProject.allprojects { @@ -22,6 +19,12 @@ rootProject.allprojects { } } +// AGP 9+ has built-in Kotlin support; older versions need the plugin explicitly. +def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int +if (agpMajor < 9) { + apply plugin: 'kotlin-android' +} + def firebaseCoreProject = findProject(':firebase_core') if (firebaseCoreProject == null) { throw new GradleException('Could not find the firebase_core FlutterFire plugin, have you added it as a dependency in your pubspec?') @@ -48,11 +51,22 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + if (agpMajor < 9) { + kotlinOptions { + jvmTarget = project.ext.javaVersion + } + } + compileOptions { sourceCompatibility project.ext.javaVersion targetCompatibility project.ext.javaVersion } + sourceSets { + main.java.srcDirs += "src/main/kotlin" + test.java.srcDirs += "src/test/kotlin" + } + buildFeatures { buildConfig true } @@ -67,7 +81,6 @@ android { implementation 'com.google.firebase:firebase-appcheck-debug' implementation 'com.google.firebase:firebase-appcheck-playintegrity' implementation 'androidx.annotation:annotation:1.7.0' - } } diff --git a/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppCheckPlugin.java b/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppCheckPlugin.java deleted file mode 100644 index a41d916f2db8..000000000000 --- a/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppCheckPlugin.java +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebase.appcheck; - -import static io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry.registerPlugin; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.gms.tasks.Task; -import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.android.gms.tasks.Tasks; -import com.google.firebase.FirebaseApp; -import com.google.firebase.appcheck.AppCheckToken; -import com.google.firebase.appcheck.FirebaseAppCheck; -import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory; -import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugins.firebase.core.FlutterFirebasePlugin; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class FlutterFirebaseAppCheckPlugin - implements FlutterFirebasePlugin, FlutterPlugin, MethodCallHandler { - - private static final String METHOD_CHANNEL_NAME = "plugins.flutter.io/firebase_app_check"; - private final Map streamHandlers = new HashMap<>(); - - private final String debugProvider = "debug"; - private final String playIntegrity = "playIntegrity"; - - @Nullable private BinaryMessenger messenger; - - private MethodChannel channel; - - private void initInstance(BinaryMessenger messenger) { - registerPlugin(METHOD_CHANNEL_NAME, this); - channel = new MethodChannel(messenger, METHOD_CHANNEL_NAME); - channel.setMethodCallHandler(this); - - this.messenger = messenger; - } - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - initInstance(binding.getBinaryMessenger()); - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - channel.setMethodCallHandler(null); - channel = null; - messenger = null; - - removeEventListeners(); - } - - private FirebaseAppCheck getAppCheck(Map arguments) { - String appName = (String) Objects.requireNonNull(arguments.get("appName")); - FirebaseApp app = FirebaseApp.getInstance(appName); - return FirebaseAppCheck.getInstance(app); - } - - private Task getLimitedUseAppCheckToken(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - FirebaseAppCheck firebaseAppCheck = getAppCheck(arguments); - AppCheckToken tokenResult = Tasks.await(firebaseAppCheck.getLimitedUseAppCheckToken()); - taskCompletionSource.setResult(tokenResult.getToken()); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task activate(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - String provider = (String) Objects.requireNonNull(arguments.get("androidProvider")); - - switch (provider) { - case debugProvider: - { - FirebaseAppCheck firebaseAppCheck = getAppCheck(arguments); - FlutterFirebaseAppRegistrar.debugToken = - (String) arguments.get("androidDebugToken"); - firebaseAppCheck.installAppCheckProviderFactory( - DebugAppCheckProviderFactory.getInstance()); - break; - } - case playIntegrity: - { - FirebaseAppCheck firebaseAppCheck = getAppCheck(arguments); - firebaseAppCheck.installAppCheckProviderFactory( - PlayIntegrityAppCheckProviderFactory.getInstance()); - break; - } - } - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task getToken(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - FirebaseAppCheck firebaseAppCheck = getAppCheck(arguments); - Boolean forceRefresh = (Boolean) Objects.requireNonNull(arguments.get("forceRefresh")); - AppCheckToken tokenResult = - Tasks.await(firebaseAppCheck.getAppCheckToken(forceRefresh)); - - taskCompletionSource.setResult(tokenResult.getToken()); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task setTokenAutoRefreshEnabled(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - FirebaseAppCheck firebaseAppCheck = getAppCheck(arguments); - Boolean isTokenAutoRefreshEnabled = - (Boolean) Objects.requireNonNull(arguments.get("isTokenAutoRefreshEnabled")); - firebaseAppCheck.setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled); - - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private Task registerTokenListener(Map arguments) { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - String appName = (String) Objects.requireNonNull(arguments.get("appName")); - FirebaseAppCheck firebaseAppCheck = getAppCheck(arguments); - - final TokenChannelStreamHandler handler = - new TokenChannelStreamHandler(firebaseAppCheck); - final String name = METHOD_CHANNEL_NAME + "/token/" + appName; - final EventChannel channel = new EventChannel(messenger, name); - channel.setStreamHandler(handler); - streamHandlers.put(channel, handler); - - taskCompletionSource.setResult(name); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - @Override - public void onMethodCall(MethodCall call, @NonNull final Result result) { - Task methodCallTask; - - switch (call.method) { - case "FirebaseAppCheck#activate": - methodCallTask = activate(call.arguments()); - break; - case "FirebaseAppCheck#getToken": - methodCallTask = getToken(call.arguments()); - break; - case "FirebaseAppCheck#setTokenAutoRefreshEnabled": - methodCallTask = setTokenAutoRefreshEnabled(call.arguments()); - break; - case "FirebaseAppCheck#registerTokenListener": - methodCallTask = registerTokenListener(call.arguments()); - break; - case "FirebaseAppCheck#getLimitedUseAppCheckToken": - methodCallTask = getLimitedUseAppCheckToken(call.arguments()); - break; - default: - result.notImplemented(); - return; - } - - methodCallTask.addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - result.success(task.getResult()); - } else { - Exception exception = task.getException(); - result.error( - "firebase_app_check", - exception != null ? exception.getMessage() : null, - getExceptionDetails(exception)); - } - }); - } - - private Map getExceptionDetails(@Nullable Exception exception) { - Map details = new HashMap<>(); - details.put("code", "unknown"); - if (exception != null) { - details.put("message", exception.getMessage()); - } else { - details.put("message", "An unknown error has occurred."); - } - return details; - } - - @Override - public Task> getPluginConstantsForFirebaseApp(FirebaseApp firebaseApp) { - TaskCompletionSource> taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - @Override - public Task didReinitializeFirebaseCore() { - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - - cachedThreadPool.execute( - () -> { - try { - taskCompletionSource.setResult(null); - } catch (Exception e) { - taskCompletionSource.setException(e); - } - }); - - return taskCompletionSource.getTask(); - } - - private void removeEventListeners() { - for (EventChannel eventChannel : streamHandlers.keySet()) { - EventChannel.StreamHandler streamHandler = streamHandlers.get(eventChannel); - assert streamHandler != null; - streamHandler.onCancel(null); - eventChannel.setStreamHandler(null); - } - streamHandlers.clear(); - } -} diff --git a/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppRegistrar.java b/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppRegistrar.java deleted file mode 100644 index 2355ec819c0c..000000000000 --- a/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppRegistrar.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebase.appcheck; - -import androidx.annotation.Keep; -import androidx.annotation.Nullable; -import com.google.firebase.appcheck.debug.InternalDebugSecretProvider; -import com.google.firebase.components.Component; -import com.google.firebase.components.ComponentRegistrar; -import com.google.firebase.platforminfo.LibraryVersionComponent; -import java.util.Arrays; -import java.util.List; - -@Keep -public class FlutterFirebaseAppRegistrar - implements ComponentRegistrar, InternalDebugSecretProvider { - - private static final String DEBUG_SECRET_NAME = "fire-app-check-debug-secret"; - public static String debugToken; - - @Override - public List> getComponents() { - Component library = - LibraryVersionComponent.create(BuildConfig.LIBRARY_NAME, BuildConfig.LIBRARY_VERSION); - - Component debugSecretProvider = - Component.builder(InternalDebugSecretProvider.class) - .name(DEBUG_SECRET_NAME) - .factory(container -> this) - .build(); - - return Arrays.asList(library, debugSecretProvider); - } - - @Nullable - @Override - public String getDebugSecret() { - return debugToken; - } -} diff --git a/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/TokenChannelStreamHandler.java b/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/TokenChannelStreamHandler.java deleted file mode 100644 index 3f1367002c70..000000000000 --- a/packages/firebase_app_check/firebase_app_check/android/src/main/java/io/flutter/plugins/firebase/appcheck/TokenChannelStreamHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022, 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. - */ - -package io.flutter.plugins.firebase.appcheck; - -import com.google.firebase.appcheck.FirebaseAppCheck; -import io.flutter.plugin.common.EventChannel; -import java.util.HashMap; -import java.util.Map; - -public class TokenChannelStreamHandler implements EventChannel.StreamHandler { - - private final FirebaseAppCheck firebaseAppCheck; - private FirebaseAppCheck.AppCheckListener listener; - - public TokenChannelStreamHandler(FirebaseAppCheck firebaseAppCheck) { - this.firebaseAppCheck = firebaseAppCheck; - } - - @Override - public void onListen(Object arguments, EventChannel.EventSink events) { - - listener = - result -> { - Map event = new HashMap<>(); - event.put("token", result.getToken()); - events.success(event); - }; - - firebaseAppCheck.addAppCheckListener(listener); - } - - @Override - public void onCancel(Object arguments) { - if (listener != null) { - firebaseAppCheck.removeAppCheckListener(listener); - listener = null; - } - } -} 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 new file mode 100644 index 000000000000..0dc0ce26dc7e --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/FirebaseAppCheckPlugin.kt @@ -0,0 +1,173 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package io.flutter.plugins.firebase.appcheck + +import android.os.Handler +import android.os.Looper +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 io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.EventChannel.EventSink +import io.flutter.plugins.firebase.core.FlutterFirebasePlugin +import io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry +import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.TaskCompletionSource +import com.google.android.gms.tasks.Tasks + +class FirebaseAppCheckPlugin : + FlutterFirebasePlugin, FlutterPlugin, FirebaseAppCheckHostApi { + + private val streamHandlers: MutableMap = HashMap() + private val eventChannels: MutableMap = HashMap() + private val mainThreadHandler = Handler(Looper.getMainLooper()) + private var messenger: BinaryMessenger? = null + + companion object { + const val METHOD_CHANNEL = "plugins.flutter.io/firebase_app_check" + const val EVENT_CHANNEL_PREFIX = "plugins.flutter.io/firebase_app_check/token/" + } + + override fun onAttachedToEngine(binding: FlutterPluginBinding) { + messenger = binding.binaryMessenger + FirebaseAppCheckHostApi.setUp(binding.binaryMessenger, this) + FlutterFirebasePluginRegistry.registerPlugin(METHOD_CHANNEL, this) + } + + override fun onDetachedFromEngine(binding: FlutterPluginBinding) { + FirebaseAppCheckHostApi.setUp(binding.binaryMessenger, null) + messenger = null + removeEventListeners() + } + + private fun getAppCheck(appName: String): FirebaseAppCheck { + val app = FirebaseApp.getInstance(appName) + return FirebaseAppCheck.getInstance(app) + } + + override fun activate( + appName: String, + androidProvider: String?, + appleProvider: String?, + debugToken: String?, + callback: (Result) -> Unit + ) { + try { + val firebaseAppCheck = getAppCheck(appName) + when (androidProvider) { + "debug" -> { + FlutterFirebaseAppRegistrar.debugToken = debugToken + firebaseAppCheck.installAppCheckProviderFactory( + DebugAppCheckProviderFactory.getInstance() + ) + } + else -> { + firebaseAppCheck.installAppCheckProviderFactory( + PlayIntegrityAppCheckProviderFactory.getInstance() + ) + } + } + callback(Result.success(Unit)) + } catch (e: Exception) { + callback(Result.failure(FlutterError("unknown", e.message, null))) + } + } + + override fun getToken( + appName: String, + forceRefresh: Boolean, + callback: (Result) -> Unit + ) { + val firebaseAppCheck = getAppCheck(appName) + firebaseAppCheck.getAppCheckToken(forceRefresh).addOnCompleteListener { task -> + if (task.isSuccessful) { + callback(Result.success(task.result?.token)) + } else { + callback(Result.failure( + FlutterError("firebase_app_check", task.exception?.message, null) + )) + } + } + } + + override fun setTokenAutoRefreshEnabled( + appName: String, + isTokenAutoRefreshEnabled: Boolean, + callback: (Result) -> Unit + ) { + try { + val firebaseAppCheck = getAppCheck(appName) + firebaseAppCheck.setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled) + callback(Result.success(Unit)) + } catch (e: Exception) { + callback(Result.failure(FlutterError("unknown", e.message, null))) + } + } + + override fun registerTokenListener( + appName: String, + callback: (Result) -> Unit + ) { + try { + val firebaseAppCheck = getAppCheck(appName) + val name = EVENT_CHANNEL_PREFIX + appName + + val handler = TokenChannelStreamHandler(firebaseAppCheck) + val channel = EventChannel(messenger, name) + channel.setStreamHandler(handler) + eventChannels[name] = channel + streamHandlers[name] = handler + + callback(Result.success(name)) + } catch (e: Exception) { + callback(Result.failure(FlutterError("unknown", e.message, null))) + } + } + + override fun getLimitedUseAppCheckToken( + appName: String, + callback: (Result) -> Unit + ) { + val firebaseAppCheck = getAppCheck(appName) + firebaseAppCheck.limitedUseAppCheckToken.addOnCompleteListener { task -> + if (task.isSuccessful) { + callback(Result.success(task.result?.token ?: "")) + } else { + callback(Result.failure( + FlutterError("firebase_app_check", task.exception?.message, null) + )) + } + } + } + + override fun getPluginConstantsForFirebaseApp( + firebaseApp: FirebaseApp + ): Task> { + val taskCompletionSource = TaskCompletionSource>() + taskCompletionSource.setResult(HashMap()) + return taskCompletionSource.task + } + + override fun didReinitializeFirebaseCore(): Task { + val taskCompletionSource = TaskCompletionSource() + removeEventListeners() + taskCompletionSource.setResult(null) + return taskCompletionSource.task + } + + private fun removeEventListeners() { + for ((name, channel) in eventChannels) { + channel.setStreamHandler(null) + } + for ((name, handler) in streamHandlers) { + handler.onCancel(null) + } + eventChannels.clear() + streamHandlers.clear() + } +} diff --git a/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppRegistrar.kt b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppRegistrar.kt new file mode 100644 index 000000000000..d7146eba3a7f --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/FlutterFirebaseAppRegistrar.kt @@ -0,0 +1,38 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package io.flutter.plugins.firebase.appcheck + +import androidx.annotation.Keep +import com.google.firebase.appcheck.debug.InternalDebugSecretProvider +import com.google.firebase.components.Component +import com.google.firebase.components.ComponentRegistrar +import com.google.firebase.platforminfo.LibraryVersionComponent + +@Keep +class FlutterFirebaseAppRegistrar : ComponentRegistrar, InternalDebugSecretProvider { + + companion object { + private const val DEBUG_SECRET_NAME = "fire-app-check-debug-secret" + + @JvmStatic + var debugToken: String? = null + } + + override fun getComponents(): List> { + val library = LibraryVersionComponent.create( + BuildConfig.LIBRARY_NAME, BuildConfig.LIBRARY_VERSION + ) + + val debugSecretProvider = Component.builder(InternalDebugSecretProvider::class.java) + .name(DEBUG_SECRET_NAME) + .factory { this } + .build() + + return listOf(library, debugSecretProvider) + } + + override fun getDebugSecret(): String? { + return debugToken + } +} 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 new file mode 100644 index 000000000000..0a046dd5aa47 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/GeneratedAndroidFirebaseAppCheck.g.kt @@ -0,0 +1,185 @@ +// 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. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +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 { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + 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() +private open class GeneratedAndroidFirebaseAppCheckPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + 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 getToken(appName: String, forceRefresh: 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`. */ + @JvmOverloads + 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) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val appNameArg = args[0] as String + 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 -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + 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 + val appNameArg = args[0] as String + val forceRefreshArg = args[1] as Boolean + api.getToken(appNameArg, forceRefreshArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + 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 -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapError(error)) + } else { + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + 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 + val appNameArg = args[0] as String + api.registerTokenListener(appNameArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + 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 + val appNameArg = args[0] as String + api.getLimitedUseAppCheckToken(appNameArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedAndroidFirebaseAppCheckPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/TokenChannelStreamHandler.kt b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/TokenChannelStreamHandler.kt new file mode 100644 index 000000000000..2062821507c8 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/TokenChannelStreamHandler.kt @@ -0,0 +1,30 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +package io.flutter.plugins.firebase.appcheck + +import com.google.firebase.appcheck.FirebaseAppCheck +import io.flutter.plugin.common.EventChannel + +class TokenChannelStreamHandler( + private val firebaseAppCheck: FirebaseAppCheck +) : EventChannel.StreamHandler { + + private var listener: FirebaseAppCheck.AppCheckListener? = null + + override fun onListen(arguments: Any?, events: EventChannel.EventSink) { + listener = FirebaseAppCheck.AppCheckListener { result -> + val event = HashMap() + event["token"] = result.token + events.success(event) + } + firebaseAppCheck.addAppCheckListener(listener!!) + } + + override fun onCancel(arguments: Any?) { + listener?.let { + firebaseAppCheck.removeAppCheckListener(it) + listener = null + } + } +} diff --git a/packages/firebase_app_check/firebase_app_check/example/.metadata b/packages/firebase_app_check/firebase_app_check/example/.metadata index 784ce1298249..827d9a16d24a 100644 --- a/packages/firebase_app_check/firebase_app_check/example/.metadata +++ b/packages/firebase_app_check/firebase_app_check/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3" + revision: "90673a4eef275d1a6692c26ac80d6d746d41a73a" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - platform: web - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: windows + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a # User provided section diff --git a/packages/firebase_app_check/firebase_app_check/example/android/app/build.gradle b/packages/firebase_app_check/firebase_app_check/example/android/app/build.gradle index ef73138ff489..92298e7270e5 100644 --- a/packages/firebase_app_check/firebase_app_check/example/android/app/build.gradle +++ b/packages/firebase_app_check/firebase_app_check/example/android/app/build.gradle @@ -45,7 +45,7 @@ android { applicationId = "io.flutter.plugins.firebase.appcheck.example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdk = 23 + minSdkVersion = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutterVersionCode.toInteger() versionName = flutterVersionName diff --git a/packages/firebase_app_check/firebase_app_check/example/ios/Flutter/AppFrameworkInfo.plist b/packages/firebase_app_check/firebase_app_check/example/ios/Flutter/AppFrameworkInfo.plist index 7c5696400627..391a902b2beb 100644 --- a/packages/firebase_app_check/firebase_app_check/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/firebase_app_check/firebase_app_check/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 12.0 diff --git a/packages/firebase_app_check/firebase_app_check/example/ios/Podfile b/packages/firebase_app_check/firebase_app_check/example/ios/Podfile deleted file mode 100644 index bc779113f7ca..000000000000 --- a/packages/firebase_app_check/firebase_app_check/example/ios/Podfile +++ /dev/null @@ -1,50 +0,0 @@ -# Uncomment this line to define a global platform for your project -platform :ios, '15.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end - - installer.generated_projects.each do |project| - project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' - end - end - end -end - diff --git a/packages/firebase_app_check/firebase_app_check/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_app_check/firebase_app_check/example/ios/Runner.xcodeproj/project.pbxproj index 7d86b897a846..1ef35c770b2f 100644 --- a/packages/firebase_app_check/firebase_app_check/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_app_check/firebase_app_check/example/ios/Runner.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4632D5BC275CD47A0059DC83 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 46A64A032996811C003FC4F3 /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -66,6 +67,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, diff --git a/packages/firebase_app_check/firebase_app_check/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_app_check/firebase_app_check/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 6f83bdc750b0..8a6c683e3d89 100644 --- a/packages/firebase_app_check/firebase_app_check/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/firebase_app_check/firebase_app_check/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -44,6 +44,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/packages/firebase_app_check/firebase_app_check/example/lib/firebase_options.dart b/packages/firebase_app_check/firebase_app_check/example/lib/firebase_options.dart index 2a78f25ed1b0..8bbd98affc15 100644 --- a/packages/firebase_app_check/firebase_app_check/example/lib/firebase_options.dart +++ b/packages/firebase_app_check/firebase_app_check/example/lib/firebase_options.dart @@ -31,10 +31,7 @@ class DefaultFirebaseOptions { case TargetPlatform.macOS: return macos; case TargetPlatform.windows: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for windows - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); + return web; case TargetPlatform.linux: throw UnsupportedError( 'DefaultFirebaseOptions have not been configured for linux - ' 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 7a1e778d35a0..8f26c6439a83 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 @@ -2,6 +2,8 @@ // 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. +// ignore_for_file: do_not_use_environment + import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_app_check/firebase_app_check.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -12,6 +14,15 @@ import 'firebase_options.dart'; const kWebRecaptchaSiteKey = '6Lemcn0dAAAAABLkf6aiiHvpGD6x-zF3nOSDU2M8'; +// Windows: create a debug token in the Firebase Console +// (App Check > Apps > Manage debug tokens), then paste it here +// or set the APP_CHECK_DEBUG_TOKEN environment variable. +const kWindowsDebugToken = String.fromEnvironment( + 'APP_CHECK_DEBUG_TOKEN', + // ignore: avoid_redundant_argument_values + defaultValue: '', +); + Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( @@ -20,14 +31,21 @@ Future main() async { // Activate app check after initialization, but before // usage of any Firebase services. - await FirebaseAppCheck.instance - // Your personal reCaptcha public key goes here: - .activate( + await FirebaseAppCheck.instance.activate( providerWeb: kDebugMode ? WebDebugProvider() : ReCaptchaV3Provider(kWebRecaptchaSiteKey), providerAndroid: const AndroidDebugProvider(), providerApple: const AppleDebugProvider(), + // On Windows, only the debug provider is available. + // You must supply a debug token — the desktop C++ SDK does not + // auto-generate one. Create one in the Firebase Console under + // App Check > Apps > Manage debug tokens, then either: + // - pass it via --dart-define=APP_CHECK_DEBUG_TOKEN= + // - or set the APP_CHECK_DEBUG_TOKEN environment variable + providerWindows: WindowsDebugProvider( + debugToken: kWindowsDebugToken.isNotEmpty ? kWindowsDebugToken : null, + ), ); runApp(MyApp()); @@ -36,7 +54,6 @@ Future main() async { class MyApp extends StatelessWidget { final String title = 'Firebase App Check'; - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( @@ -81,65 +98,136 @@ class _FirebaseAppCheck extends State { }); } + Future _activate({ + AndroidAppCheckProvider? android, + AppleAppCheckProvider? apple, + WindowsAppCheckProvider? windows, + }) async { + try { + await appCheck.activate( + providerAndroid: android ?? const AndroidPlayIntegrityProvider(), + providerApple: apple ?? const AppleDeviceCheckProvider(), + providerWeb: ReCaptchaV3Provider(kWebRecaptchaSiteKey), + providerWindows: windows ?? const WindowsDebugProvider(), + ); + final providerName = windows?.runtimeType.toString() ?? + apple?.runtimeType.toString() ?? + android?.runtimeType.toString() ?? + 'default'; + setMessage('Activated with $providerName'); + } catch (e) { + setMessage('activate error: $e'); + } + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), - body: Center( + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + const Text( + 'Providers', + 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, + ), + ), + child: const Text('activate(Debug)'), + ), + ElevatedButton( + onPressed: () => _activate( + android: const AndroidPlayIntegrityProvider(), + apple: const AppleDeviceCheckProvider(), + ), + child: const Text('activate(PlayIntegrity / DeviceCheck)'), + ), + if (!kIsWeb) + ElevatedButton( + onPressed: () => _activate( + apple: const AppleAppAttestProvider(), + ), + child: const Text('activate(AppAttest)'), + ), + if (!kIsWeb) + ElevatedButton( + onPressed: () => _activate( + apple: const AppleAppAttestWithDeviceCheckFallbackProvider(), + ), + child: const Text( + 'activate(AppAttest + DeviceCheck fallback)', + ), + ), + const SizedBox(height: 16), + const Text( + 'Actions', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), ElevatedButton( onPressed: () async { - // Use this button to check whether the request was validated on the Firebase console - // Gets first document in collection - final result = await FirebaseFirestore.instance - .collection('flutter-tests') - .limit(1) - .get(); - - if (result.docs.isNotEmpty) { - setMessage('Document found'); - } else { - setMessage( - 'Document not found, please add a document to the collection', - ); + try { + final token = await appCheck.getToken(true); + setMessage('Token: ${token?.substring(0, 20)}...'); + } catch (e) { + setMessage('getToken error: $e'); } }, - child: const Text('Test App Check validates requests'), + child: const Text('getToken(forceRefresh: true)'), ), ElevatedButton( onPressed: () async { - if (kIsWeb) { - print( - 'Pass in your "webRecaptchaSiteKey" key found on you Firebase Console to activate if using on the web platform.', + try { + final token = await appCheck.getLimitedUseToken(); + setMessage( + 'Limited use token: ${token.substring(0, 20)}...', ); + } catch (e) { + setMessage('getLimitedUseToken error: $e'); } - await appCheck.activate( - providerWeb: ReCaptchaV3Provider(kWebRecaptchaSiteKey), - ); - setMessage('activated!!'); }, - child: const Text('activate()'), + child: const Text('getLimitedUseToken()'), ), ElevatedButton( onPressed: () async { - // Token will be passed to `onTokenChange()` event handler - await appCheck.getToken(true); + await appCheck.setTokenAutoRefreshEnabled(true); + setMessage('Token auto-refresh enabled'); }, - child: const Text('getToken()'), + child: const Text('setTokenAutoRefreshEnabled(true)'), ), ElevatedButton( onPressed: () async { - await appCheck.setTokenAutoRefreshEnabled(true); - setMessage('successfully set auto token refresh!!'); + try { + final result = await FirebaseFirestore.instance + .collection('flutter-tests') + .limit(1) + .get(); + setMessage( + result.docs.isNotEmpty + ? 'Firestore: Document found' + : 'Firestore: No documents', + ); + } catch (e) { + setMessage('Firestore error: $e'); + } }, - child: const Text('setTokenAutoRefreshEnabled()'), + child: const Text('Test Firestore with App Check'), ), const SizedBox(height: 20), Text( - _message, //#007bff + _message, style: const TextStyle( color: Color.fromRGBO(47, 79, 79, 1), fontSize: 16, @@ -147,10 +235,10 @@ class _FirebaseAppCheck extends State { ), const SizedBox(height: 20), Text( - 'Token received from tokenChanges() API: $_eventToken', //#007bff + 'Token from onTokenChange: $_eventToken', style: const TextStyle( color: Color.fromRGBO(128, 0, 128, 1), - fontSize: 16, + fontSize: 14, ), ), ], diff --git a/packages/firebase_app_check/firebase_app_check/example/macos/Podfile b/packages/firebase_app_check/firebase_app_check/example/macos/Podfile deleted file mode 100644 index 9ec46f8cd53c..000000000000 --- a/packages/firebase_app_check/firebase_app_check/example/macos/Podfile +++ /dev/null @@ -1,40 +0,0 @@ -platform :osx, '10.15' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/packages/firebase_app_check/firebase_app_check/example/macos/Runner.xcodeproj/project.pbxproj b/packages/firebase_app_check/firebase_app_check/example/macos/Runner.xcodeproj/project.pbxproj index 7c41affca666..c0dc38604806 100644 --- a/packages/firebase_app_check/firebase_app_check/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_app_check/firebase_app_check/example/macos/Runner.xcodeproj/project.pbxproj @@ -28,7 +28,6 @@ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - BB0DE7CB0DF8ACBEFD8915B8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 292B95E8595C74EC66477907 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -56,10 +55,7 @@ /* Begin PBXFileReference section */ 0DC934EE60634F0D37DD0EC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; - 24274BFFB66F90649AACE273 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 25624AEB275E1E7900B1E491 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; - 292B95E8595C74EC66477907 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3206A1D9CA5124387AA36F3A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* firebase_app_check_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = firebase_app_check_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -76,7 +72,6 @@ 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - B06DD14F985336F6BE0D10BF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,7 +80,6 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - BB0DE7CB0DF8ACBEFD8915B8 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -109,7 +103,6 @@ 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, 74CB5F55BD11E25520F6FF45 /* Pods */, 0DC934EE60634F0D37DD0EC3 /* GoogleService-Info.plist */, ); @@ -162,21 +155,10 @@ 74CB5F55BD11E25520F6FF45 /* Pods */ = { isa = PBXGroup; children = ( - 24274BFFB66F90649AACE273 /* Pods-Runner.debug.xcconfig */, - 3206A1D9CA5124387AA36F3A /* Pods-Runner.release.xcconfig */, - B06DD14F985336F6BE0D10BF /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 292B95E8595C74EC66477907 /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -184,7 +166,6 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 8FAF6BECF1FF5E1A559C7452 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, @@ -304,28 +285,6 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 8FAF6BECF1FF5E1A559C7452 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/.gitignore b/packages/firebase_app_check/firebase_app_check/example/windows/.gitignore new file mode 100644 index 000000000000..d492d0d98c8f --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/CMakeLists.txt b/packages/firebase_app_check/firebase_app_check/example/windows/CMakeLists.txt new file mode 100644 index 000000000000..ffc5f0c5bd79 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(firebase_app_check_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "firebase_app_check_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/flutter/CMakeLists.txt b/packages/firebase_app_check/firebase_app_check/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000000..903f4899d6fc --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/CMakeLists.txt b/packages/firebase_app_check/firebase_app_check/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000000..394917c053a0 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/Runner.rc b/packages/firebase_app_check/firebase_app_check/example/windows/runner/Runner.rc new file mode 100644 index 000000000000..26d198ffd138 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "io.flutter.plugins.firebase.appcheck" "\0" + VALUE "FileDescription", "firebase_app_check_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "firebase_app_check_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 io.flutter.plugins.firebase.appcheck. All rights reserved." "\0" + VALUE "OriginalFilename", "firebase_app_check_example.exe" "\0" + VALUE "ProductName", "firebase_app_check_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/flutter_window.cpp b/packages/firebase_app_check/firebase_app_check/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000000..d1bd86f4967a --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/flutter_window.cpp @@ -0,0 +1,73 @@ +// 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. + +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/flutter_window.h b/packages/firebase_app_check/firebase_app_check/example/windows/runner/flutter_window.h new file mode 100644 index 000000000000..243c83529a77 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/flutter_window.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/main.cpp b/packages/firebase_app_check/firebase_app_check/example/windows/runner/main.cpp new file mode 100644 index 000000000000..129ed2c35f81 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/main.cpp @@ -0,0 +1,46 @@ +// 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. + +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t* command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"firebase_app_check_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/resource.h b/packages/firebase_app_check/firebase_app_check/example/windows/runner/resource.h new file mode 100644 index 000000000000..91d70fa37f5d --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/resource.h @@ -0,0 +1,20 @@ +// 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. + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/resources/app_icon.ico b/packages/firebase_app_check/firebase_app_check/example/windows/runner/resources/app_icon.ico new file mode 100644 index 000000000000..c04e20caf637 Binary files /dev/null and b/packages/firebase_app_check/firebase_app_check/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/runner.exe.manifest b/packages/firebase_app_check/firebase_app_check/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000000..153653e8d67f --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/utils.cpp b/packages/firebase_app_check/firebase_app_check/example/windows/runner/utils.cpp new file mode 100644 index 000000000000..3b1344754669 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +// 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. + +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE* unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, + nullptr, 0, nullptr, nullptr) - + 1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, input_length, + utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/utils.h b/packages/firebase_app_check/firebase_app_check/example/windows/runner/utils.h new file mode 100644 index 000000000000..8ec0c44d5bfe --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/utils.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/win32_window.cpp b/packages/firebase_app_check/firebase_app_check/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000000..82754b04cd09 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/win32_window.cpp @@ -0,0 +1,284 @@ +// 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. + +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: +/// https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = + L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { ++g_active_window_count; } + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { return ShowWindow(window_handle_, SW_SHOWNORMAL); } + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { return window_handle_; } + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = + RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, + &light_mode, &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/packages/firebase_app_check/firebase_app_check/example/windows/runner/win32_window.h b/packages/firebase_app_check/firebase_app_check/example/windows/runner/win32_window.h new file mode 100644 index 000000000000..dd2425483126 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/example/windows/runner/win32_window.h @@ -0,0 +1,104 @@ +// 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. + +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check.podspec b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check.podspec index a1a5161f0e7f..b4d81500ec2a 100644 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check.podspec +++ b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check.podspec @@ -24,8 +24,7 @@ Pod::Spec.new do |s| s.license = { :file => '../LICENSE' } s.authors = 'The Chromium Authors' s.source = { :path => '.' } - s.source_files = 'firebase_app_check/Sources/firebase_app_check/**/*.{h,m}' - s.public_header_files = 'firebase_app_check/Sources/firebase_app_check/include/*.h' + s.source_files = 'firebase_app_check/Sources/firebase_app_check/**/*.swift' s.ios.deployment_target = '15.0' # Flutter dependencies diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Package.swift b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Package.swift index 55b8a05577b1..59c19bba50af 100644 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Package.swift +++ b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Package.swift @@ -7,7 +7,6 @@ import PackageDescription -let library_version = "0.4.1-5" let firebase_sdk_version: Version = "12.9.0" let package = Package( @@ -31,11 +30,6 @@ let package = Package( ], resources: [ .process("Resources"), - ], - cSettings: [ - .headerSearchPath("include"), - .define("LIBRARY_VERSION", to: "\"\(library_version)\""), - .define("LIBRARY_NAME", to: "\"flutter-fire-appcheck\""), ] ), ] diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/Constants.swift b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/Constants.swift new file mode 100644 index 000000000000..7dbfc84ba33b --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/Constants.swift @@ -0,0 +1,6 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Auto-generated file. Do not edit. +public let versionNumber = "0.4.2" diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProvider.m b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProvider.m deleted file mode 100644 index aacddaf48f2f..000000000000 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProvider.m +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FLTAppCheckProvider.h" - -@implementation FLTAppCheckProvider - -- (id)initWithApp:app { - self = [super init]; - if (self) { - self.app = app; - } - return self; -} - -- (void)configure:(FIRApp *)app - providerName:(NSString *)providerName - debugToken:(NSString *)debugToken { - if ([providerName isEqualToString:@"debug"]) { - if (debugToken != nil) { - // We have a debug token, so just need to stuff it in the environment and it will hook up - char *key = "FIRAAppCheckDebugToken", *value = (char *)[debugToken UTF8String]; - int overwrite = 1; - setenv(key, value, overwrite); - } - FIRAppCheckDebugProvider *provider = [[FIRAppCheckDebugProvider alloc] initWithApp:app]; - if (debugToken == nil) NSLog(@"Firebase App Check Debug Token: %@", [provider localDebugToken]); - self.delegateProvider = provider; - } - - if ([providerName isEqualToString:@"deviceCheck"]) { - self.delegateProvider = [[FIRDeviceCheckProvider alloc] initWithApp:app]; - } - - if ([providerName isEqualToString:@"appAttest"]) { - if (@available(iOS 14.0, macCatalyst 14.0, tvOS 15.0, watchOS 9.0, *)) { - self.delegateProvider = [[FIRAppAttestProvider alloc] initWithApp:app]; - } else { - // This is not a valid environment, setup debug provider. - self.delegateProvider = [[FIRAppCheckDebugProvider alloc] initWithApp:app]; - } - } - - if ([providerName isEqualToString:@"appAttestWithDeviceCheckFallback"]) { - if (@available(iOS 14.0, *)) { - self.delegateProvider = [[FIRAppAttestProvider alloc] initWithApp:app]; - } else { - self.delegateProvider = [[FIRDeviceCheckProvider alloc] initWithApp:app]; - } - } -} - -- (void)getTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken *_Nullable, - NSError *_Nullable))handler { - // Proxying to delegateProvider - [self.delegateProvider getTokenWithCompletion:handler]; -} - -@end diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProviderFactory.m b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProviderFactory.m deleted file mode 100644 index 3eee91a08fc8..000000000000 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProviderFactory.m +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -@import FirebaseAppCheck; -@import FirebaseCore; - -#import "FLTAppCheckProviderFactory.h" - -#import "FLTAppCheckProvider.h" - -@implementation FLTAppCheckProviderFactory - -- (nullable id)createProviderWithApp:(FIRApp *)app { - // The SDK may try to call this before we have been configured, - // so we will configure ourselves and set the provider up as a default to start - // pre-configure - if (self.providers == nil) { - self.providers = [NSMutableDictionary new]; - } - - if (self.providers[app.name] == nil) { - self.providers[app.name] = [FLTAppCheckProvider new]; - FLTAppCheckProvider *provider = self.providers[app.name]; - // We set "deviceCheck" as this is currently what is default. Backward compatible. - [provider configure:app providerName:@"deviceCheck" debugToken:nil]; - } - - return self.providers[app.name]; -} - -- (void)configure:(FIRApp *)app - providerName:(NSString *)providerName - debugToken:(NSString *)debugToken { - if (self.providers == nil) { - self.providers = [NSMutableDictionary new]; - } - - if (self.providers[app.name] == nil) { - self.providers[app.name] = [FLTAppCheckProvider new]; - } - - FLTAppCheckProvider *provider = self.providers[app.name]; - [provider configure:app providerName:providerName debugToken:debugToken]; -} - -@end diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTFirebaseAppCheckPlugin.m b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTFirebaseAppCheckPlugin.m deleted file mode 100644 index 7c47283e60b9..000000000000 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTFirebaseAppCheckPlugin.m +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FLTFirebaseAppCheckPlugin.h" -#import "FLTTokenRefreshStreamHandler.h" - -@import FirebaseAppCheck; - -#if __has_include() -#import -#else -#import -#endif - -#import "FLTAppCheckProviderFactory.h" - -NSString *const kFLTFirebaseAppCheckChannelName = @"plugins.flutter.io/firebase_app_check"; - -@interface FLTFirebaseAppCheckPlugin () -@end - -@implementation FLTFirebaseAppCheckPlugin { - NSMutableDictionary *_eventChannels; - NSMutableDictionary *> *_streamHandlers; - NSObject *_binaryMessenger; - FLTAppCheckProviderFactory *_Nullable providerFactory; -} - -#pragma mark - FlutterPlugin - -- (instancetype)init:(NSObject *)messenger { - self = [super init]; - if (self) { - self->providerFactory = [[FLTAppCheckProviderFactory alloc] init]; - [FIRAppCheck setAppCheckProviderFactory:self->providerFactory]; - - [[FLTFirebasePluginRegistry sharedInstance] registerFirebasePlugin:self]; - _binaryMessenger = messenger; - _eventChannels = [NSMutableDictionary dictionary]; - _streamHandlers = [NSMutableDictionary dictionary]; - } - return self; -} - -+ (void)registerWithRegistrar:(NSObject *)registrar { - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:kFLTFirebaseAppCheckChannelName - binaryMessenger:[registrar messenger]]; - FLTFirebaseAppCheckPlugin *instance = - [[FLTFirebaseAppCheckPlugin alloc] init:registrar.messenger]; - [registrar addMethodCallDelegate:instance channel:channel]; -} - -- (void)cleanupWithCompletion:(void (^)(void))completion { - for (FlutterEventChannel *channel in self->_eventChannels.allValues) { - [channel setStreamHandler:nil]; - } - [self->_eventChannels removeAllObjects]; - for (NSObject *handler in self->_streamHandlers.allValues) { - [handler onCancelWithArguments:nil]; - } - [self->_streamHandlers removeAllObjects]; - - if (completion != nil) completion(); -} - -- (void)detachFromEngineForRegistrar:(NSObject *)registrar { - [self cleanupWithCompletion:nil]; -} - -- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutterResult { - FLTFirebaseMethodCallErrorBlock errorBlock = ^( - NSString *_Nullable code, NSString *_Nullable message, NSDictionary *_Nullable details, - NSError *_Nullable error) { - NSMutableDictionary *errorDetails = [NSMutableDictionary dictionary]; - NSString *errorCode; - - switch (error.code) { - case FIRAppCheckErrorCodeServerUnreachable: - errorCode = @"server-unreachable"; - break; - case FIRAppCheckErrorCodeInvalidConfiguration: - errorCode = @"invalid-configuration"; - break; - case FIRAppCheckErrorCodeKeychain: - errorCode = @"code-keychain"; - break; - case FIRAppCheckErrorCodeUnsupported: - errorCode = @"code-unsupported"; - break; - case FIRAppCheckErrorCodeUnknown: - default: - errorCode = @"unknown"; - } - - NSString *errorMessage = error.localizedDescription; - errorDetails[@"code"] = errorCode; - errorDetails[@"message"] = errorMessage; - flutterResult([FlutterError errorWithCode:errorCode message:errorMessage details:errorDetails]); - }; - - FLTFirebaseMethodCallResult *methodCallResult = - [FLTFirebaseMethodCallResult createWithSuccess:flutterResult andErrorBlock:errorBlock]; - - if ([@"FirebaseAppCheck#activate" isEqualToString:call.method]) { - [self activate:call.arguments withMethodCallResult:methodCallResult]; - } else if ([@"FirebaseAppCheck#getToken" isEqualToString:call.method]) { - [self getToken:call.arguments withMethodCallResult:methodCallResult]; - } else if ([@"FirebaseAppCheck#setTokenAutoRefreshEnabled" isEqualToString:call.method]) { - [self setTokenAutoRefreshEnabled:call.arguments withMethodCallResult:methodCallResult]; - } else if ([@"FirebaseAppCheck#registerTokenListener" isEqualToString:call.method]) { - [self registerTokenListener:call.arguments withMethodCallResult:methodCallResult]; - } else if ([@"FirebaseAppCheck#getLimitedUseAppCheckToken" isEqualToString:call.method]) { - [self getLimitedUseAppCheckToken:call.arguments withMethodCallResult:methodCallResult]; - } else { - flutterResult(FlutterMethodNotImplemented); - } -} - -#pragma mark - Firebase App Check API - -- (void)activate:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { - NSString *appNameDart = arguments[@"appName"]; - NSString *providerName = arguments[@"appleProvider"]; - NSString *debugToken = arguments[@"appleDebugToken"]; - - FIRApp *app = [FLTFirebasePlugin firebaseAppNamed:appNameDart]; - [self->providerFactory configure:app providerName:providerName debugToken:debugToken]; - result.success(nil); -} - -- (void)registerTokenListener:(id)arguments - withMethodCallResult:(FLTFirebaseMethodCallResult *)result { - NSString *appName = arguments[@"appName"]; - NSString *name = - [NSString stringWithFormat:@"%@/token/%@", kFLTFirebaseAppCheckChannelName, appName]; - - FlutterEventChannel *channel = [FlutterEventChannel eventChannelWithName:name - binaryMessenger:_binaryMessenger]; - - FLTTokenRefreshStreamHandler *handler = [[FLTTokenRefreshStreamHandler alloc] init]; - [channel setStreamHandler:handler]; - - [_eventChannels setObject:channel forKey:name]; - [_streamHandlers setObject:handler forKey:name]; - result.success(name); -} - -- (void)getToken:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { - FIRAppCheck *appCheck = [self getFIRAppCheckFromArguments:arguments]; - bool forceRefresh = [arguments[@"forceRefresh"] boolValue]; - - [appCheck tokenForcingRefresh:forceRefresh - completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - if (error != nil) { - result.error(nil, nil, nil, error); - } else { - result.success(token.token); - } - }]; -} - -- (void)getLimitedUseAppCheckToken:(id)arguments - withMethodCallResult:(FLTFirebaseMethodCallResult *)result { - FIRAppCheck *appCheck = [self getFIRAppCheckFromArguments:arguments]; - [appCheck - limitedUseTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - if (error != nil) { - result.error(nil, nil, nil, error); - } else { - result.success(token.token); - } - }]; -} - -- (void)setTokenAutoRefreshEnabled:(id)arguments - withMethodCallResult:(FLTFirebaseMethodCallResult *)result { - FIRAppCheck *appCheck = [self getFIRAppCheckFromArguments:arguments]; - bool isTokenAutoRefreshEnabled = arguments[@"isTokenAutoRefreshEnabled"]; - appCheck.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled; - result.success(nil); -} - -#pragma mark - FLTFirebasePlugin - -- (void)didReinitializeFirebaseCore:(void (^)(void))completion { - [self cleanupWithCompletion:completion]; -} - -- (NSDictionary *_Nonnull)pluginConstantsForFIRApp:(FIRApp *)firebase_app { - return @{}; -} - -- (NSString *_Nonnull)firebaseLibraryName { - return @LIBRARY_NAME; -} - -- (NSString *_Nonnull)firebaseLibraryVersion { - return @LIBRARY_VERSION; -} - -- (NSString *_Nonnull)flutterChannelName { - return kFLTFirebaseAppCheckChannelName; -} - -#pragma mark - Utilities - -- (FIRAppCheck *_Nullable)getFIRAppCheckFromArguments:(NSDictionary *)arguments { - NSString *appNameDart = arguments[@"appName"]; - FIRApp *app = [FLTFirebasePlugin firebaseAppNamed:appNameDart]; - FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:app]; - - return appCheck; -} - -@end diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTTokenRefreshStreamHandler.m b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTTokenRefreshStreamHandler.m deleted file mode 100644 index 4b411926d616..000000000000 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FLTTokenRefreshStreamHandler.m +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FLTTokenRefreshStreamHandler.h" -#import "FLTFirebaseAppCheckPlugin.h" - -const NSNotificationName kNotififactionEvent = @"FIRAppCheckAppCheckTokenDidChangeNotification"; - -NSString *const kTokenKey = @"FIRAppCheckTokenNotificationKey"; - -@implementation FLTTokenRefreshStreamHandler { - id _observer; -} - -- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events { - _observer = - [NSNotificationCenter.defaultCenter addObserverForName:kNotififactionEvent - object:nil - queue:nil - usingBlock:^(NSNotification *_Nonnull note) { - NSString *token = note.userInfo[kTokenKey]; - - events(@{@"token" : token}); - }]; - - return nil; -} - -- (FlutterError *)onCancelWithArguments:(id)arguments { - [NSNotificationCenter.defaultCenter removeObserver:_observer]; - return nil; -} - -@end 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 new file mode 100644 index 000000000000..1842cfe9c240 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift @@ -0,0 +1,233 @@ +// 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. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Sendable? + + init(code: String, message: String?, details: Sendable?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +private class FirebaseAppCheckMessagesPigeonCodecReader: FlutterStandardReader {} + +private class FirebaseAppCheckMessagesPigeonCodecWriter: FlutterStandardWriter {} + +private class FirebaseAppCheckMessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + FirebaseAppCheckMessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + FirebaseAppCheckMessagesPigeonCodecWriter(data: data) + } +} + +class FirebaseAppCheckMessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + 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 registerTokenListener(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 = "") { + 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 { + 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 + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } 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 { + getTokenChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let appNameArg = args[0] as! String + let forceRefreshArg = args[1] as! Bool + api.getToken(appName: appNameArg, forceRefresh: forceRefreshArg) { result in + switch result { + case let .success(res): + reply(wrapResult(res)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } 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 { + 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 + switch result { + case .success: + reply(wrapResult(nil)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } 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 { + registerTokenListenerChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let appNameArg = args[0] as! String + api.registerTokenListener(appName: appNameArg) { result in + switch result { + case let .success(res): + reply(wrapResult(res)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } 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 { + getLimitedUseAppCheckTokenChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let appNameArg = args[0] as! String + api.getLimitedUseAppCheckToken(appName: appNameArg) { result in + switch result { + case let .success(res): + reply(wrapResult(res)) + case let .failure(error): + reply(wrapError(error)) + } + } + } + } else { + getLimitedUseAppCheckTokenChannel.setMessageHandler(nil) + } + } +} 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 new file mode 100644 index 000000000000..5aec32b1ad2c --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift @@ -0,0 +1,300 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if canImport(FlutterMacOS) + import FlutterMacOS +#else + import Flutter +#endif + +#if canImport(firebase_core) + import firebase_core +#else + import firebase_core_shared +#endif +import FirebaseAppCheck +import FirebaseCore + +let kFirebaseAppCheckChannelName = "plugins.flutter.io/firebase_app_check" +let kFirebaseAppCheckTokenChannelPrefix = "plugins.flutter.io/firebase_app_check/token/" + +extension FlutterError: @retroactive Error {} + +public class FirebaseAppCheckPlugin: NSObject, FlutterPlugin, + FLTFirebasePluginProtocol, FirebaseAppCheckHostApi { + private var eventChannels: [String: FlutterEventChannel] = [:] + private var streamHandlers: [String: AppCheckTokenStreamHandler] = [:] + private var providerFactory: FlutterAppCheckProviderFactory? + + static let shared: FirebaseAppCheckPlugin = { + let instance = FirebaseAppCheckPlugin() + instance.providerFactory = FlutterAppCheckProviderFactory() + AppCheck.setAppCheckProviderFactory(instance.providerFactory) + FLTFirebasePluginRegistry.sharedInstance().register(instance) + return instance + }() + + public static func register(with registrar: FlutterPluginRegistrar) { + let binaryMessenger: FlutterBinaryMessenger + + #if os(macOS) + binaryMessenger = registrar.messenger + #elseif os(iOS) + binaryMessenger = registrar.messenger() + #endif + + let instance = shared + instance.binaryMessenger = binaryMessenger + FirebaseAppCheckHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: instance) + + if FirebaseApp.responds(to: NSSelectorFromString("registerLibrary:withVersion:")) { + FirebaseApp.perform( + NSSelectorFromString("registerLibrary:withVersion:"), + with: instance.firebaseLibraryName(), + with: instance.firebaseLibraryVersion() + ) + } + } + + private var binaryMessenger: FlutterBinaryMessenger? + + func activate(appName: String, androidProvider: String?, appleProvider: String?, + debugToken: String?, + completion: @escaping (Result) -> Void) { + guard let app = FLTFirebasePlugin.firebaseAppNamed(appName) else { + completion(.failure(FlutterError( + code: "unknown", message: "Firebase app not found: \(appName)", details: nil + ))) + return + } + let provider = appleProvider ?? "deviceCheck" + + providerFactory?.configure(app: app, providerName: provider, debugToken: debugToken) + + completion(.success(())) + } + + func getToken(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?.token)) + } + } + } + + func setTokenAutoRefreshEnabled(appName: String, isTokenAutoRefreshEnabled: 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.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled + completion(.success(())) + } + + func registerTokenListener(appName: String, + completion: @escaping (Result) -> Void) { + let name = kFirebaseAppCheckTokenChannelPrefix + appName + + guard let messenger = binaryMessenger else { + completion(.failure(FlutterError( + code: "no-messenger", + message: "Binary messenger not available", + details: nil + ))) + return + } + + let channel = FlutterEventChannel(name: name, binaryMessenger: messenger) + let handler = AppCheckTokenStreamHandler() + channel.setStreamHandler(handler) + + eventChannels[name] = channel + streamHandlers[name] = handler + + completion(.success(name)) + } + + func getLimitedUseAppCheckToken(appName: String, + 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.limitedUseToken { token, error in + if let error { + completion(.failure(self.createFlutterError(error))) + } else { + completion(.success(token?.token ?? "")) + } + } + } + + // MARK: - FLTFirebasePluginProtocol + + public func didReinitializeFirebaseCore(_ completion: @escaping () -> Void) { + for (_, channel) in eventChannels { + channel.setStreamHandler(nil) + } + for (_, handler) in streamHandlers { + handler.onCancel(withArguments: nil) + } + eventChannels.removeAll() + streamHandlers.removeAll() + completion() + } + + public func pluginConstants(for firebaseApp: FirebaseApp) -> [AnyHashable: Any] { + [:] + } + + public func firebaseLibraryName() -> String { + "flutter-fire-appcheck" + } + + public func firebaseLibraryVersion() -> String { + versionNumber + } + + public func flutterChannelName() -> String { + kFirebaseAppCheckChannelName + } + + private func createFlutterError(_ error: Error) -> FlutterError { + let nsError = error as NSError + var code = "unknown" + switch nsError.code { + case 0: // FIRAppCheckErrorCodeServerUnreachable + code = "server-unreachable" + case 1: // FIRAppCheckErrorCodeInvalidConfiguration + code = "invalid-configuration" + case 2: // FIRAppCheckErrorCodeKeychain + code = "code-keychain" + case 3: // FIRAppCheckErrorCodeUnsupported + code = "code-unsupported" + default: + code = "unknown" + } + return FlutterError( + code: code, + message: nsError.localizedDescription, + details: nil + ) + } +} + +// MARK: - Token Stream Handler + +class AppCheckTokenStreamHandler: NSObject, FlutterStreamHandler { + private var observer: NSObjectProtocol? + + func onListen(withArguments arguments: Any?, + eventSink events: @escaping FlutterEventSink) -> FlutterError? { + observer = NotificationCenter.default.addObserver( + forName: NSNotification.Name("FIRAppCheckAppCheckTokenDidChangeNotification"), + object: nil, + queue: nil + ) { notification in + if let token = notification.userInfo?["FIRAppCheckTokenNotificationKey"] as? String { + events(["token": token]) + } + } + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + if let observer { + NotificationCenter.default.removeObserver(observer) + self.observer = nil + } + return nil + } +} + +// MARK: - App Check Provider Factory + +class FlutterAppCheckProviderFactory: NSObject, AppCheckProviderFactory { + private var providers: [String: AppCheckProviderWrapper] = [:] + + func createProvider(with app: FirebaseApp) -> (any AppCheckProvider)? { + 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) + providers[app.name] = wrapper + } + return providers[app.name] + } + + 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) + } +} + +class AppCheckProviderWrapper: NSObject, AppCheckProvider { + private var delegateProvider: (any AppCheckProvider)? + + func configure(app: FirebaseApp, providerName: String, debugToken: String?) { + switch providerName { + case "debug": + if let debugToken { + setenv("FIRAAppCheckDebugToken", debugToken, 1) + } + delegateProvider = AppCheckDebugProvider(app: app) + if debugToken == nil, let debugProvider = delegateProvider as? AppCheckDebugProvider { + print("Firebase App Check Debug Token: \(debugProvider.localDebugToken())") + } + case "appAttest": + if #available(iOS 14.0, macOS 14.0, macCatalyst 14.0, tvOS 15.0, watchOS 9.0, *) { + delegateProvider = AppAttestProvider(app: app) + } else { + delegateProvider = AppCheckDebugProvider(app: app) + } + case "appAttestWithDeviceCheckFallback": + if #available(iOS 14.0, macOS 14.0, *) { + delegateProvider = AppAttestProvider(app: app) + } else { + delegateProvider = DeviceCheckProvider(app: app) + } + default: + // deviceCheck + delegateProvider = DeviceCheckProvider(app: app) + } + } + + func getToken(completion handler: @escaping (AppCheckToken?, Error?) -> Void) { + guard let delegateProvider else { + handler(nil, NSError( + domain: "firebase_app_check", code: -1, + userInfo: [NSLocalizedDescriptionKey: "Provider not configured"] + )) + return + } + delegateProvider.getToken(completion: handler) + } +} diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProvider.h b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProvider.h deleted file mode 100644 index da9efde18370..000000000000 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProvider.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import FirebaseAppCheck; - -@interface FLTAppCheckProvider : NSObject - -@property FIRApp *app; - -@property id delegateProvider; - -- (void)configure:(FIRApp *)app - providerName:(NSString *)providerName - debugToken:(NSString *)debugToken; - -- (id)initWithApp:(FIRApp *)app; - -@end diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProviderFactory.h b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProviderFactory.h deleted file mode 100644 index 8e5511ebea94..000000000000 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProviderFactory.h +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -#import - -@interface FLTAppCheckProviderFactory : NSObject - -@property NSMutableDictionary *_Nullable providers; - -- (void)configure:(FIRApp *_Nonnull)app - providerName:(NSString *_Nonnull)providerName - debugToken:(NSString *_Nullable)debugToken; - -@end diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTFirebaseAppCheckPlugin.h b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTFirebaseAppCheckPlugin.h deleted file mode 100644 index 9d07d0ebc671..000000000000 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTFirebaseAppCheckPlugin.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -#import - -#if TARGET_OS_OSX -#import -#else -#import -#endif - -#import -#if __has_include() -#import -#else -#import -#endif -#import "FLTAppCheckProviderFactory.h" - -@interface FLTFirebaseAppCheckPlugin : FLTFirebasePlugin -@end diff --git a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTTokenRefreshStreamHandler.h b/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTTokenRefreshStreamHandler.h deleted file mode 100644 index acd570bc3b75..000000000000 --- a/packages/firebase_app_check/firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/include/FLTTokenRefreshStreamHandler.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -#if TARGET_OS_OSX -#import -#else -#import -#endif - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface FLTTokenRefreshStreamHandler : NSObject -@end - -NS_ASSUME_NONNULL_END 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 c5f5ae1ad633..cd569468c12c 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 @@ -21,7 +21,9 @@ export 'package:firebase_app_check_platform_interface/firebase_app_check_platfor AppleAppAttestWithDeviceCheckFallbackProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider, - WebDebugProvider; + WebDebugProvider, + WindowsAppCheckProvider, + WindowsDebugProvider; export 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' show FirebaseException; diff --git a/packages/firebase_app_check/firebase_app_check/lib/src/firebase_app_check.dart b/packages/firebase_app_check/firebase_app_check/lib/src/firebase_app_check.dart index d605174a3366..25a1845449e8 100644 --- a/packages/firebase_app_check/firebase_app_check/lib/src/firebase_app_check.dart +++ b/packages/firebase_app_check/firebase_app_check/lib/src/firebase_app_check.dart @@ -61,6 +61,13 @@ class FirebaseAppCheck extends FirebasePluginPlatform { /// "app attest with fallback to device check" via `AppleAppCheckProvider`. /// Note: App Attest is only available on iOS 14.0+ and macOS 14.0+. /// + /// **Windows**: Only the debug provider is supported. You **must** supply a + /// debug token — the desktop C++ SDK does not auto-generate one. Either pass + /// it via `providerWindows: WindowsDebugProvider(debugToken: 'your-token')` + /// or set the `APP_CHECK_DEBUG_TOKEN` environment variable. The token must + /// first be registered in the Firebase Console under + /// *App Check → Apps → Manage debug tokens*. + /// /// ## Migration Notice /// /// The `androidProvider` and `appleProvider` parameters will be deprecated @@ -89,6 +96,7 @@ class FirebaseAppCheck extends FirebasePluginPlatform { AndroidAppCheckProvider providerAndroid = const AndroidPlayIntegrityProvider(), AppleAppCheckProvider providerApple = const AppleDeviceCheckProvider(), + WindowsAppCheckProvider providerWindows = const WindowsDebugProvider(), }) { return _delegate.activate( webProvider: providerWeb ?? webProvider, @@ -98,6 +106,7 @@ class FirebaseAppCheck extends FirebasePluginPlatform { appleProvider: appleProvider, providerAndroid: providerAndroid, providerApple: providerApple, + providerWindows: providerWindows, ); } diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check.podspec b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check.podspec index d1b3b7eb6fe8..497fbc642162 100644 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check.podspec +++ b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check.podspec @@ -43,8 +43,7 @@ Pod::Spec.new do |s| s.authors = 'The Chromium Authors' s.source = { :path => '.' } - s.source_files = 'firebase_app_check/Sources/firebase_app_check/**/*.{h,m}' - s.public_header_files = 'firebase_app_check/Sources/firebase_app_check/include/*.h' + s.source_files = 'firebase_app_check/Sources/firebase_app_check/**/*.swift' s.platform = :osx, '10.13' diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Package.swift b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Package.swift index 092bbc07f8ed..b78be7a14dba 100644 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Package.swift +++ b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Package.swift @@ -7,7 +7,6 @@ import PackageDescription -let library_version = "0.4.1-5" let firebase_sdk_version: Version = "12.9.0" let package = Package( @@ -31,11 +30,6 @@ let package = Package( ], resources: [ .process("Resources"), - ], - cSettings: [ - .headerSearchPath("include"), - .define("LIBRARY_VERSION", to: "\"\(library_version)\""), - .define("LIBRARY_NAME", to: "\"flutter-fire-appcheck\""), ] ), ] diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/Constants.swift b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/Constants.swift new file mode 120000 index 000000000000..4a4a4bdd92d1 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/Constants.swift @@ -0,0 +1 @@ +../../../../ios/firebase_app_check/Sources/firebase_app_check/Constants.swift \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProvider.m b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProvider.m deleted file mode 120000 index 57fc55914ef1..000000000000 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProvider.m +++ /dev/null @@ -1 +0,0 @@ -../../../../ios/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProvider.m \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProviderFactory.m b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProviderFactory.m deleted file mode 120000 index 90899e731380..000000000000 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProviderFactory.m +++ /dev/null @@ -1 +0,0 @@ -../../../../ios/firebase_app_check/Sources/firebase_app_check/FLTAppCheckProviderFactory.m \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTFirebaseAppCheckPlugin.m b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTFirebaseAppCheckPlugin.m deleted file mode 120000 index 3bef267bc202..000000000000 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTFirebaseAppCheckPlugin.m +++ /dev/null @@ -1 +0,0 @@ -../../../../ios/firebase_app_check/Sources/firebase_app_check/FLTFirebaseAppCheckPlugin.m \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTTokenRefreshStreamHandler.m b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTTokenRefreshStreamHandler.m deleted file mode 120000 index 93cf7946f352..000000000000 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FLTTokenRefreshStreamHandler.m +++ /dev/null @@ -1 +0,0 @@ -../../../../ios/firebase_app_check/Sources/firebase_app_check/FLTTokenRefreshStreamHandler.m \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift new file mode 120000 index 000000000000..3593b6772bba --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift @@ -0,0 +1 @@ +../../../../ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift new file mode 120000 index 000000000000..37995e4b53db --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift @@ -0,0 +1 @@ +../../../../ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckPlugin.swift \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProvider.h b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProvider.h deleted file mode 120000 index 62bc70731543..000000000000 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProvider.h +++ /dev/null @@ -1 +0,0 @@ -../../../../../ios/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProvider.h \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProviderFactory.h b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProviderFactory.h deleted file mode 120000 index 5638882c87c2..000000000000 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProviderFactory.h +++ /dev/null @@ -1 +0,0 @@ -../../../../../ios/firebase_app_check/Sources/firebase_app_check/include/FLTAppCheckProviderFactory.h \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTFirebaseAppCheckPlugin.h b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTFirebaseAppCheckPlugin.h deleted file mode 120000 index e64b79a492b6..000000000000 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTFirebaseAppCheckPlugin.h +++ /dev/null @@ -1 +0,0 @@ -../../../../../ios/firebase_app_check/Sources/firebase_app_check/include/FLTFirebaseAppCheckPlugin.h \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTTokenRefreshStreamHandler.h b/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTTokenRefreshStreamHandler.h deleted file mode 120000 index f4b967d7d5b3..000000000000 --- a/packages/firebase_app_check/firebase_app_check/macos/firebase_app_check/Sources/firebase_app_check/include/FLTTokenRefreshStreamHandler.h +++ /dev/null @@ -1 +0,0 @@ -../../../../../ios/firebase_app_check/Sources/firebase_app_check/include/FLTTokenRefreshStreamHandler.h \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check/pubspec.yaml b/packages/firebase_app_check/firebase_app_check/pubspec.yaml index ae655d160abc..48ed3305f04e 100644 --- a/packages/firebase_app_check/firebase_app_check/pubspec.yaml +++ b/packages/firebase_app_check/firebase_app_check/pubspec.yaml @@ -36,10 +36,12 @@ flutter: platforms: android: package: io.flutter.plugins.firebase.appcheck - pluginClass: FlutterFirebaseAppCheckPlugin + pluginClass: FirebaseAppCheckPlugin ios: - pluginClass: FLTFirebaseAppCheckPlugin + pluginClass: FirebaseAppCheckPlugin macos: - pluginClass: FLTFirebaseAppCheckPlugin + pluginClass: FirebaseAppCheckPlugin web: default_package: firebase_app_check_web + windows: + pluginClass: FirebaseAppCheckPluginCApi diff --git a/packages/firebase_app_check/firebase_app_check/test/firebase_app_check_test.dart b/packages/firebase_app_check/firebase_app_check/test/firebase_app_check_test.dart index 98aca115dc08..3c07056759cf 100755 --- a/packages/firebase_app_check/firebase_app_check/test/firebase_app_check_test.dart +++ b/packages/firebase_app_check/firebase_app_check/test/firebase_app_check_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:firebase_app_check/firebase_app_check.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -13,7 +11,6 @@ import './mock.dart'; void main() { setupFirebaseAppCheckMocks(); late FirebaseApp secondaryApp; - late FirebaseAppCheck appCheck; group('$FirebaseAppCheck', () { setUpAll(() async { @@ -27,15 +24,8 @@ void main() { messagingSenderId: '1234567890', ), ); - appCheck = FirebaseAppCheck.instance; - }); - - setUp(() async { - methodCallLog.clear(); }); - tearDown(methodCallLog.clear); - group('instance', () { test('successful call', () async { final appCheck = FirebaseAppCheck.instance; @@ -53,80 +43,5 @@ void main() { expect(appCheck.app.name, 'secondaryApp'); }); }); - - group('activate', () { - test('successful call', () async { - await appCheck.activate( - providerWeb: ReCaptchaV3Provider('key'), - providerAndroid: const AndroidDebugProvider( - debugToken: 'androidDebug', - ), - providerApple: const AppleDebugProvider( - debugToken: 'appleDebug', - ), - ); - - expect( - methodCallLog, - [ - isMethodCall( - 'FirebaseAppCheck#activate', - arguments: { - 'appName': defaultFirebaseAppName, - 'androidProvider': 'debug', - 'appleProvider': 'debug', - 'androidDebugToken': 'androidDebug', - 'appleDebugToken': 'appleDebug', - }, - ), - ], - ); - }); - }); - group('getToken', () { - test('successful call', () async { - await appCheck.getToken(true); - - expect( - methodCallLog, - [ - isMethodCall( - 'FirebaseAppCheck#getToken', - arguments: { - 'appName': defaultFirebaseAppName, - 'forceRefresh': true, - }, - ), - ], - ); - }); - }); - - group('setTokenAutoRefreshEnabled', () { - test('successful call', () async { - await appCheck.setTokenAutoRefreshEnabled(false); - - expect( - methodCallLog, - [ - isMethodCall( - 'FirebaseAppCheck#setTokenAutoRefreshEnabled', - arguments: { - 'appName': defaultFirebaseAppName, - 'isTokenAutoRefreshEnabled': false, - }, - ), - ], - ); - }); - }); - - group('tokenChanges', () { - test('successful call', () async { - final stream = appCheck.onTokenChange; - - expect(stream, isA>()); - }); - }); }); } diff --git a/packages/firebase_app_check/firebase_app_check/test/mock.dart b/packages/firebase_app_check/firebase_app_check/test/mock.dart index 2da26a89c239..1f151cf1cac2 100644 --- a/packages/firebase_app_check/firebase_app_check/test/mock.dart +++ b/packages/firebase_app_check/firebase_app_check/test/mock.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:firebase_app_check_platform_interface/firebase_app_check_platform_interface.dart'; import 'package:firebase_core_platform_interface/test.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -15,21 +14,4 @@ void setupFirebaseAppCheckMocks([Callback? customHandlers]) { TestWidgetsFlutterBinding.ensureInitialized(); setupFirebaseCoreMocks(); - - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(MethodChannelFirebaseAppCheck.channel, - (MethodCall methodCall) async { - if (methodCall.method != 'FirebaseAppCheck#registerTokenListener') { - methodCallLog.add(methodCall); - } - - switch (methodCall.method) { - case 'FirebaseAppCheck#registerTokenListener': - return 'channelName'; - case 'FirebaseAppCheck#getToken': - return 'test-token'; - default: - return false; - } - }); } diff --git a/packages/firebase_app_check/firebase_app_check/windows/CMakeLists.txt b/packages/firebase_app_check/firebase_app_check/windows/CMakeLists.txt new file mode 100644 index 000000000000..7c40c200c5e9 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/windows/CMakeLists.txt @@ -0,0 +1,81 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "firebase_app_check") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "firebase_app_check_plugin") + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "firebase_app_check_plugin.cpp" + "firebase_app_check_plugin.h" + "messages.g.cpp" + "messages.g.h" +) + +# Read version from pubspec.yaml +file(STRINGS "../pubspec.yaml" pubspec_content) +foreach(line ${pubspec_content}) + string(FIND ${line} "version: " has_version) + + if("${has_version}" STREQUAL "0") + string(FIND ${line} ": " version_start_pos) + math(EXPR version_start_pos "${version_start_pos} + 2") + string(LENGTH ${line} version_end_pos) + math(EXPR len "${version_end_pos} - ${version_start_pos}") + string(SUBSTRING ${line} ${version_start_pos} ${len} PLUGIN_VERSION) + break() + endif() +endforeach(line) + +configure_file(plugin_version.h.in ${CMAKE_BINARY_DIR}/generated/firebase_app_check/plugin_version.h) +include_directories(${CMAKE_BINARY_DIR}/generated/) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} STATIC + "include/firebase_app_check/firebase_app_check_plugin_c_api.h" + "firebase_app_check_plugin_c_api.cpp" + ${PLUGIN_SOURCES} + ${CMAKE_BINARY_DIR}/generated/firebase_app_check/plugin_version.h +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PUBLIC FLUTTER_PLUGIN_IMPL) + +# Enable firebase-cpp-sdk's platform logging api. +target_compile_definitions(${PLUGIN_NAME} PRIVATE -DINTERNAL_EXPERIMENTAL=1) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +set(MSVC_RUNTIME_MODE MD) +set(firebase_libs firebase_core_plugin firebase_app_check) +target_link_libraries(${PLUGIN_NAME} PRIVATE "${firebase_libs}") + +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PUBLIC flutter flutter_wrapper_plugin) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(firebase_app_check_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin.cpp b/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin.cpp new file mode 100644 index 000000000000..d9a0a72d7014 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin.cpp @@ -0,0 +1,249 @@ +// 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. + +#include "firebase_app_check_plugin.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "firebase/app.h" +#include "firebase/app_check.h" +#include "firebase/app_check/debug_provider.h" +#include "firebase/future.h" +#include "firebase_app_check/plugin_version.h" +#include "firebase_core/firebase_core_plugin_c_api.h" +#include "messages.g.h" + +using ::firebase::App; +using ::firebase::Future; +using ::firebase::app_check::AppCheck; +using ::firebase::app_check::AppCheckListener; +using ::firebase::app_check::AppCheckToken; +using ::firebase::app_check::DebugAppCheckProviderFactory; + +namespace firebase_app_check_windows { + +static const std::string kLibraryName = "flutter-fire-app-check"; +static const std::string kEventChannelNamePrefix = + "plugins.flutter.io/firebase_app_check/token/"; + +flutter::BinaryMessenger* FirebaseAppCheckPlugin::binaryMessenger = nullptr; +std::map>> + FirebaseAppCheckPlugin::event_channels_; +std::map + FirebaseAppCheckPlugin::listeners_map_; + +// AppCheckListener implementation that forwards token changes to an EventSink. +class FlutterAppCheckListener : public AppCheckListener { + public: + void SetEventSink( + std::unique_ptr> event_sink) { + event_sink_ = std::move(event_sink); + } + + void OnAppCheckTokenChanged(const AppCheckToken& token) override { + if (event_sink_) { + flutter::EncodableMap event; + event[flutter::EncodableValue("token")] = + flutter::EncodableValue(token.token); + event_sink_->Success(flutter::EncodableValue(event)); + } + } + + private: + std::unique_ptr> event_sink_; +}; + +// StreamHandler for token change events. +class TokenStreamHandler + : public flutter::StreamHandler { + public: + TokenStreamHandler(AppCheck* app_check, const std::string& app_name) + : app_check_(app_check), app_name_(app_name) {} + + std::unique_ptr> + OnListenInternal( + const flutter::EncodableValue* arguments, + std::unique_ptr>&& events) + override { + listener_ = std::make_unique(); + listener_->SetEventSink(std::move(events)); + app_check_->AddAppCheckListener(listener_.get()); + FirebaseAppCheckPlugin::listeners_map_[app_name_] = listener_.get(); + return nullptr; + } + + std::unique_ptr> + OnCancelInternal(const flutter::EncodableValue* arguments) override { + if (listener_) { + app_check_->RemoveAppCheckListener(listener_.get()); + FirebaseAppCheckPlugin::listeners_map_.erase(app_name_); + listener_.reset(); + } + return nullptr; + } + + private: + AppCheck* app_check_; + std::string app_name_; + std::unique_ptr listener_; +}; + +static AppCheck* GetAppCheckFromPigeon(const std::string& app_name) { + App* app = App::GetInstance(app_name.c_str()); + return AppCheck::GetInstance(app); +} + +static FlutterError ParseError(const firebase::FutureBase& completed_future) { + std::string error_code = "unknown"; + int error = completed_future.error(); + switch (error) { + case firebase::app_check::kAppCheckErrorServerUnreachable: + error_code = "server-unreachable"; + break; + case firebase::app_check::kAppCheckErrorInvalidConfiguration: + error_code = "invalid-configuration"; + break; + case firebase::app_check::kAppCheckErrorSystemKeychain: + error_code = "system-keychain"; + break; + case firebase::app_check::kAppCheckErrorUnsupportedProvider: + error_code = "unsupported-provider"; + break; + default: + error_code = "unknown"; + break; + } + + std::string error_message = completed_future.error_message() + ? completed_future.error_message() + : "An unknown error occurred"; + + return FlutterError(error_code, error_message); +} + +// static +void FirebaseAppCheckPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows* registrar) { + auto plugin = std::make_unique(); + + FirebaseAppCheckHostApi::SetUp(registrar->messenger(), plugin.get()); + + registrar->AddPlugin(std::move(plugin)); + + binaryMessenger = registrar->messenger(); + + // Register for platform logging + App::RegisterLibrary(kLibraryName.c_str(), getPluginVersion().c_str(), + nullptr); +} + +FirebaseAppCheckPlugin::FirebaseAppCheckPlugin() {} + +FirebaseAppCheckPlugin::~FirebaseAppCheckPlugin() { + for (auto& [app_name, listener] : listeners_map_) { + App* app = App::GetInstance(app_name.c_str()); + if (app) { + AppCheck* app_check = AppCheck::GetInstance(app); + if (app_check) { + app_check->RemoveAppCheckListener(listener); + } + } + } + listeners_map_.clear(); + event_channels_.clear(); +} + +void FirebaseAppCheckPlugin::Activate( + const std::string& app_name, const std::string* android_provider, + const std::string* apple_provider, const std::string* debug_token, + std::function reply)> result) { + // On Windows/desktop, only the Debug provider is available. + DebugAppCheckProviderFactory* factory = + DebugAppCheckProviderFactory::GetInstance(); + + if (debug_token != nullptr && !debug_token->empty()) { + factory->SetDebugToken(*debug_token); + } + + AppCheck::SetAppCheckProviderFactory(factory); + + result(std::nullopt); +} + +void FirebaseAppCheckPlugin::GetToken( + const std::string& app_name, bool force_refresh, + std::function> reply)> result) { + AppCheck* app_check = GetAppCheckFromPigeon(app_name); + + Future future = app_check->GetAppCheckToken(force_refresh); + future.OnCompletion([result](const Future& completed_future) { + if (completed_future.error() != 0) { + result(ParseError(completed_future)); + } else { + const AppCheckToken* token = completed_future.result(); + if (token) { + result(std::optional(token->token)); + } else { + result(std::optional(std::nullopt)); + } + } + }); +} + +void FirebaseAppCheckPlugin::SetTokenAutoRefreshEnabled( + const std::string& app_name, bool is_token_auto_refresh_enabled, + std::function reply)> result) { + AppCheck* app_check = GetAppCheckFromPigeon(app_name); + app_check->SetTokenAutoRefreshEnabled(is_token_auto_refresh_enabled); + result(std::nullopt); +} + +void FirebaseAppCheckPlugin::RegisterTokenListener( + const std::string& app_name, + std::function reply)> result) { + AppCheck* app_check = GetAppCheckFromPigeon(app_name); + + const std::string name = kEventChannelNamePrefix + app_name; + + auto event_channel = + std::make_unique>( + binaryMessenger, name, &flutter::StandardMethodCodec::GetInstance()); + event_channel->SetStreamHandler( + std::make_unique(app_check, app_name)); + + event_channels_[app_name] = std::move(event_channel); + + result(name); +} + +void FirebaseAppCheckPlugin::GetLimitedUseAppCheckToken( + const std::string& app_name, + std::function reply)> result) { + AppCheck* app_check = GetAppCheckFromPigeon(app_name); + + Future future = app_check->GetLimitedUseAppCheckToken(); + future.OnCompletion([result](const Future& completed_future) { + if (completed_future.error() != 0) { + result(ParseError(completed_future)); + } else { + const AppCheckToken* token = completed_future.result(); + if (token) { + result(token->token); + } else { + result(FlutterError("unknown", "Failed to get limited use token")); + } + } + }); +} + +} // namespace firebase_app_check_windows diff --git a/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin.h b/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin.h new file mode 100644 index 000000000000..baabf2bd5931 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef FLUTTER_PLUGIN_FIREBASE_APP_CHECK_PLUGIN_H_ +#define FLUTTER_PLUGIN_FIREBASE_APP_CHECK_PLUGIN_H_ + +#include +#include +#include + +#include +#include +#include + +#include "firebase/app.h" +#include "firebase/app_check.h" +#include "firebase/future.h" +#include "messages.g.h" + +namespace firebase_app_check_windows { + +class TokenStreamHandler; + +class FirebaseAppCheckPlugin : public flutter::Plugin, + public FirebaseAppCheckHostApi { + friend class TokenStreamHandler; + + public: + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); + + FirebaseAppCheckPlugin(); + + virtual ~FirebaseAppCheckPlugin(); + + // Disallow copy and assign. + FirebaseAppCheckPlugin(const FirebaseAppCheckPlugin&) = delete; + FirebaseAppCheckPlugin& operator=(const FirebaseAppCheckPlugin&) = delete; + + // FirebaseAppCheckHostApi methods. + void Activate( + const std::string& app_name, const std::string* android_provider, + const std::string* apple_provider, const std::string* debug_token, + std::function reply)> result) override; + void GetToken(const std::string& app_name, bool force_refresh, + std::function> reply)> + result) override; + void SetTokenAutoRefreshEnabled( + const std::string& app_name, bool is_token_auto_refresh_enabled, + std::function reply)> result) override; + void RegisterTokenListener( + const std::string& app_name, + std::function reply)> result) override; + void GetLimitedUseAppCheckToken( + const std::string& app_name, + std::function reply)> result) override; + + private: + static flutter::BinaryMessenger* binaryMessenger; + static std::map< + std::string, + std::unique_ptr>> + event_channels_; + static std::map + listeners_map_; +}; + +} // namespace firebase_app_check_windows + +#endif // FLUTTER_PLUGIN_FIREBASE_APP_CHECK_PLUGIN_H_ diff --git a/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin_c_api.cpp b/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin_c_api.cpp new file mode 100644 index 000000000000..f0ace78f6241 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/windows/firebase_app_check_plugin_c_api.cpp @@ -0,0 +1,16 @@ +// 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. + +#include "include/firebase_app_check/firebase_app_check_plugin_c_api.h" + +#include + +#include "firebase_app_check_plugin.h" + +void FirebaseAppCheckPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + firebase_app_check_windows::FirebaseAppCheckPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/packages/firebase_app_check/firebase_app_check/windows/include/firebase_app_check/firebase_app_check_plugin_c_api.h b/packages/firebase_app_check/firebase_app_check/windows/include/firebase_app_check/firebase_app_check_plugin_c_api.h new file mode 100644 index 000000000000..ab2d9e9464ee --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/windows/include/firebase_app_check/firebase_app_check_plugin_c_api.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef FLUTTER_PLUGIN_FIREBASE_APP_CHECK_PLUGIN_C_API_H_ +#define FLUTTER_PLUGIN_FIREBASE_APP_CHECK_PLUGIN_C_API_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void FirebaseAppCheckPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_FIREBASE_APP_CHECK_PLUGIN_C_API_H_ 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 new file mode 100644 index 000000000000..0343b73ea815 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/windows/messages.g.cpp @@ -0,0 +1,307 @@ +// 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. +// See also: https://pub.dev/packages/pigeon + +#undef _HAS_EXCEPTIONS + +#include "messages.g.h" + +#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; + +FlutterError CreateConnectionError(const std::string channel_name) { + return FlutterError( + "channel-error", + "Unable to establish connection on channel: '" + channel_name + "'.", + EncodableValue("")); +} + +PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {} + +EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( + 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); +} + +/// The codec used by FirebaseAppCheckHostApi. +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) { + FirebaseAppCheckHostApi::SetUp(binary_messenger, api, ""); +} + +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 + : ""; + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_app_check_platform_interface." + "FirebaseAppCheckHostApi.activate" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_name_arg = args.at(0); + if (encodable_app_name_arg.IsNull()) { + reply(WrapError("app_name_arg unexpectedly null.")); + return; + } + const auto& app_name_arg = + std::get(encodable_app_name_arg); + const auto& encodable_android_provider_arg = args.at(1); + const auto* android_provider_arg = + std::get_if(&encodable_android_provider_arg); + const auto& encodable_apple_provider_arg = args.at(2); + const auto* apple_provider_arg = + std::get_if(&encodable_apple_provider_arg); + const auto& encodable_debug_token_arg = args.at(3); + const auto* debug_token_arg = + std::get_if(&encodable_debug_token_arg); + api->Activate(app_name_arg, android_provider_arg, + apple_provider_arg, debug_token_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_app_check_platform_interface." + "FirebaseAppCheckHostApi.getToken" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_name_arg = args.at(0); + if (encodable_app_name_arg.IsNull()) { + reply(WrapError("app_name_arg unexpectedly null.")); + return; + } + const auto& app_name_arg = + std::get(encodable_app_name_arg); + const auto& encodable_force_refresh_arg = args.at(1); + if (encodable_force_refresh_arg.IsNull()) { + reply(WrapError("force_refresh_arg unexpectedly null.")); + return; + } + const auto& force_refresh_arg = + std::get(encodable_force_refresh_arg); + api->GetToken( + app_name_arg, force_refresh_arg, + [reply](ErrorOr>&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back( + EncodableValue(std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_app_check_platform_interface." + "FirebaseAppCheckHostApi.setTokenAutoRefreshEnabled" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_name_arg = args.at(0); + if (encodable_app_name_arg.IsNull()) { + reply(WrapError("app_name_arg unexpectedly null.")); + return; + } + const auto& app_name_arg = + std::get(encodable_app_name_arg); + const auto& encodable_is_token_auto_refresh_enabled_arg = + args.at(1); + if (encodable_is_token_auto_refresh_enabled_arg.IsNull()) { + reply(WrapError( + "is_token_auto_refresh_enabled_arg unexpectedly null.")); + return; + } + const auto& is_token_auto_refresh_enabled_arg = + std::get(encodable_is_token_auto_refresh_enabled_arg); + api->SetTokenAutoRefreshEnabled( + app_name_arg, is_token_auto_refresh_enabled_arg, + [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_app_check_platform_interface." + "FirebaseAppCheckHostApi.registerTokenListener" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_name_arg = args.at(0); + if (encodable_app_name_arg.IsNull()) { + reply(WrapError("app_name_arg unexpectedly null.")); + return; + } + const auto& app_name_arg = + std::get(encodable_app_name_arg); + api->RegisterTokenListener( + app_name_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel( + binary_messenger, + "dev.flutter.pigeon.firebase_app_check_platform_interface." + "FirebaseAppCheckHostApi.getLimitedUseAppCheckToken" + + prepended_suffix, + &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler( + [api](const EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_app_name_arg = args.at(0); + if (encodable_app_name_arg.IsNull()) { + reply(WrapError("app_name_arg unexpectedly null.")); + return; + } + const auto& app_name_arg = + std::get(encodable_app_name_arg); + api->GetLimitedUseAppCheckToken( + app_name_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back( + EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } +} + +EncodableValue FirebaseAppCheckHostApi::WrapError( + std::string_view error_message) { + return EncodableValue( + EncodableList{EncodableValue(std::string(error_message)), + EncodableValue("Error"), EncodableValue()}); +} + +EncodableValue FirebaseAppCheckHostApi::WrapError(const FlutterError& error) { + return EncodableValue(EncodableList{EncodableValue(error.code()), + EncodableValue(error.message()), + error.details()}); +} + +} // namespace firebase_app_check_windows 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 new file mode 100644 index 000000000000..c56d71862604 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/windows/messages.g.h @@ -0,0 +1,118 @@ +// 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. +// See also: https://pub.dev/packages/pigeon + +#ifndef PIGEON_MESSAGES_G_H_ +#define PIGEON_MESSAGES_G_H_ +#include +#include +#include +#include + +#include +#include +#include + +namespace firebase_app_check_windows { + +// Generated class from Pigeon. + +class FlutterError { + public: + explicit FlutterError(const std::string& code) : code_(code) {} + 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) + : 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_; } + + private: + std::string code_; + std::string message_; + flutter::EncodableValue details_; +}; + +template +class ErrorOr { + public: + ErrorOr(const T& rhs) : v_(rhs) {} + ErrorOr(const T&& rhs) : v_(std::move(rhs)) {} + ErrorOr(const FlutterError& rhs) : v_(rhs) {} + ErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {} + + bool has_error() const { return std::holds_alternative(v_); } + const T& value() const { return std::get(v_); }; + const FlutterError& error() const { return std::get(v_); }; + + private: + friend class FirebaseAppCheckHostApi; + ErrorOr() = default; + T TakeValue() && { return std::get(std::move(v_)); } + + std::variant v_; +}; + +class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { + public: + PigeonInternalCodecSerializer(); + inline static PigeonInternalCodecSerializer& GetInstance() { + static PigeonInternalCodecSerializer sInstance; + return sInstance; + } + + void WriteValue(const flutter::EncodableValue& value, + flutter::ByteStreamWriter* stream) const override; + + protected: + flutter::EncodableValue ReadValueOfType( + uint8_t type, flutter::ByteStreamReader* stream) const override; +}; + +// Generated interface from Pigeon that represents a handler of messages from +// Flutter. +class FirebaseAppCheckHostApi { + public: + FirebaseAppCheckHostApi(const FirebaseAppCheckHostApi&) = delete; + FirebaseAppCheckHostApi& operator=(const FirebaseAppCheckHostApi&) = delete; + virtual ~FirebaseAppCheckHostApi() {} + virtual void Activate( + const std::string& app_name, const std::string* android_provider, + const std::string* apple_provider, const std::string* debug_token, + std::function reply)> result) = 0; + virtual void GetToken( + const std::string& app_name, bool force_refresh, + std::function> reply)> + result) = 0; + virtual void SetTokenAutoRefreshEnabled( + const std::string& app_name, bool is_token_auto_refresh_enabled, + std::function reply)> result) = 0; + virtual void RegisterTokenListener( + const std::string& app_name, + std::function reply)> result) = 0; + virtual void GetLimitedUseAppCheckToken( + const std::string& app_name, + std::function reply)> result) = 0; + + // The codec used by FirebaseAppCheckHostApi. + 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, + FirebaseAppCheckHostApi* api); + 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); + + protected: + FirebaseAppCheckHostApi() = default; +}; +} // namespace firebase_app_check_windows +#endif // PIGEON_MESSAGES_G_H_ diff --git a/packages/firebase_app_check/firebase_app_check/windows/plugin_version.h.in b/packages/firebase_app_check/firebase_app_check/windows/plugin_version.h.in new file mode 100644 index 000000000000..0a52f845105a --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check/windows/plugin_version.h.in @@ -0,0 +1,13 @@ +// 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. + +#ifndef PLUGIN_VERSION_CONFIG_H +#define PLUGIN_VERSION_CONFIG_H + +namespace firebase_app_check_windows { + +std::string getPluginVersion() { return "@PLUGIN_VERSION@"; } +} // namespace firebase_app_check_windows + +#endif // PLUGIN_VERSION_CONFIG_H diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/firebase_app_check_platform_interface.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/firebase_app_check_platform_interface.dart index c8292c8b7eaf..53a285e48729 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/firebase_app_check_platform_interface.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/firebase_app_check_platform_interface.dart @@ -10,3 +10,4 @@ export 'src/apple_providers.dart'; export 'src/method_channel/method_channel_firebase_app_check.dart'; export 'src/platform_interface/platform_interface_firebase_app_check.dart'; export 'src/web_providers.dart'; +export 'src/windows_providers.dart'; diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/method_channel/method_channel_firebase_app_check.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/method_channel/method_channel_firebase_app_check.dart index 59f05fa8b527..96afb6f2c999 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/method_channel/method_channel_firebase_app_check.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/method_channel/method_channel_firebase_app_check.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../../firebase_app_check_platform_interface.dart'; +import '../pigeon/messages.pigeon.dart'; import 'utils/exception.dart'; import 'utils/provider_to_string.dart'; @@ -19,10 +20,8 @@ class MethodChannelFirebaseAppCheck extends FirebaseAppCheckPlatform { : super(appInstance: app) { _tokenChangesListeners[app.name] = StreamController.broadcast(); - channel.invokeMethod('FirebaseAppCheck#registerTokenListener', { - 'appName': app.name, - }).then((channelName) { - final events = EventChannel(channelName!, channel.codec); + _pigeonApi.registerTokenListener(app.name).then((channelName) { + final events = EventChannel(channelName); events .receiveGuardedBroadcastStream(onError: convertPlatformException) .listen( @@ -34,6 +33,10 @@ class MethodChannelFirebaseAppCheck extends FirebaseAppCheckPlatform { controller.add(result['token'] as String?); }, ); + // ignore: avoid_catches_without_on_clauses + }).catchError((_) { + // Silently ignore errors during token listener registration. + // This can happen in test environments where the host API is not set up. }); } @@ -44,10 +47,8 @@ class MethodChannelFirebaseAppCheck extends FirebaseAppCheckPlatform { _methodChannelFirebaseAppCheckInstances = {}; - /// The [MethodChannel] used to communicate with the native plugin - static MethodChannel channel = const MethodChannel( - 'plugins.flutter.io/firebase_app_check', - ); + /// The Pigeon API used for platform communication. + final FirebaseAppCheckHostApi _pigeonApi = FirebaseAppCheckHostApi(); /// Returns a stub instance to allow the platform interface to access /// the class instance statically. @@ -88,30 +89,39 @@ class MethodChannelFirebaseAppCheck extends FirebaseAppCheckPlatform { AppleProvider? appleProvider, AndroidAppCheckProvider? providerAndroid, AppleAppCheckProvider? providerApple, + WindowsAppCheckProvider? providerWindows, }) async { try { - await channel.invokeMethod('FirebaseAppCheck#activate', { - 'appName': app.name, - // Allow value to pass for debug mode for unit testing - if (defaultTargetPlatform == TargetPlatform.android || kDebugMode) - 'androidProvider': getAndroidProviderString( - legacyProvider: androidProvider, - newProvider: providerAndroid, - ), - if (providerAndroid is AndroidDebugProvider && - providerAndroid.debugToken != null) - 'androidDebugToken': providerAndroid.debugToken, - if (defaultTargetPlatform == TargetPlatform.iOS || - defaultTargetPlatform == TargetPlatform.macOS || - kDebugMode) - 'appleProvider': getAppleProviderString( - legacyProvider: appleProvider, - newProvider: providerApple, - ), - if (providerApple is AppleDebugProvider && - providerApple.debugToken != null) - 'appleDebugToken': providerApple.debugToken, - }); + String? debugToken; + if (providerAndroid is AndroidDebugProvider && + providerAndroid.debugToken != null) { + debugToken = providerAndroid.debugToken; + } else if (providerApple is AppleDebugProvider && + providerApple.debugToken != null) { + debugToken = providerApple.debugToken; + } else if (providerWindows is WindowsDebugProvider && + providerWindows.debugToken != null) { + debugToken = providerWindows.debugToken; + } + + await _pigeonApi.activate( + app.name, + defaultTargetPlatform == TargetPlatform.android || kDebugMode + ? getAndroidProviderString( + legacyProvider: androidProvider, + newProvider: providerAndroid, + ) + : null, + defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS || + kDebugMode + ? getAppleProviderString( + legacyProvider: appleProvider, + newProvider: providerApple, + ) + : null, + debugToken, + ); } on PlatformException catch (e, s) { convertPlatformException(e, s); } @@ -120,12 +130,7 @@ class MethodChannelFirebaseAppCheck extends FirebaseAppCheckPlatform { @override Future getToken(bool forceRefresh) async { try { - final result = await channel.invokeMethod( - 'FirebaseAppCheck#getToken', - {'appName': app.name, 'forceRefresh': forceRefresh}, - ); - - return result; + return await _pigeonApi.getToken(app.name, forceRefresh); } on PlatformException catch (e, s) { convertPlatformException(e, s); } @@ -136,12 +141,9 @@ class MethodChannelFirebaseAppCheck extends FirebaseAppCheckPlatform { bool isTokenAutoRefreshEnabled, ) async { try { - await channel.invokeMethod( - 'FirebaseAppCheck#setTokenAutoRefreshEnabled', - { - 'appName': app.name, - 'isTokenAutoRefreshEnabled': isTokenAutoRefreshEnabled, - }, + await _pigeonApi.setTokenAutoRefreshEnabled( + app.name, + isTokenAutoRefreshEnabled, ); } on PlatformException catch (e, s) { convertPlatformException(e, s); @@ -156,14 +158,7 @@ class MethodChannelFirebaseAppCheck extends FirebaseAppCheckPlatform { @override Future getLimitedUseToken() async { try { - final result = await channel.invokeMethod( - 'FirebaseAppCheck#getLimitedUseAppCheckToken', - { - 'appName': app.name, - }, - ); - - return result; + return await _pigeonApi.getLimitedUseAppCheckToken(app.name); } on PlatformException catch (e, s) { convertPlatformException(e, s); } 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 new file mode 100644 index 000000000000..fab0d53008a2 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -0,0 +1,198 @@ +// 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. +// 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, require_trailing_commas + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +class FirebaseAppCheckHostApi { + /// Constructor for [FirebaseAppCheckHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + FirebaseAppCheckHostApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future activate(String appName, String? androidProvider, + String? appleProvider, String? debugToken) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.activate$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel 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; + } + } + + Future getToken(String appName, bool forceRefresh) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getToken$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel 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?); + } + } + + Future setTokenAutoRefreshEnabled( + String appName, bool isTokenAutoRefreshEnabled) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.setTokenAutoRefreshEnabled$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel 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; + } + } + + Future registerTokenListener(String appName) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.registerTokenListener$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel 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?)!; + } + } + + Future getLimitedUseAppCheckToken(String appName) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.firebase_app_check_platform_interface.FirebaseAppCheckHostApi.getLimitedUseAppCheckToken$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel 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?)!; + } + } +} diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/platform_interface/platform_interface_firebase_app_check.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/platform_interface/platform_interface_firebase_app_check.dart index 988d7e0b8e1f..346c745a5652 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/platform_interface/platform_interface_firebase_app_check.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/platform_interface/platform_interface_firebase_app_check.dart @@ -67,6 +67,13 @@ abstract class FirebaseAppCheckPlatform extends PlatformInterface { /// "app attest with fallback to device check" via `AppleAppCheckProvider`. /// Note: App Attest is only available on iOS 14.0+ and macOS 14.0+. /// + /// **Windows**: Only the debug provider is supported. You **must** supply a + /// debug token — the desktop C++ SDK does not auto-generate one. Either pass + /// it via `providerWindows: WindowsDebugProvider(debugToken: 'your-token')` + /// or set the `APP_CHECK_DEBUG_TOKEN` environment variable. The token must + /// first be registered in the Firebase Console under + /// *App Check → Apps → Manage debug tokens*. + /// /// ## Migration Notice /// /// The `androidProvider` and `appleProvider` parameters will be deprecated @@ -89,6 +96,7 @@ abstract class FirebaseAppCheckPlatform extends PlatformInterface { AppleProvider? appleProvider, AndroidAppCheckProvider? providerAndroid, AppleAppCheckProvider? providerApple, + WindowsAppCheckProvider? providerWindows, }) { throw UnimplementedError('activate() is not implemented'); } diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/windows_providers.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/windows_providers.dart new file mode 100644 index 000000000000..b6b09e55b20b --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/lib/src/windows_providers.dart @@ -0,0 +1,44 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Base class for Windows App Check providers. +/// +/// On Windows, only the [WindowsDebugProvider] is supported. The Firebase C++ +/// SDK does not support platform attestation providers (such as Play Integrity +/// or DeviceCheck) on desktop platforms. +abstract class WindowsAppCheckProvider { + final String type; + const WindowsAppCheckProvider(this.type); +} + +/// Debug provider for Windows. +/// +/// This is the **only** provider available on Windows. Unlike mobile platforms, +/// the desktop C++ SDK does **not** auto-generate a debug token. You must +/// supply one explicitly. +/// +/// ## Setup +/// +/// 1. Generate a debug token (a UUID v4) — for example using an online +/// generator or a CLI tool. +/// 2. Register it in the **Firebase Console** under +/// *App Check → Apps → Manage debug tokens*. +/// 3. Pass it here via [debugToken], **or** set the `APP_CHECK_DEBUG_TOKEN` +/// environment variable before launching your app. +/// +/// If neither a [debugToken] nor the environment variable is provided, +/// `getToken()` will fail with an `invalid-configuration` error. +/// +/// **Do not ship the debug provider or debug tokens in production builds.** +class WindowsDebugProvider extends WindowsAppCheckProvider { + /// Creates a Windows debug provider. + /// + /// [debugToken] is the debug token registered in the Firebase Console. + /// If omitted, the C++ SDK falls back to the `APP_CHECK_DEBUG_TOKEN` + /// environment variable. + const WindowsDebugProvider({this.debugToken}) : super('debug'); + + /// The debug token for this provider. + final String? debugToken; +} diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/pigeons/copyright.txt b/packages/firebase_app_check/firebase_app_check_platform_interface/pigeons/copyright.txt new file mode 100644 index 000000000000..4e197781c6db --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/pigeons/copyright.txt @@ -0,0 +1,3 @@ +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. \ No newline at end of file diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/pigeons/messages.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/pigeons/messages.dart new file mode 100644 index 000000000000..e84ff78ab5f4 --- /dev/null +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/pigeons/messages.dart @@ -0,0 +1,48 @@ +// Copyright 2025 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/pigeon/messages.pigeon.dart', + dartPackageName: 'firebase_app_check_platform_interface', + kotlinOut: + '../firebase_app_check/android/src/main/kotlin/io/flutter/plugins/firebase/appcheck/GeneratedAndroidFirebaseAppCheck.g.kt', + kotlinOptions: KotlinOptions( + package: 'io.flutter.plugins.firebase.appcheck', + ), + swiftOut: + '../firebase_app_check/ios/firebase_app_check/Sources/firebase_app_check/FirebaseAppCheckMessages.g.swift', + cppHeaderOut: '../firebase_app_check/windows/messages.g.h', + cppSourceOut: '../firebase_app_check/windows/messages.g.cpp', + cppOptions: CppOptions(namespace: 'firebase_app_check_windows'), + copyrightHeader: 'pigeons/copyright.txt', + ), +) +@HostApi(dartHostTestHandler: 'TestFirebaseAppCheckHostApi') +abstract class FirebaseAppCheckHostApi { + @async + void activate( + String appName, + String? androidProvider, + String? appleProvider, + String? debugToken, + ); + + @async + String? getToken(String appName, bool forceRefresh); + + @async + void setTokenAutoRefreshEnabled( + String appName, + bool isTokenAutoRefreshEnabled, + ); + + @async + String registerTokenListener(String appName); + + @async + String getLimitedUseAppCheckToken(String appName); +} diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/pubspec.yaml b/packages/firebase_app_check/firebase_app_check_platform_interface/pubspec.yaml index 98f24a09895a..7fd0731274db 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/pubspec.yaml +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/pubspec.yaml @@ -20,3 +20,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 + pigeon: 25.3.2 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 721dc2de0b7c..5f215cec19fc 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 @@ -2,24 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:firebase_app_check_platform_interface/firebase_app_check_platform_interface.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter/services.dart'; import '../mock.dart'; void main() { setupFirebaseAppCheckMocks(); - late FirebaseAppCheckPlatform appCheck; late FirebaseApp secondaryApp; - final List methodCallLogger = []; group('$MethodChannelFirebaseAppCheck', () { setUpAll(() async { - FirebaseApp app = await Firebase.initializeApp(); + await Firebase.initializeApp(); secondaryApp = await Firebase.initializeApp( name: 'secondaryApp', options: const FirebaseOptions( @@ -29,28 +24,11 @@ void main() { messagingSenderId: '1234567890', ), ); - handleMethodCall((call) async { - methodCallLogger.add(call); - - switch (call.method) { - case 'FirebaseAppCheck#registerTokenListener': - return 'channelName'; - case 'FirebaseAppCheck#getToken': - return 'test-token'; - default: - return true; - } - }); - - appCheck = MethodChannelFirebaseAppCheck(app: app); - }); - - setUp(() async { - methodCallLogger.clear(); }); group('delegateFor()', () { test('returns a [FirebaseAppCheckPlatform]', () { + final appCheck = FirebaseAppCheckPlatform.instance; expect( // ignore: invalid_use_of_protected_member appCheck.delegateFor(app: secondaryApp), @@ -61,96 +39,10 @@ void main() { group('setInitialValues()', () { test('returns a [MethodChannelFirebaseAppCheck]', () { + final appCheck = MethodChannelFirebaseAppCheck.instance; // ignore: invalid_use_of_protected_member expect(appCheck.setInitialValues(), appCheck); }); }); - - test('activate', () async { - await appCheck.activate( - webProvider: ReCaptchaV3Provider('test-key'), - providerAndroid: const AndroidPlayIntegrityProvider(), - providerApple: const AppleDeviceCheckProvider(), - ); - expect( - methodCallLogger, - [ - isMethodCall( - 'FirebaseAppCheck#activate', - arguments: { - 'appName': defaultFirebaseAppName, - 'androidProvider': 'playIntegrity', - 'appleProvider': 'deviceCheck', - }, - ), - ], - ); - }); - - test('activate with debug providers', () async { - await appCheck.activate( - webProvider: ReCaptchaV3Provider('test-key'), - providerAndroid: const AndroidDebugProvider(debugToken: 'androidDebug'), - providerApple: const AppleDebugProvider(debugToken: 'appleDebug'), - ); - expect( - methodCallLogger, - [ - isMethodCall( - 'FirebaseAppCheck#activate', - arguments: { - 'appName': defaultFirebaseAppName, - 'androidProvider': 'debug', - 'appleProvider': 'debug', - 'androidDebugToken': 'androidDebug', - 'appleDebugToken': 'appleDebug', - }, - ), - ], - ); - }); - - test('getToken', () async { - final tokenResult = await appCheck.getToken(true); - expect( - methodCallLogger, - [ - isMethodCall( - 'FirebaseAppCheck#getToken', - arguments: { - 'appName': defaultFirebaseAppName, - 'forceRefresh': true, - }, - ), - ], - ); - - expect(tokenResult, isA()); - }); - - test('setTokenAutoRefreshEnabled', () async { - await appCheck.setTokenAutoRefreshEnabled(false); - expect( - methodCallLogger, - [ - isMethodCall( - 'FirebaseAppCheck#setTokenAutoRefreshEnabled', - arguments: { - 'appName': defaultFirebaseAppName, - 'isTokenAutoRefreshEnabled': false, - }, - ), - ], - ); - }); - - test('tokenChanges', () async { - final stream = appCheck.onTokenChange; - expect(stream, isA>()); - }); }); } - -class TestMethodChannelFirebaseAppCheck extends MethodChannelFirebaseAppCheck { - TestMethodChannelFirebaseAppCheck(FirebaseApp app) : super(app: app); -} diff --git a/packages/firebase_app_check/firebase_app_check_platform_interface/test/mock.dart b/packages/firebase_app_check/firebase_app_check_platform_interface/test/mock.dart index 9cefc319fe4c..454b7253292b 100644 --- a/packages/firebase_app_check/firebase_app_check_platform_interface/test/mock.dart +++ b/packages/firebase_app_check/firebase_app_check_platform_interface/test/mock.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:firebase_app_check_platform_interface/firebase_app_check_platform_interface.dart'; -import 'package:firebase_app_check_platform_interface/src/method_channel/method_channel_firebase_app_check.dart'; import 'package:firebase_core_platform_interface/test.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -17,21 +15,4 @@ void setupFirebaseAppCheckMocks([Callback? customHandlers]) { TestWidgetsFlutterBinding.ensureInitialized(); setupFirebaseCoreMocks(); - - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(MethodChannelFirebaseAppCheck.channel, - (MethodCall methodCall) async { - methodCallLog.add(methodCall); - switch (methodCall.method) { - default: - return false; - } - }); } - -void handleMethodCall(MethodCallCallback methodCallCallback) => - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(MethodChannelFirebaseAppCheck.channel, - (call) async { - return await methodCallCallback(call); - }); 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 8524c3a1352c..cfb4a24e9ce0 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 @@ -132,6 +132,7 @@ class FirebaseAppCheckWeb extends FirebaseAppCheckPlatform { AppleProvider? appleProvider, AndroidAppCheckProvider? providerAndroid, AppleAppCheckProvider? providerApple, + WindowsAppCheckProvider? providerWindows, }) async { // save the recaptcha type and site key for future startups if (webProvider != null) { diff --git a/packages/firebase_app_check/firebase_app_check_web/test/firebase_app_check_web_test.mocks.dart b/packages/firebase_app_check/firebase_app_check_web/test/firebase_app_check_web_test.mocks.dart index 122699cb4b89..357b30beeca5 100644 --- a/packages/firebase_app_check/firebase_app_check_web/test/firebase_app_check_web_test.mocks.dart +++ b/packages/firebase_app_check/firebase_app_check_web/test/firebase_app_check_web_test.mocks.dart @@ -141,6 +141,7 @@ class MockFirebaseAppCheckWeb extends _i1.Mock _i3.AppleProvider? appleProvider, _i3.AndroidAppCheckProvider? providerAndroid, _i3.AppleAppCheckProvider? providerApple, + _i3.WindowsAppCheckProvider? providerWindows, }) => (super.noSuchMethod( Invocation.method( @@ -152,6 +153,7 @@ class MockFirebaseAppCheckWeb extends _i1.Mock #appleProvider: appleProvider, #providerAndroid: providerAndroid, #providerApple: providerApple, + #providerWindows: providerWindows, }, ), returnValue: _i5.Future.value(), diff --git a/packages/firebase_core/firebase_core/windows/CMakeLists.txt b/packages/firebase_core/firebase_core/windows/CMakeLists.txt index d944944c8c8a..277ea0e10c24 100644 --- a/packages/firebase_core/firebase_core/windows/CMakeLists.txt +++ b/packages/firebase_core/firebase_core/windows/CMakeLists.txt @@ -122,7 +122,7 @@ add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) target_include_directories(${PLUGIN_NAME} INTERFACE "${FIREBASE_CPP_SDK_DIR}/include") -set(FIREBASE_RELEASE_PATH_LIBS firebase_app firebase_auth firebase_remote_config firebase_storage firebase_firestore firebase_database) +set(FIREBASE_RELEASE_PATH_LIBS firebase_app firebase_auth firebase_remote_config firebase_storage firebase_firestore firebase_database firebase_app_check) foreach(firebase_lib IN ITEMS ${FIREBASE_RELEASE_PATH_LIBS}) get_target_property(firebase_lib_path ${firebase_lib} IMPORTED_LOCATION) string(REPLACE "Debug" "Release" firebase_lib_release_path ${firebase_lib_path}) diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.mocks.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.mocks.dart index 625e092f5989..828b48d4f7e7 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.mocks.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.mocks.dart @@ -778,6 +778,8 @@ class MockFirebaseAppCheck extends _i1.Mock implements _i10.FirebaseAppCheck { const _i11.AndroidPlayIntegrityProvider(), _i11.AppleAppCheckProvider? providerApple = const _i11.AppleDeviceCheckProvider(), + _i11.WindowsAppCheckProvider? providerWindows = + const _i11.WindowsDebugProvider(), }) => (super.noSuchMethod( Invocation.method( @@ -790,6 +792,7 @@ class MockFirebaseAppCheck extends _i1.Mock implements _i10.FirebaseAppCheck { #appleProvider: appleProvider, #providerAndroid: providerAndroid, #providerApple: providerApple, + #providerWindows: providerWindows, }, ), returnValue: _i6.Future.value(), diff --git a/scripts/generate_versions_spm.dart b/scripts/generate_versions_spm.dart index 8bc30470bd46..27485fd76e32 100644 --- a/scripts/generate_versions_spm.dart +++ b/scripts/generate_versions_spm.dart @@ -25,9 +25,10 @@ void main(List args) async { final firebaseiOSVersion = getFirebaseiOSVersion(firebaseCoreIosVersionFile); // Update hard-coded versions in all plugin Package.swift files - final firebaseCoreVersion = - loadYaml(File('${firebaseCorePackage.path}/pubspec.yaml').readAsStringSync())['version'] - .toString(); + final firebaseCoreVersion = loadYaml( + File('${firebaseCorePackage.path}/pubspec.yaml') + .readAsStringSync())['version'] + .toString(); updatePluginPackageSwiftVersions( workspace, firebaseiOSVersion, @@ -128,6 +129,8 @@ void updateLibraryVersionPureSwiftPlugins() { 'firebase_ml_model_downloader', 'firebase_app_installations', 'cloud_functions', + 'firebase_remote_config', + 'firebase_app_check', ]; for (final package in packages) { diff --git a/tests/integration_test/e2e_test.dart b/tests/integration_test/e2e_test.dart index 75818dc8f3a0..12511585363c 100644 --- a/tests/integration_test/e2e_test.dart +++ b/tests/integration_test/e2e_test.dart @@ -78,6 +78,7 @@ void main() { firebase_auth.main(); firebase_remote_config.main(); firebase_storage.main(); + firebase_app_check.main(); break; default: throw UnsupportedError( diff --git a/tests/windows/flutter/generated_plugin_registrant.cc b/tests/windows/flutter/generated_plugin_registrant.cc index 9a180bb54481..b6d55c18d245 100644 --- a/tests/windows/flutter/generated_plugin_registrant.cc +++ b/tests/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -13,6 +14,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FirebaseAppCheckPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseAppCheckPluginCApi")); FirebaseAuthPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); FirebaseCorePluginCApiRegisterWithRegistrar( diff --git a/tests/windows/flutter/generated_plugins.cmake b/tests/windows/flutter/generated_plugins.cmake index 1a9da22be867..89f9a20f171a 100644 --- a/tests/windows/flutter/generated_plugins.cmake +++ b/tests/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + firebase_app_check firebase_auth firebase_core firebase_database