chrome.runtime.sendMessage serializes with a JSON-like algorithm that drops
ArrayBuffer/TypedArray payloads — they arrive as empty objects, so a carrier
JPEG reached the service worker as 0 bytes and failed with the opaque WASM
error "no SOF marker found in JPEG". This broke extension vault creation.
- shared/message-binary.ts: encodeBinary/decodeBinary deep-walk a message tree
and base64-envelope every binary buffer so it survives the channel (chunked
base64 to avoid stack overflow on multi-MB buffers); plain number[] untouched.
- Wire encode/decode at all four message boundaries: popup, service-worker
onMessage listener (decode request, encode response), setup swSend, vault
postToServiceWorker.
- handleCreateVault: magic-byte guard classifies empty / non-JPEG carriers into
actionable errors (carrier_image_empty / carrier_image_not_jpeg) instead of
the opaque WASM error; error-copy.ts adds user-facing text; setup errors now
route through lookupErrorCopy.
- router SETUP_ALLOWED: add create_vault / attach_vault / generate_recovery_qr —
the Phase-3 setup wizard sends these from the setup page and they were being
rejected as unauthorized_sender.
- Tests: message-binary round-trip suite (7) + carrier-guard (2) + setup
allowlist (3). 435/435 vitest, build:all clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pe8qw5KePDqAEBsAxnVQuJ
Closes three audit gaps in one pass:
1. Sync now button in the popup settings view (📤). Triggers the existing
{ type: 'sync' } SW message and surfaces success / failure inline. The
SW message was already wired but had no UI entry point.
2. Device registration from the popup. The "Register this device" button
on the devices view used to error out with a "not yet implemented"
message; it now opens an inline name input (default = browser+OS), and
on confirm sends a new register_this_device SW message that generates
an ed25519 keypair via WASM, persists private_key + name to
chrome.storage.local, and writes the public key to the remote
devices.json. No setup-wizard detour.
3. Vault tab is now an authorized sender for popup-only SW messages. The
router accepts vault.html alongside popup.html, so the fullscreen tab
can drive the same flows. Test covers acceptance from the vault tab.
New SW message: register_this_device { name }. Added to PopupMessage and
POPUP_ONLY_TYPES, handled in router/popup-only.ts.
Tests: 5 new vitest cases (3 in settings.test.ts, 2 in devices.test.ts)
+ 1 router test for vault-tab acceptance. All 194 extension tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The router was doing exact URL match for popup.html, but when
opened in a tab with params (?view=add&type=card), it failed.
Changed to startsWith match like setup.html already uses.
Co-Authored-By: Claude <noreply@anthropic.com>
Bug: setup tab's zxcvbn meter silently stayed at score=-1 because the
router's isSetup exception only allowed save_setup, so rate_passphrase
got unauthorized_sender. Result: the "create vault" button stayed
disabled forever even with a strong passphrase.
Fix: add a narrow SETUP_ALLOWED set containing save_setup,
rate_passphrase, and is_unlocked (step-4 extension detection). Reject
everything else from the setup tab. Also clean up setup.ts's unlock
call — it was passing the raw 32-byte imageSecret where JPEG bytes with
embedded secret are required; the Rust-side unlock calls imgsecret::
extract internally.
Diagnostic logging across the message path so the next silent failure
speaks up:
- [relicario setup] staged logs through vault-init; console.error
with the failure stage name in the UI banner.
- [relicario setup] rate_passphrase lastError / rejected / threw
branches each log their own warning.
- [relicario router] console.warn on unauthorized_sender (with sender
classification) and unknown_message_type.
- [relicario sw] first-message wasm init announced; per-message
non-ok result logged; thrown errors console.error'd.
Tests: +3 setup-allowlist tests (rate_passphrase accepted, is_unlocked
accepted, fill_credentials + unlock rejected). 55/55 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>