webtap-tool 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
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.
Potentially problematic release.
This version of webtap-tool might be problematic. Click here for more details.
- webtap/__init__.py +6 -2
- webtap/api.py +71 -3
- webtap/app.py +3 -0
- webtap/cdp/query.py +2 -1
- webtap/commands/launch.py +3 -17
- webtap/services/body.py +1 -1
- webtap/services/console.py +1 -1
- webtap/services/fetch.py +1 -1
- webtap/services/network.py +2 -2
- webtap/services/setup.py +20 -10
- {webtap_tool-0.1.2.dist-info → webtap_tool-0.1.3.dist-info}/METADATA +1 -1
- {webtap_tool-0.1.2.dist-info → webtap_tool-0.1.3.dist-info}/RECORD +14 -14
- {webtap_tool-0.1.2.dist-info → webtap_tool-0.1.3.dist-info}/WHEEL +0 -0
- {webtap_tool-0.1.2.dist-info → webtap_tool-0.1.3.dist-info}/entry_points.txt +0 -0
webtap/__init__.py
CHANGED
|
@@ -47,8 +47,12 @@ def main():
|
|
|
47
47
|
def _start_api_server_safely():
|
|
48
48
|
"""Start API server with error handling."""
|
|
49
49
|
try:
|
|
50
|
-
start_api_server(app.state)
|
|
51
|
-
|
|
50
|
+
thread = start_api_server(app.state)
|
|
51
|
+
if thread and app.state:
|
|
52
|
+
app.state.api_thread = thread
|
|
53
|
+
logger.info("API server started on port 8765")
|
|
54
|
+
else:
|
|
55
|
+
logger.info("Port 8765 in use by another instance")
|
|
52
56
|
except Exception as e:
|
|
53
57
|
logger.warning(f"Failed to start API server: {e}")
|
|
54
58
|
|
webtap/api.py
CHANGED
|
@@ -5,6 +5,8 @@ PUBLIC API:
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
+
import os
|
|
9
|
+
import socket
|
|
8
10
|
import threading
|
|
9
11
|
from typing import Any, Dict
|
|
10
12
|
|
|
@@ -184,7 +186,39 @@ async def disable_all_filters() -> Dict[str, Any]:
|
|
|
184
186
|
return {"enabled": [], "total": 0}
|
|
185
187
|
|
|
186
188
|
|
|
187
|
-
|
|
189
|
+
@api.get("/instance")
|
|
190
|
+
async def get_instance_info() -> Dict[str, Any]:
|
|
191
|
+
"""Get info about this WebTap instance."""
|
|
192
|
+
if not app_state:
|
|
193
|
+
return {"error": "WebTap not initialized"}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
"pid": os.getpid(),
|
|
197
|
+
"connected_to": app_state.cdp.current_page_title if app_state.cdp.is_connected else None,
|
|
198
|
+
"events": app_state.cdp.event_count,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@api.post("/release")
|
|
203
|
+
async def release_port() -> Dict[str, Any]:
|
|
204
|
+
"""Release API port for another WebTap instance."""
|
|
205
|
+
logger.info("Releasing API port for another instance")
|
|
206
|
+
|
|
207
|
+
# Schedule graceful shutdown after response
|
|
208
|
+
def shutdown():
|
|
209
|
+
# Just set the flag to stop uvicorn, don't kill the whole process
|
|
210
|
+
global _shutdown_requested
|
|
211
|
+
_shutdown_requested = True
|
|
212
|
+
|
|
213
|
+
threading.Timer(0.5, shutdown).start()
|
|
214
|
+
return {"message": "Releasing port 8765"}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# Flag to signal shutdown
|
|
218
|
+
_shutdown_requested = False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def start_api_server(state, host: str = "127.0.0.1", port: int = 8765) -> threading.Thread | None:
|
|
188
222
|
"""Start the API server in a background thread.
|
|
189
223
|
|
|
190
224
|
Args:
|
|
@@ -193,8 +227,16 @@ def start_api_server(state, host: str = "127.0.0.1", port: int = 8765):
|
|
|
193
227
|
port: Port to bind to. Defaults to 8765.
|
|
194
228
|
|
|
195
229
|
Returns:
|
|
196
|
-
Thread instance running the server.
|
|
230
|
+
Thread instance running the server, or None if port is in use.
|
|
197
231
|
"""
|
|
232
|
+
# Check port availability first
|
|
233
|
+
try:
|
|
234
|
+
with socket.socket() as s:
|
|
235
|
+
s.bind((host, port))
|
|
236
|
+
except OSError:
|
|
237
|
+
logger.info(f"Port {port} already in use")
|
|
238
|
+
return None
|
|
239
|
+
|
|
198
240
|
global app_state
|
|
199
241
|
app_state = state
|
|
200
242
|
|
|
@@ -208,13 +250,39 @@ def start_api_server(state, host: str = "127.0.0.1", port: int = 8765):
|
|
|
208
250
|
def run_server(host: str, port: int):
|
|
209
251
|
"""Run the FastAPI server in a thread."""
|
|
210
252
|
try:
|
|
211
|
-
uvicorn.
|
|
253
|
+
config = uvicorn.Config(
|
|
212
254
|
api,
|
|
213
255
|
host=host,
|
|
214
256
|
port=port,
|
|
215
257
|
log_level="error",
|
|
216
258
|
access_log=False,
|
|
217
259
|
)
|
|
260
|
+
server = uvicorn.Server(config)
|
|
261
|
+
|
|
262
|
+
# Run with checking for shutdown flag
|
|
263
|
+
import asyncio
|
|
264
|
+
|
|
265
|
+
loop = asyncio.new_event_loop()
|
|
266
|
+
asyncio.set_event_loop(loop)
|
|
267
|
+
|
|
268
|
+
async def serve():
|
|
269
|
+
await server.serve()
|
|
270
|
+
|
|
271
|
+
# Start serving
|
|
272
|
+
task = loop.create_task(serve())
|
|
273
|
+
|
|
274
|
+
# Check for shutdown flag
|
|
275
|
+
while not _shutdown_requested:
|
|
276
|
+
loop.run_until_complete(asyncio.sleep(0.1))
|
|
277
|
+
if task.done():
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
# Shutdown if requested
|
|
281
|
+
if _shutdown_requested:
|
|
282
|
+
logger.info("API server shutting down")
|
|
283
|
+
server.should_exit = True
|
|
284
|
+
loop.run_until_complete(server.shutdown())
|
|
285
|
+
|
|
218
286
|
except Exception as e:
|
|
219
287
|
logger.error(f"API server failed: {e}")
|
|
220
288
|
|
webtap/app.py
CHANGED
|
@@ -6,6 +6,7 @@ PUBLIC API:
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import sys
|
|
9
|
+
import threading
|
|
9
10
|
from dataclasses import dataclass, field
|
|
10
11
|
|
|
11
12
|
from replkit2 import App
|
|
@@ -24,10 +25,12 @@ class WebTapState:
|
|
|
24
25
|
Attributes:
|
|
25
26
|
cdp: Chrome DevTools Protocol session instance.
|
|
26
27
|
service: WebTapService orchestrating all domain services.
|
|
28
|
+
api_thread: Thread running the FastAPI server (if this instance owns the port).
|
|
27
29
|
"""
|
|
28
30
|
|
|
29
31
|
cdp: CDPSession = field(default_factory=CDPSession)
|
|
30
32
|
service: WebTapService = field(init=False)
|
|
33
|
+
api_thread: threading.Thread | None = None
|
|
31
34
|
|
|
32
35
|
def __post_init__(self):
|
|
33
36
|
"""Initialize service with self reference after dataclass init."""
|
webtap/cdp/query.py
CHANGED
|
@@ -74,7 +74,8 @@ def build_query(
|
|
|
74
74
|
pattern = value
|
|
75
75
|
path_conditions.append(f"json_extract_string(event, '{json_path}') LIKE '{pattern}'")
|
|
76
76
|
elif isinstance(value, (int, float)):
|
|
77
|
-
|
|
77
|
+
# Use string comparison for numeric values to avoid type conversion errors
|
|
78
|
+
path_conditions.append(f"json_extract_string(event, '{json_path}') = '{value}'")
|
|
78
79
|
elif isinstance(value, bool):
|
|
79
80
|
path_conditions.append(f"json_extract_string(event, '{json_path}') = '{str(value).lower()}'")
|
|
80
81
|
elif value is None:
|
webtap/commands/launch.py
CHANGED
|
@@ -46,23 +46,9 @@ def run_chrome(state, detach: bool = True, port: int = 9222) -> dict:
|
|
|
46
46
|
],
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
-
#
|
|
49
|
+
# Simple: use clean temp profile for debugging
|
|
50
50
|
temp_config = Path("/tmp/webtap-chrome-debug")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if not temp_config.exists():
|
|
54
|
-
temp_config.mkdir(parents=True)
|
|
55
|
-
|
|
56
|
-
# Symlink Default profile
|
|
57
|
-
default_profile = real_config / "Default"
|
|
58
|
-
if default_profile.exists():
|
|
59
|
-
(temp_config / "Default").symlink_to(default_profile)
|
|
60
|
-
|
|
61
|
-
# Copy essential files
|
|
62
|
-
for file in ["Local State", "First Run"]:
|
|
63
|
-
src = real_config / file
|
|
64
|
-
if src.exists():
|
|
65
|
-
(temp_config / file).write_text(src.read_text())
|
|
51
|
+
temp_config.mkdir(parents=True, exist_ok=True)
|
|
66
52
|
|
|
67
53
|
# Launch Chrome
|
|
68
54
|
cmd = [chrome_exe, f"--remote-debugging-port={port}", "--remote-allow-origins=*", f"--user-data-dir={temp_config}"]
|
|
@@ -74,7 +60,7 @@ def run_chrome(state, detach: bool = True, port: int = 9222) -> dict:
|
|
|
74
60
|
details={
|
|
75
61
|
"Port": str(port),
|
|
76
62
|
"Mode": "Background (detached)",
|
|
77
|
-
"Profile":
|
|
63
|
+
"Profile": "Temporary (clean)",
|
|
78
64
|
"Next step": "Run connect() to attach WebTap",
|
|
79
65
|
},
|
|
80
66
|
)
|
webtap/services/body.py
CHANGED
webtap/services/console.py
CHANGED
webtap/services/fetch.py
CHANGED
webtap/services/network.py
CHANGED
|
@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class NetworkService:
|
|
13
|
-
"""
|
|
13
|
+
"""Network event queries and monitoring."""
|
|
14
14
|
|
|
15
15
|
def __init__(self):
|
|
16
16
|
"""Initialize network service."""
|
|
@@ -81,7 +81,7 @@ class NetworkService:
|
|
|
81
81
|
json_extract_string(event, '$.params.response.statusText') as StatusText
|
|
82
82
|
FROM events
|
|
83
83
|
WHERE json_extract_string(event, '$.method') = 'Network.responseReceived'
|
|
84
|
-
AND
|
|
84
|
+
AND json_extract_string(event, '$.params.response.status') >= '400'
|
|
85
85
|
ORDER BY rowid DESC LIMIT {limit}
|
|
86
86
|
"""
|
|
87
87
|
|
webtap/services/setup.py
CHANGED
|
@@ -177,22 +177,32 @@ class SetupService:
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
wrapper_script = """#!/bin/bash
|
|
180
|
-
# Chrome wrapper
|
|
180
|
+
# Chrome wrapper using bindfs for perfect state sync with debug port
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
DEBUG_DIR="$HOME/.config/google-chrome-debug"
|
|
183
|
+
REAL_DIR="$HOME/.config/google-chrome"
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
cp "$REAL_CONFIG/First Run" "$DEBUG_CONFIG/" 2>/dev/null || true
|
|
185
|
+
# Check if bindfs is installed
|
|
186
|
+
if ! command -v bindfs &>/dev/null; then
|
|
187
|
+
echo "Error: bindfs not installed. Install with: yay -S bindfs" >&2
|
|
188
|
+
exit 1
|
|
190
189
|
fi
|
|
191
190
|
|
|
191
|
+
# Mount real profile via bindfs if not already mounted
|
|
192
|
+
if ! mountpoint -q "$DEBUG_DIR" 2>/dev/null; then
|
|
193
|
+
mkdir -p "$DEBUG_DIR"
|
|
194
|
+
if ! bindfs --no-allow-other "$REAL_DIR" "$DEBUG_DIR"; then
|
|
195
|
+
echo "Error: Failed to mount Chrome profile via bindfs" >&2
|
|
196
|
+
exit 1
|
|
197
|
+
fi
|
|
198
|
+
echo "Chrome debug profile mounted. To unmount: fusermount -u $DEBUG_DIR" >&2
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# Launch Chrome with debugging on bindfs mount
|
|
192
202
|
exec /usr/bin/google-chrome-stable \\
|
|
193
203
|
--remote-debugging-port=9222 \\
|
|
194
|
-
--remote-allow-origins
|
|
195
|
-
--user-data-dir="$
|
|
204
|
+
--remote-allow-origins='*' \\
|
|
205
|
+
--user-data-dir="$DEBUG_DIR" \\
|
|
196
206
|
"$@"
|
|
197
207
|
"""
|
|
198
208
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
webtap/VISION.md,sha256=kfoJfEPVc4chOrD9tNMDmYBY9rX9KB-286oZj70ALCE,7681
|
|
2
|
-
webtap/__init__.py,sha256=
|
|
3
|
-
webtap/api.py,sha256=
|
|
4
|
-
webtap/app.py,sha256=
|
|
2
|
+
webtap/__init__.py,sha256=4MD5ir_kyfHcI3kND_As9jF-UUqGEzWKV5Cg6PKzef4,1787
|
|
3
|
+
webtap/api.py,sha256=1VV_YPwvGuYanDm5M_NuWUYiGIQbPd3IKicK5Jplq44,7987
|
|
4
|
+
webtap/app.py,sha256=ISPSmwqCZ2R_kVDmFEsBLltzfbHGUkzcczTGCs2KcDI,2738
|
|
5
5
|
webtap/filters.py,sha256=nphF2bFRbFtS2ue-CbV1uzKpuK3IYBbwjLeLhMDdLEk,11034
|
|
6
6
|
webtap/cdp/README.md,sha256=0TS0V_dRgRAzBqhddpXWD4S0YVi5wI4JgFJSll_KUBE,5660
|
|
7
7
|
webtap/cdp/__init__.py,sha256=c6NFG0XJnAa5GTe9MLr9mDZcLZqoTQN7A1cvvOfLcgY,453
|
|
8
|
-
webtap/cdp/query.py,sha256=
|
|
8
|
+
webtap/cdp/query.py,sha256=x2Cy7KMolYkTelpROGezOfMFgYnbSlCvHkvvW1v_gLI,4229
|
|
9
9
|
webtap/cdp/session.py,sha256=BTajmDH6nel2dvia7IX9k6C-RinkN2NxY4rxtUJAJE0,12362
|
|
10
10
|
webtap/cdp/schema/README.md,sha256=hnWCzbXYcYtWaZb_SgjVaFBiG81S9b9Y3x-euQFwQDo,1222
|
|
11
11
|
webtap/cdp/schema/cdp_protocol.json,sha256=dp9_OLYLuVsQb1oV5r6MZfMzURscBLyAXUckdaPWyv4,1488452
|
|
@@ -25,19 +25,19 @@ webtap/commands/fetch.py,sha256=_TzOvJfVzPaw4ZmyI95Qb7rS3iKx2nmp_IL3jaQO_6g,7772
|
|
|
25
25
|
webtap/commands/filters.py,sha256=trZvcbHTaF0FZC8dyMAmhmS2dlBoA82VXe5_DDS3eU8,8986
|
|
26
26
|
webtap/commands/inspect.py,sha256=6PGN7iDT1oLzQJboNeYozLILrW2VsAzmtMpF3_XhD30,5746
|
|
27
27
|
webtap/commands/javascript.py,sha256=QpQdqqoQwwTyz1lpibZ92XKOL89scu_ndgSjkhaYuDk,3195
|
|
28
|
-
webtap/commands/launch.py,sha256
|
|
28
|
+
webtap/commands/launch.py,sha256=iZDLundKlxKRLKf3Vz5at42-tp2f-Uj5wZf7fbhBfA0,2202
|
|
29
29
|
webtap/commands/navigation.py,sha256=Mapawp2AZTJQaws2uwlTgMUhqz7HlVTLxiZ06n_MQc0,6071
|
|
30
30
|
webtap/commands/network.py,sha256=hwZshGGdVsJ_9MFjOKJXT07I990JjZInw2LLnKXLQ5Y,2910
|
|
31
31
|
webtap/commands/setup.py,sha256=ewoOTvgCAzMPcFm6cbk-93nY_BI2IXqPZGGGpc1rJiw,4459
|
|
32
32
|
webtap/services/README.md,sha256=rala_jtnNgSiQ1lFLM7x_UQ4SJZDceAm7dpkQMRTYaI,2346
|
|
33
33
|
webtap/services/__init__.py,sha256=IjFqu0Ak6D-r18aokcQMtenDV3fbelvfjTCejGv6CZ0,570
|
|
34
|
-
webtap/services/body.py,sha256=
|
|
35
|
-
webtap/services/console.py,sha256=
|
|
36
|
-
webtap/services/fetch.py,sha256=
|
|
34
|
+
webtap/services/body.py,sha256=XQPa19y5eUc3XJ2TuwVK6kffO1VQoKqNs33MBBz7hzU,3913
|
|
35
|
+
webtap/services/console.py,sha256=XVfSKTvEHyyOdujsg85S3wtj1CdZhzKtWwlx25MvSv8,3768
|
|
36
|
+
webtap/services/fetch.py,sha256=nl6bpU2Vnf40kau4-mqAnIkhC-7Lx2vbTJKUglz9KnE,13602
|
|
37
37
|
webtap/services/main.py,sha256=HcXdPuI7hzsxsNvfN0npGhj_M7HObc83Lr3fuy7BMeE,5673
|
|
38
|
-
webtap/services/network.py,sha256=
|
|
39
|
-
webtap/services/setup.py,sha256=
|
|
40
|
-
webtap_tool-0.1.
|
|
41
|
-
webtap_tool-0.1.
|
|
42
|
-
webtap_tool-0.1.
|
|
43
|
-
webtap_tool-0.1.
|
|
38
|
+
webtap/services/network.py,sha256=0o_--F6YvmXqqFqrcjL1gc6Vr9V1Ytb_U7r_DSUWupA,3444
|
|
39
|
+
webtap/services/setup.py,sha256=dXzqlXR37-chZO2qCsuiVq9N7fELXtND5jH_9QQwsws,7932
|
|
40
|
+
webtap_tool-0.1.3.dist-info/METADATA,sha256=8fezx8WvoSf5sTT5-2ibcGwLDvVldjPLpGndETihBIw,17457
|
|
41
|
+
webtap_tool-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
42
|
+
webtap_tool-0.1.3.dist-info/entry_points.txt,sha256=iFe575I0CIb1MbfPt0oX2VYyY5gSU_dA551PKVR83TU,39
|
|
43
|
+
webtap_tool-0.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|