Spaces:
Paused
Paused
Julian Bilcke
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: "
|
| 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": "
|
| 7 |
"version.json": "68350cac7987de2728345c72918dd067",
|
| 8 |
"tikslop.png": "570e1db759046e2d224fef729983634e",
|
| 9 |
"index.html": "3a7029b3672560e7938aab6fa4d30a46",
|
| 10 |
"/": "3a7029b3672560e7938aab6fa4d30a46",
|
| 11 |
-
"main.dart.js": "
|
| 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=
|
| 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 |
-
//
|
| 245 |
-
await websocket.
|
| 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 |
-
//
|
| 246 |
if (_connectionId == null) {
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 267 |
final now = DateTime.now().millisecondsSinceEpoch;
|
|
|
|
| 268 |
connections.removeWhere((key, value) {
|
| 269 |
if (value is! int) return true;
|
| 270 |
-
return now - value >
|
| 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');
|