e5e254e53ce7
fix: Add Firefox data collection consent manifest (v3.6.65)
- time
- 2026-05-20 16:42 UTC · 5 months ago
- machine
- koh2
- parent
- 2af360f19338
↓ peach-browser-e5e254e5.tar.gzatlas at this save
files changed6 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 −0manifest.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": {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",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",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": {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, "'");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];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 };