2022-01-31 09:10:27 -08:00
|
|
|
|
// Copyright 2022 Google LLC
|
2022-01-26 15:24:08 -05:00
|
|
|
|
//
|
2022-01-31 09:10:27 -08:00
|
|
|
|
// 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
|
2022-01-26 15:24:08 -05:00
|
|
|
|
//
|
2022-01-31 09:10:27 -08:00
|
|
|
|
// 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.
|
2022-01-26 15:24:08 -05:00
|
|
|
|
|
2025-03-19 10:26:53 -04:00
|
|
|
|
@preconcurrency import FirebaseAppCheckInterop
|
|
|
|
|
|
@preconcurrency import FirebaseAuthInterop
|
|
|
|
|
|
@preconcurrency import FirebaseMessagingInterop
|
2023-11-21 12:12:05 -08:00
|
|
|
|
import Foundation
|
2022-01-26 15:24:08 -05:00
|
|
|
|
|
2024-08-22 17:48:48 +02:00
|
|
|
|
/// `FunctionsContext` is a helper object that holds metadata for a function call.
|
|
|
|
|
|
struct FunctionsContext {
|
2022-01-26 15:24:08 -05:00
|
|
|
|
let authToken: String?
|
|
|
|
|
|
let fcmToken: String?
|
|
|
|
|
|
let appCheckToken: String?
|
2023-05-16 15:33:15 -07:00
|
|
|
|
let limitedUseAppCheckToken: String?
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-19 10:26:53 -04:00
|
|
|
|
struct FunctionsContextProvider: Sendable {
|
2024-08-22 17:48:48 +02:00
|
|
|
|
private let auth: AuthInterop?
|
|
|
|
|
|
private let messaging: MessagingInterop?
|
|
|
|
|
|
private let appCheck: AppCheckInterop?
|
2022-01-26 15:24:08 -05:00
|
|
|
|
|
|
|
|
|
|
init(auth: AuthInterop?, messaging: MessagingInterop?, appCheck: AppCheckInterop?) {
|
|
|
|
|
|
self.auth = auth
|
|
|
|
|
|
self.messaging = messaging
|
|
|
|
|
|
self.appCheck = appCheck
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-29 17:47:34 +01:00
|
|
|
|
@available(iOS 13, macCatalyst 13, macOS 10.15, tvOS 13, watchOS 7, *)
|
|
|
|
|
|
func context(options: HTTPSCallableOptions?) async throws -> FunctionsContext {
|
|
|
|
|
|
async let authToken = auth?.getToken(forcingRefresh: false)
|
|
|
|
|
|
async let appCheckToken = getAppCheckToken(options: options)
|
|
|
|
|
|
async let limitedUseAppCheckToken = getLimitedUseAppCheckToken(options: options)
|
|
|
|
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
|
fcmToken: messaging?.fcmToken,
|
|
|
|
|
|
appCheckToken: appCheckToken,
|
|
|
|
|
|
limitedUseAppCheckToken: limitedUseAppCheckToken
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@available(iOS 13, macCatalyst 13, macOS 10.15, tvOS 13, watchOS 7, *)
|
|
|
|
|
|
private func getAppCheckToken(options: HTTPSCallableOptions?) async -> String? {
|
|
|
|
|
|
guard
|
|
|
|
|
|
options?.requireLimitedUseAppCheckTokens != true,
|
2025-02-19 12:17:08 -05:00
|
|
|
|
let tokenResult = await appCheck?.getToken(forcingRefresh: false)
|
2024-10-29 17:47:34 +01:00
|
|
|
|
else { return nil }
|
2025-02-19 12:17:08 -05:00
|
|
|
|
// The placeholder token should be used in the case of App Check error.
|
2024-10-29 17:47:34 +01:00
|
|
|
|
return tokenResult.token
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@available(iOS 13, macCatalyst 13, macOS 10.15, tvOS 13, watchOS 7, *)
|
|
|
|
|
|
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<String?, Never>) 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
|
2025-02-19 12:17:08 -05:00
|
|
|
|
// The placeholder token should be used in the case of App Check error.
|
2024-10-29 17:47:34 +01:00
|
|
|
|
continuation.resume(returning: tokenResult.token)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-01-26 15:24:08 -05:00
|
|
|
|
|
2023-11-21 12:12:05 -08:00
|
|
|
|
func getContext(options: HTTPSCallableOptions? = nil,
|
|
|
|
|
|
_ completion: @escaping ((FunctionsContext, Error?) -> Void)) {
|
2022-01-26 15:24:08 -05:00
|
|
|
|
let dispatchGroup = DispatchGroup()
|
|
|
|
|
|
|
2022-01-31 09:10:27 -08:00
|
|
|
|
var authToken: String?
|
|
|
|
|
|
var appCheckToken: String?
|
|
|
|
|
|
var error: Error?
|
2023-05-16 15:33:15 -07:00
|
|
|
|
var limitedUseAppCheckToken: String?
|
2022-01-26 15:24:08 -05:00
|
|
|
|
|
2024-05-03 17:50:20 +02:00
|
|
|
|
if let auth {
|
2022-01-26 15:24:08 -05:00
|
|
|
|
dispatchGroup.enter()
|
|
|
|
|
|
|
2022-02-02 16:20:48 -08:00
|
|
|
|
auth.getToken(forcingRefresh: false) { token, authError in
|
|
|
|
|
|
authToken = token
|
|
|
|
|
|
error = authError
|
2022-01-26 15:24:08 -05:00
|
|
|
|
dispatchGroup.leave()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-03 17:50:20 +02:00
|
|
|
|
if let appCheck {
|
2022-01-26 15:24:08 -05:00
|
|
|
|
dispatchGroup.enter()
|
|
|
|
|
|
|
2023-05-16 15:33:15 -07:00
|
|
|
|
if options?.requireLimitedUseAppCheckTokens == true {
|
2024-10-14 17:14:38 +02:00
|
|
|
|
// `getLimitedUseToken(completion:)` is an optional protocol method.
|
|
|
|
|
|
// If it’s not implemented, we still need to leave the dispatch group.
|
|
|
|
|
|
if let limitedUseTokenClosure = appCheck.getLimitedUseToken {
|
|
|
|
|
|
limitedUseTokenClosure { tokenResult in
|
2025-02-19 12:17:08 -05:00
|
|
|
|
// In the case of an error, the token will be the placeholder token.
|
|
|
|
|
|
limitedUseAppCheckToken = tokenResult.token
|
2024-10-14 17:14:38 +02:00
|
|
|
|
dispatchGroup.leave()
|
2023-05-16 15:33:15 -07:00
|
|
|
|
}
|
2024-10-14 17:14:38 +02:00
|
|
|
|
} else {
|
2023-05-16 15:33:15 -07:00
|
|
|
|
dispatchGroup.leave()
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
appCheck.getToken(forcingRefresh: false) { tokenResult in
|
2025-02-19 12:17:08 -05:00
|
|
|
|
// In the case of an error, the token will be the placeholder token.
|
|
|
|
|
|
appCheckToken = tokenResult.token
|
2023-05-16 15:33:15 -07:00
|
|
|
|
dispatchGroup.leave()
|
2022-02-09 13:11:56 -09:00
|
|
|
|
}
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dispatchGroup.notify(queue: .main) {
|
|
|
|
|
|
let context = FunctionsContext(authToken: authToken,
|
|
|
|
|
|
fcmToken: self.messaging?.fcmToken,
|
2023-05-16 15:33:15 -07:00
|
|
|
|
appCheckToken: appCheckToken,
|
|
|
|
|
|
limitedUseAppCheckToken: limitedUseAppCheckToken)
|
2022-01-26 15:24:08 -05:00
|
|
|
|
completion(context, error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|