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
4 files src/background/messages.test.ts+0 −28src/background/messages.ts+5 −33src/popup/views/SyncPairing.tsx+7 −129src/shared/types.ts+0 −5
src/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",

view file

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> {

view file

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>

view file

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"

view file