diff --git a/Sources/DataConnect.swift b/Sources/DataConnect.swift index 8fa0e69..4688380 100644 --- a/Sources/DataConnect.swift +++ b/Sources/DataConnect.swift @@ -24,7 +24,7 @@ public class DataConnect { private var app: FirebaseApp private var settings: DataConnectSettings - private var grpcClient: GrpcClient + private(set) var grpcClient: GrpcClient private var operationsManager: OperationsManager private static var instanceStore = InstanceStore() @@ -57,12 +57,12 @@ public class DataConnect { // self.grpcClient.close // self.operations.close - guard let projectID = app.options.projectID else { + guard app.options.projectID != nil else { fatalError("Firebase DataConnect requires the projectID to be set in the app options") } grpcClient = GrpcClient( - projectId: projectID, + app: app, settings: settings, connectorConfig: connectorConfig, auth: Auth.auth(app: app), @@ -79,12 +79,12 @@ public class DataConnect { self.settings = settings self.connectorConfig = connectorConfig - guard let projectID = app.options.projectID else { + guard app.options.projectID != nil else { fatalError("Firebase DataConnect requires the projectID to be set in the app options") } grpcClient = GrpcClient( - projectId: projectID, + app: self.app, settings: settings, connectorConfig: connectorConfig, auth: Auth.auth(app: app), diff --git a/Sources/Internal/GrpcClient.swift b/Sources/Internal/GrpcClient.swift index c8829c4..fe37b7a 100644 --- a/Sources/Internal/GrpcClient.swift +++ b/Sources/Internal/GrpcClient.swift @@ -16,7 +16,7 @@ import Foundation import FirebaseAppCheck import FirebaseAuth -import FirebaseCoreInternal +import FirebaseCore import GRPC import NIOCore import NIOHPACK @@ -28,6 +28,8 @@ import SwiftProtobuf actor GrpcClient: CustomStringConvertible { nonisolated let description: String + private let app: FirebaseApp + private let projectId: String private let threadPoolSize = 1 @@ -42,10 +44,11 @@ actor GrpcClient: CustomStringConvertible { private let appCheck: AppCheck? - private enum RequestHeaders { + enum RequestHeaders { static let googRequestParamsHeader = "x-goog-request-params" static let authorizationHeader = "x-firebase-auth-token" static let appCheckHeader = "X-Firebase-AppCheck" + static let firebaseAppId = "x-firebase-gmpid" } private let googRequestHeaderValue: String @@ -69,10 +72,16 @@ actor GrpcClient: CustomStringConvertible { } }() - init(projectId: String, settings: DataConnectSettings, connectorConfig: ConnectorConfig, + init(app: FirebaseApp, settings: DataConnectSettings, connectorConfig: ConnectorConfig, auth: Auth, appCheck: AppCheck?) { + self.app = app + + guard let projectId = app.options.projectID else { + fatalError("Data Connect requires a Firebase project ID to be specified.") + } self.projectId = projectId + serverSettings = settings self.connectorConfig = connectorConfig self.auth = auth @@ -173,10 +182,11 @@ actor GrpcClient: CustomStringConvertible { } } - private func createCallOptions() async -> CallOptions { + func createCallOptions() async -> CallOptions { var headers = HPACKHeaders() headers.add(name: RequestHeaders.googRequestParamsHeader, value: googRequestHeaderValue) + headers.add(name: RequestHeaders.firebaseAppId, value: app.options.googleAppID) // Add Auth token if available do { diff --git a/Tests/Unit/HeaderTests.swift b/Tests/Unit/HeaderTests.swift new file mode 100644 index 0000000..68207f1 --- /dev/null +++ b/Tests/Unit/HeaderTests.swift @@ -0,0 +1,53 @@ +// Copyright 2024 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. + +import FirebaseCore +@testable import FirebaseDataConnect +import Foundation +import GRPC + +import XCTest + +final class HeaderTests: XCTestCase { + static var defaultApp: FirebaseApp? + + static var options: FirebaseOptions = { + let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:testAppId", + gcmSenderID: "00000000000000000-00000000000-000000000") + options.projectID = "fdc-test" + options.apiKey = "testDummyApiKey" + return options + }() + + var fakeConnectorConfigOne = ConnectorConfig( + serviceId: "dataconnect", + location: "us-central1", + connector: "kitchensink" + ) + + override class func setUp() { + FirebaseApp.configure(options: options) + defaultApp = FirebaseApp.app() + } + + func testGmpAppIdHeader() async throws { + let dcOne = DataConnect.dataConnect(connectorConfig: fakeConnectorConfigOne) + let callOptions = await dcOne.grpcClient.createCallOptions() + let values = callOptions.customMetadata.values( + forHeader: GrpcClient.RequestHeaders.firebaseAppId, canonicalForm: false + ) + let contains = values.contains { $0 == HeaderTests.defaultApp!.options.googleAppID } + XCTAssertTrue(contains) + } +}