91e964e65906

fix: propagate mesh relay settings (v4.0.45)

time
2026-07-26 04:38 UTC · 3 months ago
machine
koh2
parent
c6c9978bbb7c
8 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 −3
manifest.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": {

view file

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",

view file

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",

view file

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 () => {

view file

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 from

view file

src/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 () => {

view file

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 }

view file

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"

view file