rate limit handling

This commit is contained in:
2026-01-05 20:55:37 -06:00
parent d4e7e4bb65
commit 6302018647
6 changed files with 100 additions and 13 deletions

View File

@@ -27,7 +27,7 @@ Future<http.Response?> secureGet(String path, {Map<String, dynamic>? queryParame
queryParameters: queryParameters?.map((key, value) => MapEntry(key, value.toString())),
);
return await http
var response = await http
.get(
uri,
headers: {
@@ -35,7 +35,23 @@ Future<http.Response?> secureGet(String path, {Map<String, dynamic>? queryParame
'Content-Type': 'application/json',
},
)
.timeout(const Duration(seconds: 10)); // 🚀 Timeout added
.timeout(const Duration(seconds: 10));
// Retry once if rate limited
if (response.statusCode == 429) {
await Future.delayed(const Duration(seconds: 1));
response = await http
.get(
uri,
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
)
.timeout(const Duration(seconds: 10));
}
return response;
}
Future<http.Response?> securePost(Map<String, dynamic> payload, String path) async{
@@ -47,7 +63,7 @@ Future<http.Response?> securePost(Map<String, dynamic> payload, String path) asy
path: path,
);
return await http.post(
var response = await http.post(
uri,
headers: {
'Authorization': 'Bearer $token',
@@ -55,7 +71,23 @@ Future<http.Response?> securePost(Map<String, dynamic> payload, String path) asy
},
body: json.encode(payload),
)
.timeout(const Duration(seconds: 10)); // 🚀 Timeout added
.timeout(const Duration(seconds: 10));
// Retry once if rate limited
if (response.statusCode == 429) {
await Future.delayed(const Duration(seconds: 1));
response = await http.post(
uri,
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: json.encode(payload),
)
.timeout(const Duration(seconds: 10));
}
return response;
}
Future<http.Response> regularGet(String path) async {
@@ -63,13 +95,27 @@ Future<http.Response> regularGet(String path) async {
path: path,
);
return await http.get(
var response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
}
)
.timeout(const Duration(seconds: 10)); // 🚀 Timeout added
.timeout(const Duration(seconds: 10));
// Retry once if rate limited
if (response.statusCode == 429) {
await Future.delayed(const Duration(seconds: 1));
response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
}
)
.timeout(const Duration(seconds: 10));
}
return response;
}
Future<http.Response> regularPost(Map<String, dynamic> payload, String path) async{
@@ -77,13 +123,27 @@ Future<http.Response> regularPost(Map<String, dynamic> payload, String path) asy
path: path,
);
return await http.post(
var response = await http.post(
uri,
headers: {
'Content-Type': 'application/json',
},
body: json.encode(payload)
).timeout(const Duration(seconds: 10)); // 🚀 Timeout added
).timeout(const Duration(seconds: 10));
// Retry once if rate limited
if (response.statusCode == 429) {
await Future.delayed(const Duration(seconds: 1));
response = await http.post(
uri,
headers: {
'Content-Type': 'application/json',
},
body: json.encode(payload)
).timeout(const Duration(seconds: 10));
}
return response;
}
Future<IO.Socket?> connectSocket() async {

View File

@@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:flutter/material.dart';
@@ -57,6 +59,11 @@ class _CreateUserScreenState extends State<CreateUserScreen> {
}
} else {
if (response.statusCode == 409) throw Exception('Email Already In Use!');
if (response.statusCode == 429) {
final body = json.decode(response.body);
final retryAfter = body['retryAfter'] ?? 'some time';
throw Exception('Too many account creation attempts. Please try again in $retryAfter minutes.');
}
throw Exception('Create failed: ${response.statusCode}');
}

View File

@@ -51,6 +51,11 @@ class _LoginScreenState extends State<LoginScreen> {
final response = await regularPost(payload, 'login');
if (response.statusCode != 200) {
if (response.statusCode == 400) {throw Exception('Email and Password Necessary');}
else if (response.statusCode == 429) {
final body = json.decode(response.body);
final retryAfter = body['retryAfter'] ?? 'some time';
throw Exception('Too many login attempts. Please try again in $retryAfter minutes.');
}
else if (response.statusCode == 500) {throw Exception('Server Error');}
else {throw Exception('Incorrect email or password');}
}

View File

@@ -19,7 +19,7 @@ class _CreateGroupDialogState extends State<CreateGroupDialog> {
bool isLoading = true;
String? errorMessage;
final bool dev = true;
final bool dev = false;
@override
void initState() {

View File

@@ -27,8 +27,6 @@ class _EditGroupDialogState extends State<EditGroupDialog> {
bool isLoading = true;
String? errorMessage;
final bool dev = true;
@override
void initState() {
super.initState();
@@ -91,7 +89,7 @@ class _EditGroupDialogState extends State<EditGroupDialog> {
}
Future<void> _updateGroup() async {
if (selectedPeripheralIds.length < 2 && !dev) {
if (selectedPeripheralIds.length < 2) {
setState(() {
errorMessage = 'Please select at least 2 blinds';
});
@@ -151,7 +149,7 @@ class _EditGroupDialogState extends State<EditGroupDialog> {
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: selectedPeripheralIds.length >= 2 || dev
color: selectedPeripheralIds.length >= 2
? Theme.of(context).primaryColorLight.withValues(alpha: 0.5)
: Colors.orange.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(10),

View File

@@ -67,6 +67,23 @@ class _PeripheralScreenState extends State<PeripheralScreen> {
try {
socket = await connectSocket();
if (socket == null) throw Exception("Unsuccessful socket connection");
// Handle rate limiting errors
socket?.on("error", (data) async {
if (data is Map<String, dynamic>) {
if (data['code'] == 429) {
// Rate limited - wait and reconnect
print("Rate limited: ${data['message']}. Reconnecting in 1 second...");
socket?.disconnect();
socket?.dispose();
await Future.delayed(const Duration(seconds: 1));
if (mounted) {
initSocket();
}
}
}
});
socket?.on("success", (_) {
socket?.on("device_connected", (data) {
if (data is Map<String, dynamic>) {