Mac app and Safari extension, sharing one wallet #21

Merged
backmeupplz merged 11 commits from macos into main 2026-10-05 20:30:11 +00:00
backmeupplz commented 2026-10-05 19:10:49 +00:00 (Migrated from github.com)

A Mac app that is also the Safari extension: one app bundle, one wallet. macOS 0.2.5 (build 2) is in App Store review on the iOS app's record.

Merge #7 (iOS) first: this branch has it merged in (with main), so after #7 lands only the Mac changes remain here. The Mac app's version is now 0.3.0, matching package.json, for its next build.

  • Install instructions for Mac/Safari, iPhone and Firefox on the landing page (docs/index.html, with a Mac screenshot) and in the README. The App Store links work once Apple approves the apps; Firefox stays build-it-yourself until the AMO review finishes.

  • Safari extension: the extension built with wxt build -b safari --mv3, without the side panel (Safari has none) and with nativeMessaging. npm run build:macos builds it into macos/safari and the wallet page into macos/web.

  • Mac app (macos/PlainWallet/PlainWallet.swift): the mobile apps' wallet page (entrypoints/android*) in a window, served from the bundle under plainwallet://, with no browser of its own; dapps go through Safari. Links open in the default browser. No favorites; it keeps the auto-lock switch, with its own note under it.

  • Locking: the app locks when the Mac sleeps or its screen locks, after 15 minutes without use (unless that's turned off), and when the window closes, which quits the app.

  • One wallet: storage.local for both the app and the Safari extension is a JSON file in the app group ACWP4F58HZ.com.borodutch.plainwallet (macos/Shared/Storage.swift).

    • Every request takes a file lock. A file that can't be read is an error, never treated as an empty wallet.
    • Every read goes to the file (lib/shared-storage.ts), so neither side saves over the other with a stale copy.
    • Safari mirrors writes into its own storage.local only so storage.onChanged still drives chainChanged / accountsChanged.
    • Each side unlocks on its own.
  • All platforms: a storage error now shows on screen instead of leaving a blank page.

Security impact

  • Where the wallet lives: the vault and signed state move, on the Mac only, from browser/WebView storage to a file in ~/Library/Group Containers/ACWP4F58HZ.com.borodutch.plainwallet/.
    • The vault stays AES-GCM under the password, and the rest stays MAC'd under the vault key, as before.
    • Deleting the app leaves that file behind (PRIVACY.md says so).
  • Native handler: the Safari extension's native handler gives whoever can call browser.runtime.sendNativeMessage read/write access to that file. In Safari that should be the extension's own pages and background only, not content scripts. Not verified.
  • Signing: app groups need team signing. An ad-hoc build is kept out of the group container when launched normally, and then shows the storage error.

Checked

  • npm test (including the new test-macos.js: the shim and store over the shared-storage protocol, a second client as the extension, errors never read as an empty wallet), tsc --noEmit, npm run build.
  • Mac app with the synthetic test … junk seed:
    • import; the vault survives relaunch;
    • mainnet balances load;
    • a simulated screen-lock notification locks it;
    • the wallet round-trips through the shared file;
    • Send dialog and Settings work.
  • Release archive and cloud-signed Mac App Store export: app and extension signed Apple Distribution: Borodutch Labs Inc. with the sandbox and app group. altool --validate-app and upload passed; build 2 is VALID and attached to macOS 0.2.5.

Not checked

  • The Safari extension in Safari. Not exercised at all: provider injection (world: MAIN), connect / sign / send approvals, native messaging, and which storage areas content scripts can reach.
  • Concurrent edits: two writes at the same moment from the app and Safari can still lose one (each save is read-modify-write).
  • Megapot: still available on the Mac, unlike iOS.
  • Security review: none. These changes need human review; no claim that they're secure.

🤖 Generated with Claude Code

A Mac app that is also the Safari extension: one app bundle, one wallet. macOS 0.2.5 (build 2) is in App Store review on the iOS app's record. **Merge #7 (iOS) first:** this branch has it merged in (with main), so after #7 lands only the Mac changes remain here. The Mac app's version is now 0.3.0, matching package.json, for its next build. - **Install instructions** for Mac/Safari, iPhone and Firefox on the landing page (`docs/index.html`, with a Mac screenshot) and in the README. The App Store links work once Apple approves the apps; Firefox stays build-it-yourself until the AMO review finishes. - **Safari extension:** the extension built with `wxt build -b safari --mv3`, without the side panel (Safari has none) and with `nativeMessaging`. `npm run build:macos` builds it into `macos/safari` and the wallet page into `macos/web`. - **Mac app** (`macos/PlainWallet/PlainWallet.swift`): the mobile apps' wallet page (`entrypoints/android*`) in a window, served from the bundle under `plainwallet://`, with no browser of its own; dapps go through Safari. Links open in the default browser. No favorites; it keeps the auto-lock switch, with its own note under it. - **Locking:** the app locks when the Mac sleeps or its screen locks, after 15 minutes without use (unless that's turned off), and when the window closes, which quits the app. - **One wallet:** `storage.local` for both the app and the Safari extension is a JSON file in the app group `ACWP4F58HZ.com.borodutch.plainwallet` (`macos/Shared/Storage.swift`). - Every request takes a file lock. A file that can't be read is an error, never treated as an empty wallet. - Every read goes to the file (`lib/shared-storage.ts`), so neither side saves over the other with a stale copy. - Safari mirrors writes into its own `storage.local` only so `storage.onChanged` still drives `chainChanged` / `accountsChanged`. - Each side unlocks on its own. - **All platforms:** a storage error now shows on screen instead of leaving a blank page. ## Security impact - **Where the wallet lives:** the vault and signed state move, on the Mac only, from browser/WebView storage to a file in `~/Library/Group Containers/ACWP4F58HZ.com.borodutch.plainwallet/`. - The vault stays AES-GCM under the password, and the rest stays MAC'd under the vault key, as before. - Deleting the app leaves that file behind (PRIVACY.md says so). - **Native handler:** the Safari extension's native handler gives whoever can call `browser.runtime.sendNativeMessage` read/write access to that file. In Safari that should be the extension's own pages and background only, not content scripts. **Not verified.** - **Signing:** app groups need team signing. An ad-hoc build is kept out of the group container when launched normally, and then shows the storage error. ## Checked - `npm test` (including the new `test-macos.js`: the shim and store over the shared-storage protocol, a second client as the extension, errors never read as an empty wallet), `tsc --noEmit`, `npm run build`. - Mac app with the synthetic `test … junk` seed: - import; the vault survives relaunch; - mainnet balances load; - a simulated screen-lock notification locks it; - the wallet round-trips through the shared file; - Send dialog and Settings work. - Release archive and cloud-signed Mac App Store export: app and extension signed `Apple Distribution: Borodutch Labs Inc.` with the sandbox and app group. `altool --validate-app` and upload passed; build 2 is VALID and attached to macOS 0.2.5. ## Not checked - **The Safari extension in Safari.** Not exercised at all: provider injection (`world: MAIN`), connect / sign / send approvals, native messaging, and which storage areas content scripts can reach. - **Concurrent edits:** two writes at the same moment from the app and Safari can still lose one (each `save` is read-modify-write). - **Megapot:** still available on the Mac, unlike iOS. - **Security review:** none. These changes need human review; no claim that they're secure. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No description provided.