👔 No longer rapid websocket reconnect (stops after 5 times in a minute)

This commit is contained in:
2025-12-24 22:15:33 +08:00
parent 2e3e988125
commit d655840e85
2 changed files with 63 additions and 12 deletions

View File

@@ -52,6 +52,11 @@ class WebSocketService {
DateTime? _heartbeatAt;
Duration? heartbeatDelay;
// Reconnection tracking
int _reconnectCount = 0;
DateTime? _reconnectWindowStart;
static const int _maxReconnectsPerMinute = 5;
Stream<WebSocketPacket> get dataStream => _streamController.stream;
Stream<WebSocketState> get statusStream => _statusStreamController.stream;
@@ -79,8 +84,9 @@ class WebSocketService {
_scheduleHeartbeat();
_channel!.stream.listen(
(data) {
final dataStr =
data is Uint8List ? utf8.decode(data) : data.toString();
final dataStr = data is Uint8List
? utf8.decode(data)
: data.toString();
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
if (packet.type == 'error.dupe') {
_statusStreamController.sink.add(WebSocketState.duplicateDevice());
@@ -123,6 +129,34 @@ class WebSocketService {
}
void _scheduleReconnect() {
// Check if we've exceeded the reconnect limit
final now = DateTime.now();
if (_reconnectWindowStart == null ||
now.difference(_reconnectWindowStart!).inMinutes >= 1) {
// Reset window if it's been more than 1 minute since the window started
_reconnectWindowStart = now;
_reconnectCount = 0;
}
_reconnectCount++;
if (_reconnectCount > _maxReconnectsPerMinute) {
talker.error(
'[WebSocket] Reconnect limit exceeded: $_maxReconnectsPerMinute reconnections in the last minute. Stopping auto-reconnect.',
);
_statusStreamController.sink.add(WebSocketState.serverDown());
return;
}
_reconnectTimer?.cancel();
_reconnectTimer = Timer(const Duration(milliseconds: 500), () {
_statusStreamController.sink.add(WebSocketState.connecting());
connect(_ref);
});
}
void manualReconnect() {
talker.info('[WebSocket] Manual reconnect triggered by user');
_reconnectTimer?.cancel();
_reconnectTimer = Timer(const Duration(milliseconds: 500), () {
_statusStreamController.sink.add(WebSocketState.connecting());
@@ -204,4 +238,9 @@ class WebSocketStateNotifier extends Notifier<WebSocketState> {
_reconnectTimer?.cancel();
state = const WebSocketState.disconnected();
}
void manualReconnect() {
final service = ref.read(websocketProvider);
service.manualReconnect();
}
}

View File

@@ -530,6 +530,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
Color indicatorColor;
String indicatorText;
bool isInteractive = false;
if (websocketState == WebSocketState.connected()) {
indicatorColor = Colors.green;
@@ -537,12 +538,16 @@ class _WebSocketIndicator extends HookConsumerWidget {
} else if (websocketState == WebSocketState.connecting()) {
indicatorColor = Colors.teal;
indicatorText = 'connectionReconnecting';
} else if (websocketState == WebSocketState.serverDown()) {
indicatorColor = Colors.red;
indicatorText = 'connectionServerDown';
isInteractive = true;
} else {
indicatorColor = Colors.red;
indicatorText = 'connectionDisconnected';
}
return AnimatedPositioned(
final widget = AnimatedPositioned(
duration: Duration(milliseconds: 1850),
top:
user.value == null ||
@@ -554,7 +559,6 @@ class _WebSocketIndicator extends HookConsumerWidget {
left: 0,
right: 0,
height: indicatorHeight,
child: IgnorePointer(
child: Material(
elevation:
user.value == null || websocketState == WebSocketState.connected()
@@ -563,6 +567,12 @@ class _WebSocketIndicator extends HookConsumerWidget {
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
color: indicatorColor,
child: InkWell(
onTap: isInteractive
? () {
ref.read(websocketStateProvider.notifier).manualReconnect();
}
: null,
child: Center(
child: Text(
indicatorText,
@@ -573,5 +583,7 @@ class _WebSocketIndicator extends HookConsumerWidget {
),
),
);
return isInteractive ? widget : IgnorePointer(child: widget);
}
}