66b318659107

fix: DESTROY_VAULT preserves peach_license and peach_pro_celebrated — Pro survives vault reset; drop unused beforeEach import unblocking typecheck (v3.6.110)

time
2026-05-28 20:05 UTC · 5 months ago
machine
koh2
parent
49d919333ee0
6 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/messages.test.ts+34 −0src/background/messages.ts+15 −0src/shared/browser.test.ts+1 −1
manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22   "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33   "manifest_version": 3,44   "name": "Peach Password Manager",5-  "version": "3.6.109",5+  "version": "3.6.110",66   "description": "Zero-knowledge password manager",77   "browser_specific_settings": {88     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "3.6.109",4+  "version": "3.6.110",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": "3.6.109",3+  "version": "3.6.110",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/background/messages.test.ts+34 −0
@@ -75,6 +75,9 @@7575               delete storage[key];7676             }7777           }),78+          clear: vi.fn(async () => {79+            for (const key of Object.keys(storage)) delete storage[key];80+          }),7881         },7982         session: {8083           get: vi.fn(async (key?: string | string[]) => {@@ -766,5 +769,36 @@766769       payload: { category: "Bug report", message: "hi", version: "2.1.0", browser: "Chrome" },767770     });768771     expect(alarmsCreate).not.toHaveBeenCalled();772+  });773+774+  it("DESTROY_VAULT preserves the Pro license and celebration flag", async () => {775+    const vault = await import("./vault");776+    await vault.createVault("correct-pass-12345");777+    await vault.addEntry({ title: "Gmail", username: "june", password: "s", url: "https://mail.google.com" });778+779+    // Simulate an activated Pro license + already-celebrated marker.780+    storage.peach_license = "PEACH-PRO-TEST-KEY";781+    storage.peach_pro_celebrated = true;782+783+    const res = await dispatch({ type: "DESTROY_VAULT" });784+    expect(res).toMatchObject({ ok: true });785+786+    // Vault data is gone…787+    expect(storage.peach_vault_a).toBeUndefined();788+    // …but the paid license and celebration flag survive the wipe.789+    expect(storage.peach_license).toBe("PEACH-PRO-TEST-KEY");790+    expect(storage.peach_pro_celebrated).toBe(true);791+  });792+793+  it("DESTROY_VAULT with no license clears storage without resurrecting keys", async () => {794+    const vault = await import("./vault");795+    await vault.createVault("correct-pass-12345");796+797+    const res = await dispatch({ type: "DESTROY_VAULT" });798+    expect(res).toMatchObject({ ok: true });799+800+    expect(storage.peach_vault_a).toBeUndefined();801+    expect(storage.peach_license).toBeUndefined();802+    expect(storage.peach_pro_celebrated).toBeUndefined();769803   });770804 });

view file

src/background/messages.ts+15 −0
@@ -950,9 +950,24 @@950950         // onboarded flag, history, sync state, declined cards, recall, etc.);951951         // session.clear() wipes pending passkey requests + pro-notice queues.952952         // Caller (Panic.tsx) handles UI navigation back to onboarding.953+        //954+        // The Pro license is the one thing that must survive: the user paid for955+        // it, and destroying a vault (panic wipe, fresh start) is not a refund.956+        // Capture it before the clear and restore it after. peach_pro_celebrated957+        // rides along so the Pro celebration doesn't replay on the next unlock.958+        const preserved = await chrome.storage.local.get([959+          "peach_license",960+          "peach_pro_celebrated",961+        ]);953962         vault.lock();954963         await storageClear();955964         await vault.clearVaultHistory();965+        const toRestore: Record<string, unknown> = {};966+        if (preserved.peach_license !== undefined) toRestore.peach_license = preserved.peach_license;967+        if (preserved.peach_pro_celebrated !== undefined) toRestore.peach_pro_celebrated = preserved.peach_pro_celebrated;968+        if (Object.keys(toRestore).length > 0) {969+          await chrome.storage.local.set(toRestore);970+        }956971         if (chrome.storage.session) {957972           await chrome.storage.session.clear();958973         }

view file

src/shared/browser.test.ts+1 −1
@@ -9,7 +9,7 @@99  * surfaces match the upstream browser.1010  */1111 12-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";12+import { afterEach, describe, expect, it, vi } from "vitest";1313 1414 import { detectBrowser, firefoxVersion } from "./browser";1515 

view file