diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart index 5fb2e011ae31..c44dbf6eb97c 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/common/common_library.dart @@ -29,6 +29,10 @@ String getGoogApiVal(CallerSDKType sdkType, String packageVersion) { return apiClientValue; } +String getFirebaseClientVal(String packageVersion) { + return 'flutter-fire-dc/$packageVersion'; +} + /// Transport Options for connecting to a specific host. class TransportOptions { /// Constructor diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart index 85456aa8c38e..40ee4eefdd0b 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/grpc_transport.dart @@ -77,6 +77,7 @@ class GRPCTransport implements DataConnectTransport { Map metadata = { 'x-goog-request-params': 'location=${options.location}&frontend=data', 'x-goog-api-client': getGoogApiVal(sdkType, packageVersion), + 'x-firebase-client': getFirebaseClientVal(packageVersion) }; if (authToken != null) { diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart index c01d2af6985d..c4bef7cb9a6f 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart @@ -85,6 +85,7 @@ class RestTransport implements DataConnectTransport { 'Content-Type': 'application/json', 'Accept': 'application/json', 'x-goog-api-client': getGoogApiVal(sdkType, packageVersion), + 'x-firebase-client': getFirebaseClientVal(packageVersion) }; String? appCheckToken; try { diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart index 3bfd51fb12bc..3f13e0e73fd1 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart @@ -17,6 +17,7 @@ import 'dart:convert'; import 'package:firebase_app_check/firebase_app_check.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_data_connect/src/common/common_library.dart'; +import 'package:firebase_data_connect/src/dataconnect_version.dart'; import 'package:firebase_data_connect/src/network/rest_library.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; @@ -260,6 +261,42 @@ void main() { ), ).called(1); }); + test('invokeOperation should include x-firebase-client headers', () async { + final mockResponse = http.Response('{"data": {"key": "value"}}', 200); + when( + mockHttpClient.post( + any, + headers: anyNamed('headers'), + body: anyNamed('body'), + ), + ).thenAnswer((_) async => mockResponse); + + when(mockUser.getIdToken()).thenAnswer((_) async => 'authToken123'); + when(mockAppCheck.getToken()).thenAnswer((_) async => 'appCheckToken123'); + + final deserializer = (String data) => 'Deserialized Data'; + + await transport.invokeOperation( + 'testQuery', + 'executeQuery', + deserializer, + null, + null, + 'authToken123', + ); + + verify( + mockHttpClient.post( + any, + headers: argThat( + containsPair( + 'x-firebase-client', getFirebaseClientVal(packageVersion)), + named: 'headers', + ), + body: anyNamed('body'), + ), + ).called(1); + }); test( 'invokeOperation should handle missing auth and appCheck tokens gracefully',