diff --git a/melos.yaml b/melos.yaml index a114d44b5..6aad57703 100644 --- a/melos.yaml +++ b/melos.yaml @@ -59,6 +59,14 @@ scripts: flutter: true dependsOn: pana private: false + pods: + run: cd ios && pod install --repo-update + description: running pod install + exec: + concurrency: 1 + orderDependents: true + packageFilters: + fileExists: 'ios/PodFile' score: run: dart run pana --no-warning --exit-code-threshold 0 exec: diff --git a/packages/instabug_http_client/CHANGELOG.md b/packages/instabug_http_client/CHANGELOG.md index 42a8d011e..ebe79c1b8 100644 --- a/packages/instabug_http_client/CHANGELOG.md +++ b/packages/instabug_http_client/CHANGELOG.md @@ -1,9 +1,10 @@ # Changelog -## Unreleased +## [2.5.0] - 18/11/2024 -- Enables `InstabugHttpClient` to wrap an internal `http` client. -- Add support for `http` v1 ([#20](https://github.com/Instabug/Instabug-Dart-http-Adapter/pull/20)). +### Added + +- Add support for tracing network requests from Instabug to services like Datadog and New Relic ([#21](https://github.com/Instabug/Instabug-Dart-http-Adapter/pull/21)). ## [2.4.0] - 7/05/2024 diff --git a/packages/instabug_http_client/example/android/app/src/main/AndroidManifest.xml b/packages/instabug_http_client/example/android/app/src/main/AndroidManifest.xml index 3f41384db..da82b5dba 100644 --- a/packages/instabug_http_client/example/android/app/src/main/AndroidManifest.xml +++ b/packages/instabug_http_client/example/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,9 @@ + android:icon="@mipmap/ic_launcher" + android:networkSecurityConfig="@xml/network_security_config" + android:usesCleartextTraffic="true"> =3.5.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/packages/instabug_http_client/lib/instabug_http_client.dart b/packages/instabug_http_client/lib/instabug_http_client.dart index 9b64489a1..25ec7a2bb 100644 --- a/packages/instabug_http_client/lib/instabug_http_client.dart +++ b/packages/instabug_http_client/lib/instabug_http_client.dart @@ -1,3 +1,5 @@ +// ignore_for_file: invalid_use_of_internal_member + library instabug_http_client; import 'dart:convert'; @@ -7,19 +9,16 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:http/http.dart' as http; +import 'package:instabug_flutter/instabug_flutter.dart'; import 'package:instabug_http_client/instabug_http_logger.dart'; import 'package:meta/meta.dart'; class InstabugHttpClient extends InstabugHttpLogger implements http.Client { - /// Constructs a new [InstabugHttpClient]. - /// - /// Provide a value for [client] in order to override the internal client used - /// by this class. This can be useful if you are working with other libraries - /// that require other custom client implementations - InstabugHttpClient({http.Client? client}) : client = client ?? http.Client() { + InstabugHttpClient() : client = http.Client() { logger = this; } + final NetworkLogger _networklogger = NetworkLogger(); @visibleForTesting http.Client client; @@ -31,66 +30,92 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { @override Future delete(Uri url, - {Map? headers, Object? body, Encoding? encoding}) { + {Map? headers, Object? body, Encoding? encoding}) async { final DateTime startTime = DateTime.now(); + final Map requestHeader = headers ?? {}; + final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); return client - .delete(url, body: body, headers: headers, encoding: encoding) + .delete(url, body: body, headers: requestHeader, encoding: encoding) .then((http.Response response) { - logger.onLogger(response, startTime: startTime); + logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader); return response; }); } + Future getW3cHeader(Map requestHeader, DateTime startTime) async { + final W3CHeader? w3cHeader = await _networklogger.getW3CHeader( + requestHeader, startTime.millisecondsSinceEpoch); + if (w3cHeader?.isW3cHeaderFound == false && + w3cHeader?.w3CGeneratedHeader != null) { + requestHeader['traceparent'] = w3cHeader!.w3CGeneratedHeader!; + } + return w3cHeader; + } + @override - Future get(Uri url, {Map? headers}) { + Future get(Uri url, {Map? headers}) async { final DateTime startTime = DateTime.now(); - return client.get(url, headers: headers).then((http.Response response) { - logger.onLogger(response, startTime: startTime); + final Map requestHeader = headers ?? {}; + final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + return client + .get(url, headers: requestHeader) + .then((http.Response response) { + logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader); return response; }); } @override - Future head(Uri url, {Map? headers}) { + Future head(Uri url, {Map? headers}) async { final DateTime startTime = DateTime.now(); - return client.head(url, headers: headers).then((http.Response response) { - logger.onLogger(response, startTime: startTime); + final Map requestHeader = headers ?? {}; + final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); + return client + .head(url, headers: requestHeader) + .then((http.Response response) { + logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader); return response; }); } @override Future patch(Uri url, - {Map? headers, Object? body, Encoding? encoding}) { + {Map? headers, Object? body, Encoding? encoding}) async { final DateTime startTime = DateTime.now(); + final Map requestHeader = headers ?? {}; + final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); return client - .patch(url, headers: headers, body: body, encoding: encoding) + .patch(url, headers: requestHeader, body: body, encoding: encoding) .then((http.Response response) { - logger.onLogger(response, startTime: startTime); + logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader); return response; }); } @override Future post(Uri url, - {Map? headers, Object? body, Encoding? encoding}) { + {Map? headers, Object? body, Encoding? encoding}) async { final DateTime startTime = DateTime.now(); + final Map requestHeader = headers ?? {}; + final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); return client - .post(url, headers: headers, body: body, encoding: encoding) + .post(url, headers: requestHeader, body: body, encoding: encoding) .then((http.Response response) { - logger.onLogger(response, startTime: startTime); + logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader); return response; }); } @override Future put(Uri url, - {Map? headers, Object? body, Encoding? encoding}) { + {Map? headers, Object? body, Encoding? encoding}) async { final DateTime startTime = DateTime.now(); + final Map requestHeader = headers ?? {}; + final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); return client - .put(url, headers: headers, body: body, encoding: encoding) + .put(url, headers: requestHeader, body: body, encoding: encoding) .then((http.Response response) { - logger.onLogger(response, startTime: startTime); + logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader); return response; }); } @@ -104,12 +129,14 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client { client.readBytes(url, headers: headers); @override - Future send(http.BaseRequest request) { + Future send(http.BaseRequest request) async { final DateTime startTime = DateTime.now(); + final Map requestHeader = request.headers; + final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime); return client.send(request).then((http.StreamedResponse streamedResponse) => http.Response.fromStream(streamedResponse) .then((http.Response response) { - logger.onLogger(response, startTime: startTime); + logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader); // Need to return new StreamedResponse, as body only can be listened once return http.StreamedResponse( Stream>.value(response.bodyBytes), diff --git a/packages/instabug_http_client/lib/instabug_http_logger.dart b/packages/instabug_http_client/lib/instabug_http_logger.dart index f88cd2781..13bfce62c 100644 --- a/packages/instabug_http_client/lib/instabug_http_logger.dart +++ b/packages/instabug_http_client/lib/instabug_http_logger.dart @@ -4,7 +4,7 @@ import 'package:http/http.dart' as http; import 'package:instabug_flutter/instabug_flutter.dart'; class InstabugHttpLogger { - void onLogger(http.Response response, {DateTime? startTime}) { + void onLogger(http.Response response, {DateTime? startTime,W3CHeader? w3CHeader}) { final NetworkLogger networkLogger = NetworkLogger(); final Map requestHeaders = {}; @@ -29,6 +29,7 @@ class InstabugHttpLogger { url: request.url.toString(), requestHeaders: requestHeaders, requestBody: requestBody, + w3cHeader: w3CHeader ); final DateTime endTime = DateTime.now(); diff --git a/packages/instabug_http_client/pubspec.lock b/packages/instabug_http_client/pubspec.lock index 872d09006..576d3ee35 100644 --- a/packages/instabug_http_client/pubspec.lock +++ b/packages/instabug_http_client/pubspec.lock @@ -294,7 +294,7 @@ packages: path: "../instabug_flutter" relative: true source: path - version: "13.4.0" + version: "14.0.0" io: dependency: transitive description: diff --git a/packages/instabug_http_client/test/instabug_http_client_test.dart b/packages/instabug_http_client/test/instabug_http_client_test.dart index 63717d05f..1448982a2 100644 --- a/packages/instabug_http_client/test/instabug_http_client_test.dart +++ b/packages/instabug_http_client/test/instabug_http_client_test.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:io'; - // to maintain supported versions prior to Flutter 3.3 // ignore: unnecessary_import import 'dart:typed_data'; @@ -8,6 +7,8 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter/src/generated/instabug.api.g.dart'; import 'package:instabug_http_client/instabug_http_client.dart'; import 'package:instabug_http_client/instabug_http_logger.dart'; import 'package:mockito/annotations.dart'; @@ -18,8 +19,22 @@ import 'instabug_http_client_test.mocks.dart'; @GenerateMocks([ InstabugHttpLogger, InstabugHttpClient, + InstabugHostApi, ]) Future main() async { + TestWidgetsFlutterBinding.ensureInitialized(); + final MockInstabugHostApi mHost = MockInstabugHostApi(); + + setUpAll(() { + Instabug.$setHostApi(mHost); + NetworkLogger.$setHostApi(mHost); + when(mHost.isW3CFeatureFlagsEnabled()).thenAnswer((_)=> Future>.value({ + 'isW3cCaughtHeaderEnabled': true, + 'isW3cExternalGeneratedHeaderEnabled': false, + 'isW3cExternalTraceIDEnabled': true, + })); + }); + const Map fakeResponse = { 'id': '123', 'activationCode': '111111', @@ -28,21 +43,17 @@ Future main() async { final http.Response mockedResponse = http.Response(json.encode(fakeResponse), 200); + late InstabugHttpClient instabugHttpClient; + setUp(() { url = Uri.parse('http://www.instabug.com'); - }); - - InstabugHttpClient buildClient({http.Client? mockClient}) { - final InstabugHttpClient instabugHttpClient = - InstabugHttpClient(client: mockClient ?? MockInstabugHttpClient()); + instabugHttpClient = InstabugHttpClient(); + instabugHttpClient.client = MockInstabugHttpClient(); instabugHttpClient.logger = MockInstabugHttpLogger(); - - return instabugHttpClient; - } + }); test('expect instabug http client GET to return response', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); - when(instabugHttpClient.client.get(url)) + when(instabugHttpClient.client.get(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); final http.Response result = await instabugHttpClient.get(url); expect(result, isInstanceOf()); @@ -53,8 +64,7 @@ Future main() async { }); test('expect instabug http client HEAD to return response', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); - when(instabugHttpClient.client.head(url)) + when(instabugHttpClient.client.head(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); final http.Response result = await instabugHttpClient.head(url); expect(result, isInstanceOf()); @@ -65,8 +75,7 @@ Future main() async { }); test('expect instabug http client DELETE to return response', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); - when(instabugHttpClient.client.delete(url)) + when(instabugHttpClient.client.delete(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); final http.Response result = await instabugHttpClient.delete(url); expect(result, isInstanceOf()); @@ -77,8 +86,7 @@ Future main() async { }); test('expect instabug http client PATCH to return response', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); - when(instabugHttpClient.client.patch(url)) + when(instabugHttpClient.client.patch(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); final http.Response result = await instabugHttpClient.patch(url); expect(result, isInstanceOf()); @@ -89,8 +97,7 @@ Future main() async { }); test('expect instabug http client POST to return response', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); - when(instabugHttpClient.client.post(url)) + when(instabugHttpClient.client.post(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); final http.Response result = await instabugHttpClient.post(url); expect(result, isInstanceOf()); @@ -101,8 +108,7 @@ Future main() async { }); test('expect instabug http client PUT to return response', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); - when(instabugHttpClient.client.put(url)) + when(instabugHttpClient.client.put(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); final http.Response result = await instabugHttpClient.put(url); expect(result, isInstanceOf()); @@ -113,9 +119,8 @@ Future main() async { }); test('expect instabug http client READ to return response', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); const String response = 'Some response string'; - when(instabugHttpClient.client.read(url)) + when(instabugHttpClient.client.read(url,headers: anyNamed('headers'))) .thenAnswer((_) async => response); final String result = await instabugHttpClient.read(url); @@ -125,7 +130,8 @@ Future main() async { test('expect instabug http client READBYTES to return response', () async { final Uint8List response = Uint8List(3); - final InstabugHttpClient instabugHttpClient = buildClient(mockClient: MockClient((_) async => http.Response.bytes(response, 200))); + instabugHttpClient.client = + MockClient((_) async => http.Response.bytes(response, 200)); final Uint8List result = await instabugHttpClient.readBytes(url); expect(result, isInstanceOf()); @@ -133,7 +139,6 @@ Future main() async { }); test('expect instabug http client SEND to return response', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); final http.StreamedResponse response = http.StreamedResponse( const Stream>.empty(), 200, contentLength: 0); @@ -166,15 +171,13 @@ Future main() async { }); test('expect instabug http client CLOSE to be called', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); instabugHttpClient.close(); verify(instabugHttpClient.client.close()); }); test('stress test for GET method', () async { - final InstabugHttpClient instabugHttpClient = buildClient(); - when(instabugHttpClient.client.get(url)) + when(instabugHttpClient.client.get(url,headers: anyNamed('headers'))) .thenAnswer((_) async => mockedResponse); for (int i = 0; i < 10000; i++) { await instabugHttpClient.get(url);