// Copyright 2022 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. @preconcurrency import FirebaseAppCheckInterop @preconcurrency import FirebaseAuthInterop @preconcurrency import FirebaseMessagingInterop import Foundation /// `FunctionsContext` is a helper object that holds metadata for a function call. struct FunctionsContext { let authToken: String? let fcmToken: String? let appCheckToken: String? let limitedUseAppCheckToken: String? } struct FunctionsContextProvider: Sendable { private let auth: AuthInterop? private let messaging: MessagingInterop? private let appCheck: AppCheckInterop? init(auth: AuthInterop?, messaging: MessagingInterop?, appCheck: AppCheckInterop?) { self.auth = auth self.messaging = messaging self.appCheck = appCheck } func context(options: HTTPSCallableOptions?) async throws -> FunctionsContext { // Previously, this section used `async let`, but that was changed for a // `Task`-based approach to work around a Swift 6.3 regression in Xcode 26.4. // - Context: https://github.com/firebase/firebase-ios-sdk/issues/15974 let authToken = Task { try await auth?.getToken(forcingRefresh: false) } let appCheckToken = Task { await getAppCheckToken(options: options) } let limitedUseAppCheckToken = Task { await getLimitedUseAppCheckToken(options: options) } return try await withTaskCancellationHandler { defer { authToken.cancel() appCheckToken.cancel() limitedUseAppCheckToken.cancel() } // Only `authToken` is throwing, but the formatter script removes the `try` // from `try authToken` and puts it in front of the initializer call. return try await FunctionsContext( authToken: authToken.value, fcmToken: messaging?.fcmToken, appCheckToken: appCheckToken.value, limitedUseAppCheckToken: limitedUseAppCheckToken.value ) } onCancel: { authToken.cancel() appCheckToken.cancel() limitedUseAppCheckToken.cancel() } } private func getAppCheckToken(options: HTTPSCallableOptions?) async -> String? { guard options?.requireLimitedUseAppCheckTokens != true, let appCheck else { return nil } return await withCheckedContinuation { continuation in appCheck.getToken(forcingRefresh: false) { result in continuation.resume(returning: result.token) } } } private func getLimitedUseAppCheckToken(options: HTTPSCallableOptions?) async -> String? { // At the moment, `await` doesn’t get along with Objective-C’s optional protocol methods. await withCheckedContinuation { (continuation: CheckedContinuation) in guard options?.requireLimitedUseAppCheckTokens == true, let appCheck, // `getLimitedUseToken(completion:)` is an optional protocol method. Optional binding // is performed to make sure `continuation` is called even if the method’s not implemented. let limitedUseTokenClosure = appCheck.getLimitedUseToken else { return continuation.resume(returning: nil) } limitedUseTokenClosure { tokenResult in // The placeholder token should be used in the case of App Check error. continuation.resume(returning: tokenResult.token) } } } }