refactor(shopinbit): consolidate poll backoff into the client
What changed, and why it matters
This commit is a simple code cleanup: it moves the same retry-backoff calculation used by three different screens into one shared helper inside the ShopInBit client. There is no change in behavior, no bug fix, and no security improvement or regression.
No security action needed; this is a non-functional refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff removes identical private _nextBackoff(Duration) methods from shopinbit_car_research_payment_view.dart, shopinbit_payment_view.dart, and shopinbit_ticket_detail.dart, and adds a public static helper ShopInBitClient.nextPollBackoff(Duration current, Duration max) that performs the exact same logic (current * 2, capped at max). Callers are updated to use the shared helper. The polling behavior remains unchanged.
Changed components
lib/pages/shopinbit/shopinbit_car_research_payment_view.dartlib/pages/shopinbit/shopinbit_payment_view.dartlib/pages/shopinbit/shopinbit_ticket_detail.dartlib/services/shopinbit/src/client.dartInspect captured patch +18 / −18
diff --git a/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart b/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart
index 08c81eb..2896158 100644
--- a/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart
+++ b/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart
@@ -8,6 +8,7 @@ import '../../app_config.dart';
import '../../notifications/show_flush_bar.dart';
import '../../providers/global/shopin_bit_service_provider.dart';
import '../../providers/providers.dart';
+import '../../services/shopinbit/src/client.dart';
import '../../services/shopinbit/src/models/car_research.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/assets.dart';
@@ -229,11 +230,6 @@ class _ShopInBitCarResearchPaymentViewState
_pollTimer = Timer(_pollInterval, _pollTick);
}
- Duration _nextBackoff(Duration current) {
- final Duration next = current * 2;
- return next > _kMaxPollInterval ? _kMaxPollInterval : next;
- }
-
/// Periodic driver: poll once, then reschedule with backoff on failure and
/// reset on success. Stops once the flow is terminal or finalizing.
Future<void> _pollTick() async {
@@ -244,7 +240,9 @@ class _ShopInBitCarResearchPaymentViewState
_flowState == _PaymentFlowState.complete) {
return;
}
- _pollInterval = ok ? _kBasePollInterval : _nextBackoff(_pollInterval);
+ _pollInterval = ok
+ ? _kBasePollInterval
+ : ShopInBitClient.nextPollBackoff(_pollInterval, _kMaxPollInterval);
_scheduleNextPoll();
}
diff --git a/lib/pages/shopinbit/shopinbit_payment_view.dart b/lib/pages/shopinbit/shopinbit_payment_view.dart
index f306982..2934a97 100644
--- a/lib/pages/shopinbit/shopinbit_payment_view.dart
+++ b/lib/pages/shopinbit/shopinbit_payment_view.dart
@@ -10,6 +10,7 @@ import '../../app_config.dart';
import '../../notifications/show_flush_bar.dart';
import '../../providers/global/shopin_bit_service_provider.dart';
import '../../providers/providers.dart';
+import '../../services/shopinbit/src/client.dart';
import '../../services/shopinbit/src/models/payment.dart';
import '../../themes/coin_icon_provider.dart';
import '../../themes/stack_colors.dart';
@@ -132,11 +133,6 @@ class _ShopInBitPaymentViewState extends ConsumerState<ShopInBitPaymentView>
_pollTimer = Timer(_pollInterval, _pollPayment);
}
- Duration _nextBackoff(Duration current) {
- final Duration next = current * 2;
- return next > _kMaxPollInterval ? _kMaxPollInterval : next;
- }
-
Future<void> _pollPayment() async {
bool ok = false;
try {
@@ -164,7 +160,9 @@ class _ShopInBitPaymentViewState extends ConsumerState<ShopInBitPaymentView>
}
// Back off on failure (e.g. a 429), reset to base on success, so a rate
// limit slows us down instead of getting hammered every 15s.
- _pollInterval = ok ? _kBasePollInterval : _nextBackoff(_pollInterval);
+ _pollInterval = ok
+ ? _kBasePollInterval
+ : ShopInBitClient.nextPollBackoff(_pollInterval, _kMaxPollInterval);
_scheduleNextPoll();
}
diff --git a/lib/pages/shopinbit/shopinbit_ticket_detail.dart b/lib/pages/shopinbit/shopinbit_ticket_detail.dart
index 49fac0e..2918d54 100644
--- a/lib/pages/shopinbit/shopinbit_ticket_detail.dart
+++ b/lib/pages/shopinbit/shopinbit_ticket_detail.dart
@@ -9,6 +9,7 @@ import 'package:intl/intl.dart';
import '../../db/drift/shared_db/shared_database.dart';
import '../../models/shopinbit/shopinbit_enums.dart';
import '../../providers/global/shopin_bit_service_provider.dart';
+import '../../services/shopinbit/src/client.dart';
import '../../services/shopinbit/src/models/message.dart';
import '../../services/shopinbit/src/models/ticket.dart';
import '../../themes/stack_colors.dart';
@@ -111,15 +112,12 @@ class _ShopInBitTicketDetailState extends ConsumerState<ShopInBitTicketDetail>
}
// Back off on failure (e.g. a 429), reset on success.
- _pollInterval = ok ? _kBasePollInterval : _nextBackoff(_pollInterval);
+ _pollInterval = ok
+ ? _kBasePollInterval
+ : ShopInBitClient.nextPollBackoff(_pollInterval, _kMaxPollInterval);
_pollingTimer = Timer(_pollInterval, _poll);
}
- Duration _nextBackoff(Duration current) {
- final Duration next = current * 2;
- return next > _kMaxPollInterval ? _kMaxPollInterval : next;
- }
-
void _startPolling() {
_pollingTimer?.cancel();
_pollInterval = _kBasePollInterval;
diff --git a/lib/services/shopinbit/src/client.dart b/lib/services/shopinbit/src/client.dart
index 8eb3855..42981d1 100644
--- a/lib/services/shopinbit/src/client.dart
+++ b/lib/services/shopinbit/src/client.dart
@@ -632,6 +632,12 @@ class ShopInBitClient {
}
}
+ /// Next poll interval after a failed poll: double [current], capped at [max].
+ static Duration nextPollBackoff(Duration current, Duration max) {
+ final Duration next = current * 2;
+ return next > max ? max : next;
+ }
+
/// How long to wait before retrying a 429. Prefers a sane `Retry-After`
/// header; otherwise 1s, 2s, 4s... with jitter, capped at [_kMaxBackoff].
Duration _backoffDelay(int attempt, Map<String, String> headers) {
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.