From 7b7bc5210c8bcf63433852e9dab7ade91bd26d33 Mon Sep 17 00:00:00 2001 From: Jens Becker Date: Thu, 26 Sep 2024 11:15:20 +0200 Subject: [PATCH 1/8] feat(offline_first_with_rest): add request/response callbacks to the RestOfflineQueueClient --- .../offline_first_with_rest_repository.dart | 9 ++++++ .../rest_offline_queue_client.dart | 29 +++++++++++++++---- ...ffline_first_with_supabase_repository.dart | 5 ++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart index e68d43db..96c924b6 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart @@ -47,6 +47,13 @@ abstract class OfflineFirstWithRestRepository /// This property is forwarded to `RestOfflineQueueClient` and assumes /// its defaults List? reattemptForStatusCodes, + + /// This property is forwarded to `RestOfflineRequestQueue`. + void Function(http.Request request, http.StreamedResponse response)? + onReattemptableResponse, + + /// This property is forwarded to `RestOfflineRequestQueue`. + void Function(http.Request, Object)? onRequestError, required RestProvider restProvider, required super.sqliteProvider, }) : remoteProvider = restProvider, @@ -57,6 +64,8 @@ abstract class OfflineFirstWithRestRepository restProvider.client, offlineQueueManager, reattemptForStatusCodes: reattemptForStatusCodes, + onRequestError: onRequestError, + onReattemptableResponse: onReattemptableResponse, ); offlineRequestQueue = RestOfflineRequestQueue( client: remoteProvider.client as RestOfflineQueueClient, diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart b/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart index 70928355..007835e9 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart @@ -19,6 +19,14 @@ class RestOfflineQueueClient extends http.BaseClient { final RequestSqliteCacheManager requestManager; + /// A callback triggered when the response of a request has a status code + /// which is present in the [reattemptForStatusCodes] list. + void Function(http.Request request, http.StreamedResponse response)? + onReattemptableResponse; + + /// A callback triggered when a request throws an exception during execution. + void Function(http.Request request, Object error)? onRequestError; + /// If the response returned from the client is one of these error codes, the request /// **will not** be removed from the queue. For example, if the result of a request produces a /// 404 status code response (such as in a Tunnel not found exception), the request will @@ -37,6 +45,8 @@ class RestOfflineQueueClient extends http.BaseClient { RestOfflineQueueClient( this._inner, this.requestManager, { + this.onReattemptableResponse, + this.onRequestError, List? reattemptForStatusCodes, /// Any request URI that begins with one of these paths will not be @@ -89,17 +99,26 @@ class RestOfflineQueueClient extends http.BaseClient { // Attempt to make HTTP Request final resp = await _inner.send(request); - if (cacheItem.requestIsPush && !reattemptForStatusCodes.contains(resp.statusCode)) { - final db = await requestManager.getDb(); - // request was successfully sent and can be removed - _logger.finest('removing from queue: ${cacheItem.toSqlite()}'); - await cacheItem.delete(db); + if (cacheItem.requestIsPush) { + if (!reattemptForStatusCodes.contains(resp.statusCode)) { + final db = await requestManager.getDb(); + // request was successfully sent and can be removed + _logger.finest('removing from queue: ${cacheItem.toSqlite()}'); + await cacheItem.delete(db); + } else { + _logger.finest( + 'request failed, will be reattempted: ${cacheItem.toSqlite()}'); + onReattemptableResponse?.call(request, resp); + } } return resp; } catch (e) { + // e.g. SocketExceptions will be caught here + onRequestError?.call(request, e); _logger.warning('#send: $e'); } finally { + // unlock the request which results in a reattempt final db = await requestManager.getDb(); await cacheItem.unlock(db); } diff --git a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart index 89f5d31e..28f3bbf1 100644 --- a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart +++ b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart @@ -189,6 +189,9 @@ abstract class OfflineFirstWithSupabaseRepository 504, ], bool? serialProcessing, + void Function(http.Request request, http.StreamedResponse response)? + onReattemptableResponse, + void Function(http.Request, Object)? onRequestError, }) { final client = RestOfflineQueueClient( innerClient ?? http.Client(), @@ -200,6 +203,8 @@ abstract class OfflineFirstWithSupabaseRepository ), ignorePaths: ignorePaths, reattemptForStatusCodes: reattemptForStatusCodes, + onReattemptableResponse: onReattemptableResponse, + onRequestError: onRequestError, ); return (client, RestOfflineRequestQueue(client: client)); } From be725a7a01a49f93b436cdec7b107c6900ebd58c Mon Sep 17 00:00:00 2001 From: Jens Becker Date: Sat, 12 Oct 2024 17:52:04 +0200 Subject: [PATCH 2/8] refactor(OfflineFirstWithRestRepository): make callbacks abstract methods instead of params --- .../src/offline_first_with_rest_repository.dart | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart index 96c924b6..287bb1fb 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart @@ -30,6 +30,17 @@ abstract class OfflineFirstWithRestRepository @protected late RestOfflineRequestQueue offlineRequestQueue; + /// A callback triggered when the response of a request has a status code + /// which is present in the [reattemptForStatusCodes] list. + /// + /// Forwarded to [RestOfflineQueueClient]. + void Function(http.Request request, http.StreamedResponse response)? onReattemptableResponse; + + /// A callback triggered when a request throws an exception during execution. + /// + /// Forwarded to [RestOfflineQueueClient]. + void Function(http.Request, Object)? onRequestError; + OfflineFirstWithRestRepository({ super.autoHydrate, super.loggerName, @@ -49,11 +60,10 @@ abstract class OfflineFirstWithRestRepository List? reattemptForStatusCodes, /// This property is forwarded to `RestOfflineRequestQueue`. - void Function(http.Request request, http.StreamedResponse response)? - onReattemptableResponse, + this.onReattemptableResponse, /// This property is forwarded to `RestOfflineRequestQueue`. - void Function(http.Request, Object)? onRequestError, + this.onRequestError, required RestProvider restProvider, required super.sqliteProvider, }) : remoteProvider = restProvider, From c22504d08ecfc727f9ba40ef4db9a210551bde3f Mon Sep 17 00:00:00 2001 From: Jens Becker Date: Sat, 12 Oct 2024 17:56:46 +0200 Subject: [PATCH 3/8] refactor: guard logging onReattemptableRespons --- .../src/offline_queue/rest_offline_queue_client.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart b/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart index 007835e9..dd5886c5 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart @@ -21,8 +21,7 @@ class RestOfflineQueueClient extends http.BaseClient { /// A callback triggered when the response of a request has a status code /// which is present in the [reattemptForStatusCodes] list. - void Function(http.Request request, http.StreamedResponse response)? - onReattemptableResponse; + void Function(http.Request request, http.StreamedResponse response)? onReattemptableResponse; /// A callback triggered when a request throws an exception during execution. void Function(http.Request request, Object error)? onRequestError; @@ -105,9 +104,10 @@ class RestOfflineQueueClient extends http.BaseClient { // request was successfully sent and can be removed _logger.finest('removing from queue: ${cacheItem.toSqlite()}'); await cacheItem.delete(db); - } else { - _logger.finest( - 'request failed, will be reattempted: ${cacheItem.toSqlite()}'); + } else if (onReattemptableResponse != null) { + _logger.finer( + 'request failed, will be reattempted: ${cacheItem.toSqlite()}', + ); onReattemptableResponse?.call(request, resp); } } From d659ee65c025026bc46c1543e2fbe931f2357343 Mon Sep 17 00:00:00 2001 From: Jens Becker Date: Sat, 12 Oct 2024 17:58:09 +0200 Subject: [PATCH 4/8] docs: extend doc comment for onRequestError callback --- .../lib/src/offline_queue/rest_offline_queue_client.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart b/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart index dd5886c5..55f5b84b 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart @@ -24,6 +24,8 @@ class RestOfflineQueueClient extends http.BaseClient { void Function(http.Request request, http.StreamedResponse response)? onReattemptableResponse; /// A callback triggered when a request throws an exception during execution. + /// + /// `SocketException`s (errors thrown due to missing connectivity) will also be forwarded to this callback. void Function(http.Request request, Object error)? onRequestError; /// If the response returned from the client is one of these error codes, the request @@ -118,7 +120,7 @@ class RestOfflineQueueClient extends http.BaseClient { onRequestError?.call(request, e); _logger.warning('#send: $e'); } finally { - // unlock the request which results in a reattempt + // unlock the request for a later a reattempt final db = await requestManager.getDb(); await cacheItem.unlock(db); } From bd2992728312176faedb0451481521757947841d Mon Sep 17 00:00:00 2001 From: Jens Becker Date: Mon, 14 Oct 2024 10:48:55 +0200 Subject: [PATCH 5/8] run dart format --- .../lib/src/offline_first_with_supabase_repository.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart index 28f3bbf1..834a1c7c 100644 --- a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart +++ b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart @@ -189,8 +189,7 @@ abstract class OfflineFirstWithSupabaseRepository 504, ], bool? serialProcessing, - void Function(http.Request request, http.StreamedResponse response)? - onReattemptableResponse, + void Function(http.Request request, http.StreamedResponse response)? onReattemptableResponse, void Function(http.Request, Object)? onRequestError, }) { final client = RestOfflineQueueClient( From f78bb9d0c42dec90679bfca7440c5c27339737c5 Mon Sep 17 00:00:00 2001 From: Jens Becker Date: Mon, 14 Oct 2024 15:00:11 +0200 Subject: [PATCH 6/8] onReattemptableResponse: return response status code instead of StreamedResponse --- .../lib/src/offline_first_with_rest_repository.dart | 2 +- .../lib/src/offline_queue/rest_offline_queue_client.dart | 4 ++-- .../lib/src/offline_first_with_supabase_repository.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart index 287bb1fb..2f9d6fe1 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart @@ -34,7 +34,7 @@ abstract class OfflineFirstWithRestRepository /// which is present in the [reattemptForStatusCodes] list. /// /// Forwarded to [RestOfflineQueueClient]. - void Function(http.Request request, http.StreamedResponse response)? onReattemptableResponse; + void Function(http.Request request, int statusCode)? onReattemptableResponse; /// A callback triggered when a request throws an exception during execution. /// diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart b/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart index 55f5b84b..202d4495 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_queue/rest_offline_queue_client.dart @@ -21,7 +21,7 @@ class RestOfflineQueueClient extends http.BaseClient { /// A callback triggered when the response of a request has a status code /// which is present in the [reattemptForStatusCodes] list. - void Function(http.Request request, http.StreamedResponse response)? onReattemptableResponse; + void Function(http.Request request, int statusCode)? onReattemptableResponse; /// A callback triggered when a request throws an exception during execution. /// @@ -110,7 +110,7 @@ class RestOfflineQueueClient extends http.BaseClient { _logger.finer( 'request failed, will be reattempted: ${cacheItem.toSqlite()}', ); - onReattemptableResponse?.call(request, resp); + onReattemptableResponse?.call(request, resp.statusCode); } } diff --git a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart index 834a1c7c..d4549c6c 100644 --- a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart +++ b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart @@ -189,7 +189,7 @@ abstract class OfflineFirstWithSupabaseRepository 504, ], bool? serialProcessing, - void Function(http.Request request, http.StreamedResponse response)? onReattemptableResponse, + void Function(http.Request request, int statusCode)? onReattemptableResponse, void Function(http.Request, Object)? onRequestError, }) { final client = RestOfflineQueueClient( From 69b269287b6d875f0a5f1d09836b46a6c72b23d6 Mon Sep 17 00:00:00 2001 From: Jens Becker Date: Tue, 15 Oct 2024 07:46:54 +0200 Subject: [PATCH 7/8] fix doc comments --- .../lib/src/offline_first_with_rest_repository.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart index 2f9d6fe1..ed477087 100644 --- a/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart +++ b/packages/brick_offline_first_with_rest/lib/src/offline_first_with_rest_repository.dart @@ -59,10 +59,10 @@ abstract class OfflineFirstWithRestRepository /// its defaults List? reattemptForStatusCodes, - /// This property is forwarded to `RestOfflineRequestQueue`. + /// This property is forwarded to `RestOfflineQueueClient`. this.onReattemptableResponse, - /// This property is forwarded to `RestOfflineRequestQueue`. + /// This property is forwarded to `RestOfflineQueueClient`. this.onRequestError, required RestProvider restProvider, required super.sqliteProvider, From 327caad0c972b58f64402678dbec5578f7e94aa3 Mon Sep 17 00:00:00 2001 From: Jens Becker Date: Tue, 15 Oct 2024 08:07:54 +0200 Subject: [PATCH 8/8] add callbacks to GraphqlOfflineQueueLink --- .../lib/src/graphql_offline_queue_link.dart | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/brick_offline_first_with_graphql/lib/src/graphql_offline_queue_link.dart b/packages/brick_offline_first_with_graphql/lib/src/graphql_offline_queue_link.dart index da995143..7247e7a7 100644 --- a/packages/brick_offline_first_with_graphql/lib/src/graphql_offline_queue_link.dart +++ b/packages/brick_offline_first_with_graphql/lib/src/graphql_offline_queue_link.dart @@ -15,8 +15,17 @@ class GraphqlOfflineQueueLink extends Link { final GraphqlRequestSqliteCacheManager requestManager; - GraphqlOfflineQueueLink(this.requestManager) - : _logger = Logger('GraphqlOfflineQueueLink#${requestManager.databaseName}'); + /// A callback triggered when a request failed, but will be reattempted. + final void Function(Request request, int statusCode)? onReattemptableResponse; + + /// A callback triggered when a request throws an exception during execution. + final void Function(Request request, Object error)? onRequestError; + + GraphqlOfflineQueueLink( + this.requestManager, { + this.onReattemptableResponse, + this.onRequestError, + }) : _logger = Logger('GraphqlOfflineQueueLink#${requestManager.databaseName}'); @override Stream request(Request request, [NextLink? forward]) async* { @@ -33,6 +42,7 @@ class GraphqlOfflineQueueLink extends Link { yield* forward!(request).handleError( (e) async { _logger.warning('GraphqlOfflineQueueLink#request: error $e'); + onRequestError?.call(request, e); final db = await requestManager.getDb(); await cacheItem.unlock(db); }, @@ -46,6 +56,8 @@ class GraphqlOfflineQueueLink extends Link { // request was successfully sent and can be removed _logger.finest('removing from queue: ${cacheItem.toSqlite()}'); await cacheItem.delete(db); + } else { + onReattemptableResponse?.call(request, response.response['statusCode'] as int); } final db = await requestManager.getDb(); await cacheItem.unlock(db);