## Accept button regression — diagnosed from a user log
Field report: incoming call → callee taps Accept → debug log
shows the dual-path race being skipped with
`connect:dual_path_skipped {"has_own":false,"has_peer":true,
"role":"None"}` and the call falling to relay-only on the
callee side.
Root cause: the Accept button was calling `answer_call` with
`mode: 2` which falls through to `AcceptGeneric` (privacy
mode). By design, privacy mode SKIPS the reflex query on the
callee so the callee's IP stays hidden from the caller — but
the side effect is that `own_reflex_addr` never gets cached in
`SignalState`. When `connect` runs a moment later, it sees
`own_reflex_addr = None`, can't compute the deterministic role
for the dual-path race, and falls back to relay.
For a normal VoIP app where P2P is the desired default, the
right behavior is `AcceptTrusted` — which queries reflect,
advertises the callee's addr in the answer, and enables direct
P2P. Privacy mode can come back as a dedicated second button
if anyone actually needs it.
Changed `acceptCallBtn` click handler from `mode: 2` to
`mode: 1`. The next call from a Phase-5 APK should show
`connect:dual_path_race_start` + `connect:dual_path_race_won
{"path":"Direct"}` on a cone-NAT-to-cone-NAT pair.
## Debug log export — new Copy / Share buttons
Field-testing the GUI debug log required me to keep asking the
user to type out what they saw. Added two new buttons next to
Clear:
- **Copy log** — serialises the rolling buffer as plain text
(same HH:MM:SS.mmm format the on-screen panel uses) and
writes to `navigator.clipboard`. Falls back to the old
selection-based `execCommand("copy")` for WebViews that
refuse the new API without a permission prompt.
- **Share** — tries the Web Share API (`navigator.share(...)`)
first. On Android WebView this opens the system share sheet
so the user can send the text straight to a messaging app.
Falls back to clipboard copy on WebViews that don't expose
navigator.share (most desktop ones). Also falls back if the
user cancels the share sheet.
Flash status line below the buttons shows a 2.5s confirmation
("✓ Copied 47 entries") or an error hint. The log is plain
text so anyone can paste a log fragment into a message and
send it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
282 lines
13 KiB
HTML
282 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta
|
|
name="viewport"
|
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
|
/>
|
|
<title>WarzonePhone</title>
|
|
<link rel="stylesheet" href="/src/style.css" />
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<!-- Connect screen -->
|
|
<div id="connect-screen">
|
|
<h1>WarzonePhone</h1>
|
|
<p class="subtitle">Encrypted Voice</p>
|
|
<div class="form">
|
|
<label>Relay
|
|
<button id="relay-selected" class="relay-selected" type="button">
|
|
<span id="relay-dot" class="dot"></span>
|
|
<span id="relay-label">Select relay...</span>
|
|
<span class="arrow">⚙</span>
|
|
</button>
|
|
</label>
|
|
<label>Room
|
|
<input id="room" type="text" value="general" />
|
|
</label>
|
|
<label>Alias
|
|
<input id="alias" type="text" placeholder="your name" />
|
|
</label>
|
|
<div class="form-row">
|
|
<label class="checkbox">
|
|
<input id="os-aec" type="checkbox" checked />
|
|
OS Echo Cancel
|
|
</label>
|
|
<button id="settings-btn-home" class="icon-btn" title="Settings (Cmd+,)">⚙</button>
|
|
</div>
|
|
<!-- Mode toggle -->
|
|
<div class="mode-toggle" style="display:flex;gap:8px;margin-bottom:8px;">
|
|
<button id="mode-room" class="mode-btn active" style="flex:1">Room</button>
|
|
<button id="mode-direct" class="mode-btn" style="flex:1">Direct Call</button>
|
|
</div>
|
|
|
|
<!-- Room mode (default) -->
|
|
<div id="room-mode">
|
|
<button id="connect-btn" class="primary">Connect</button>
|
|
</div>
|
|
|
|
<!-- Direct call mode -->
|
|
<div id="direct-mode" class="hidden">
|
|
<button id="register-btn" class="primary" style="background:#2196F3">Register on Relay</button>
|
|
<div id="direct-registered" class="hidden" style="margin-top:12px">
|
|
<div class="direct-registered-header">
|
|
<p id="registered-status" style="color:var(--green);font-size:13px;margin:0">✅ Registered — waiting for calls</p>
|
|
<button id="deregister-btn" class="secondary-btn small">Deregister</button>
|
|
</div>
|
|
<div id="incoming-call-panel" class="hidden" style="background:#1B5E20;padding:12px;border-radius:8px;margin:8px 0">
|
|
<p style="font-weight:bold;margin:0 0 4px 0">Incoming Call</p>
|
|
<p id="incoming-caller" style="font-size:12px;opacity:0.8;margin:0 0 8px 0">From: unknown</p>
|
|
<div style="display:flex;gap:8px">
|
|
<button id="accept-call-btn" style="flex:1;background:var(--green);color:white;border:none;padding:8px;border-radius:6px;cursor:pointer">Accept</button>
|
|
<button id="reject-call-btn" style="flex:1;background:var(--red);color:white;border:none;padding:8px;border-radius:6px;cursor:pointer">Reject</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent contacts -->
|
|
<div id="recent-contacts-section" class="hidden">
|
|
<div class="history-header">Recent contacts</div>
|
|
<div id="recent-contacts-list" class="history-list"></div>
|
|
</div>
|
|
|
|
<!-- Call history -->
|
|
<div id="call-history-section" class="hidden">
|
|
<div class="history-header">
|
|
History
|
|
<button id="clear-history-btn" class="link-btn">clear</button>
|
|
</div>
|
|
<div id="call-history-list" class="history-list"></div>
|
|
</div>
|
|
|
|
<label style="margin-top:8px">Call by fingerprint
|
|
<input id="target-fp" type="text" placeholder="xxxx:xxxx:xxxx:..." />
|
|
</label>
|
|
<button id="call-btn" class="primary" style="margin-top:8px">Call</button>
|
|
<p id="call-status-text" style="color:var(--yellow);font-size:13px;margin-top:4px"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<p id="connect-error" class="error"></p>
|
|
</div>
|
|
<div class="identity-info">
|
|
<span id="my-identicon"></span>
|
|
<span id="my-fingerprint" class="fp-display"></span>
|
|
</div>
|
|
<div class="recent-rooms" id="recent-rooms"></div>
|
|
</div>
|
|
|
|
<!-- In-call screen -->
|
|
<div id="call-screen" class="hidden">
|
|
<div class="call-header">
|
|
<div class="call-header-row">
|
|
<div id="room-name" class="room-name"></div>
|
|
<button id="settings-btn-call" class="icon-btn small" title="Settings (Cmd+,)">⚙</button>
|
|
</div>
|
|
<div class="call-meta">
|
|
<span id="call-status" class="status-dot"></span>
|
|
<span id="call-timer" class="call-timer">0:00</span>
|
|
</div>
|
|
</div>
|
|
<div class="level-meter">
|
|
<div id="level-bar" class="level-bar-fill"></div>
|
|
</div>
|
|
<div id="participants" class="participants"></div>
|
|
<div class="controls">
|
|
<button id="mic-btn" class="control-btn" title="Toggle Mic (m)">
|
|
<span class="icon" id="mic-icon">Mic</span>
|
|
</button>
|
|
<button id="hangup-btn" class="control-btn hangup" title="Hang Up (q)">
|
|
<span class="icon">End</span>
|
|
</button>
|
|
<button id="spk-btn" class="control-btn" title="Toggle Speaker (s)">
|
|
<span class="icon" id="spk-icon">Spk</span>
|
|
</button>
|
|
</div>
|
|
<div id="stats" class="stats"></div>
|
|
</div>
|
|
|
|
<!-- Settings panel -->
|
|
<div id="settings-panel" class="hidden">
|
|
<div class="settings-card">
|
|
<div class="settings-header">
|
|
<h2>Settings</h2>
|
|
<button id="settings-close" class="icon-btn">×</button>
|
|
</div>
|
|
<div class="settings-section">
|
|
<h3>Connection</h3>
|
|
<label>Default Room
|
|
<input id="s-room" type="text" />
|
|
</label>
|
|
<label>Alias
|
|
<input id="s-alias" type="text" />
|
|
</label>
|
|
</div>
|
|
<div class="settings-section">
|
|
<h3>Audio</h3>
|
|
<div class="quality-control">
|
|
<div class="quality-header">
|
|
<span class="setting-label">QUALITY</span>
|
|
<span id="s-quality-label" class="quality-label">Auto</span>
|
|
</div>
|
|
<input id="s-quality" type="range" min="0" max="7" step="1" value="3" class="quality-slider" />
|
|
<div class="quality-ticks">
|
|
<span>64k</span>
|
|
<span>48k</span>
|
|
<span>32k</span>
|
|
<span>Auto</span>
|
|
<span>24k</span>
|
|
<span>6k</span>
|
|
<span>C2</span>
|
|
<span>1.2k</span>
|
|
</div>
|
|
</div>
|
|
<label class="checkbox">
|
|
<input id="s-os-aec" type="checkbox" />
|
|
OS Echo Cancellation (macOS VoiceProcessingIO)
|
|
</label>
|
|
<label class="checkbox">
|
|
<input id="s-agc" type="checkbox" checked />
|
|
Automatic Gain Control
|
|
</label>
|
|
<label class="checkbox">
|
|
<input id="s-dred-debug" type="checkbox" />
|
|
DRED debug logs (verbose, dev only)
|
|
</label>
|
|
<label class="checkbox">
|
|
<input id="s-call-debug" type="checkbox" />
|
|
Call flow debug logs (trace every step of a call)
|
|
</label>
|
|
</div>
|
|
<div class="settings-section" id="s-call-debug-section" style="display:none">
|
|
<h3>Call Debug Log</h3>
|
|
<div id="s-call-debug-log" style="max-height:220px;overflow-y:auto;background:#0a0a0a;color:#e0e0e0;font-family:ui-monospace,Menlo,Monaco,'Courier New',monospace;font-size:10px;padding:6px;border-radius:4px;line-height:1.4;white-space:pre-wrap"></div>
|
|
<div style="display:flex;gap:6px;margin-top:6px">
|
|
<button id="s-call-debug-copy" class="secondary-btn" style="flex:1">Copy log</button>
|
|
<button id="s-call-debug-share" class="secondary-btn" style="flex:1">Share</button>
|
|
<button id="s-call-debug-clear" class="secondary-btn" style="flex:1">Clear log</button>
|
|
</div>
|
|
<small id="s-call-debug-copy-status" style="display:block;margin-top:4px;color:var(--text-dim);font-size:10px"></small>
|
|
<small style="color:var(--text-dim);display:block;margin-top:4px">
|
|
Rolling buffer of the last 200 call-flow events. Turned off by
|
|
default — the GUI overlay only populates when the checkbox above
|
|
is on, but logcat (adb) always keeps a copy regardless.
|
|
</small>
|
|
</div>
|
|
<div class="settings-section">
|
|
<h3>Identity</h3>
|
|
<div class="setting-row">
|
|
<span class="setting-label">Fingerprint</span>
|
|
<span id="s-fingerprint" class="fp-display-large"></span>
|
|
</div>
|
|
<div class="setting-row">
|
|
<span class="setting-label">Identity file</span>
|
|
<span class="fp-display">~/.wzp/identity</span>
|
|
</div>
|
|
</div>
|
|
<div class="settings-section">
|
|
<h3>Network</h3>
|
|
<div class="setting-row">
|
|
<span class="setting-label">Public address</span>
|
|
<span id="s-reflected-addr" class="fp-display">(not queried)</span>
|
|
<button id="s-reflect-btn" class="secondary-btn">Detect</button>
|
|
</div>
|
|
<small style="color:var(--text-dim);display:block;margin-top:4px">
|
|
Asks the registered relay to echo back the IP:port it sees for this
|
|
connection (QUIC-native NAT reflection, replaces STUN).
|
|
</small>
|
|
<div class="setting-row" style="margin-top:10px">
|
|
<span class="setting-label">NAT type</span>
|
|
<span id="s-nat-type" class="fp-display">(not detected)</span>
|
|
<button id="s-nat-detect-btn" class="secondary-btn">Detect NAT</button>
|
|
</div>
|
|
<div id="s-nat-probes" style="margin-top:6px;font-size:11px;color:var(--text-dim)"></div>
|
|
<small style="color:var(--text-dim);display:block;margin-top:4px">
|
|
Probes every configured relay in parallel and compares the results
|
|
to classify the NAT: cone (P2P viable), symmetric (must relay),
|
|
multiple, or unknown.
|
|
</small>
|
|
</div>
|
|
<div class="settings-section">
|
|
<h3>Recent Rooms</h3>
|
|
<div id="s-recent-rooms" class="recent-rooms-list"></div>
|
|
<button id="s-clear-recent" class="secondary-btn">Clear History</button>
|
|
</div>
|
|
<button id="settings-save" class="primary">Save</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Manage Relays dialog -->
|
|
<div id="relay-dialog" class="hidden">
|
|
<div class="settings-card relay-dialog-card">
|
|
<div class="settings-header">
|
|
<h2>Manage Relays</h2>
|
|
<button id="relay-dialog-close" class="icon-btn">×</button>
|
|
</div>
|
|
<div id="relay-dialog-list" class="relay-dialog-list"></div>
|
|
<div class="relay-add-row">
|
|
<div class="relay-add-inputs">
|
|
<input id="relay-add-name" type="text" placeholder="Name" />
|
|
<input id="relay-add-addr" type="text" placeholder="host:port" />
|
|
</div>
|
|
<button id="relay-add-btn" class="primary">Add Relay</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Key changed warning dialog -->
|
|
<div id="key-warning" class="hidden">
|
|
<div class="settings-card key-warning-card">
|
|
<div class="key-warning-icon">⚠</div>
|
|
<h2>Server Key Changed</h2>
|
|
<p class="key-warning-text">The relay's identity has changed since you last connected. This usually happens when the server was restarted, but could also indicate a security issue.</p>
|
|
<div class="key-warning-fps">
|
|
<div class="key-fp-row">
|
|
<span class="key-fp-label">Previously known</span>
|
|
<code id="kw-old-fp" class="key-fp"></code>
|
|
</div>
|
|
<div class="key-fp-row">
|
|
<span class="key-fp-label">New key</span>
|
|
<code id="kw-new-fp" class="key-fp"></code>
|
|
</div>
|
|
</div>
|
|
<div class="key-warning-actions">
|
|
<button id="kw-accept" class="primary">Accept New Key</button>
|
|
<button id="kw-cancel" class="secondary-btn">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script type="module" src="/src/main.ts"></script>
|
|
</body>
|
|
</html>
|