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
↓ peach-browser-35602e4a.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+17 −8manifest.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": {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",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",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 }