Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
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');
|