d1b56b8c77de
remove dead handlers START_PAIRING/START_PEER_DISCOVERY/STOP_PEER_DISCOVERY/INITIATE_PAIR_WITH_PEER/CONNECT_WITH_CODE; drop desktopTriggerSync stub; strip MessageType; simplify SyncPairing.tsx — show-code-only flow
- time
- 2026-05-09 00:55 UTC · 5 months ago
- machine
- koh2
- parent
- 8d53fdb48f58
↓ peach-browser-d1b56b8c.tar.gzatlas at this save
files changed4 files
src/background/messages.test.ts+0 −28src/background/messages.ts+5 −33src/popup/views/SyncPairing.tsx+7 −129src/shared/types.ts+0 −5src/background/messages.test.ts+0 −28
@@ -697,34 +697,6 @@697697 expect(hits[0]!.useCount).toBe(3);698698 });699699 700- it("START_PEER_DISCOVERY is Pro-gated and rejects free users", async () => {701- const res = await dispatch({ type: "START_PEER_DISCOVERY" });702- expect(res.ok).toBe(false);703- expect((res as { error?: string }).error).toBe("pro_required");704- });705-706- it("INITIATE_PAIR_WITH_PEER is Pro-gated and rejects free users", async () => {707- const res = await dispatch({708- type: "INITIATE_PAIR_WITH_PEER",709- payload: {710- peer: {711- deviceId: "p1",712- deviceName: "iPhone",713- host: "192.168.1.10",714- port: 44100,715- x25519PublicKey: "abc",716- },717- },718- });719- expect(res.ok).toBe(false);720- expect((res as { error?: string }).error).toBe("pro_required");721- });722-723- it("STOP_PEER_DISCOVERY succeeds even when discovery never started", async () => {724- const res = await dispatch({ type: "STOP_PEER_DISCOVERY" });725- expect(res.ok).toBe(true);726- });727-728700 it("DEBUG_INJECT_INCOMING_PAIRING + GET returns the request and 60s expiry window", async () => {729701 const inject = await dispatch({730702 type: "DEBUG_INJECT_INCOMING_PAIRING",src/background/messages.ts+5 −33
@@ -680,11 +680,6 @@680680 681681 return { ok: true, data: { pairedDevices, trustedNetworks, status, lastSyncAt, isPro: pro } };682682 }683- case "START_PAIRING": {684- const gate = await requirePro("sync");685- if (!gate.allowed) return { ok: false, error: "pro_required" };686- return { ok: true };687- }688683 case "ACCEPT_PAIRING": {689684 // One-time mobile→desktop transfer is free; only continuous sync gates on Pro.690685 return { ok: true };@@ -740,14 +735,6 @@740735 }741736 return { ok: true, data: { accepted: accept, deviceName: request.deviceName } };742737 }743- case "START_PEER_DISCOVERY": {744- const gate = await requirePro("sync");745- if (!gate.allowed) return { ok: false, error: "pro_required" };746- return { ok: false, error: "no_native_host" };747- }748- case "STOP_PEER_DISCOVERY": {749- return { ok: true };750- }751738 case "START_PAIRING_SERVER": {752739 // One-time mobile→desktop transfer is free; only continuous sync gates on Pro.753740 const token = generateSixDigitToken();@@ -761,20 +748,10 @@761748 } catch { /* best-effort */ }762749 return { ok: true };763750 }764- case "CONNECT_WITH_CODE": {765- const gate = await requirePro("sync");766- if (!gate.allowed) return { ok: false, error: "pro_required" };767- return { ok: false, error: "no_native_host" };768- }769751 case "GET_RECENT_PAIRING": {770752 const recent = await consumeRecentPairing();771753 return { ok: true, data: recent };772754 }773- case "INITIATE_PAIR_WITH_PEER": {774- const gate = await requirePro("sync");775- if (!gate.allowed) return { ok: false, error: "pro_required" };776- return { ok: false, error: "no_native_host" };777- }778755 case "DEBUG_INJECT_INCOMING_PAIRING": {779756 // Manual E2E rehearsal hook. Inserts a synthetic incoming pairing780757 // request into session storage so Phase 7 banner UX can be exercised@@ -959,7 +936,11 @@959936 bridge?.ensureServerRunning?.();960937 return { ok: true, data: { syncedCount: 0, mode: "server" as const } };961938 }962- return await desktopTriggerSync({ getLastKnownIp: () => null });939+ // Desktop has no outbound sync path — paired devices are mobile, and940+ // the mobile side initiates. From desktop, "trigger sync" just means941+ // "make sure the pairing server is reachable", which is handled by942+ // START_PAIRING_SERVER. Surface no_paired_devices to keep callers tidy.943+ return { ok: false, error: "no_paired_devices" };963944 }964945 // Android-only: biometric unlock with pre-derived key.965946 case "UNLOCK_WITH_KEY": {@@ -1027,15 +1008,6 @@10271008 }10281009 10291010 // --- Mobile platform helpers ---1030-1031-// Desktop: triggers sync for all paired devices.1032-async function desktopTriggerSync(deps: {1033- getLastKnownIp: () => string | null;1034-}): Promise<{ ok: true; syncedCount: number } | { ok: false; error: string }> {1035- const ip = deps.getLastKnownIp();1036- if (!ip) return { ok: false, error: "no_paired_devices" };1037- return { ok: true, syncedCount: 0 };1038-}10391011 10401012 // Android: populates the in-memory vault used by PeachAutofillService.10411013 async function publishInMemoryVault(entries: VaultEntry[]): Promise<void> {src/popup/views/SyncPairing.tsx+7 −129
@@ -2,15 +2,11 @@22 import { sendMessage } from "../App";33 import type { PairedDevice } from "../../shared/sync-types";44 5-const SHOW_MODE_AVAILABLE = true;6-75 interface SyncPairingProps {86 onClose: () => void;97 onPaired: (device: PairedDevice) => void;108 }119 12-type Mode = "show" | "enter";13-1410 function Spinner() {1511 return <span class="peach-pairing-spinner" />;1612 }@@ -21,15 +17,10 @@2117 }2218 2319 export function SyncPairingView(props: SyncPairingProps) {24- const [mode, setMode] = createSignal<Mode>(SHOW_MODE_AVAILABLE ? "show" : "enter");2520 const [code, setCode] = createSignal<string | null>(null);2621 const [expiresAt, setExpiresAt] = createSignal<number | null>(null);2722 const [showError, setShowError] = createSignal<string | null>(null);2823 29- const [enteredCode, setEnteredCode] = createSignal("");30- const [connecting, setConnecting] = createSignal(false);31- const [enterError, setEnterError] = createSignal<string | null>(null);32-3324 const [pairedDevice, setPairedDevice] = createSignal<PairedDevice | null>(null);3425 3526 let expiryTimer: ReturnType<typeof setTimeout> | null = null;@@ -44,7 +35,6 @@4435 function handleRuntimeMessage(msg: unknown) {4536 const m = msg as { type?: string; payload?: { deviceName?: string } };4637 if (m?.type === "PAIRING_COMPLETED" && m.payload?.deviceName) {47- // Background completed an auto-accept while we were on the show screen.4838 const device: PairedDevice = {4939 deviceId: m.payload.deviceName,5040 displayName: m.payload.deviceName,@@ -100,66 +90,6 @@10090 await startShow();10191 }10292 103- async function switchTo(target: Mode) {104- if (mode() === target) return;105- if (target === "enter") {106- await stopShow();107- setEnterError(null);108- setEnteredCode("");109- setMode("enter");110- } else {111- setEnterError(null);112- setMode("show");113- await startShow();114- }115- }116-117- function onEnterInput(e: InputEvent) {118- const target = e.currentTarget as HTMLInputElement;119- const raw = target.value.replace(/[^0-9]/g, "").slice(0, 6);120- setEnteredCode(formatCode(raw));121- if (enterError()) setEnterError(null);122- }123-124- function digitsEntered(): string {125- return enteredCode().replace(/\D/g, "");126- }127-128- async function connect() {129- const token = digitsEntered();130- if (token.length !== 6) return;131- setConnecting(true);132- setEnterError(null);133- const res = await sendMessage({134- type: "CONNECT_WITH_CODE",135- payload: { token },136- });137- setConnecting(false);138- const response = res as { ok: boolean; error?: string; data?: { deviceName?: string } };139- if (!response.ok) {140- const msg =141- response.error === "no peer responded to pairing code" ||142- (response.error ?? "").includes("resolve failed")143- ? "Couldn't find that device. Make sure both are on the same WiFi."144- : response.error === "pair_rejected"145- ? "The other device declined."146- : response.error === "invalid_code"147- ? "Enter all six digits."148- : "Couldn't connect. Try again.";149- setEnterError(msg);150- return;151- }152- const name = response.data?.deviceName ?? "Peach";153- const device: PairedDevice = {154- deviceId: name,155- displayName: name,156- x25519PublicKey: "",157- pairedAt: Date.now(),158- };159- setPairedDevice(device);160- props.onPaired(device);161- }162-16393 onMount(() => {16494 chrome.runtime.onMessage.addListener(handleRuntimeMessage);16595 void startShow();@@ -173,15 +103,16 @@173103 // closes. The background clears it on success or vault-lock.174104 });175105 106+ // expiresAt is read by callers that want to display a countdown; keep the107+ // signal even though this view itself only uses it as a refresh trigger.108+ void expiresAt;109+176110 return (177111 <div class="peach-overlay-view ov-enter-right">178112 <div class="peach-overlay-header">179113 <button180114 class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"181115 onClick={() => {182- // Closing the popup is fine; if user explicitly hits back, also183- // tear down the active pairing server so they don't leave a token184- // active without realising.185116 void sendMessage({ type: "STOP_PAIRING_SERVER" });186117 props.onClose();187118 }}@@ -219,7 +150,7 @@219150 )}220151 </Show>221152 222- <Show when={!pairedDevice() && mode() === "show"}>153+ <Show when={!pairedDevice()}>223154 <div class="peach-pairing-content peach-pairing-code-screen">224155 <p class="peach-pairing-headline">Add a device</p>225156 @@ -245,67 +176,14 @@245176 </Show>246177 247178 <p class="peach-pairing-subline peach-pairing-subline--center">248- Open Peach on your other device, tap179+ Open Peach on your other device and scan249180 <br />250- Add a device, and enter this code.181+ this code to pair.251182 </p>252183 <p class="peach-pairing-hint">Both devices must be on the same WiFi.</p>253184 254185 <Show when={showError()}>255186 <p class="peach-pairing-error peach-pairing-error--muted">{showError()}</p>256- </Show>257-258- <Show when={SHOW_MODE_AVAILABLE}>259- <button260- class="peach-pairing-toggle"261- onClick={() => void switchTo("enter")}262- >263- I have a code to enter →264- </button>265- </Show>266- </div>267- </Show>268-269- <Show when={!pairedDevice() && mode() === "enter"}>270- <div class="peach-pairing-content peach-pairing-code-screen">271- <p class="peach-pairing-headline">Add a device</p>272-273- <input274- class="peach-pairing-code-input"275- type="text"276- inputmode="numeric"277- autocomplete="off"278- autocorrect="off"279- spellcheck={false}280- placeholder="000 000"281- value={enteredCode()}282- onInput={onEnterInput}283- disabled={connecting()}284- aria-label="enter pairing code"285- />286-287- <button288- class="peach-btn peach-btn--accent peach-btn--md peach-lock-submit"289- onClick={() => void connect()}290- disabled={digitsEntered().length !== 6 || connecting()}291- >292- {connecting() ? "Connecting…" : "Connect →"}293- </button>294-295- <p class="peach-pairing-hint">Both devices must be on the same WiFi.</p>296-297- <Show when={enterError()}>298- <p class="peach-pairing-error peach-pairing-error--muted">{enterError()}</p>299- </Show>300-301- <Show when={SHOW_MODE_AVAILABLE}>302- <button303- class="peach-pairing-toggle"304- onClick={() => void switchTo("show")}305- disabled={connecting()}306- >307- ← Show my code instead308- </button>309187 </Show>310188 </div>311189 </Show>src/shared/types.ts+0 −5
@@ -246,7 +246,6 @@246246 | "GET_SITE_PASSWORD_POLICY"247247 | "GET_SYNC_STATE"248248 | "TRIGGER_SYNC"249- | "START_PAIRING"250249 | "ACCEPT_PAIRING"251250 | "REMOVE_PAIRED_DEVICE"252251 | "REMOVE_TRUSTED_NETWORK"@@ -255,12 +254,8 @@255254 | "GET_INCOMING_PAIRING_REQUEST"256255 | "RESPOND_INCOMING_PAIRING"257256 | "DEBUG_INJECT_INCOMING_PAIRING"258- | "START_PEER_DISCOVERY"259- | "STOP_PEER_DISCOVERY"260- | "INITIATE_PAIR_WITH_PEER"261257 | "START_PAIRING_SERVER"262258 | "STOP_PAIRING_SERVER"263- | "CONNECT_WITH_CODE"264259 | "GET_RECENT_PAIRING"265260 | "GET_PENDING_PASSKEY"266261 | "APPROVE_PASSKEY"