jbilcke-hf HF Staff commited on
Commit
69af9b5
·
1 Parent(s): 65214e5

tentative fix for websocket issues

Browse files
build/web/flutter_bootstrap.js CHANGED
@@ -38,6 +38,6 @@ _flutter.buildConfig = {"engineRevision":"1c9c20e7c3dd48c66f400a24d48ea806b4ab31
38
 
39
  _flutter.loader.load({
40
  serviceWorkerSettings: {
41
- serviceWorkerVersion: "3948338569"
42
  }
43
  });
 
38
 
39
  _flutter.loader.load({
40
  serviceWorkerSettings: {
41
+ serviceWorkerVersion: "3662004008"
42
  }
43
  });
build/web/flutter_service_worker.js CHANGED
@@ -3,12 +3,12 @@ const MANIFEST = 'flutter-app-manifest';
3
  const TEMP = 'flutter-temp-cache';
4
  const CACHE_NAME = 'flutter-app-cache';
5
 
6
- const RESOURCES = {"flutter_bootstrap.js": "b98de8eaf32c5bc183c9879c24e42172",
7
  "version.json": "68350cac7987de2728345c72918dd067",
8
  "tikslop.png": "570e1db759046e2d224fef729983634e",
9
  "index.html": "3a7029b3672560e7938aab6fa4d30a46",
10
  "/": "3a7029b3672560e7938aab6fa4d30a46",
11
- "main.dart.js": "a897b61edcb30264f6ad2d89a6931afc",
12
  "tikslop.svg": "26140ba0d153b213b122bc6ebcc17f6c",
13
  "flutter.js": "888483df48293866f9f41d3d9274a779",
14
  "favicon.png": "c8a183c516004e648a7bac7497c89b97",
 
3
  const TEMP = 'flutter-temp-cache';
4
  const CACHE_NAME = 'flutter-app-cache';
5
 
6
+ const RESOURCES = {"flutter_bootstrap.js": "da908042a784a46384f779dd4c199a53",
7
  "version.json": "68350cac7987de2728345c72918dd067",
8
  "tikslop.png": "570e1db759046e2d224fef729983634e",
9
  "index.html": "3a7029b3672560e7938aab6fa4d30a46",
10
  "/": "3a7029b3672560e7938aab6fa4d30a46",
11
+ "main.dart.js": "b0e4eb9c26998dfbb3b2616a19bf9c7d",
12
  "tikslop.svg": "26140ba0d153b213b122bc6ebcc17f6c",
13
  "flutter.js": "888483df48293866f9f41d3d9274a779",
14
  "favicon.png": "c8a183c516004e648a7bac7497c89b97",
build/web/index.html CHANGED
@@ -156,7 +156,7 @@
156
  </script>
157
 
158
  <!-- Add version parameter for cache busting -->
159
- <script src="flutter_bootstrap.js?v=1753289445" async></script>
160
 
161
  <!-- Add cache busting script -->
162
  <script>
 
156
  </script>
157
 
158
  <!-- Add version parameter for cache busting -->
159
+ <script src="flutter_bootstrap.js?v=1753290193" async></script>
160
 
161
  <!-- Add cache busting script -->
162
  <script>
build/web/main.dart.js CHANGED
The diff for this file is too large to render. See raw diff
 
lib/screens/settings_screen.dart CHANGED
@@ -241,14 +241,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
241
  // Reinitialize the websocket connection when the API key changes
242
  final websocket = WebSocketApiService();
243
  try {
244
- // First dispose the current connection
245
- await websocket.dispose();
246
-
247
- // Then create a new connection with the new API key
248
- await websocket.connect();
249
-
250
- // Finally, initialize the connection completely
251
- await websocket.initialize();
252
 
253
  // Show success message
254
  if (context.mounted) {
@@ -694,6 +688,27 @@ class _SettingsScreenState extends State<SettingsScreen> {
694
  _settingsService.setEnableSimulation(value);
695
  },
696
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  ],
698
  ),
699
  ),
 
241
  // Reinitialize the websocket connection when the API key changes
242
  final websocket = WebSocketApiService();
243
  try {
244
+ // Force reconnection with the new API key
245
+ await websocket.reconnect();
 
 
 
 
 
 
246
 
247
  // Show success message
248
  if (context.mounted) {
 
688
  _settingsService.setEnableSimulation(value);
689
  },
690
  ),
691
+ const SizedBox(height: 16),
692
+ // Clear device connections button
693
+ ListTile(
694
+ title: const Text('Clear Device Connections'),
695
+ subtitle: const Text('Clear all cached device connections (useful if you see "Too many connections" error)'),
696
+ trailing: ElevatedButton(
697
+ onPressed: () {
698
+ // Clear all device connections
699
+ WebSocketApiService.clearAllDeviceConnections();
700
+
701
+ // Show confirmation message
702
+ ScaffoldMessenger.of(context).showSnackBar(
703
+ const SnackBar(
704
+ content: Text('Device connections cleared. Please reload the page.'),
705
+ backgroundColor: Colors.green,
706
+ ),
707
+ );
708
+ },
709
+ child: const Text('Clear All'),
710
+ ),
711
+ ),
712
  ],
713
  ),
714
  ),
lib/services/websocket_api_service.dart CHANGED
@@ -144,6 +144,35 @@ class WebSocketApiService {
144
 
145
  try {
146
  debugPrint('WebSocketApiService: Initializing and connecting...');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  await connect();
148
 
149
  // Only continue if we're properly connected
@@ -242,9 +271,34 @@ class WebSocketApiService {
242
  if (!kIsWeb) return true; // Only apply on web platform
243
 
244
  try {
245
- // Generate a unique ID for this connection instance
246
  if (_connectionId == null) {
247
- _connectionId = const Uuid().v4();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
  // Store connection ID in localStorage
250
  html.window.localStorage[_connectionIdKey] = _connectionId!;
@@ -263,13 +317,18 @@ class WebSocketApiService {
263
  }
264
  }
265
 
266
- // Clean up stale connections (older than 30 seconds)
267
  final now = DateTime.now().millisecondsSinceEpoch;
 
268
  connections.removeWhere((key, value) {
269
  if (value is! int) return true;
270
- return now - value > 30000; // 30 seconds timeout
271
  });
272
 
 
 
 
 
273
  // Add/update this connection
274
  connections[_connectionId!] = now;
275
 
@@ -280,6 +339,7 @@ class WebSocketApiService {
280
  // For anonymous users, we rely on the server-side IP check
281
  if (_userRole != 'anon' && connections.length > _maxDeviceConnections) {
282
  debugPrint('Device connection limit exceeded: ${connections.length} connections for $_userRole user');
 
283
  return false;
284
  }
285
 
@@ -343,6 +403,9 @@ class WebSocketApiService {
343
  // Store back to localStorage
344
  html.window.localStorage[_connectionCountKey] = json.encode(connections);
345
 
 
 
 
346
  // Stop the heartbeat timer
347
  _connectionHeartbeatTimer?.cancel();
348
  _connectionHeartbeatTimer = null;
@@ -351,6 +414,19 @@ class WebSocketApiService {
351
  }
352
  }
353
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  // Start the connection heartbeat timer
355
  void _startConnectionHeartbeat() {
356
  if (!kIsWeb) return;
@@ -361,6 +437,36 @@ class WebSocketApiService {
361
  });
362
  }
363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  Future<void> connect() async {
365
  if (_disposed) {
366
  throw Exception('WebSocketApiService has been disposed');
 
144
 
145
  try {
146
  debugPrint('WebSocketApiService: Initializing and connecting...');
147
+
148
+ // Add page unload handler for web platform
149
+ if (kIsWeb) {
150
+ try {
151
+ // Add beforeunload listener to clean up connection
152
+ html.window.onBeforeUnload.listen((_) {
153
+ debugPrint('WebSocketApiService: Page unloading, cleaning up connection...');
154
+ _unregisterDeviceConnection();
155
+ });
156
+
157
+ // Also add pagehide listener as a fallback
158
+ html.window.addEventListener('pagehide', (event) {
159
+ debugPrint('WebSocketApiService: Page hiding, cleaning up connection...');
160
+ _unregisterDeviceConnection();
161
+ });
162
+
163
+ // And visibilitychange for when the page is being closed
164
+ html.document.addEventListener('visibilitychange', (event) {
165
+ if (html.document.hidden == true) {
166
+ debugPrint('WebSocketApiService: Page becoming hidden, updating heartbeat...');
167
+ // Update heartbeat when page becomes hidden to keep connection alive
168
+ _updateConnectionHeartbeat();
169
+ }
170
+ });
171
+ } catch (e) {
172
+ debugPrint('Error setting up page unload handlers: $e');
173
+ }
174
+ }
175
+
176
  await connect();
177
 
178
  // Only continue if we're properly connected
 
271
  if (!kIsWeb) return true; // Only apply on web platform
272
 
273
  try {
274
+ // Try to reuse existing connection ID from localStorage if valid
275
  if (_connectionId == null) {
276
+ final storedId = html.window.localStorage[_connectionIdKey];
277
+ if (storedId != null && storedId.isNotEmpty) {
278
+ // Check if this connection ID is still active
279
+ final countJson = html.window.localStorage[_connectionCountKey];
280
+ if (countJson != null && countJson.isNotEmpty) {
281
+ try {
282
+ final connections = json.decode(countJson) as Map<String, dynamic>;
283
+ final now = DateTime.now().millisecondsSinceEpoch;
284
+ final connectionTimestamp = connections[storedId];
285
+
286
+ // Reuse the ID if it's still recent (within 15 seconds)
287
+ if (connectionTimestamp is int && now - connectionTimestamp < 15000) {
288
+ _connectionId = storedId;
289
+ debugPrint('Reusing existing connection ID: $_connectionId');
290
+ }
291
+ } catch (e) {
292
+ debugPrint('Error checking stored connection: $e');
293
+ }
294
+ }
295
+ }
296
+
297
+ // Generate new ID if we couldn't reuse one
298
+ if (_connectionId == null) {
299
+ _connectionId = const Uuid().v4();
300
+ debugPrint('Generated new connection ID: $_connectionId');
301
+ }
302
 
303
  // Store connection ID in localStorage
304
  html.window.localStorage[_connectionIdKey] = _connectionId!;
 
317
  }
318
  }
319
 
320
+ // Clean up stale connections (older than 20 seconds - more aggressive)
321
  final now = DateTime.now().millisecondsSinceEpoch;
322
+ final beforeCleanup = connections.length;
323
  connections.removeWhere((key, value) {
324
  if (value is! int) return true;
325
+ return now - value > 20000; // 20 seconds timeout (more aggressive)
326
  });
327
 
328
+ if (beforeCleanup != connections.length) {
329
+ debugPrint('Cleaned up ${beforeCleanup - connections.length} stale connections');
330
+ }
331
+
332
  // Add/update this connection
333
  connections[_connectionId!] = now;
334
 
 
339
  // For anonymous users, we rely on the server-side IP check
340
  if (_userRole != 'anon' && connections.length > _maxDeviceConnections) {
341
  debugPrint('Device connection limit exceeded: ${connections.length} connections for $_userRole user');
342
+ debugPrint('Active connections: ${connections.keys.toList()}');
343
  return false;
344
  }
345
 
 
403
  // Store back to localStorage
404
  html.window.localStorage[_connectionCountKey] = json.encode(connections);
405
 
406
+ // Also remove the stored connection ID
407
+ html.window.localStorage.remove(_connectionIdKey);
408
+
409
  // Stop the heartbeat timer
410
  _connectionHeartbeatTimer?.cancel();
411
  _connectionHeartbeatTimer = null;
 
414
  }
415
  }
416
 
417
+ // Public method to clear all device connections (useful for debugging)
418
+ static void clearAllDeviceConnections() {
419
+ if (!kIsWeb) return;
420
+
421
+ try {
422
+ html.window.localStorage.remove(_connectionCountKey);
423
+ html.window.localStorage.remove(_connectionIdKey);
424
+ debugPrint('WebSocketApiService: Cleared all device connections from localStorage');
425
+ } catch (e) {
426
+ debugPrint('Error clearing device connections: $e');
427
+ }
428
+ }
429
+
430
  // Start the connection heartbeat timer
431
  void _startConnectionHeartbeat() {
432
  if (!kIsWeb) return;
 
437
  });
438
  }
439
 
440
+ /// Reconnect to the WebSocket server, closing any existing connection first
441
+ Future<void> reconnect() async {
442
+ if (_disposed) {
443
+ throw Exception('WebSocketApiService has been disposed');
444
+ }
445
+
446
+ debugPrint('WebSocketApiService: Forcing reconnection...');
447
+
448
+ // Unregister the current connection
449
+ _unregisterDeviceConnection();
450
+
451
+ // Close existing connection if any
452
+ if (_channel != null) {
453
+ try {
454
+ await _channel!.sink.close();
455
+ } catch (e) {
456
+ debugPrint('WebSocketApiService: Error closing existing channel: $e');
457
+ }
458
+ _channel = null;
459
+ }
460
+
461
+ // Reset connection state and ID
462
+ _setStatus(ConnectionStatus.disconnected);
463
+ _reconnectAttempts = 0;
464
+ _connectionId = null; // Clear connection ID to force a new one
465
+
466
+ // Connect again
467
+ await connect();
468
+ }
469
+
470
  Future<void> connect() async {
471
  if (_disposed) {
472
  throw Exception('WebSocketApiService has been disposed');