e5e254e53ce7

fix: Add Firefox data collection consent manifest (v3.6.65)

- Declare Firefox data_collection_permissions for AMO validation - Gate Settings feedback transmission on Firefox data consent when available - Add contract coverage for denied consent

time
2026-05-20 16:42 UTC · 5 months ago
machine
koh2
parent
2af360f19338
6 files manifest.firefox.json+10 −2manifest.json+1 −1package-lock.json+2 −2package.json+1 −1src/popup/views/Settings.tsx+29 −0src/popup/views/contracts.test.tsx+58 −0
manifest.firefox.json+10 −2
@@ -2,11 +2,19 @@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",5-  "version": "3.6.64",5+  "version": "3.6.65",66   "description": "Zero-knowledge password manager",77   "browser_specific_settings": {88     "gecko": {9-      "id": "[email protected]"9+      "id": "[email protected]",10+      "data_collection_permissions": {11+        "required": [12+          "none"13+        ],14+        "optional": [15+          "technicalAndInteraction"16+        ]17+      }1018     }1119   },1220   "action": {

view file

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

view file

package-lock.json+2 −2
@@ -1,12 +1,12 @@11 {22   "name": "peach",3-  "version": "3.6.64",3+  "version": "3.6.65",44   "lockfileVersion": 3,55   "requires": true,66   "packages": {77     "": {88       "name": "peach",9-      "version": "3.6.64",9+      "version": "3.6.65",1010       "dependencies": {1111         "@bokuweb/zstd-wasm": "^0.0.27",1212         "@stablelib/blake2s": "^2.0.1",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "3.6.64",3+  "version": "3.6.65",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/popup/views/Settings.tsx+29 −0
@@ -73,6 +73,7 @@7373 }7474 7575 const FEEDBACK_CATEGORIES = ["Bug report", "Feature request", "Question", "Other"];76+const FEEDBACK_DATA_PERMISSION = "technicalAndInteraction";7677 const MAX_FEEDBACK_MESSAGE_LENGTH = 4000;7778 7879 function sanitizeFeedbackMessage(value: string): string {@@ -87,6 +88,28 @@8788     .replace(/'/g, "&#39;");8889 }8990 91+type DataCollectionPermissionApi = {92+  permissions?: {93+    getAll?: () => Promise<{ data_collection?: string[] }>;94+    request?: (permissions: { data_collection: string[] }) => Promise<boolean>;95+  };96+};97+98+async function requestFeedbackDataPermission(): Promise<boolean> {99+  const api = ((globalThis as { browser?: DataCollectionPermissionApi }).browser ??100+    (typeof chrome !== "undefined" ? chrome : undefined)) as DataCollectionPermissionApi | undefined;101+  if (!api?.permissions?.getAll) return true;102+103+  try {104+    const permissions = await api.permissions.getAll();105+    if (!("data_collection" in permissions)) return true;106+    if (permissions.data_collection?.includes(FEEDBACK_DATA_PERMISSION)) return true;107+    return await (api.permissions.request?.({ data_collection: [FEEDBACK_DATA_PERMISSION] }) ?? Promise.resolve(false));108+  } catch {109+    return false;110+  }111+}112+90113 function formatActivatedSince(issued: string | undefined): string {91114   if (!issued) return "Active";92115   try {@@ -596,6 +619,12 @@596619     setFeedbackState('sending');597620 598621     try {622+      const hasDataPermission = await requestFeedbackDataPermission();623+      if (!hasDataPermission) {624+        setFeedbackState('idle');625+        return;626+      }627+599628       const category = FEEDBACK_CATEGORIES.includes(fbCategory())600629         ? fbCategory()601630         : FEEDBACK_CATEGORIES[0];

view file

src/popup/views/contracts.test.tsx+58 −0
@@ -429,6 +429,64 @@429429     expect(container.querySelector(".peach-feedback-submit")).toBeNull();430430   });431431 432+  it("Settings feedback does not transmit when Firefox data consent is denied", async () => {433+    sendMessageMock.mockResolvedValue({ ok: true, data: null });434+    const fetchMock = vi.fn();435+    const requestMock = vi.fn(async () => false);436+    vi.stubGlobal("fetch", fetchMock);437+    vi.stubGlobal("browser", {438+      permissions: {439+        getAll: vi.fn(async () => ({ data_collection: [] })),440+        request: requestMock,441+      },442+    });443+444+    const container = mount(445+      <SettingsView446+        entriesCount={1}447+        breachMonitoring={true}448+        lockOnClose={true}449+        autoLockMinutes={5}450+        trashRetentionDays={30}451+        theme="dark"452+        accentColor="peach"453+        textSize="medium"454+        reduceMotion={false}455+        initialSection="feedback"456+        onBreachMonitoringChange={vi.fn()}457+        onLockOnCloseChange={vi.fn()}458+        onAutoLockMinutesChange={vi.fn()}459+        onTrashRetentionChange={vi.fn()}460+        onThemeChange={vi.fn()}461+        onAccentChange={vi.fn()}462+        onTextSizeChange={vi.fn()}463+        onReduceMotionChange={vi.fn()}464+        shortcuts={DEFAULT_BINDINGS}465+        onShortcutsChange={vi.fn()}466+        onBack={vi.fn()}467+        onImport={vi.fn()}468+        onExport={vi.fn()}469+        onBreachCheckDone={vi.fn()}470+      />471+    );472+    await flushView();473+474+    inputText(475+      container.querySelector(".peach-feedback-textarea") as HTMLTextAreaElement,476+      "Please improve the feedback flow."477+    );478+    const sendButton = Array.from(container.querySelectorAll("button")).find(479+      (candidate) => candidate.textContent?.trim() === "Send"480+    ) as HTMLButtonElement;481+    sendButton.click();482+483+    await vi.waitFor(() => {484+      expect(requestMock).toHaveBeenCalledWith({ data_collection: ["technicalAndInteraction"] });485+    });486+    expect(fetchMock).not.toHaveBeenCalled();487+    expect(container.querySelector(".peach-feedback-textarea")).not.toBeNull();488+  });489+432490   it("Settings sends CHANGE_PASSWORD and forwards auto-lock/theme changes through callbacks", async () => {433491     sendMessageMock.mockImplementation(async (msg: unknown) => {434492       const m = msg as { type: string };

view file