Skip to content

Commit 7b0c2a0

Browse files
authored
Merge pull request #9 from DoktorShift/websockets_extension
own websockets - own connection tracking fixed
2 parents 87c5cb8 + 426fbba commit 7b0c2a0

3 files changed

Lines changed: 77 additions & 55 deletions

File tree

static/js/index.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,8 @@ window.app = Vue.createApp({
410410
return
411411
}
412412

413-
// Use extension WebSocket endpoint
414-
const websocketUrl = this.wsLocation + '/devicetimer/api/v1/ws/' + deviceId
413+
// Use extension WebSocket endpoint with browser type (doesn't count as hardware connection)
414+
const websocketUrl = this.wsLocation + '/devicetimer/api/v1/ws/' + deviceId + '?type=browser'
415415
this.websocketMessage = 'Connecting...'
416416
this.activeWebsocketDeviceId = deviceId
417417

@@ -420,8 +420,7 @@ window.app = Vue.createApp({
420420
this.activeWebsocket = ws
421421

422422
ws.onopen = () => {
423-
this.websocketMessage = 'Connected'
424-
this.fetchConnectionStatus()
423+
this.websocketMessage = 'Watching for payments...'
425424
}
426425

427426
ws.onmessage = (event) => {
@@ -431,7 +430,7 @@ window.app = Vue.createApp({
431430
}
432431

433432
ws.onclose = () => {
434-
this.websocketMessage = 'Disconnected'
433+
this.websocketMessage = ''
435434
if (this.activeWebsocket === ws) {
436435
this.activeWebsocket = null
437436
this.activeWebsocketDeviceId = null

templates/devicetimer/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,12 +1096,12 @@
10961096
</div>
10971097

10981098
<!-- WebSocket Status -->
1099-
<div class="q-mt-sm">
1099+
<div class="q-mt-sm" v-if="websocketMessage">
11001100
<q-chip
1101-
:color="websocketMessage.includes('connected') ? 'positive' : websocketMessage.includes('Payment') ? 'positive' : 'grey-6'"
1101+
:color="websocketMessage.includes('Payment') ? 'positive' : 'grey-6'"
11021102
text-color="white"
11031103
size="sm"
1104-
:icon="websocketMessage.includes('connected') ? 'wifi' : websocketMessage.includes('Payment') ? 'check_circle' : 'wifi_off'"
1104+
:icon="websocketMessage.includes('Payment') ? 'check_circle' : 'visibility'"
11051105
>
11061106
{% raw %}{{ wsMessage }}{% endraw %}
11071107
</q-chip>

websocket.py

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,116 @@
22
WebSocket management for DeviceTimer extension.
33
44
Handles device connections and message broadcasting.
5-
Tracks connected devices to show real-time status in UI.
5+
Tracks connected hardware devices to show real-time status in UI.
6+
Browser connections (for watching payments) are tracked separately.
67
"""
78

8-
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
9+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query
910
from loguru import logger
10-
from typing import Dict, Set
11+
from typing import Dict, Set, Optional
1112

1213
devicetimer_websocket_router = APIRouter()
1314

14-
# Track connected devices: device_id -> set of WebSocket connections
15-
# Multiple connections per device are supported (e.g., multiple browser tabs)
16-
_connected_clients: Dict[str, Set[WebSocket]] = {}
15+
# Track hardware device connections: device_id -> set of WebSocket connections
16+
_hardware_clients: Dict[str, Set[WebSocket]] = {}
17+
18+
# Track browser connections (for payment notifications in UI)
19+
_browser_clients: Dict[str, Set[WebSocket]] = {}
1720

1821

1922
def get_connected_device_ids() -> list[str]:
20-
"""Return list of device IDs with active WebSocket connections."""
21-
return list(_connected_clients.keys())
23+
"""Return list of device IDs with active hardware connections."""
24+
return list(_hardware_clients.keys())
2225

2326

2427
def is_device_connected(device_id: str) -> bool:
25-
"""Check if a device has any active WebSocket connections."""
26-
return device_id in _connected_clients and len(_connected_clients[device_id]) > 0
28+
"""Check if a hardware device has any active WebSocket connections."""
29+
return device_id in _hardware_clients and len(_hardware_clients[device_id]) > 0
2730

2831

2932
async def send_to_device(device_id: str, message: str) -> bool:
3033
"""
31-
Send a message to all WebSocket connections for a device.
34+
Send a message to all WebSocket connections for a device (hardware + browser).
3235
Returns True if message was sent to at least one client.
3336
"""
34-
if device_id not in _connected_clients:
35-
logger.warning(f"No WebSocket connections for device {device_id}")
36-
return False
37-
3837
sent = False
39-
dead_connections: Set[WebSocket] = set()
40-
41-
for websocket in _connected_clients[device_id]:
42-
try:
43-
await websocket.send_text(message)
44-
sent = True
45-
except Exception as e:
46-
logger.debug(f"Failed to send to WebSocket: {e}")
47-
dead_connections.add(websocket)
4838

49-
# Clean up dead connections
50-
for ws in dead_connections:
51-
_connected_clients[device_id].discard(ws)
52-
53-
# Remove device entry if no connections left
54-
if device_id in _connected_clients and not _connected_clients[device_id]:
55-
del _connected_clients[device_id]
39+
# Send to hardware clients
40+
if device_id in _hardware_clients:
41+
dead_connections: Set[WebSocket] = set()
42+
for websocket in _hardware_clients[device_id]:
43+
try:
44+
await websocket.send_text(message)
45+
sent = True
46+
except Exception as e:
47+
logger.debug(f"Failed to send to hardware: {e}")
48+
dead_connections.add(websocket)
49+
for ws in dead_connections:
50+
_hardware_clients[device_id].discard(ws)
51+
if not _hardware_clients[device_id]:
52+
del _hardware_clients[device_id]
53+
54+
# Send to browser clients (so UI shows payment received)
55+
if device_id in _browser_clients:
56+
dead_connections = set()
57+
for websocket in _browser_clients[device_id]:
58+
try:
59+
await websocket.send_text(message)
60+
sent = True
61+
except Exception as e:
62+
logger.debug(f"Failed to send to browser: {e}")
63+
dead_connections.add(websocket)
64+
for ws in dead_connections:
65+
_browser_clients[device_id].discard(ws)
66+
if not _browser_clients[device_id]:
67+
del _browser_clients[device_id]
68+
69+
if not sent:
70+
logger.warning(f"No WebSocket connections for device {device_id}")
5671

5772
return sent
5873

5974

6075
@devicetimer_websocket_router.websocket("/api/v1/ws/{device_id}")
61-
async def websocket_endpoint(websocket: WebSocket, device_id: str):
76+
async def websocket_endpoint(
77+
websocket: WebSocket,
78+
device_id: str,
79+
type: Optional[str] = Query(default="hardware")
80+
):
6281
"""
6382
WebSocket endpoint for device connections.
64-
Hardware devices connect here to receive payment notifications.
83+
84+
Query params:
85+
type: "hardware" (default) for ESP32 devices, "browser" for UI connections
6586
"""
6687
await websocket.accept()
6788

89+
# Select the appropriate client pool
90+
is_browser = type == "browser"
91+
clients = _browser_clients if is_browser else _hardware_clients
92+
client_type = "browser" if is_browser else "hardware"
93+
6894
# Add to tracking
69-
if device_id not in _connected_clients:
70-
_connected_clients[device_id] = set()
71-
_connected_clients[device_id].add(websocket)
95+
if device_id not in clients:
96+
clients[device_id] = set()
97+
clients[device_id].add(websocket)
7298

73-
logger.info(f"Device {device_id} connected. Total connections: {len(_connected_clients[device_id])}")
99+
logger.info(f"{client_type.capitalize()} connected for device {device_id}")
74100

75101
try:
76102
while True:
77-
# Keep connection alive, wait for messages (ping/pong handled automatically)
78103
data = await websocket.receive_text()
79-
# Hardware might send status updates, we just acknowledge
80-
logger.debug(f"Received from {device_id}: {data}")
104+
logger.debug(f"Received from {device_id} ({client_type}): {data}")
81105
except WebSocketDisconnect:
82-
logger.info(f"Device {device_id} disconnected")
106+
logger.info(f"{client_type.capitalize()} disconnected for device {device_id}")
83107
except Exception as e:
84108
logger.debug(f"WebSocket error for {device_id}: {e}")
85109
finally:
86-
# Remove from tracking
87-
if device_id in _connected_clients:
88-
_connected_clients[device_id].discard(websocket)
89-
if not _connected_clients[device_id]:
90-
del _connected_clients[device_id]
91-
logger.info(f"Device {device_id} cleaned up. Connected devices: {list(_connected_clients.keys())}")
110+
if device_id in clients:
111+
clients[device_id].discard(websocket)
112+
if not clients[device_id]:
113+
del clients[device_id]
114+
logger.debug(f"Connected hardware devices: {list(_hardware_clients.keys())}")
92115

93116

94117
@devicetimer_websocket_router.get("/api/v1/ws/status")

0 commit comments

Comments
 (0)