91e964e65906
fix: propagate mesh relay settings (v4.0.45)
- time
- 2026-07-26 04:38 UTC · 3 months ago
- machine
- koh2
- parent
- c6c9978bbb7c
↓ peach-browser-91e964e6.tar.gzatlas at this save
files changed8 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/paired-devices.test.ts+19 −0src/background/paired-devices.ts+16 −0src/background/relay-settings.test.ts+24 −3src/background/relay-settings.ts+2 −0src/popup/views/Settings.tsx+4 −3manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.0.44",4+ "version": "4.0.45",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.44",4+ "version": "4.0.45",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.44",3+ "version": "4.0.45",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/paired-devices.test.ts+19 −0
@@ -12,6 +12,7 @@1212 removePairedDeviceById,1313 setLastKnownIp,1414 setLastSyncAt,15+ setRelayUrlForAllPairedDevices,1516 upsertPairedDevice,1617 } from "./paired-devices";1718 @@ -116,6 +117,24 @@116117 expect(found?.lastSyncedAt).toBe(1700001000000);117118 const untouched = await findPairedDeviceById(other.deviceId);118119 expect(untouched?.lastSyncedAt).toBeUndefined();120+ });121+122+ it("moves every existing pair to a new mesh relay", async () => {123+ const other: PairedDevice = {124+ ...SAMPLE,125+ deviceId: "BBB-222",126+ longTermPublicKeyHex: "1".repeat(64),127+ relayUrl: "wss://old-relay.example",128+ };129+ await upsertPairedDevice(SAMPLE);130+ await upsertPairedDevice(other);131+132+ await setRelayUrlForAllPairedDevices("wss://new-relay.example/base");133+134+ expect((await listPairedDevices()).map((device) => device.relayUrl)).toEqual([135+ "wss://new-relay.example/base",136+ "wss://new-relay.example/base",137+ ]);119138 });120139 121140 it("getLastKnownIp / setLastKnownIp round-trip", async () => {src/background/paired-devices.ts+16 −0
@@ -70,6 +70,22 @@7070 }7171 7272 /**73+ * Move every existing pair to the user-selected mesh relay.74+ *75+ * Reconnect prefers the relay stored on each pair over the global fallback, so76+ * changing only the setting would leave established devices on the old relay.77+ */78+export async function setRelayUrlForAllPairedDevices(79+ relayUrl: string,80+): Promise<void> {81+ const devices = await listPairedDevices();82+ if (devices.length === 0) return;83+ await chrome.storage.local.set({84+ [REGISTRY_KEY]: devices.map((device) => ({ ...device, relayUrl })),85+ });86+}87+88+/**7389 * Removes the device from the LOCAL paired-devices registry only.7490 *7591 * SECURITY NOTE (MV-8): this does NOT prevent the removed device fromsrc/background/relay-settings.test.ts+24 −3
@@ -14,9 +14,14 @@1414 (globalThis as { chrome?: unknown }).chrome = {1515 storage: {1616 local: {17- get: vi.fn(async (keys: string[]) =>18- Object.fromEntries(keys.filter((key) => key in stored).map((key) => [key, stored[key]])),19- ),17+ get: vi.fn(async (keys: string | string[]) => {18+ const requested = Array.isArray(keys) ? keys : [keys];19+ return Object.fromEntries(20+ requested21+ .filter((key) => key in stored)22+ .map((key) => [key, stored[key]]),23+ );24+ }),2025 set: vi.fn(async (values: Record<string, unknown>) => {2126 Object.assign(stored, values);2227 }),@@ -34,6 +39,16 @@3439 });3540 3641 it("persists a normalized self-hosted wss URL and LAN-only opt-out", async () => {42+ stored.peach_paired_devices_v3 = [43+ {44+ deviceId: "phone",45+ deviceName: "Phone",46+ deviceType: "android",47+ longTermPublicKeyHex: "0".repeat(64),48+ pairedAt: 1,49+ relayUrl: "wss://old-relay.example",50+ },51+ ];3752 await expect(setRelayUrl("wss://relay.example.test/base/")).resolves.toBe(3853 "wss://relay.example.test/base",3954 );@@ -42,6 +57,12 @@4257 relayUrl: "wss://relay.example.test/base",4358 lanOnly: true,4459 });60+ expect(stored.peach_paired_devices_v3).toEqual([61+ expect.objectContaining({62+ deviceId: "phone",63+ relayUrl: "wss://relay.example.test/base",64+ }),65+ ]);4566 });4667 4768 it("rejects ws and ignores an invalid stored URL", async () => {src/background/relay-settings.ts+2 −0
@@ -1,4 +1,5 @@11 import { DEFAULT_RELAY_URL, normalizeRelayUrl } from "../shared/rendezvous";2+import { setRelayUrlForAllPairedDevices } from "./paired-devices";23 34 const RELAY_URL_KEY = "peach_relay_url_v1";45 const LAN_ONLY_KEY = "peach_sync_lan_only_v1";@@ -27,6 +28,7 @@2728 2829 export async function setRelayUrl(input: string): Promise<string> {2930 const relayUrl = normalizeRelayUrl(input);31+ await setRelayUrlForAllPairedDevices(relayUrl);3032 await chrome.storage.local.set({ [RELAY_URL_KEY]: relayUrl });3133 return relayUrl;3234 }src/popup/views/Settings.tsx+4 −3
@@ -1602,9 +1602,10 @@16021602 <p class="peach-settings-sub-label" style="margin-top:12px">CONNECTION</p>16031603 <div class="peach-settings-row peach-settings-row--top">16041604 <div class="peach-settings-row-main">1605- <span>LAN only</span>1605+ <span>Use local network only (no relay)</span>16061606 <span class="peach-settings-value" style="font-size:11px;line-height:1.5;color:var(--peach-t2)">1607- Disable internet relay fallback. Nearby-device sync still works.1607+ Sync will only work when your devices share the same local network.1608+ It won't work over VPN or when your devices are in different locations.16081609 </span>16091610 </div>16101611 <label class="peach-switch">@@ -1621,7 +1622,7 @@16211622 <span class="peach-switch-track" />16221623 </label>16231624 </div>1624- <label class="peach-label" for="sync-relay-url">relay URL</label>1625+ <label class="peach-label" for="sync-relay-url">mesh relay URL</label>16251626 <input16261627 id="sync-relay-url"16271628 class="peach-underline-input"