Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.2] - 2025-12-26

### Changed
- **Better HAR Recording**: Improved HAR file recording and capture functionality

## [0.2.1] - 2025-12-26

### Added
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "reverse-api-engineer"
version = "0.2.1"
version = "0.2.2"
description = "A tool to capture browser traffic for API reverse engineering"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion src/reverse_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Reverse API - Browser traffic capture for API reverse engineering."""

__version__ = "0.2.1"
__version__ = "0.2.2"
50 changes: 36 additions & 14 deletions src/reverse_api/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,6 @@ def _inject_stealth(self, page: Page) -> None:

def _start_with_real_chrome(self, start_url: Optional[str] = None) -> Path:
"""Start using the real Chrome browser with user's profile."""
# We need to use a COPY of the profile to avoid locking issues
# Chrome locks its profile when running, so we can't use it directly
import shutil
import tempfile

Expand All @@ -257,12 +255,12 @@ def _start_with_real_chrome(self, start_url: Optional[str] = None) -> Path:
temp_profile_dir = Path(tempfile.mkdtemp(prefix="chrome_profile_"))

console.print(f" [dim]using real chrome (profile copy)[/dim]")
console.print(f" [dim]note: close chrome if you have it open[/dim]")
console.print(f" [yellow]⚠️ please browse in the FIRST tab only[/yellow]")
console.print(f" [yellow] (new tabs may not be recorded)[/yellow]")
console.print()

try:
# Use launch_persistent_context with channel="chrome" to use real Chrome binary
# This gives us the real Chrome with a fresh profile that has all extensions/settings
self._context = self._playwright.chromium.launch_persistent_context(
user_data_dir=str(temp_profile_dir),
channel="chrome", # Use real Chrome binary
Expand All @@ -278,19 +276,24 @@ def _start_with_real_chrome(self, start_url: Optional[str] = None) -> Path:
)
self._using_persistent = True

# Get or create page
if self._context.pages:
page = self._context.pages[0]
else:
page = self._context.new_page()
for existing_page in self._context.pages:
try:
existing_page.close()
except Exception:
pass

# For HAR recording & context
page = self._context.new_page()

if start_url:
page.goto(start_url, wait_until="domcontentloaded")
else:
page.goto("https://www.google.com", wait_until="domcontentloaded")

# Wait for browser to close
try:
while self._context.pages:
self._context.pages[0].wait_for_timeout(100) # Faster polling
self._context.pages[0].wait_for_timeout(100)
except Exception:
pass

Expand Down Expand Up @@ -376,11 +379,14 @@ def _start_with_stealth_chromium(self, start_url: Optional[str] = None) -> Path:

if start_url:
page.goto(start_url, wait_until="domcontentloaded")
else:
# For HAR recording & context
page.goto("about:blank")

# Wait for browser to close
try:
while self._context.pages:
self._context.pages[0].wait_for_timeout(100) # Faster polling
self._context.pages[0].wait_for_timeout(100)
except Exception:
pass

Expand Down Expand Up @@ -423,10 +429,26 @@ def close(self) -> Path:
spinner="dots",
) as status:
try:
status.update(" [dim]flushing network traffic...[/dim]")
import time

time.sleep(1)

status.update(" [dim]saving har file...[/dim]")
self._context.close() # This saves the HAR file
except Exception:
pass
self._context.close()

if self.har_path.exists():
har_size = self.har_path.stat().st_size
status.update(f" [dim]har saved: {har_size:,} bytes[/dim]")
else:
console.print(
" [yellow]warning: har file was not created[/yellow]"
)

except Exception as e:
console.print(f" [yellow]warning: error saving har: {e}[/yellow]")
if self.har_path.exists():
console.print(f" [dim]har file exists despite error[/dim]")
self._context = None

# Only close browser if not using persistent context
Expand Down
10 changes: 7 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.