35602e4a18c2

seed the reconnect address cache when a vault is adopted, so an adopted browser can reach its first sync instead of failing subnet_unknown forever in the MV3 service worker (v4.0.36)

time
2026-07-25 00:49 UTC · 3 months ago
machine
koh2
agent
claude-opus-5
session
adoption reconnect cache seed
parent
facf0df18c16
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+17 −8
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.0.35",4+  "version": "4.0.36",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.0.35",4+  "version": "4.0.36",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "4.0.35",3+  "version": "4.0.36",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/App.tsx+17 −8
@@ -1192,16 +1192,18 @@11921192     confirmSAS(sas: string): Promise<boolean>;11931193     createVault(rootKeyHex: string): Promise<{ vaultId: string }>;11941194   }) {1195-    const [{ dialPairingChannel, openPairingSocket }, { requestAdoption }] = await Promise.all([1196-      import("../shared/pairing-dial"),1197-      import("../shared/vault-adopt"),1198-    ]);1195+    const [{ dialPairingChannel, openPairingSocket }, { requestAdoption }, { setLastKnownIp }] =1196+      await Promise.all([1197+        import("../shared/pairing-dial"),1198+        import("../shared/vault-adopt"),1199+        import("../background/paired-devices"),1200+      ]);11991201 12001202     // No `findHost`: adoption only accepts a pairing code, which carries its12011203     // own address. There is nothing to probe the LAN for.12021204     const dial = await dialPairingChannel(args.code, { openSocket: openPairingSocket });12031205     if (!dial.ok) throw new Error(dial.error);1204-    const { channel, sas, socket } = dial.dialled;1206+    const { channel, sas, socket, host } = dial.dialled;12051207 12061208     try {12071209       // Same out-of-band gate as device pairing, and for the same reason: the@@ -1223,6 +1225,16 @@12231225           const res = await sendMessage({ type: "COMPLETE_PAIRING", payload: { paired } });12241226           const r = res as { ok: boolean; error?: string };12251227           if (!r.ok) throw new Error(r.error ?? "complete_pairing_failed");1228+          // Seed the reconnect cache with the address we just dialled, exactly as1229+          // device pairing does (code-pairing.ts). Without it an adopted browser1230+          // never syncs on Chrome: the runner lives in the MV3 service worker,1231+          // its probe fallback needs RTCPeerConnection to find the /24, and that1232+          // API does not exist in a service worker — so getSubnet() always1233+          // returns null and connect() throws subnet_unknown forever. The cache1234+          // is the only route that ever reaches the peer.1235+          if (paired.longTermPublicKeyHex) {1236+            await setLastKnownIp(paired.longTermPublicKeyHex, host).catch(() => undefined);1237+          }12261238         },12271239         applyLicense: async (license) => {12281240           const res = (await sendMessage({@@ -1236,9 +1248,6 @@12361248         },12371249       });12381250       if (!source) throw new Error("adoption_rejected");1239-      // No last-known-IP cache write here: the first sync round falls back to1240-      // the subnet probe and persists the address itself once it lands, so a1241-      // popup→background message just to save that one scan isn't worth it.12421251     } finally {12431252       socket.close();12441253     }

view file