h4-debug 0.1.1__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: h4-debug
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Advanced application proxy debugger and F12-style web console
5
5
  Author-email: h4 <h4@example.com>
6
6
  Requires-Python: >=3.8
@@ -0,0 +1 @@
1
+ __version__ = "0.1.2"
@@ -7,13 +7,9 @@ import sys
7
7
  import threading
8
8
  import time
9
9
  import traceback
10
+ import select
10
11
  from datetime import datetime
11
12
 
12
- try:
13
- from websockets.sync.client import connect
14
- except ImportError:
15
- connect = None
16
-
17
13
  # We must keep references to original functions to avoid infinite recursion
18
14
  _original_open = builtins.open
19
15
  _original_socket = socket.socket
@@ -27,37 +23,35 @@ class TelemetryClient:
27
23
  self.thread.start()
28
24
 
29
25
  def _run(self):
30
- ws_url = f"ws://127.0.0.1:{self.port}/ws/telemetry"
31
- if not connect:
32
- return
33
-
26
+ # Connect to the local TCP server spawned by h4-debug
27
+ server_port = self.port + 1
28
+
34
29
  while True:
35
30
  try:
36
- with connect(ws_url) as websocket:
31
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as conn:
32
+ conn.connect(("127.0.0.1", server_port))
33
+
37
34
  while True:
38
35
  # 1. Send all queued outgoing telemetry
39
36
  while not self.queue.empty():
40
37
  try:
41
38
  msg = self.queue.get_nowait()
42
- websocket.send(json.dumps(msg))
43
- self.queue.task_done()
39
+ conn.sendall((json.dumps(msg) + "\n").encode("utf-8"))
44
40
  except queue.Empty:
45
41
  break
46
42
 
47
43
  # 2. Check for incoming commands (with short timeout)
48
- try:
49
- # Using recv(timeout) requires catching TimeoutError
50
- cmd_data = websocket.recv(timeout=0.05)
51
- self._handle_command(cmd_data)
52
- except TimeoutError:
53
- pass
54
- except Exception as e:
55
- # If it's not a timeout, might be a real error
56
- if "timed out" not in str(e).lower():
57
- raise
58
-
44
+ r, _, _ = select.select([conn], [], [], 0.05)
45
+ if r:
46
+ data = conn.recv(4096)
47
+ if not data:
48
+ break # Connection closed
49
+ for line in data.decode("utf-8").split("\n"):
50
+ if line.strip():
51
+ self._handle_command(line)
52
+
59
53
  time.sleep(0.01)
60
- except Exception as e:
54
+ except Exception:
61
55
  time.sleep(1)
62
56
 
63
57
  def _handle_command(self, cmd_data):
@@ -10,7 +10,32 @@ app = FastAPI()
10
10
 
11
11
  # Store connected dashboard clients and the active telemetry process
12
12
  dashboard_clients = []
13
- active_telemetry_client = None
13
+ active_telemetry_client = None # This will now be a TCP socket
14
+
15
+ # For broadcasting logs to WebSockets
16
+ def broadcast_log(log_item_str):
17
+ try:
18
+ log_item = json.loads(log_item_str)
19
+ log_history.append(log_item)
20
+ if len(log_history) > MAX_HISTORY:
21
+ log_history.pop(0)
22
+ except Exception:
23
+ pass
24
+
25
+ disconnected_clients = []
26
+ # Needs to be scheduled on the asyncio loop
27
+ async def _send(client):
28
+ try:
29
+ await client.send_text(log_item_str)
30
+ except Exception:
31
+ disconnected_clients.append(client)
32
+
33
+ for client in dashboard_clients.copy():
34
+ asyncio.run_coroutine_threadsafe(_send(client), loop)
35
+
36
+ for client in disconnected_clients:
37
+ if client in dashboard_clients:
38
+ dashboard_clients.remove(client)
14
39
 
15
40
  # To persist logs for when a dashboard connects slightly after startup
16
41
  log_history = []
@@ -31,40 +56,6 @@ async def get_dashboard():
31
56
  return HTMLResponse(f.read())
32
57
  return HTMLResponse("<h1>Dashboard not found</h1>")
33
58
 
34
- @app.websocket("/ws/telemetry")
35
- async def websocket_telemetry(websocket: WebSocket):
36
- """Endpoint for the intercepted process to push logs and receive commands."""
37
- global active_telemetry_client
38
- await websocket.accept()
39
- active_telemetry_client = websocket
40
-
41
- try:
42
- while True:
43
- data = await websocket.receive_text()
44
-
45
- try:
46
- log_item = json.loads(data)
47
- log_history.append(log_item)
48
- if len(log_history) > MAX_HISTORY:
49
- log_history.pop(0)
50
- except Exception:
51
- pass
52
-
53
- # Broadcast to all dashboards
54
- disconnected_clients = []
55
- for client in dashboard_clients:
56
- try:
57
- await client.send_text(data)
58
- except Exception:
59
- disconnected_clients.append(client)
60
-
61
- for client in disconnected_clients:
62
- if client in dashboard_clients:
63
- dashboard_clients.remove(client)
64
- except WebSocketDisconnect:
65
- if active_telemetry_client == websocket:
66
- active_telemetry_client = None
67
-
68
59
  @app.websocket("/ws/dashboard")
69
60
  async def websocket_dashboard(websocket: WebSocket):
70
61
  """Endpoint for the web dashboard to receive logs and send commands."""
@@ -87,20 +78,60 @@ async def websocket_dashboard(websocket: WebSocket):
87
78
  if cmd.get("action") == "clear":
88
79
  log_history.clear()
89
80
  elif cmd.get("action") in ["evaluate", "set_mode"]:
90
- # Forward commands to the active telemetry process
81
+ # Forward commands to the active telemetry process via TCP
91
82
  if active_telemetry_client:
92
83
  try:
93
- await active_telemetry_client.send_text(json.dumps(cmd))
84
+ active_telemetry_client.sendall((json.dumps(cmd) + "\n").encode("utf-8"))
94
85
  except Exception:
95
- pass
86
+ active_telemetry_client = None
96
87
  except WebSocketDisconnect:
97
88
  if websocket in dashboard_clients:
98
89
  dashboard_clients.remove(websocket)
99
90
 
91
+ import socket
92
+ import threading
93
+
94
+ def run_tcp_server(port: int):
95
+ global active_telemetry_client
96
+ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
97
+ server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
98
+ server.bind(("127.0.0.1", port + 1))
99
+ server.listen(1)
100
+
101
+ while True:
102
+ conn, addr = server.accept()
103
+ active_telemetry_client = conn
104
+
105
+ def handle_client(c):
106
+ global active_telemetry_client
107
+ f = c.makefile("r", encoding="utf-8")
108
+ try:
109
+ while True:
110
+ line = f.readline()
111
+ if not line:
112
+ break
113
+ broadcast_log(line)
114
+ except Exception:
115
+ pass
116
+ finally:
117
+ if active_telemetry_client == c:
118
+ active_telemetry_client = None
119
+ c.close()
120
+
121
+ threading.Thread(target=handle_client, args=(conn,), daemon=True).start()
122
+
100
123
  def run_server(port: int):
101
124
  # Disable uvicorn access logs to keep terminal clean
102
125
  import logging
103
126
  log = logging.getLogger("uvicorn.access")
104
127
  log.setLevel(logging.WARNING)
105
128
 
106
- uvicorn.run(app, host="127.0.0.1", port=port, log_level="warning")
129
+ global loop
130
+ loop = asyncio.new_event_loop()
131
+ asyncio.set_event_loop(loop)
132
+
133
+ threading.Thread(target=run_tcp_server, args=(port,), daemon=True).start()
134
+
135
+ config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="warning", loop="asyncio")
136
+ server = uvicorn.Server(config)
137
+ loop.run_until_complete(server.serve())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: h4-debug
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Advanced application proxy debugger and F12-style web console
5
5
  Author-email: h4 <h4@example.com>
6
6
  Requires-Python: >=3.8
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "h4-debug"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "Advanced application proxy debugger and F12-style web console"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1 +0,0 @@
1
- __version__ = "0.1.1"
File without changes
File without changes
File without changes