From 43f9f07d4cd51a7b8dc2edb50feee32716658738 Mon Sep 17 00:00:00 2001 From: Santiago Garcia Gil Date: Tue, 11 Feb 2025 18:48:58 -0500 Subject: [PATCH 1/8] test: add unit tests --- .gitignore | 2 + lib/flutter_client_sse.dart | 9 +- pubspec.lock | 78 ++++++--------- pubspec.yaml | 1 + test/flutter_client_sse_test.dart | 161 ++++++++++++++++++++++++++++++ test/sse_event_model_test.dart | 17 ++++ 6 files changed, 220 insertions(+), 48 deletions(-) create mode 100644 test/flutter_client_sse_test.dart create mode 100644 test/sse_event_model_test.dart diff --git a/.gitignore b/.gitignore index 1985397..676e730 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,5 @@ build/ !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 + +**/coverage/** diff --git a/lib/flutter_client_sse.dart b/lib/flutter_client_sse.dart index 57db0b3..427cb9d 100644 --- a/lib/flutter_client_sse.dart +++ b/lib/flutter_client_sse.dart @@ -2,8 +2,10 @@ library flutter_client_sse; import 'dart:async'; import 'dart:convert'; + import 'package:flutter_client_sse/constants/sse_request_type_enum.dart'; import 'package:http/http.dart' as http; + part 'sse_event_model.dart'; /// A client for subscribing to Server-Sent Events (SSE). @@ -41,6 +43,10 @@ class SSEClient { /// [url] is the URL of the SSE endpoint. /// [header] is a map of request headers. /// [body] is an optional request body for POST requests. + /// [oldStreamController] stream controller, used to retry to persist the + /// stream from the old connection. + /// [client] is an optional http client used for testing purpose + /// or custom client. /// /// Returns a [Stream] of [SSEModel] representing the SSE events. static Stream subscribeToSSE( @@ -48,6 +54,7 @@ class SSEClient { required String url, required Map header, StreamController? oldStreamController, + http.Client? client, Map? body}) { StreamController streamController = StreamController(); if (oldStreamController != null) { @@ -58,7 +65,7 @@ class SSEClient { print("--SUBSCRIBING TO SSE---"); while (true) { try { - _client = http.Client(); + _client = client ?? http.Client(); var request = new http.Request( method == SSERequestType.GET ? "GET" : "POST", Uri.parse(url), diff --git a/pubspec.lock b/pubspec.lock index 8d66057..615f727 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.1" fake_async: dependency: transitive description: @@ -83,62 +83,54 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" - leak_tracker: + js: dependency: transitive description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 - url: "https://pub.dev" - source: hosted - version: "2.0.1" + version: "0.6.7" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.2.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.9.1" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" sky_engine: dependency: transitive description: flutter @@ -148,26 +140,26 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -188,10 +180,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.5.1" typed_data: dependency: transitive description: @@ -208,14 +200,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" sdks: - dart: ">=3.2.0-0 <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=1.17.0" diff --git a/pubspec.yaml b/pubspec.yaml index c7fd9bf..8ccc9c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + mocktail: ^1.0.4 flutter: diff --git a/test/flutter_client_sse_test.dart b/test/flutter_client_sse_test.dart new file mode 100644 index 0000000..a1dc91e --- /dev/null +++ b/test/flutter_client_sse_test.dart @@ -0,0 +1,161 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_client_sse/constants/sse_request_type_enum.dart'; +import 'package:flutter_client_sse/flutter_client_sse.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:mocktail/mocktail.dart'; + +class MockHttp extends Mock implements http.Client {} + +void main() { + group('SSE Client', () { + setUpAll(() { + registerFallbackValue( + http.Request('GET', Uri.parse('http://localhost:3001'))); + }); + + test('GET', () async { + var mockHttp = MockHttp(); + var elements = [ + utf8.encode('id: 1\nevent: message\ndata: Hello\n\n'), + utf8.encode('id: 2\nevent: message\ndata: World\n\n'), + utf8.encode('id: 3\nevent: message\ndata: World\n\n'), + ]; + + when(() => mockHttp.send(any())).thenAnswer((_) async { + return http.StreamedResponse(Stream.fromIterable(elements), 200, + headers: { + HttpHeaders.contentTypeHeader: 'text/event-stream', + HttpHeaders.cacheControlHeader: 'no-cache', + HttpHeaders.connectionHeader: 'keep-alive', + }); + }); + int lines = 0; + SSEClient.subscribeToSSE( + client: mockHttp, + method: SSERequestType.GET, + url: 'http://localhost:3001', + header: { + "Cookie": + 'jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2NDMyMTAyMzEsImV4cCI6MTY0MzgxNTAzMX0.U0aCAM2fKE1OVnGFbgAU_UVBvNwOMMquvPY8QaLD138; Path=/; Expires=Wed, 02 Feb 2022 15:17:11 GMT; HttpOnly; SameSite=Strict', + "Accept": "text/event-stream", + "Cache-Control": "no-cache", + }).listen( + (event) { + lines++; + print('Id: ' + (event.id ?? "")); + print('Event: ' + (event.event ?? "")); + print('Data: ' + (event.data ?? "")); + }, + ); + await Future.delayed(const Duration(seconds: 1)); + expect(lines, 3); + }); + test('POST', () async { + int lines = 0; + + var mockHttp = MockHttp(); + var elements = [ + utf8.encode('id: 1\nevent: message\ndata: Hello\n\n'), + utf8.encode('id: 2\nevent: message\ndata: World\n\n'), + ]; + + when(() => mockHttp.send(any())).thenAnswer((_) async { + return http.StreamedResponse(Stream.fromIterable(elements), 200, + headers: { + HttpHeaders.contentTypeHeader: 'text/event-stream', + HttpHeaders.cacheControlHeader: 'no-cache', + HttpHeaders.connectionHeader: 'keep-alive', + }); + }); + SSEClient.subscribeToSSE( + client: mockHttp, + method: SSERequestType.POST, + url: + 'http://192.168.1.2:3000/api/activity-stream?historySnapshot=FIVE_MINUTE', + header: { + "Cookie": + 'jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2NDMyMTAyMzEsImV4cCI6MTY0MzgxNTAzMX0.U0aCAM2fKE1OVnGFbgAU_UVBvNwOMMquvPY8QaLD138; Path=/; Expires=Wed, 02 Feb 2022 15:17:11 GMT; HttpOnly; SameSite=Strict', + "Accept": "text/event-stream", + "Cache-Control": "no-cache", + }, + body: { + "name": "Hello", + "customerInfo": {"age": 25, "height": 168} + }).listen( + (event) { + lines++; + print('Id: ' + event.id!); + print('Event: ' + event.event!); + print('Data: ' + event.data!); + }, + ); + await Future.delayed(const Duration(seconds: 2)); + expect(lines, 2); + }); + test('Should retry when receive field undefined', () async { + var mockHttp = MockHttp(); + var elements = [ + utf8.encode('anothe: 1\n\n'), + utf8.encode('id: 3\nevent: message\ndata: World\n\n'), + ]; + + when(() => mockHttp.send(any())).thenAnswer((_) async { + return http.StreamedResponse(Stream.fromIterable(elements), 200, + headers: { + HttpHeaders.contentTypeHeader: 'text/event-stream', + HttpHeaders.cacheControlHeader: 'no-cache', + HttpHeaders.connectionHeader: 'keep-alive', + }); + }); + int lines = 0; + SSEClient.subscribeToSSE( + client: mockHttp, + method: SSERequestType.GET, + url: 'http://localhost:3001', + header: { + "Cookie": + 'jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2NDMyMTAyMzEsImV4cCI6MTY0MzgxNTAzMX0.U0aCAM2fKE1OVnGFbgAU_UVBvNwOMMquvPY8QaLD138; Path=/; Expires=Wed, 02 Feb 2022 15:17:11 GMT; HttpOnly; SameSite=Strict', + "Accept": "text/event-stream", + "Cache-Control": "no-cache", + }).listen( + (event) { + lines++; + print('Id: ' + (event.id ?? "")); + print('Event: ' + (event.event ?? "")); + print('Data: ' + (event.data ?? "")); + }, + ); + await Future.delayed(const Duration(seconds: 7)); + expect(lines, 2); + }); + + test('Should retry when throw exception the client', () async { + var mockHttpExpection = MockHttp(); + + when(() => mockHttpExpection.send(any())).thenThrow(Exception()); + int lines = 0; + SSEClient.subscribeToSSE( + client: mockHttpExpection, + method: SSERequestType.GET, + url: 'http://localhost:3001', + header: { + "Cookie": + 'jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2NDMyMTAyMzEsImV4cCI6MTY0MzgxNTAzMX0.U0aCAM2fKE1OVnGFbgAU_UVBvNwOMMquvPY8QaLD138; Path=/; Expires=Wed, 02 Feb 2022 15:17:11 GMT; HttpOnly; SameSite=Strict', + "Accept": "text/event-stream", + "Cache-Control": "no-cache", + }).listen( + (event) { + lines++; + print('Id: ' + (event.id ?? "")); + print('Event: ' + (event.event ?? "")); + print('Data: ' + (event.data ?? "")); + }, + ); + await Future.delayed(const Duration(seconds: 7)); + expect(lines, 0); + }); + }); +} diff --git a/test/sse_event_model_test.dart b/test/sse_event_model_test.dart new file mode 100644 index 0000000..f1bc866 --- /dev/null +++ b/test/sse_event_model_test.dart @@ -0,0 +1,17 @@ +import 'package:flutter_client_sse/flutter_client_sse.dart'; +import 'package:flutter_test/flutter_test.dart'; + +main() { + test('SSE Event Model Test', () { + final sseModel = SSEModel(data: 'data', id: 'id', event: 'event'); + expect(sseModel.id, 'id'); + expect(sseModel.event, 'event'); + expect(sseModel.data, 'data'); + }); + test('SSE Event Model Test', () { + final sseModel = SSEModel.fromData('id: id\nevent: event\ndata: data'); + expect(sseModel.id, ' id'); + expect(sseModel.event, ' event'); + expect(sseModel.data, ' data'); + }); +} From de225581ec2f6d32a71702b8b88c06f1e922e03f Mon Sep 17 00:00:00 2001 From: Santiago Garcia Gil Date: Thu, 13 Feb 2025 11:08:29 -0500 Subject: [PATCH 2/8] feat: retry --- lib/flutter_client_sse.dart | 100 +++++++++++++++++++++++++++++------- lib/utils.dart | 34 ++++++++++++ pubspec.yaml | 1 + test/utils_test.dart | 62 ++++++++++++++++++++++ 4 files changed, 178 insertions(+), 19 deletions(-) create mode 100644 lib/utils.dart create mode 100644 test/utils_test.dart diff --git a/lib/flutter_client_sse.dart b/lib/flutter_client_sse.dart index 427cb9d..d7c13b2 100644 --- a/lib/flutter_client_sse.dart +++ b/lib/flutter_client_sse.dart @@ -4,13 +4,16 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter_client_sse/constants/sse_request_type_enum.dart'; +import 'package:flutter_client_sse/utils.dart'; import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; part 'sse_event_model.dart'; /// A client for subscribing to Server-Sent Events (SSE). class SSEClient { static http.Client _client = new http.Client(); + static final _log = Logger('SSEClient'); /// Retry the SSE connection after a delay. /// @@ -19,24 +22,58 @@ class SSEClient { /// [header] is a map of request headers. /// [body] is an optional request body for POST requests. /// [streamController] is required to persist the stream from the old connection + /// [maxRetryTime] is the maximum time to retry + /// [minRetryTime] is the minimum time to retry + /// [maxRetry] is the maximum number of retries before giving up. if set to 0, + /// it will retry indefinitely. + /// [currentRetry] is the current retry count. + /// static void _retryConnection( {required SSERequestType method, required String url, required Map header, required StreamController streamController, - Map? body}) { - print('---RETRY CONNECTION---'); - Future.delayed(Duration(seconds: 5), () { + Map? body, + required int maxRetryTime, + required int minRetryTime, + required int maxRetry, + required int currentRetry}) { + _log.finest('$currentRetry retry of $maxRetry retries'); + + if (maxRetry != 0 && currentRetry >= maxRetry) { + _log.info('---MAX RETRY REACHED---'); + streamController.close(); + return; + } + _log.info('---RETRY CONNECTION---'); + int delay = _delay(currentRetry, minRetryTime, maxRetryTime); + _log.finest('waiting for $delay ms'); + + Future.delayed(Duration(milliseconds: delay), () { subscribeToSSE( - method: method, - url: url, - header: header, - body: body, - oldStreamController: streamController, - ); + method: method, + url: url, + header: header, + body: body, + oldStreamController: streamController, + maxRetryTime: maxRetryTime, + minRetryTime: minRetryTime, + maxRetry: maxRetry, + retryCount: currentRetry + 1); }); } + static int _delay(int currentRetry, int minRetryTime, int retryTime) { + return Utils.expBackoff( + minRetryTime, retryTime, currentRetry, _defaultJitterFn); + } + + static int _defaultJitterFn(int num) { + var randomFactor = 0.26; + + return Utils.jitter(num, randomFactor); + } + /// Subscribe to Server-Sent Events. /// /// [method] is the request method (GET or POST). @@ -47,6 +84,11 @@ class SSEClient { /// stream from the old connection. /// [client] is an optional http client used for testing purpose /// or custom client. + /// [maxRetryTime] is the maximum time to retry + /// [maxRetry] is the maximum number of retries before giving up. if set to 0, + /// it will retry indefinitely. + /// [minRetryTime] is the minimum time to retry + /// [retryCount] is the current retry count. /// /// Returns a [Stream] of [SSEModel] representing the SSE events. static Stream subscribeToSSE( @@ -55,14 +97,18 @@ class SSEClient { required Map header, StreamController? oldStreamController, http.Client? client, - Map? body}) { + Map? body, + int maxRetryTime = 5000, + int minRetryTime = 5000, + int maxRetry = 5, + int retryCount = 0}) { StreamController streamController = StreamController(); if (oldStreamController != null) { streamController = oldStreamController; } var lineRegex = RegExp(r'^([^:]*)(?::)?(?: )?(.*)?$'); var currentSSEModel = SSEModel(data: '', id: '', event: ''); - print("--SUBSCRIBING TO SSE---"); + _log.info("--SUBSCRIBING TO SSE---"); while (true) { try { _client = client ?? http.Client(); @@ -126,48 +172,64 @@ class SSEClient { case 'retry': break; default: - print('---ERROR---'); - print(dataLine); + _log.severe('---ERROR---'); + _log.severe(dataLine); _retryConnection( method: method, url: url, header: header, streamController: streamController, + maxRetryTime: maxRetryTime, + minRetryTime: minRetryTime, + currentRetry: retryCount, + maxRetry: maxRetry, ); } }, onError: (e, s) { - print('---ERROR---'); - print(e); + _log.severe('---ERROR---'); + _log.severe(e); _retryConnection( method: method, url: url, header: header, body: body, streamController: streamController, + maxRetryTime: maxRetryTime, + minRetryTime: minRetryTime, + currentRetry: retryCount, + maxRetry: maxRetry, ); }, ); }, onError: (e, s) { - print('---ERROR---'); - print(e); + _log.severe('---ERROR---'); + _log.severe(e); _retryConnection( method: method, url: url, header: header, body: body, streamController: streamController, + maxRetryTime: maxRetryTime, + minRetryTime: minRetryTime, + currentRetry: retryCount, + maxRetry: maxRetry, ); }); } catch (e) { - print('---ERROR---'); - print(e); + _log.severe('---ERROR---'); + _log.severe(e); _retryConnection( method: method, url: url, header: header, body: body, streamController: streamController, + maxRetryTime: maxRetryTime, + minRetryTime: minRetryTime, + currentRetry: retryCount, + maxRetry: maxRetry, ); } return streamController.stream; diff --git a/lib/utils.dart b/lib/utils.dart new file mode 100644 index 0000000..611db95 --- /dev/null +++ b/lib/utils.dart @@ -0,0 +1,34 @@ +import 'dart:math'; + +class Utils { + static int _defaultJitterFn(int num) => num; + + static int jitter(int baseTime, double randomFactor) { + var rest = baseTime * randomFactor; + var rng = Random(); + var jitter = (baseTime - rest) + rng.nextDouble() * rest; + + return jitter.toInt(); + } + + static int expBackoff( + int initial, + int max, + int actualRetry, [ + Function? jitterFn, + ]) { + Function curatedFn; + curatedFn = jitterFn ?? _defaultJitterFn; + var base = initial * pow(2, actualRetry); + var willWait = 0; + willWait = base > max ? curatedFn(max) : curatedFn(base); + + return willWait.toInt(); + } + + static String? checkString(String data) { + var trim = data.trim(); + + return trim.isEmpty ? '' : trim; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 8ccc9c3..9720bac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: flutter: sdk: flutter http: ^1.1.0 + logging: ^1.2.0 dev_dependencies: flutter_test: diff --git a/test/utils_test.dart b/test/utils_test.dart new file mode 100644 index 0000000..274ca5b --- /dev/null +++ b/test/utils_test.dart @@ -0,0 +1,62 @@ +import 'package:flutter_client_sse/utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Utils tests.', () { + test('Should generate random jitter', () { + for (var i = 0; i < 100; i++) { + var result = Utils.jitter(1000, 0.25); + assert(result > 749); + assert(result < 1000); + } + }); + + test('Should generate Exp Backoff no Jitter', () { + const expected = [ + [0, 10], + [1, 20], + [2, 40], + [3, 80], + [4, 160], + [5, 320], + [6, 640], + [7, 1280], + [8, 2560], + [9, 5120], + [10, 6000], + [11, 6000], + ]; + + for (final e in expected) { + var result = Utils.expBackoff(10, 6000, e[0]); + assert(result == e[1]); + } + }); + + test('Should generate Exp Backoff with Jitter', () { + const expected = [ + [0, 10], + [1, 20], + [2, 40], + [3, 80], + [4, 160], + [5, 320], + [6, 640], + [7, 1280], + [8, 2560], + [9, 5120], + [10, 6000], + [11, 6000], + ]; + + var jitterFactor = 0.25; + int jitterFn(int num) => Utils.jitter(num, jitterFactor); + + for (final e in expected) { + var result = Utils.expBackoff(10, 6000, e[0], jitterFn); + assert(result > (e[1] * (1 - jitterFactor)) - 1); + assert(result < e[1]); + } + }); + }); +} From cbd4b14f8c56aae361a1751e2450b06b31c14e3f Mon Sep 17 00:00:00 2001 From: Santiago Garcia Gil Date: Fri, 14 Feb 2025 16:20:12 -0500 Subject: [PATCH 3/8] feat: add limitReachedCallback --- .gitignore | 3 ++ lib/flutter_client_sse.dart | 56 ++++++++++++++++++++++++++++--------- pubspec.yaml | 1 + 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 676e730..f7c2bd6 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,6 @@ build/ !**/ios/**/default.perspectivev3 **/coverage/** + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/lib/flutter_client_sse.dart b/lib/flutter_client_sse.dart index d7c13b2..3c5264e 100644 --- a/lib/flutter_client_sse.dart +++ b/lib/flutter_client_sse.dart @@ -27,6 +27,8 @@ class SSEClient { /// [maxRetry] is the maximum number of retries before giving up. if set to 0, /// it will retry indefinitely. /// [currentRetry] is the current retry count. + /// [limitReachedCallback] is a callback function that will be called when the + /// maximum number of retries is reached. /// static void _retryConnection( {required SSERequestType method, @@ -37,11 +39,13 @@ class SSEClient { required int maxRetryTime, required int minRetryTime, required int maxRetry, - required int currentRetry}) { + required int currentRetry, + Future Function()? limitReachedCallback}) { _log.finest('$currentRetry retry of $maxRetry retries'); if (maxRetry != 0 && currentRetry >= maxRetry) { _log.info('---MAX RETRY REACHED---'); + limitReachedCallback?.call(); streamController.close(); return; } @@ -59,7 +63,8 @@ class SSEClient { maxRetryTime: maxRetryTime, minRetryTime: minRetryTime, maxRetry: maxRetry, - retryCount: currentRetry + 1); + retryCount: currentRetry + 1, + limitReachedCallback: limitReachedCallback); }); } @@ -89,19 +94,23 @@ class SSEClient { /// it will retry indefinitely. /// [minRetryTime] is the minimum time to retry /// [retryCount] is the current retry count. + /// [limitReachedCallback] is a callback function that will be called when the + /// maximum number of retries is reached. /// /// Returns a [Stream] of [SSEModel] representing the SSE events. - static Stream subscribeToSSE( - {required SSERequestType method, - required String url, - required Map header, - StreamController? oldStreamController, - http.Client? client, - Map? body, - int maxRetryTime = 5000, - int minRetryTime = 5000, - int maxRetry = 5, - int retryCount = 0}) { + static Stream subscribeToSSE({ + required SSERequestType method, + required String url, + required Map header, + StreamController? oldStreamController, + http.Client? client, + Map? body, + int maxRetryTime = 5000, + int minRetryTime = 5000, + int maxRetry = 5, + int retryCount = 0, + Future Function()? limitReachedCallback, + }) { StreamController streamController = StreamController(); if (oldStreamController != null) { streamController = oldStreamController; @@ -131,6 +140,23 @@ class SSEClient { /// Listening to the response as a stream response.asStream().listen((data) { + if (data.statusCode != 200) { + _log.severe('---ERROR CODE ${data.statusCode}---'); + _retryConnection( + method: method, + url: url, + header: header, + body: body, + streamController: streamController, + maxRetryTime: maxRetryTime, + minRetryTime: minRetryTime, + currentRetry: retryCount, + maxRetry: maxRetry, + limitReachedCallback: limitReachedCallback, + ); + return; + } + /// Applying transforms and listening to it data.stream ..transform(Utf8Decoder()).transform(LineSplitter()).listen( @@ -183,6 +209,7 @@ class SSEClient { minRetryTime: minRetryTime, currentRetry: retryCount, maxRetry: maxRetry, + limitReachedCallback: limitReachedCallback, ); } }, @@ -199,6 +226,7 @@ class SSEClient { minRetryTime: minRetryTime, currentRetry: retryCount, maxRetry: maxRetry, + limitReachedCallback: limitReachedCallback, ); }, ); @@ -215,6 +243,7 @@ class SSEClient { minRetryTime: minRetryTime, currentRetry: retryCount, maxRetry: maxRetry, + limitReachedCallback: limitReachedCallback, ); }); } catch (e) { @@ -230,6 +259,7 @@ class SSEClient { minRetryTime: minRetryTime, currentRetry: retryCount, maxRetry: maxRetry, + limitReachedCallback: limitReachedCallback, ); } return streamController.stream; diff --git a/pubspec.yaml b/pubspec.yaml index 9720bac..5c7d16d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: logging: ^1.2.0 dev_dependencies: + test: ^1.21.4 flutter_test: sdk: flutter mocktail: ^1.0.4 From 18fa8b28e06fbda9f4bf87840a7a376e62e6aae5 Mon Sep 17 00:00:00 2001 From: Santiago Garcia Gil Date: Sat, 22 Mar 2025 10:40:47 -0500 Subject: [PATCH 4/8] feat: add retry options class --- lib/flutter_client_sse.dart | 79 ++++++++++++++----------------------- lib/retry_options.dart | 13 ++++++ 2 files changed, 43 insertions(+), 49 deletions(-) create mode 100644 lib/retry_options.dart diff --git a/lib/flutter_client_sse.dart b/lib/flutter_client_sse.dart index 3c5264e..62111d2 100644 --- a/lib/flutter_client_sse.dart +++ b/lib/flutter_client_sse.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter_client_sse/constants/sse_request_type_enum.dart'; +import 'package:flutter_client_sse/retry_options.dart'; import 'package:flutter_client_sse/utils.dart'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; @@ -30,41 +31,38 @@ class SSEClient { /// [limitReachedCallback] is a callback function that will be called when the /// maximum number of retries is reached. /// - static void _retryConnection( - {required SSERequestType method, - required String url, - required Map header, - required StreamController streamController, - Map? body, - required int maxRetryTime, - required int minRetryTime, - required int maxRetry, - required int currentRetry, - Future Function()? limitReachedCallback}) { - _log.finest('$currentRetry retry of $maxRetry retries'); + static void _retryConnection({ + required SSERequestType method, + required String url, + required Map header, + required StreamController streamController, + Map? body, + required RetryOptions retryOptions, + required int currentRetry, + }) { + _log.finest('$currentRetry retry of ${retryOptions.maxRetry} retries'); - if (maxRetry != 0 && currentRetry >= maxRetry) { + if (retryOptions.maxRetry != 0 && currentRetry >= retryOptions.maxRetry) { _log.info('---MAX RETRY REACHED---'); - limitReachedCallback?.call(); + retryOptions.limitReachedCallback?.call(); streamController.close(); return; } _log.info('---RETRY CONNECTION---'); - int delay = _delay(currentRetry, minRetryTime, maxRetryTime); + int delay = _delay( + currentRetry, retryOptions.minRetryTime, retryOptions.maxRetryTime); _log.finest('waiting for $delay ms'); Future.delayed(Duration(milliseconds: delay), () { subscribeToSSE( - method: method, - url: url, - header: header, - body: body, - oldStreamController: streamController, - maxRetryTime: maxRetryTime, - minRetryTime: minRetryTime, - maxRetry: maxRetry, - retryCount: currentRetry + 1, - limitReachedCallback: limitReachedCallback); + method: method, + url: url, + header: header, + body: body, + oldStreamController: streamController, + retryOptions: retryOptions, + retryCount: currentRetry + 1, + ); }); } @@ -105,12 +103,10 @@ class SSEClient { StreamController? oldStreamController, http.Client? client, Map? body, - int maxRetryTime = 5000, - int minRetryTime = 5000, - int maxRetry = 5, + RetryOptions? retryOptions, int retryCount = 0, - Future Function()? limitReachedCallback, }) { + RetryOptions _retryOptions = retryOptions ?? RetryOptions(); StreamController streamController = StreamController(); if (oldStreamController != null) { streamController = oldStreamController; @@ -148,11 +144,8 @@ class SSEClient { header: header, body: body, streamController: streamController, - maxRetryTime: maxRetryTime, - minRetryTime: minRetryTime, + retryOptions: _retryOptions, currentRetry: retryCount, - maxRetry: maxRetry, - limitReachedCallback: limitReachedCallback, ); return; } @@ -205,11 +198,8 @@ class SSEClient { url: url, header: header, streamController: streamController, - maxRetryTime: maxRetryTime, - minRetryTime: minRetryTime, + retryOptions: _retryOptions, currentRetry: retryCount, - maxRetry: maxRetry, - limitReachedCallback: limitReachedCallback, ); } }, @@ -222,11 +212,8 @@ class SSEClient { header: header, body: body, streamController: streamController, - maxRetryTime: maxRetryTime, - minRetryTime: minRetryTime, currentRetry: retryCount, - maxRetry: maxRetry, - limitReachedCallback: limitReachedCallback, + retryOptions: _retryOptions, ); }, ); @@ -239,11 +226,8 @@ class SSEClient { header: header, body: body, streamController: streamController, - maxRetryTime: maxRetryTime, - minRetryTime: minRetryTime, + retryOptions: _retryOptions, currentRetry: retryCount, - maxRetry: maxRetry, - limitReachedCallback: limitReachedCallback, ); }); } catch (e) { @@ -255,11 +239,8 @@ class SSEClient { header: header, body: body, streamController: streamController, - maxRetryTime: maxRetryTime, - minRetryTime: minRetryTime, + retryOptions: _retryOptions, currentRetry: retryCount, - maxRetry: maxRetry, - limitReachedCallback: limitReachedCallback, ); } return streamController.stream; diff --git a/lib/retry_options.dart b/lib/retry_options.dart new file mode 100644 index 0000000..1b6a01e --- /dev/null +++ b/lib/retry_options.dart @@ -0,0 +1,13 @@ +class RetryOptions { + int maxRetryTime; + int minRetryTime; + int maxRetry; + Future Function()? limitReachedCallback; + + RetryOptions({ + this.maxRetryTime = 5000, + this.minRetryTime = 5000, + this.maxRetry = 5, + this.limitReachedCallback, + }); +} From 03468b369dbdc8580afac9f0861a92e5227dacc1 Mon Sep 17 00:00:00 2001 From: Santiago Garcia Gil Date: Sat, 22 Mar 2025 10:49:06 -0500 Subject: [PATCH 5/8] feat: imporve expBackoff function --- lib/utils.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/utils.dart b/lib/utils.dart index 611db95..9a584bb 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -19,9 +19,10 @@ class Utils { ]) { Function curatedFn; curatedFn = jitterFn ?? _defaultJitterFn; - var base = initial * pow(2, actualRetry); + var base = initial << actualRetry; var willWait = 0; - willWait = base > max ? curatedFn(max) : curatedFn(base); + var isOverflowing = base <= 0; + willWait = (base > max || isOverflowing) ? curatedFn(max) : curatedFn(base); return willWait.toInt(); } From 1e3bff3bccefa321dcf3846d894b0700bc11b9c1 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Date: Tue, 25 Mar 2025 15:34:23 -0500 Subject: [PATCH 6/8] feat: add stop signal --- lib/flutter_client_sse.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/flutter_client_sse.dart b/lib/flutter_client_sse.dart index 62111d2..d4a6f67 100644 --- a/lib/flutter_client_sse.dart +++ b/lib/flutter_client_sse.dart @@ -15,6 +15,7 @@ part 'sse_event_model.dart'; class SSEClient { static http.Client _client = new http.Client(); static final _log = Logger('SSEClient'); + static bool _stopSignal = false; /// Retry the SSE connection after a delay. /// @@ -40,6 +41,12 @@ class SSEClient { required RetryOptions retryOptions, required int currentRetry, }) { + if (_stopSignal) { + _log.info('---NO RETRY: STOP SIGNAL RECEIVED---'); + streamController.close(); + return; + } + _log.finest('$currentRetry retry of ${retryOptions.maxRetry} retries'); if (retryOptions.maxRetry != 0 && currentRetry >= retryOptions.maxRetry) { @@ -249,6 +256,7 @@ class SSEClient { /// Unsubscribe from the SSE. static void unsubscribeFromSSE() { + _stopSignal = true; _client.close(); } } From 4edcd663eb93587e1a82d3b7d058c203d15d6048 Mon Sep 17 00:00:00 2001 From: Santiago Garcia Gil Date: Tue, 25 Mar 2025 17:41:08 -0500 Subject: [PATCH 7/8] feat: improve docs --- lib/flutter_client_sse.dart | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/flutter_client_sse.dart b/lib/flutter_client_sse.dart index d4a6f67..671fd15 100644 --- a/lib/flutter_client_sse.dart +++ b/lib/flutter_client_sse.dart @@ -24,13 +24,8 @@ class SSEClient { /// [header] is a map of request headers. /// [body] is an optional request body for POST requests. /// [streamController] is required to persist the stream from the old connection - /// [maxRetryTime] is the maximum time to retry - /// [minRetryTime] is the minimum time to retry - /// [maxRetry] is the maximum number of retries before giving up. if set to 0, - /// it will retry indefinitely. + /// [retryOptions] is the options for retrying the connection. /// [currentRetry] is the current retry count. - /// [limitReachedCallback] is a callback function that will be called when the - /// maximum number of retries is reached. /// static void _retryConnection({ required SSERequestType method, @@ -94,13 +89,8 @@ class SSEClient { /// stream from the old connection. /// [client] is an optional http client used for testing purpose /// or custom client. - /// [maxRetryTime] is the maximum time to retry - /// [maxRetry] is the maximum number of retries before giving up. if set to 0, - /// it will retry indefinitely. - /// [minRetryTime] is the minimum time to retry + /// [retryOptions] is the options for retrying the connection. /// [retryCount] is the current retry count. - /// [limitReachedCallback] is a callback function that will be called when the - /// maximum number of retries is reached. /// /// Returns a [Stream] of [SSEModel] representing the SSE events. static Stream subscribeToSSE({ From 4616e4a4f0151bd3997fbb2fcb5b8531b4fc9a06 Mon Sep 17 00:00:00 2001 From: Santiago Garcia Gil Date: Tue, 25 Mar 2025 17:41:39 -0500 Subject: [PATCH 8/8] feat: improve docs --- pubspec.lock | 272 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 256 insertions(+), 16 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 615f727..64544f4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" async: dependency: transitive description: @@ -45,10 +69,34 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" fake_async: dependency: transitive description: @@ -57,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -67,6 +123,22 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" http: dependency: "direct main" description: @@ -75,6 +147,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -83,6 +163,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" js: dependency: transitive description: @@ -91,14 +179,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + logging: + dependency: "direct main" + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: @@ -111,10 +207,18 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.0.4" mocktail: dependency: "direct dev" description: @@ -123,43 +227,123 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.dev" + source: hosted + version: "2.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -176,14 +360,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: d11b55850c68c1f6c0cf00eabded4e66c4043feaf6c0d7ce4a36785137df6331 + url: "https://pub.dev" + source: hosted + version: "1.25.5" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" + url: "https://pub.dev" + source: hosted + version: "0.7.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "4d070a6bc36c1c4e89f20d353bfd71dc30cdf2bd0e14349090af360a029ab292" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.2" typed_data: dependency: transitive description: @@ -200,6 +400,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 + url: "https://pub.dev" + source: hosted + version: "14.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.0.0 <4.0.0" flutter: ">=1.17.0"