/* * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Net.Http; using System.Net.WebSockets; using System.Reflection; using System.Threading.Tasks; namespace Firebase.Internal { // Contains internal helper methods for interacting with other Firebase libraries. internal static class FirebaseInterops { // The cached fields for FirebaseApp reflection. private static PropertyInfo _dataCollectionProperty = null; // The various App Check types needed to retrieve the token, cached via reflection on startup. private static Type _appCheckType; private static MethodInfo _appCheckGetInstanceMethod; private static MethodInfo _appCheckGetTokenMethod; private static MethodInfo _appCheckGetLimitedUseTokenMethod; private static PropertyInfo _appCheckTokenResultProperty; private static PropertyInfo _appCheckTokenTokenProperty; // Used to determine if the App Check reflection initialized successfully, and should work. private static bool _appCheckReflectionInitialized = false; // The header used by the AppCheck token. private const string appCheckHeader = "X-Firebase-AppCheck"; // The various Auth types needed to retrieve the token, cached via reflection on startup. private static Type _authType; private static MethodInfo _authGetAuthMethod; private static PropertyInfo _authCurrentUserProperty; private static MethodInfo _userTokenAsyncMethod; private static PropertyInfo _userTokenTaskResultProperty; // Used to determine if the Auth reflection initialized successfully, and should work. private static bool _authReflectionInitialized = false; // The header used by the AppCheck token. private const string authHeader = "Authorization"; static FirebaseInterops() { InitializeAppReflection(); InitializeAppCheckReflection(); InitializeAuthReflection(); } private static void LogError(string message) { #if FIREBASEAI_DEBUG_LOGGING UnityEngine.Debug.LogError(message); #endif } // Cache the methods needed for FirebaseApp reflection. private static void InitializeAppReflection() { try { _dataCollectionProperty = typeof(FirebaseApp).GetProperty( "IsDataCollectionDefaultEnabled", BindingFlags.Instance | BindingFlags.NonPublic); if (_dataCollectionProperty == null) { LogError("Could not find FirebaseApp.IsDataCollectionDefaultEnabled property via reflection."); return; } if (_dataCollectionProperty.PropertyType != typeof(bool)) { LogError("FirebaseApp.IsDataCollectionDefaultEnabled is not a bool, " + $"but is {_dataCollectionProperty.PropertyType}"); return; } } catch (Exception e) { LogError($"Failed to initialize FirebaseApp reflection: {e}"); } } // Gets the property FirebaseApp.IsDataCollectionDefaultEnabled. public static bool GetIsDataCollectionDefaultEnabled(FirebaseApp firebaseApp) { if (firebaseApp == null || _dataCollectionProperty == null) { return false; } try { return (bool)_dataCollectionProperty.GetValue(firebaseApp); } catch (Exception e) { LogError($"Error accessing 'IsDataCollectionDefaultEnabled': {e}"); return false; } } // SDK version to use if unable to find it. private const string _unknownSdkVersion = "unknown"; private static readonly Lazy _sdkVersionFetcher = new(() => { try { // Get the type Firebase.VersionInfo from the assembly that defines FirebaseApp. Type versionInfoType = typeof(FirebaseApp).Assembly.GetType("Firebase.VersionInfo"); if (versionInfoType == null) { LogError("Firebase.VersionInfo type not found via reflection"); return _unknownSdkVersion; } // Firebase.VersionInfo.SdkVersion PropertyInfo sdkVersionProperty = versionInfoType.GetProperty( "SdkVersion", BindingFlags.Static | BindingFlags.NonPublic); if (sdkVersionProperty == null) { LogError("Firebase.VersionInfo.SdkVersion property not found via reflection."); return _unknownSdkVersion; } return sdkVersionProperty.GetValue(null) as string ?? _unknownSdkVersion; } catch (Exception e) { LogError($"Error accessing SdkVersion via reflection: {e}"); return _unknownSdkVersion; } }); // Gets the internal property Firebase.VersionInfo.SdkVersion internal static string GetVersionInfoSdkVersion() { return _sdkVersionFetcher.Value; } // Cache the various types and methods needed for AppCheck token retrieval. private static void InitializeAppCheckReflection() { const string firebaseAppCheckTypeName = "Firebase.AppCheck.FirebaseAppCheck, Firebase.AppCheck"; const string getAppCheckTokenMethodName = "GetAppCheckTokenAsync"; const string getLimitedUseAppCheckTokenMethodName = "GetLimitedUseAppCheckTokenAsync"; try { // Set this to false, to allow easy failing out via return. _appCheckReflectionInitialized = false; _appCheckType = Type.GetType(firebaseAppCheckTypeName); if (_appCheckType == null) { return; } // Get the static method GetInstance(FirebaseApp app) _appCheckGetInstanceMethod = _appCheckType.GetMethod( "GetInstance", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(FirebaseApp) }, null); if (_appCheckGetInstanceMethod == null) { LogError("Could not find FirebaseAppCheck.GetInstance method via reflection."); return; } // Get the instance method GetAppCheckTokenAsync(bool forceRefresh) _appCheckGetTokenMethod = _appCheckType.GetMethod( getAppCheckTokenMethodName, BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(bool) }, null); if (_appCheckGetTokenMethod == null) { LogError($"Could not find {getAppCheckTokenMethodName} method via reflection."); return; } // Get the instance method GetLimitedUseAppCheckTokenAsync() _appCheckGetLimitedUseTokenMethod = _appCheckType.GetMethod( getLimitedUseAppCheckTokenMethodName, BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); if (_appCheckGetLimitedUseTokenMethod == null) { LogError($"Could not find {getLimitedUseAppCheckTokenMethodName} method via reflection."); return; } // Should be Task Type appCheckTokenTaskType = _appCheckGetTokenMethod.ReturnType; // Get the Result property from the Task _appCheckTokenResultProperty = appCheckTokenTaskType.GetProperty("Result"); if (_appCheckTokenResultProperty == null) { LogError("Could not find Result property on App Check token Task."); return; } // Should be AppCheckToken Type appCheckTokenType = _appCheckTokenResultProperty.PropertyType; _appCheckTokenTokenProperty = appCheckTokenType.GetProperty("Token"); if (_appCheckTokenTokenProperty == null) { LogError($"Could not find Token property on AppCheckToken."); return; } _appCheckReflectionInitialized = true; } catch (Exception e) { LogError($"Exception during static initialization of FirebaseInterops: {e}"); } } // Gets the AppCheck Token, assuming there is one. Otherwise, returns null. internal static async Task GetAppCheckTokenAsync(FirebaseApp firebaseApp, bool limitedUse = false) { // If AppCheck reflection failed for any reason, nothing to do. if (!_appCheckReflectionInitialized) { return null; } try { // Get the FirebaseAppCheck instance for the current FirebaseApp object appCheckInstance = _appCheckGetInstanceMethod.Invoke(null, new object[] { firebaseApp }); if (appCheckInstance == null) { LogError("Failed to get FirebaseAppCheck instance via reflection."); return null; } object taskObject; if (limitedUse) { taskObject = _appCheckGetLimitedUseTokenMethod.Invoke(appCheckInstance, null); } else { // Invoke GetAppCheckTokenAsync(false) - returns a Task taskObject = _appCheckGetTokenMethod.Invoke(appCheckInstance, new object[] { false }); } if (taskObject is not Task appCheckTokenTask) { LogError($"Invoking GetToken did not return a Task."); return null; } // Await the task to get the AppCheckToken result await appCheckTokenTask; // Check for exceptions in the task if (appCheckTokenTask.IsFaulted) { LogError($"Error getting App Check token: {appCheckTokenTask.Exception}"); return null; } // Get the Result property from the Task object tokenResult = _appCheckTokenResultProperty.GetValue(appCheckTokenTask); // This is the AppCheckToken struct if (tokenResult == null) { LogError("App Check token result was null."); return null; } // Get the Token property from the AppCheckToken struct string finalToken = _appCheckTokenTokenProperty.GetValue(tokenResult) as string; return finalToken; } catch (Exception e) { // Log any exceptions during the reflection/invocation process LogError($"An error occurred while trying to fetch App Check token: {e}"); } return null; } // Cache the various types and methods needed for Auth token retrieval. private static void InitializeAuthReflection() { const string firebaseAuthTypeName = "Firebase.Auth.FirebaseAuth, Firebase.Auth"; const string getTokenMethodName = "TokenAsync"; try { // Set this to false, to allow easy failing out via return. _authReflectionInitialized = false; _authType = Type.GetType(firebaseAuthTypeName); if (_authType == null) { // Auth assembly likely not present, fine to skip return; } // Get the static method GetAuth(FirebaseApp app): _authGetAuthMethod = _authType.GetMethod( "GetAuth", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(FirebaseApp) }, null); if (_authGetAuthMethod == null) { LogError("Could not find FirebaseAuth.GetAuth method via reflection."); return; } // Get the CurrentUser property from FirebaseAuth instance _authCurrentUserProperty = _authType.GetProperty("CurrentUser", BindingFlags.Instance | BindingFlags.Public); if (_authCurrentUserProperty == null) { LogError("Could not find FirebaseAuth.CurrentUser property via reflection."); return; } // This should be FirebaseUser type Type userType = _authCurrentUserProperty.PropertyType; // Get the TokenAsync(bool) method from FirebaseUser _userTokenAsyncMethod = userType.GetMethod( getTokenMethodName, BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(bool) }, null); if (_userTokenAsyncMethod == null) { LogError($"Could not find FirebaseUser.{getTokenMethodName}(bool) method via reflection."); return; } // The return type is Task Type tokenTaskType = _userTokenAsyncMethod.ReturnType; // Get the Result property from Task _userTokenTaskResultProperty = tokenTaskType.GetProperty("Result"); if (_userTokenTaskResultProperty == null) { LogError("Could not find Result property on Auth token Task."); return; } // Check if Result property is actually a string if (_userTokenTaskResultProperty.PropertyType != typeof(string)) { LogError("Auth token Task's Result property is not a string, " + $"but is {_userTokenTaskResultProperty.PropertyType}"); return; } _authReflectionInitialized = true; } catch (Exception e) { LogError($"Exception during static initialization of Auth reflection in FirebaseInterops: {e}"); _authReflectionInitialized = false; } } // Gets the Auth Token, assuming there is one. Otherwise, returns null. internal static async Task GetAuthTokenAsync(FirebaseApp firebaseApp) { // If Auth reflection failed for any reason, nothing to do. if (!_authReflectionInitialized) { return null; } try { // Get the FirebaseAuth instance for the given FirebaseApp. object authInstance = _authGetAuthMethod.Invoke(null, new object[] { firebaseApp }); if (authInstance == null) { LogError("Failed to get FirebaseAuth instance via reflection."); return null; } // Get the CurrentUser property object currentUser = _authCurrentUserProperty.GetValue(authInstance); if (currentUser == null) { // No user logged in, so no token return null; } // Invoke TokenAsync(false) - returns a Task object taskObject = _userTokenAsyncMethod.Invoke(currentUser, new object[] { false }); if (taskObject is not Task tokenTask) { LogError("Invoking TokenAsync did not return a Task."); return null; } // Await the task to get the token result await tokenTask; // Check for exceptions in the task if (tokenTask.IsFaulted) { LogError($"Error getting Auth token: {tokenTask.Exception}"); return null; } // Get the Result property (which is the string token) return _userTokenTaskResultProperty.GetValue(tokenTask) as string; } catch (Exception e) { // Log any exceptions during the reflection/invocation process LogError($"An error occurred while trying to fetch Auth token: {e}"); } return null; } // Adds the other Firebase tokens to the HttpRequest, as available. internal static async Task AddFirebaseTokensAsync(HttpRequestMessage request, FirebaseApp firebaseApp, string authTokenPrefix = "Firebase", bool limitedUseAppCheckTokens = false) { string appCheckToken = await GetAppCheckTokenAsync(firebaseApp, limitedUseAppCheckTokens); if (!string.IsNullOrEmpty(appCheckToken)) { request.Headers.Add(appCheckHeader, appCheckToken); } string authToken = await GetAuthTokenAsync(firebaseApp); if (!string.IsNullOrEmpty(authToken)) { request.Headers.Add(authHeader, $"{authTokenPrefix} {authToken}"); } } // Adds the other Firebase tokens to the WebSocket, as available. internal static async Task AddFirebaseTokensAsync(ClientWebSocket socket, FirebaseApp firebaseApp, string authTokenPrefix = "Firebase", bool limitedUseAppCheckTokens = false) { string appCheckToken = await GetAppCheckTokenAsync(firebaseApp, limitedUseAppCheckTokens); if (!string.IsNullOrEmpty(appCheckToken)) { socket.Options.SetRequestHeader(appCheckHeader, appCheckToken); } string authToken = await GetAuthTokenAsync(firebaseApp); if (!string.IsNullOrEmpty(authToken)) { socket.Options.SetRequestHeader(authHeader, $"{authTokenPrefix} {authToken}"); } } } }