Skip to content

feat(desktop): import from Chrome, Edge, Brave, Vivaldi, Opera, Arc and Firefox - #7260

Open
juliusmarminge wants to merge 16 commits into
browser-profile-importfrom
browser-import-more-sources
Open

feat(desktop): import from Chrome, Edge, Brave, Vivaldi, Opera, Arc and Firefox#7260
juliusmarminge wants to merge 16 commits into
browser-profile-importfrom
browser-import-more-sources

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Aug 16, 2026

Copy link
Copy Markdown
Member

Stacked on #7255 — review that first; this PR's base is browser-profile-import.

Generalises the importer from one hardcoded browser to a source registry with two engines, taking it from 1 source to 8.

Source Engine macOS Windows Linux
Chrome, Edge, Brave, Vivaldi, Opera chromium paths only¹ paths only¹
Arc, Helium chromium
Firefox firefox

¹ Chromium off macOS reports unsupportedPlatform until its key stores land — that's the next PR.

Notes for review

Chromium forks are table entries. They share the existing extractor and differ only in paths and keychain coordinates. Those are pinned per fork rather than derived, because the forks disagree — Helium uses Helium Storage Key / Helium where the others use <Name> Safe Storage / <Name>.

Firefox is a second engine with no crypto at all. It stores cookies in plaintext cookies.sqlite, so there's no key and no consent prompt. That's Mozilla's design choice, not a control being circumvented, and it's why Firefox works identically on all three platforms while Chromium needs a per-platform credential store.

Paths resolve from an injected context, not process, so Windows and Linux layouts can be checked without running on them.

The snapshot-before-read step moved to a shared module. Both engines keep the database open with WAL, and neither may have the browser's own file opened for writing.

Testing

Verified against the machine this was written on — all seven Chromium sources detected, Chrome reporting its real profile name:

chrome  installed=true  profiles=Your Chrome
helium  installed=true  profiles=You
…

Firefox isn't installed here, so it's covered by tests against a real moz_cookies fixture rather than a claim — including one asserting the source file's mtime and size are unchanged after a read.

profiles.ini parsing has its own tests because of a genuine trap: Install* sections name a default profile without describing one, so counting them invents a directory that doesn't exist.

Note

Add Chrome, Edge, Brave, Vivaldi, Opera, Arc, and Firefox as browser import sources

  • Expands BROWSER_IMPORT_SOURCES in Sources.ts from Helium-only to all major browsers, with correct per-platform user-data paths for macOS, Windows, and Linux.
  • Adds Firefox cookie import via a new readFirefoxCookies reader in FirefoxCookies.ts that reads cookies.sqlite directly (no keychain), filtering to default-container cookies only.
  • Introduces BrowserImportPathContext to unify platform, home, and appData resolution, replacing separate platform and path arguments across isSourceInstalled, isSourceRunning, listSourceProfiles, and cookieDatabasePath.
  • Moves shared snapshotting and the ImportedCookie type into CookieDatabase.ts so both Chromium and Firefox readers share them.
  • Behavioral Change: Chromium imports are now restricted to macOS (unsupportedPlatform error on other platforms); Firefox imports are cross-platform.

Macroscope summarized 7487b3c.


Note

Medium Risk
Touches local filesystem reads, keychain-backed decryption, and session cookie writes with path traversal guards; Chromium is deliberately gated off macOS, but wrong paths or lock detection could still read live DBs or mis-report availability.

Overview
Expands browser cookie import from a single Helium entry to eight sources (Chrome, Edge, Brave, Vivaldi, Opera, Arc, Helium, Firefox) via a registry with chromium and firefox engines, pinned per-fork paths/keychain coordinates, and a chromiumSource helper for macOS/Windows/Linux layouts (including Opera under roaming AppData).

Replaces SourcePaths / sourcePaths with BrowserImportPathContext / sourcePathContext (platform, home, %APPDATA% / %LOCALAPPDATA%) so listing, install detection, running checks, and DB paths are testable off-host. Chromium imports stay macOS-only until non-mac key stores exist; Firefox is cross-platform with no keychain.

Adds Firefox: profiles.ini parsing, per-profile lock detection (not root SingletonLock), cookies.sqlite via readFirefoxCookies (default-container cookies only via originAttributes = ''). Moves WAL snapshot + ImportedCookie into CookieDatabase.ts; Chromium uses the shared type and snapshot.

BrowserImport.importCookies branches on engine, maps FirefoxCookieReadError to readFailed, and treats missing cookie DB paths as unsupportedPlatform. Contracts extend BROWSER_IMPORT_SOURCE_IDS to all eight browsers.

Reviewed by Cursor Bugbot for commit 7487b3c. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 61d7b5ea-6156-4c2c-9d62-8f971cc7e83e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 16, 2026
Comment thread apps/desktop/src/preview/BrowserImport/Sources.ts
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 12.6 KiB 15.1 KiB
Codex Thread snapshot wire 6.3 KiB 7.3 KiB
Codex Live turn WebSocket wire 6.3 KiB 7.8 KiB
Codex Live turn WebSocket decoded 51.8 KiB 66.4 KiB
Codex Live turn messages 16 21
Claude Total thread wire 12.6 KiB 15.1 KiB
Claude Thread snapshot wire 6.3 KiB 7.3 KiB
Claude Live turn WebSocket wire 6.3 KiB 7.8 KiB
Claude Live turn WebSocket decoded 52.7 KiB 66.4 KiB
Claude Live turn messages 16 21

Baseline: unavailable · PR result: 7487b3c · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from bc1215b to 973b17e Compare August 16, 2026 22:08
@github-actions github-actions Bot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 16, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One convention finding: the new Firefox cookie read path models its failure as an anonymous object literal instead of a Schema.TaggedErrorClass. Details inline.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/desktop/src/preview/BrowserImport/BrowserImport.ts Outdated
Comment thread apps/desktop/src/preview/BrowserImport/CookieDatabase.ts Outdated
Comment thread apps/desktop/src/preview/BrowserImport/Sources.ts Outdated
Comment thread apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts
@juliusmarminge
juliusmarminge marked this pull request as ready for review August 16, 2026 22:18
Comment thread apps/desktop/src/preview/BrowserImport/Sources.ts
Comment thread apps/desktop/src/preview/BrowserImport/Sources.ts
@macroscopeapp

macroscopeapp Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. New feature adding browser import support for 7 browsers with substantial new logic, cross-platform path handling, and a new Firefox cookie extraction engine. New capabilities of this scope warrant human review. An unresolved High severity finding regarding Firefox cookie domain handling also requires attention.

No code changes detected at 7487b3c. Prior analysis still applies.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from 973b17e to 1b132e4 Compare August 16, 2026 22:53
url: `${secure ? "https" : "http"}://${host}${row.path}`,
name: row.name,
value: row.value,
domain: row.host,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High BrowserImport/FirefoxCookies.ts:75

Host-only Firefox cookies are imported with domain: row.host, so Electron normalizes example.com to a domain cookie and exposes it to every subdomain. Omit domain when row.host has no leading dot (and update the cookie type if needed) to preserve the original scope.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts around line 75:

Host-only Firefox cookies are imported with `domain: row.host`, so Electron normalizes `example.com` to a domain cookie and exposes it to every subdomain. Omit `domain` when `row.host` has no leading dot (and update the cookie type if needed) to preserve the original scope.

Comment thread apps/desktop/src/preview/BrowserImport/Sources.ts
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from 3343565 to 56f1705 Compare August 16, 2026 23:10

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions: one finding on the new FirefoxCookieReadError. The tagged-error union fix from the previous round looks good; what remains is the structural context the error captures.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from 56f1705 to 4d48bd9 Compare August 16, 2026 23:14
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from 4d48bd9 to 0d85caa Compare August 16, 2026 23:22
Comment thread apps/desktop/src/preview/BrowserImport/Sources.ts
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from 7a8cf25 to 27037c9 Compare August 16, 2026 23:51

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 27037c9. Configure here.

Comment thread apps/desktop/src/preview/BrowserImport/Sources.ts
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from 27037c9 to 9328514 Compare August 16, 2026 23:59
@github-actions github-actions Bot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Aug 17, 2026
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from 9328514 to de17611 Compare August 17, 2026 01:01
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from de17611 to 333c68e Compare August 17, 2026 01:06

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One retained finding in the Firefox error model; the rest of the Effect service conventions look satisfied (namespace subpath imports, tagged errors at the failure boundary, Context.Service/make/layer order preserved, no new runtimes or injected service instances).

Posted via Macroscope — Effect Service Conventions

Comment thread apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts Outdated
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from 333c68e to c2e27ca Compare August 17, 2026 01:24
juliusmarminge and others added 16 commits August 17, 2026 03:37
The desktop app needs to read browser cookie databases and should reach for
the same Effect SQL client the server already uses rather than opening
`node:sqlite` by hand. Nothing about the client is server-specific, so it moves
to `packages/shared` and both apps import it from there.

Pure move plus import updates; the client itself is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds per-profile cookie import, with Helium on macOS as the first source.
Cookies carry the logged-in sessions, which is what makes an imported profile
useful; saved passwords are out of scope because Electron exposes no password
store to put them in.

The key is read through the in-process Keychain API rather than by shelling
out to `/usr/bin/security`, because macOS attributes both the consent prompt
and the resulting ACL grant to the binary that asks. Via the CLI the prompt
said "security" and "Always Allow" granted trust to a tool every process on
the machine can invoke; in-process it names this app and the grant belongs to
it. There is deliberately no fallback when consent is denied — the techniques
that work around it exist to defeat exactly this.

The read is untimed: macOS answers it with a modal, and a timeout racing the
user means the prompt can be approved after nothing is left listening, which
reads as "approving did nothing".

Sources pin their own coordinates rather than deriving them, because Chromium
forks disagree: Helium uses the keychain service "Helium Storage Key" /
account "Helium" where Chrome and its closer relatives use "<Name> Safe
Storage" / "<Name>".

`expires_utc` counts microseconds since 1601 and exceeds JavaScript's safe
integer range, which `node:sqlite` refuses to narrow, so the division happens
in SQL and only seconds cross the boundary. The cookie database is snapshotted
before reading, since Chromium keeps it open with WAL.

Unavailable sources are reported with a reason rather than as one generic
failure — a missing keychain item is not something approving a prompt can fix.
`unsupportedPlatform` is the one that is not a permission at all: it covers
cases like Chrome on Windows, whose App-Bound Encryption is designed to stop
this and which we will not work around.

The platform binary is staged beside the loader during packaging, mirroring
the Clerk passkey handling: pnpm nests the arch package, electron-builder only
retains top-level dependencies, and the generated loader checks for a sibling
`.node` first.

Verified end to end against a real Helium profile: 5,002 cookies imported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The list was a loose stack of rows with two competing controls and a separate
"Default browser profile" setting duplicating what the list already showed.

Profiles now render as a table. Which one is default is a badge on its row and
is changed from that row's menu, so the standalone setting is gone. Each row
also gains "Clear cookies and cache", scoped to that profile's partition.

Creating and importing collapse into one "Add profile" menu, because from the
user's side they are one decision: "I want a profile with my Helium logins in
it". Each source offers its targets directly — New profile, or any existing
one — so importing into a fresh profile no longer means creating it first and
then hunting for a second control. Previously import offered no choice of
target at all.

Incognito is not listed. It keeps nothing between launches, so it has no data
to clear, no name to edit, and no state to manage; it belongs in the menu that
opens a tab. It is also excluded as an import target, since importing into it
would be discarded on quit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sers

`importCookies` forwarded the IPC-supplied `sourceProfileDirectory` straight
into the cookie database path, so `..` segments walked out of the browser's
user-data directory and imported any cookie database reachable on disk. It is
now only honoured when the source itself reported it.

The running-browser check used `access` on Chromium's `SingletonLock`, which is
a symlink to a `<host>-<pid>` target that never exists. Following it reported
every running browser as closed, so an import could read a live, mid-write
database — Helium open on this machine was detected as closed. It now stats the
link itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The import module reached for `node:fs/promises`, `node:os`, `node:path` and
`node:sqlite` directly and threaded results through hand-rolled promises, which
meant a blanket `nodeBuiltinImport:off` on two files and failures that were
plain thrown `Error`s.

It now uses FileSystem, Path and the shared Effect SQL client, and the cookie
snapshot is a scoped resource rather than a try/finally with a cleanup
callback. `node:crypto` stays — it implements the OSCrypt primitives Chromium
uses and has no Effect equivalent — with the suppression narrowed to it and a
reason attached.

Failure reasons are a typed union in contracts rather than free strings, so the
renderer maps a reason to copy instead of substring-matching an error message.

Porting `isSourceRunning` to `FileSystem.stat` reintroduced the dangling
SingletonLock bug, because `stat` and `exists` both follow symlinks; `readLink`
is the probe that answers for the entry itself. The regression test caught it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removing a profile deleted it and wiped its cookies and cache from a single
icon-menu click, while every comparable destructive action in Settings confirms
first. It now routes through the same AlertDialog.

The Default badge resolved against the unfiltered profile list while the table
renders only non-incognito rows, so a stored default of "incognito" left the
section with no default marked at all. It now resolves against the rows that
render.

Reopening the import menu kept the previous source list on screen while the
refresh was in flight, leaving a source that had since become unavailable
selectable; the list is cleared first so the menu shows its loading state.

A cookie sidecar that exists but cannot be copied is no longer ignored
alongside the missing-file case. SQLite would open the snapshot without the
write-ahead log and return a cookie set silently missing its newest
transactions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ChromiumCookieReadError` carried only a reason and a cause, so every
`readFailed` and keychain refusal logged identically. A user with several
Chromium browsers installed had no way to tell which one refused. The database
path is now a structural attribute and the message derives from it, matching
how `BrowserSession`'s errors carry their partition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Renaming the component left three consecutive doc blocks above it, two of them
describing controls that no longer exist separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Detection keyed off the browser's user-data directory, which is not evidence
the browser exists. Installers for native messaging hosts create an empty one
for every Chromium fork they know about, so a machine with only Chrome and
Helium listed Edge, Brave, Vivaldi, Opera and Arc as importable sources — each
holding nothing but a `NativeMessagingHosts` folder.

It now keys off the cookie database, which is the thing an import actually
needs. Existence is checked without opening the file, which matters for Safari:
TCC permits `stat` on the jar inside its container but refuses a read, so
Safari is still found and the user gets the Full Disk Access prompt instead of
Safari vanishing from the list.

A source that is not on the machine is now left out of the menu rather than
shown as a dead row. Every other unavailable reason stays visible, because each
names something the user can do — quit the browser, grant access.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`importInto` created the target profile before `runImport` checked for an
environment and a bridge, so choosing an import target before the environment
resolved left a new empty profile named after the source browser and produced
no toast at all. The check now happens first and says what went wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`clearProfileData` returned silently when no environment was connected, while
the menu item stayed enabled — a dead control with no explanation. It now
reports the same way `importInto` does for the same precondition.

Also switches `browserImport` to the subpath namespace import the rest of
`packages/contracts` uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Firefox

Generalises the importer from one hardcoded browser to a source registry with
two engines.

Chromium forks are table entries: Chrome, Edge, Brave, Vivaldi, Opera, Arc and
Helium all share the existing extractor and differ only in their paths and
keychain coordinates. Those coordinates are pinned per fork rather than
derived, because the forks disagree — Helium uses "Helium Storage Key" /
"Helium" where the others use "<Name> Safe Storage" / "<Name>".

Firefox is a second engine. It stores cookies unencrypted in `cookies.sqlite`,
so there is no key to fetch and no consent prompt — that is Mozilla's design
choice, not a control being circumvented, and it is why Firefox works
identically on all three platforms while Chromium still needs a per-platform
credential store.

Paths resolve for macOS, Windows and Linux from an injected context rather
than from `process`, so a platform's layout can be checked without running on
it. Chromium off macOS still reports `unsupportedPlatform` until those key
stores land; Firefox does not.

The snapshot-before-read step moves to a shared module, since both engines
keep the database open with WAL and must never have the browser's own file
opened for writing.

Firefox has tests against a real `moz_cookies` fixture, including that the
source file is left untouched, and `profiles.ini` parsing covers the
`Install*` sections that name a default profile without describing one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ontainers apart

Firefox keeps its lock files inside each profile, not at the user-data root, so
the running check never found them and offered imports from a live, mid-write
database. It now walks the source's profiles and looks for all three names the
platforms use.

Opera does not follow the local-AppData `User Data` convention its Chromium
relatives use — it lives under roaming `%APPDATA%\Opera Software\Opera Stable`
— so it was never detected on Windows.

Firefox isolates cookies per container and per private window through
`originAttributes`. Electron has no equivalent, so importing them all collapsed
several identities onto one host/name/path and handed the profile whichever
container was written last. Only the default container is imported.

The sidecar copy no longer ignores every error alongside the missing-file case;
the new snapshot test caught that the earlier fix had landed on the
pre-extraction copy of this code rather than the shared module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Firefox branch failed with an anonymous `{ reason, cause }` literal, and
typing the shared `read` value as that shape erased `ChromiumCookieReadError`'s
tag from the error channel — neither branch could then be handled with
`Effect.catchTags`.

`FirefoxCookieReadError` mirrors its Chromium counterpart, so the union stays
structurally identifiable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Firefox keeps one cookie database per profile, so a failure carrying only a
reason cannot be traced back to the profile that produced it. The path is now a
structural attribute, matching `ChromiumCookieReadError`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Firefox has one failure mode — its plaintext database would not open — so a
single-value `reason` literal encoded the same thing as the tag, and `cause`
was optional though every construction site wraps a real failure. The error now
carries the database path and a required cause, and `BrowserImport` supplies
the user-facing reason where it maps the union.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the browser-import-more-sources branch from c2e27ca to 7487b3c Compare August 17, 2026 01:45

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the mapped error union is discriminated by hand instead of with Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment on lines +186 to +191
new BrowserImportFailedError({
sourceId: definition.id,
// Firefox has one failure mode — its plaintext database would not
// open — so its error carries no reason of its own and the
// user-facing one is supplied here.
reason: cause._tag === "FirefoxCookieReadError" ? "readFailed" : cause.reason,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both failures are statically known tagged errors, so the branch belongs in Effect.catchTags rather than a hand-rolled cause._tag ternary inside mapError. That also keeps each handler's reason next to the tag it applies to.

The comment above read ("Both branches fail with a tagged error carrying a reason") is now stale too — FirefoxCookieReadError deliberately has no reason.

     const cookies = yield* read.pipe(
       Effect.scoped,
       Effect.provide(platformServices),
-      Effect.mapError(
-        (cause) =>
-          new BrowserImportFailedError({
-            sourceId: definition.id,
-            reason: cause._tag === "FirefoxCookieReadError" ? "readFailed" : cause.reason,
-            cause,
-          }),
-      ),
+      Effect.catchTags({
+        // Firefox has one failure mode — its plaintext database would not
+        // open — so the user-facing reason is supplied here.
+        FirefoxCookieReadError: (cause) =>
+          new BrowserImportFailedError({ sourceId: definition.id, reason: "readFailed", cause }),
+        ChromiumCookieReadError: (cause) =>
+          new BrowserImportFailedError({ sourceId: definition.id, reason: cause.reason, cause }),
+      }),
     );

Posted via Macroscope — Effect Service Conventions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant