Compare commits

...

22 Commits

Author SHA1 Message Date
zhom 3ebc714b23 chore: version bump 2025-06-08 20:27:50 +04:00
zhom 1acd4781b5 fix: properly handle permissions on macos 2025-06-08 20:27:17 +04:00
zhom a5b9afafcb docs: update agent instructions 2025-06-08 17:06:10 +04:00
zhom 0c8dd5ace5 Merge pull request #19 from zhom/dependabot/npm_and_yarn/frontend-dependencies-6813ab5a43
deps(deps): bump the frontend-dependencies group with 21 updates
2025-06-07 14:29:46 +04:00
dependabot[bot] e8c3188657 deps(deps): bump the frontend-dependencies group with 21 updates
Bumps the frontend-dependencies group with 21 updates:

| Package | From | To |
| --- | --- | --- |
| [@rollup/rollup-android-arm-eabi](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-android-arm64](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-darwin-arm64](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-darwin-x64](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-freebsd-arm64](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-freebsd-x64](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-arm-gnueabihf](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-arm-musleabihf](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-arm64-gnu](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-arm64-musl](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-loongarch64-gnu](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-powerpc64le-gnu](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-riscv64-gnu](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-riscv64-musl](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-s390x-gnu](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-x64-gnu](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-linux-x64-musl](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-win32-arm64-msvc](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-win32-ia32-msvc](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [@rollup/rollup-win32-x64-msvc](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |
| [rollup](https://github.com/rollup/rollup) | `4.41.1` | `4.42.0` |


Updates `@rollup/rollup-android-arm-eabi` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-android-arm64` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-darwin-arm64` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-darwin-x64` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-freebsd-arm64` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-freebsd-x64` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-arm-gnueabihf` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-arm-musleabihf` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-arm64-gnu` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-arm64-musl` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-loongarch64-gnu` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-powerpc64le-gnu` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-riscv64-gnu` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-riscv64-musl` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-s390x-gnu` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-x64-gnu` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-linux-x64-musl` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-win32-arm64-msvc` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-win32-ia32-msvc` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `@rollup/rollup-win32-x64-msvc` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

Updates `rollup` from 4.41.1 to 4.42.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.41.1...v4.42.0)

---
updated-dependencies:
- dependency-name: "@rollup/rollup-android-arm-eabi"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-android-arm64"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-darwin-arm64"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-darwin-x64"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-freebsd-arm64"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-freebsd-x64"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-arm-gnueabihf"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-arm-musleabihf"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-arm64-gnu"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-arm64-musl"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-loongarch64-gnu"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-powerpc64le-gnu"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-riscv64-gnu"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-riscv64-musl"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-s390x-gnu"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-x64-gnu"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-linux-x64-musl"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-win32-arm64-msvc"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-win32-ia32-msvc"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: "@rollup/rollup-win32-x64-msvc"
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
- dependency-name: rollup
  dependency-version: 4.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: frontend-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-07 10:21:57 +00:00
zhom e6f0b2b9e9 Merge pull request #18 from zhom/dependabot/cargo/src-tauri/rust-dependencies-b0de36e2fd
deps(rust)(deps): bump the rust-dependencies group in /src-tauri with 13 updates
2025-06-07 14:05:42 +04:00
dependabot[bot] 394406e134 deps(rust)(deps): bump the rust-dependencies group
Bumps the rust-dependencies group in /src-tauri with 13 updates:

| Package | From | To |
| --- | --- | --- |
| [bumpalo](https://github.com/fitzgen/bumpalo) | `3.18.0` | `3.18.1` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.25` | `1.2.26` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.1.1` | `1.1.2` |
| [hyper-rustls](https://github.com/rustls/hyper-rustls) | `0.27.6` | `0.27.7` |
| [liblzma-sys](https://github.com/portable-network-archive/liblzma-rs) | `0.4.3` | `0.4.4` |
| [libz-rs-sys](https://github.com/trifectatechfoundation/zlib-rs) | `0.5.0` | `0.5.1` |
| [serde_spanned](https://github.com/toml-rs/toml) | `0.6.8` | `0.6.9` |
| [smallvec](https://github.com/servo/rust-smallvec) | `1.15.0` | `1.15.1` |
| [toml_datetime](https://github.com/toml-rs/toml) | `0.6.9` | `0.6.11` |
| [toml_write](https://github.com/toml-rs/toml) | `0.1.1` | `0.1.2` |
| [tracing-attributes](https://github.com/tokio-rs/tracing) | `0.1.28` | `0.1.29` |
| [tracing-core](https://github.com/tokio-rs/tracing) | `0.1.33` | `0.1.34` |
| [zlib-rs](https://github.com/trifectatechfoundation/zlib-rs) | `0.5.0` | `0.5.1` |


Updates `bumpalo` from 3.18.0 to 3.18.1
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/v3.18.0...v3.18.1)

Updates `cc` from 1.2.25 to 1.2.26
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.25...cc-v1.2.26)

Updates `flate2` from 1.1.1 to 1.1.2
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.1.1...1.1.2)

Updates `hyper-rustls` from 0.27.6 to 0.27.7
- [Release notes](https://github.com/rustls/hyper-rustls/releases)
- [Commits](https://github.com/rustls/hyper-rustls/compare/v/0.27.6...v/0.27.7)

Updates `liblzma-sys` from 0.4.3 to 0.4.4
- [Release notes](https://github.com/portable-network-archive/liblzma-rs/releases)
- [Commits](https://github.com/portable-network-archive/liblzma-rs/compare/liblzma-sys-0.4.3...liblzma-sys-0.4.4)

Updates `libz-rs-sys` from 0.5.0 to 0.5.1
- [Release notes](https://github.com/trifectatechfoundation/zlib-rs/releases)
- [Changelog](https://github.com/trifectatechfoundation/zlib-rs/blob/main/docs/release.md)
- [Commits](https://github.com/trifectatechfoundation/zlib-rs/compare/v0.5.0...v0.5.1)

Updates `serde_spanned` from 0.6.8 to 0.6.9
- [Commits](https://github.com/toml-rs/toml/compare/serde_spanned-v0.6.8...serde_spanned-v0.6.9)

Updates `smallvec` from 1.15.0 to 1.15.1
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.15.0...v1.15.1)

Updates `toml_datetime` from 0.6.9 to 0.6.11
- [Commits](https://github.com/toml-rs/toml/compare/toml_datetime-v0.6.9...toml_datetime-v0.6.11)

Updates `toml_write` from 0.1.1 to 0.1.2
- [Commits](https://github.com/toml-rs/toml/compare/toml_write-v0.1.1...toml_write-v0.1.2)

Updates `tracing-attributes` from 0.1.28 to 0.1.29
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-attributes-0.1.28...tracing-attributes-0.1.29)

Updates `tracing-core` from 0.1.33 to 0.1.34
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-core-0.1.33...tracing-core-0.1.34)

Updates `zlib-rs` from 0.5.0 to 0.5.1
- [Release notes](https://github.com/trifectatechfoundation/zlib-rs/releases)
- [Changelog](https://github.com/trifectatechfoundation/zlib-rs/blob/main/docs/release.md)
- [Commits](https://github.com/trifectatechfoundation/zlib-rs/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: bumpalo
  dependency-version: 3.18.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cc
  dependency-version: 1.2.26
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: flate2
  dependency-version: 1.1.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: hyper-rustls
  dependency-version: 0.27.7
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: liblzma-sys
  dependency-version: 0.4.4
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: libz-rs-sys
  dependency-version: 0.5.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: serde_spanned
  dependency-version: 0.6.9
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: smallvec
  dependency-version: 1.15.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml_datetime
  dependency-version: 0.6.11
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml_write
  dependency-version: 0.1.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tracing-attributes
  dependency-version: 0.1.29
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: tracing-core
  dependency-version: 0.1.34
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: zlib-rs
  dependency-version: 0.5.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-07 09:45:20 +00:00
zhom b0ca14c184 chore: version bump 2025-06-07 06:14:34 +04:00
zhom eea94ad360 chore: linter 2025-06-07 06:12:44 +04:00
zhom 2b678ed04d fix: hide brave releases without zip files for linux 2025-06-07 06:00:55 +04:00
zhom dff201ddec chore: add cursor rule 2025-06-07 05:46:32 +04:00
zhom 743ad59348 fix: consider all brave releases nightly if there is no release name 2025-06-07 05:42:57 +04:00
zhom d43e9ef21b chore: ui copy 2025-06-07 05:30:33 +04:00
zhom 7515cbacd6 refactor: check for similar-named brave binaries 2025-06-07 05:25:57 +04:00
zhom f41172e822 fix: update brave url for linux arm 2025-06-07 05:12:04 +04:00
zhom 25ce691bbc fix: use correct file extension on linux 2025-06-07 05:02:24 +04:00
zhom b945ee7088 chore: add cursor rule 2025-06-07 04:39:57 +04:00
zhom eb3589b4c0 chore: add cursor rule 2025-06-07 02:19:55 +04:00
zhom b71b9a00ca docs: update feature list in readme 2025-06-07 02:08:03 +04:00
zhom 6535b37c98 Merge pull request #17 from zhom/docs/readme-tauri-duplicate-link
docs: remove duplicate tauri link
2025-06-07 01:20:39 +04:00
zhom bc72a837e2 docs: remove duplicate tauri link 2025-06-07 01:18:27 +04:00
zhom fb84068d30 docs: add features section to readme 2025-06-07 00:30:06 +04:00
27 changed files with 954 additions and 226 deletions
+6
View File
@@ -0,0 +1,6 @@
---
description:
globs:
alwaysApply: true
---
Don't leave comments that don't add value
+6
View File
@@ -0,0 +1,6 @@
---
description:
globs:
alwaysApply: true
---
Do not duplicate code unless you have a very good reason to do so. It is important that the same logic is not duplicated multiple times
@@ -0,0 +1,6 @@
---
description:
globs:
alwaysApply: true
---
After your changes, instead of running specific tests or linting specific files, run "pnpm format && pnpm lint && pnpm test". It means that you first format the code, then lint it, then test it, so that no part is broken after your changes.
+1
View File
@@ -15,6 +15,7 @@
"donutbrowser",
"dpkg",
"dtolnay",
"dyld",
"elif",
"esbuild",
"eslintcache",
+2 -1
View File
@@ -1,4 +1,5 @@
# Instructions for AI Agents
- If you want to run tests, only ever run them as "pnpm format && pnpm lint && pnpm test".
- After your changes, instead of running specific tests or linting specific files, run "pnpm format && pnpm lint && pnpm test". It means that you first format the code, then lint it, then test it, so that no part is broken after your changes.
- Don't leave comments that don't add value
- Do not duplicate code unless you have a very good reason to do so. It is important that the same logic is not duplicated multiple times
+8 -2
View File
@@ -25,6 +25,14 @@
![Donut Browser Preview](assets/preview.png)
## Features
- Create unlimited number of local browser profiles completely isolated from each other
- Proxy support with basic auth for all browsers except for TOR Browser
- Import profiles from your existing browsers
- Automatic updates both for browsers and for the app itself
- Set Donut Browser as your default browser to control in which profile to open links
## Download
> As of right now, the app is not signed by Apple. You need to have Gatekeeper disabled to run it. The app automatically checks for updates on each launch.
@@ -42,8 +50,6 @@ The app can be downloaded from the [releases page](https://github.com/zhom/donut
### Contributing
> Donut Browser is built with [Tauri](https://v2.tauri.app/).
See [CONTRIBUTING.md](CONTRIBUTING.md).
## Issues
+4 -2
View File
@@ -2,7 +2,7 @@
"name": "donutbrowser",
"private": true,
"license": "AGPL-3.0",
"version": "0.3.1",
"version": "0.3.3",
"type": "module",
"scripts": {
"dev": "next dev --turbopack",
@@ -38,6 +38,7 @@
"@tauri-apps/plugin-dialog": "^2.2.2",
"@tauri-apps/plugin-fs": "~2.3.0",
"@tauri-apps/plugin-opener": "^2.2.7",
"ahooks": "^3.8.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -47,7 +48,8 @@
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0"
"tailwind-merge": "^3.3.0",
"tauri-plugin-macos-permissions-api": "^2.3.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
+159 -84
View File
@@ -53,6 +53,9 @@ importers:
'@tauri-apps/plugin-opener':
specifier: ^2.2.7
version: 2.2.7
ahooks:
specifier: ^3.8.5
version: 3.8.5(react@19.1.0)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -83,6 +86,9 @@ importers:
tailwind-merge:
specifier: ^3.3.0
version: 3.3.0
tauri-plugin-macos-permissions-api:
specifier: ^2.3.0
version: 2.3.0
devDependencies:
'@biomejs/biome':
specifier: 1.9.4
@@ -255,6 +261,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/runtime@7.27.6':
resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -1168,103 +1178,103 @@ packages:
'@rolldown/pluginutils@1.0.0-beta.9':
resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==}
'@rollup/rollup-android-arm-eabi@4.41.1':
resolution: {integrity: sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==}
'@rollup/rollup-android-arm-eabi@4.42.0':
resolution: {integrity: sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.41.1':
resolution: {integrity: sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==}
'@rollup/rollup-android-arm64@4.42.0':
resolution: {integrity: sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.41.1':
resolution: {integrity: sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==}
'@rollup/rollup-darwin-arm64@4.42.0':
resolution: {integrity: sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.41.1':
resolution: {integrity: sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==}
'@rollup/rollup-darwin-x64@4.42.0':
resolution: {integrity: sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.41.1':
resolution: {integrity: sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==}
'@rollup/rollup-freebsd-arm64@4.42.0':
resolution: {integrity: sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.41.1':
resolution: {integrity: sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==}
'@rollup/rollup-freebsd-x64@4.42.0':
resolution: {integrity: sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.41.1':
resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==}
'@rollup/rollup-linux-arm-gnueabihf@4.42.0':
resolution: {integrity: sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.41.1':
resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==}
'@rollup/rollup-linux-arm-musleabihf@4.42.0':
resolution: {integrity: sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.41.1':
resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==}
'@rollup/rollup-linux-arm64-gnu@4.42.0':
resolution: {integrity: sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.41.1':
resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==}
'@rollup/rollup-linux-arm64-musl@4.42.0':
resolution: {integrity: sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-loongarch64-gnu@4.41.1':
resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==}
'@rollup/rollup-linux-loongarch64-gnu@4.42.0':
resolution: {integrity: sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==}
cpu: [loong64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==}
'@rollup/rollup-linux-powerpc64le-gnu@4.42.0':
resolution: {integrity: sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.41.1':
resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==}
'@rollup/rollup-linux-riscv64-gnu@4.42.0':
resolution: {integrity: sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-riscv64-musl@4.41.1':
resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==}
'@rollup/rollup-linux-riscv64-musl@4.42.0':
resolution: {integrity: sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.41.1':
resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==}
'@rollup/rollup-linux-s390x-gnu@4.42.0':
resolution: {integrity: sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.41.1':
resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==}
'@rollup/rollup-linux-x64-gnu@4.42.0':
resolution: {integrity: sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.41.1':
resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==}
'@rollup/rollup-linux-x64-musl@4.42.0':
resolution: {integrity: sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.41.1':
resolution: {integrity: sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==}
'@rollup/rollup-win32-arm64-msvc@4.42.0':
resolution: {integrity: sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.41.1':
resolution: {integrity: sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==}
'@rollup/rollup-win32-ia32-msvc@4.42.0':
resolution: {integrity: sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.41.1':
resolution: {integrity: sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==}
'@rollup/rollup-win32-x64-msvc@4.42.0':
resolution: {integrity: sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==}
cpu: [x64]
os: [win32]
@@ -1690,6 +1700,12 @@ packages:
resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
engines: {node: '>= 14'}
ahooks@3.8.5:
resolution: {integrity: sha512-Y+MLoJpBXVdjsnnBjE5rOSPkQ4DK+8i5aPDzLJdIOsCpo/fiAeXcBY1Y7oWgtOK0TpOz0gFa/XcyO1UGdoqLcw==}
engines: {node: '>=8.0.0'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@@ -1940,6 +1956,9 @@ packages:
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
engines: {node: '>= 0.4'}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -2409,6 +2428,9 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
intersection-observer@0.12.2:
resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
into-stream@6.0.0:
resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==}
engines: {node: '>=10'}
@@ -2556,6 +2578,10 @@ packages:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -2694,6 +2720,9 @@ packages:
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
log-update@6.1.0:
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
engines: {node: '>=18'}
@@ -2985,6 +3014,9 @@ packages:
peerDependencies:
react: ^19.1.0
react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
react-icons@5.5.0:
resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==}
peerDependencies:
@@ -3054,6 +3086,9 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -3081,8 +3116,8 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rollup@4.41.1:
resolution: {integrity: sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==}
rollup@4.42.0:
resolution: {integrity: sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -3110,6 +3145,10 @@ packages:
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
screenfull@5.2.0:
resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
engines: {node: '>=0.10.0'}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -3330,6 +3369,9 @@ packages:
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
engines: {node: '>=18'}
tauri-plugin-macos-permissions-api@2.3.0:
resolution: {integrity: sha512-pZp0jmDySysBqrGueknd1a7Rr4XEO9aXpMv9TNrT2PDHP0MSH20njieOagsFYJ5MCVb8A+wcaK0cIkjUC2dOww==}
tinyglobby@0.2.14:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
@@ -3674,6 +3716,8 @@ snapshots:
'@babel/core': 7.27.4
'@babel/helper-plugin-utils': 7.27.1
'@babel/runtime@7.27.6': {}
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -4464,64 +4508,64 @@ snapshots:
'@rolldown/pluginutils@1.0.0-beta.9': {}
'@rollup/rollup-android-arm-eabi@4.41.1':
'@rollup/rollup-android-arm-eabi@4.42.0':
optional: true
'@rollup/rollup-android-arm64@4.41.1':
'@rollup/rollup-android-arm64@4.42.0':
optional: true
'@rollup/rollup-darwin-arm64@4.41.1':
'@rollup/rollup-darwin-arm64@4.42.0':
optional: true
'@rollup/rollup-darwin-x64@4.41.1':
'@rollup/rollup-darwin-x64@4.42.0':
optional: true
'@rollup/rollup-freebsd-arm64@4.41.1':
'@rollup/rollup-freebsd-arm64@4.42.0':
optional: true
'@rollup/rollup-freebsd-x64@4.41.1':
'@rollup/rollup-freebsd-x64@4.42.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.41.1':
'@rollup/rollup-linux-arm-gnueabihf@4.42.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.41.1':
'@rollup/rollup-linux-arm-musleabihf@4.42.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.41.1':
'@rollup/rollup-linux-arm64-gnu@4.42.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.41.1':
'@rollup/rollup-linux-arm64-musl@4.42.0':
optional: true
'@rollup/rollup-linux-loongarch64-gnu@4.41.1':
'@rollup/rollup-linux-loongarch64-gnu@4.42.0':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
'@rollup/rollup-linux-powerpc64le-gnu@4.42.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.41.1':
'@rollup/rollup-linux-riscv64-gnu@4.42.0':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.41.1':
'@rollup/rollup-linux-riscv64-musl@4.42.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.41.1':
'@rollup/rollup-linux-s390x-gnu@4.42.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.41.1':
'@rollup/rollup-linux-x64-gnu@4.42.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.41.1':
'@rollup/rollup-linux-x64-musl@4.42.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.41.1':
'@rollup/rollup-win32-arm64-msvc@4.42.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.41.1':
'@rollup/rollup-win32-ia32-msvc@4.42.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.41.1':
'@rollup/rollup-win32-x64-msvc@4.42.0':
optional: true
'@rtsao/scc@1.1.0': {}
@@ -4936,6 +4980,19 @@ snapshots:
agent-base@7.1.3: {}
ahooks@3.8.5(react@19.1.0):
dependencies:
'@babel/runtime': 7.27.6
dayjs: 1.11.13
intersection-observer: 0.12.2
js-cookie: 3.0.5
lodash: 4.17.21
react: 19.1.0
react-fast-compare: 3.2.2
resize-observer-polyfill: 1.5.1
screenfull: 5.2.0
tslib: 2.8.1
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -5231,6 +5288,8 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.2
dayjs@1.11.13: {}
debug@3.2.7:
dependencies:
ms: 2.1.3
@@ -5840,6 +5899,8 @@ snapshots:
hasown: 2.0.2
side-channel: 1.1.0
intersection-observer@0.12.2: {}
into-stream@6.0.0:
dependencies:
from2: 2.3.0
@@ -5993,6 +6054,8 @@ snapshots:
jiti@2.4.2: {}
js-cookie@3.0.5: {}
js-tokens@4.0.0: {}
js-yaml@4.1.0:
@@ -6120,6 +6183,8 @@ snapshots:
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
log-update@6.1.0:
dependencies:
ansi-escapes: 7.0.0
@@ -6414,6 +6479,8 @@ snapshots:
react: 19.1.0
scheduler: 0.26.0
react-fast-compare@3.2.2: {}
react-icons@5.5.0(react@19.1.0):
dependencies:
react: 19.1.0
@@ -6493,6 +6560,8 @@ snapshots:
require-directory@2.1.1: {}
resize-observer-polyfill@1.5.1: {}
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -6518,30 +6587,30 @@ snapshots:
rfdc@1.4.1: {}
rollup@4.41.1:
rollup@4.42.0:
dependencies:
'@types/estree': 1.0.7
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.41.1
'@rollup/rollup-android-arm64': 4.41.1
'@rollup/rollup-darwin-arm64': 4.41.1
'@rollup/rollup-darwin-x64': 4.41.1
'@rollup/rollup-freebsd-arm64': 4.41.1
'@rollup/rollup-freebsd-x64': 4.41.1
'@rollup/rollup-linux-arm-gnueabihf': 4.41.1
'@rollup/rollup-linux-arm-musleabihf': 4.41.1
'@rollup/rollup-linux-arm64-gnu': 4.41.1
'@rollup/rollup-linux-arm64-musl': 4.41.1
'@rollup/rollup-linux-loongarch64-gnu': 4.41.1
'@rollup/rollup-linux-powerpc64le-gnu': 4.41.1
'@rollup/rollup-linux-riscv64-gnu': 4.41.1
'@rollup/rollup-linux-riscv64-musl': 4.41.1
'@rollup/rollup-linux-s390x-gnu': 4.41.1
'@rollup/rollup-linux-x64-gnu': 4.41.1
'@rollup/rollup-linux-x64-musl': 4.41.1
'@rollup/rollup-win32-arm64-msvc': 4.41.1
'@rollup/rollup-win32-ia32-msvc': 4.41.1
'@rollup/rollup-win32-x64-msvc': 4.41.1
'@rollup/rollup-android-arm-eabi': 4.42.0
'@rollup/rollup-android-arm64': 4.42.0
'@rollup/rollup-darwin-arm64': 4.42.0
'@rollup/rollup-darwin-x64': 4.42.0
'@rollup/rollup-freebsd-arm64': 4.42.0
'@rollup/rollup-freebsd-x64': 4.42.0
'@rollup/rollup-linux-arm-gnueabihf': 4.42.0
'@rollup/rollup-linux-arm-musleabihf': 4.42.0
'@rollup/rollup-linux-arm64-gnu': 4.42.0
'@rollup/rollup-linux-arm64-musl': 4.42.0
'@rollup/rollup-linux-loongarch64-gnu': 4.42.0
'@rollup/rollup-linux-powerpc64le-gnu': 4.42.0
'@rollup/rollup-linux-riscv64-gnu': 4.42.0
'@rollup/rollup-linux-riscv64-musl': 4.42.0
'@rollup/rollup-linux-s390x-gnu': 4.42.0
'@rollup/rollup-linux-x64-gnu': 4.42.0
'@rollup/rollup-linux-x64-musl': 4.42.0
'@rollup/rollup-win32-arm64-msvc': 4.42.0
'@rollup/rollup-win32-ia32-msvc': 4.42.0
'@rollup/rollup-win32-x64-msvc': 4.42.0
fsevents: 2.3.3
run-parallel@1.2.0:
@@ -6573,6 +6642,8 @@ snapshots:
scheduler@0.26.0: {}
screenfull@5.2.0: {}
semver@6.3.1: {}
semver@7.7.2: {}
@@ -6861,6 +6932,10 @@ snapshots:
mkdirp: 3.0.1
yallist: 5.0.0
tauri-plugin-macos-permissions-api@2.3.0:
dependencies:
'@tauri-apps/api': 2.5.0
tinyglobby@0.2.14:
dependencies:
fdir: 6.4.5(picomatch@4.0.2)
@@ -7036,7 +7111,7 @@ snapshots:
dependencies:
esbuild: 0.25.5
postcss: 8.5.4
rollup: 4.41.1
rollup: 4.42.0
optionalDependencies:
'@types/node': 22.15.30
fsevents: 2.3.3
+53 -27
View File
@@ -405,9 +405,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.18.0"
version = "3.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b094a32014c3d1f3944e4808e0e7c70e97dae0660886a8eb6dbc52d745badc"
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
[[package]]
name = "bytemuck"
@@ -518,9 +518,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.25"
version = "1.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
dependencies = [
"jobserver",
"libc",
@@ -993,7 +993,7 @@ dependencies = [
[[package]]
name = "donutbrowser"
version = "0.3.1"
version = "0.3.3"
dependencies = [
"async-trait",
"base64 0.22.1",
@@ -1012,6 +1012,7 @@ dependencies = [
"tauri-plugin-deep-link",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-macos-permissions",
"tauri-plugin-opener",
"tauri-plugin-shell",
"tempfile",
@@ -1187,9 +1188,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.1.1"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"libz-rs-sys",
@@ -1826,9 +1827,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.27.6"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
@@ -2284,9 +2285,9 @@ dependencies = [
[[package]]
name = "liblzma-sys"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5839bad90c3cc2e0b8c4ed8296b80e86040240f81d46b9c0e9bc8dd51ddd3af1"
checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
dependencies = [
"cc",
"libc",
@@ -2305,9 +2306,9 @@ dependencies = [
[[package]]
name = "libz-rs-sys"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
dependencies = [
"zlib-rs",
]
@@ -2346,6 +2347,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "macos-accessibility-client"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edf7710fbff50c24124331760978fb9086d6de6288dcdb38b25a97f8b1bdebbb"
dependencies = [
"core-foundation 0.9.4",
"core-foundation-sys",
]
[[package]]
name = "markup5ever"
version = "0.11.0"
@@ -3791,9 +3802,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
@@ -3948,9 +3959,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.15.0"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
@@ -4407,6 +4418,21 @@ dependencies = [
"url",
]
[[package]]
name = "tauri-plugin-macos-permissions"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5607e0707d37d7b20e287cf0ce396d1efebe7b833b8e9cbd2ea4257091d9c604"
dependencies = [
"macos-accessibility-client",
"objc2 0.6.1",
"objc2-foundation 0.3.1",
"serde",
"tauri",
"tauri-plugin",
"thiserror 2.0.12",
]
[[package]]
name = "tauri-plugin-opener"
version = "2.2.7"
@@ -4769,9 +4795,9 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.9"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
@@ -4814,9 +4840,9 @@ dependencies = [
[[package]]
name = "toml_write"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "tower"
@@ -4876,9 +4902,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.28"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
dependencies = [
"proc-macro2",
"quote",
@@ -4887,9 +4913,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.33"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [
"once_cell",
]
@@ -5974,9 +6000,9 @@ dependencies = [
[[package]]
name = "zlib-rs"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
[[package]]
name = "zopfli"
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "donutbrowser"
version = "0.3.1"
version = "0.3.3"
description = "Simple Yet Powerful Browser Orchestrator"
authors = ["zhom@github"]
edition = "2021"
@@ -27,6 +27,7 @@ tauri-plugin-fs = "2"
tauri-plugin-shell = "2"
tauri-plugin-deep-link = "2"
tauri-plugin-dialog = "2"
tauri-plugin-macos-permissions = "2"
directories = "6"
reqwest = { version = "0.12", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] }
+6 -26
View File
@@ -2,47 +2,27 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>Donut Browser needs camera access to enable camera functionality in web browsers. Each website will still ask for your permission individually.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Donut Browser needs microphone access to enable microphone functionality in web browsers. Each website will still ask for your permission individually.</string>
<key>CFBundleDisplayName</key>
<string>Donut Browser</string>
<key>CFBundleName</key>
<string>Donut Browser</string>
<key>CFBundleIdentifier</key>
<string>com.donutbrowser</string>
<key>CFBundleURLName</key>
<string>com.donutbrowser</string>
<key>CFBundleExecutable</key>
<string>donutbrowser</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleShortVersionString</key>
<string>0.3.1</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Web Browser</string>
<key>CFBundleURLSchemes</key>
<array>
<string>http</string>
<string>https</string>
</array>
<key>CFBundleURLIconFile</key>
<string>icon.icns</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2025 Donut Browser</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
</dict>
</plist>
+6 -1
View File
@@ -20,6 +20,11 @@
"shell:allow-stdin-write",
"deep-link:default",
"dialog:default",
"dialog:allow-open"
"dialog:allow-open",
"macos-permissions:default",
"macos-permissions:allow-request-microphone-permission",
"macos-permissions:allow-request-camera-permission",
"macos-permissions:allow-check-microphone-permission",
"macos-permissions:allow-check-camera-permission"
]
}
+16
View File
@@ -12,5 +12,21 @@
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.audio-output</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
+8 -14
View File
@@ -246,8 +246,7 @@ pub fn is_browser_version_nightly(
if let Some(name) = release_name {
!name.starts_with("Release")
} else {
// Fallback to version string analysis if no release name
is_nightly_version(version)
true
}
}
"firefox" | "firefox-developer" => {
@@ -806,22 +805,17 @@ impl ApiClient {
}) || assets.iter().any(|asset| asset.name.ends_with(".dmg"))
}
"linux" => {
// For Linux, check for architecture-specific packages (prefer ZIP for stable releases)
// For Linux, be strict about architecture matching - only allow assets that explicitly match the current architecture
let arch_pattern = if arch == "arm64" { "arm64" } else { "amd64" };
assets.iter().any(|asset| {
if assets.iter().any(|asset| {
let name = asset.name.to_lowercase();
name.contains("linux") && name.contains(arch_pattern) && name.ends_with(".zip")
}) || assets.iter().any(|asset| {
let name = asset.name.to_lowercase();
name.contains(arch_pattern) && (name.ends_with(".deb") || name.ends_with(".rpm"))
}) || assets.iter().any(|asset| {
let name = asset.name.to_lowercase();
name.contains("linux") && name.ends_with(".zip")
}) || assets.iter().any(|asset| {
let name = asset.name.to_lowercase();
name.ends_with(".deb") || name.ends_with(".rpm")
})
}) {
return true;
}
false
}
_ => false,
}
+2
View File
@@ -307,6 +307,8 @@ mod linux {
BrowserType::Brave => vec![
browser_subdir.join("brave"),
browser_subdir.join("brave-browser"),
browser_subdir.join("brave-browser-nightly"),
browser_subdir.join("brave-browser-beta"),
],
_ => vec![],
};
+7 -9
View File
@@ -442,8 +442,8 @@ impl BrowserVersionService {
format!("Firefox Setup {version}.exe"),
false,
),
("linux", "x64") => ("linux-x86_64", format!("firefox-{version}.tar.bz2"), true),
("linux", "arm64") => ("linux-aarch64", format!("firefox-{version}.tar.bz2"), true),
("linux", "x64") => ("linux-x86_64", format!("firefox-{version}.tar.xz"), true),
("linux", "arm64") => ("linux-aarch64", format!("firefox-{version}.tar.xz"), true),
("macos", _) => ("mac", format!("Firefox {version}.dmg"), true),
_ => {
return Err(
@@ -468,8 +468,8 @@ impl BrowserVersionService {
format!("Firefox Setup {version}.exe"),
false,
),
("linux", "x64") => ("linux-x86_64", format!("firefox-{version}.tar.bz2"), true),
("linux", "arm64") => ("linux-aarch64", format!("firefox-{version}.tar.bz2"), true),
("linux", "x64") => ("linux-x86_64", format!("firefox-{version}.tar.xz"), true),
("linux", "arm64") => ("linux-aarch64", format!("firefox-{version}.tar.xz"), true),
("macos", _) => ("mac", format!("Firefox {version}.dmg"), true),
_ => {
return Err(
@@ -568,8 +568,6 @@ impl BrowserVersionService {
})
}
"brave" => {
// Brave uses different asset naming conventions
// The actual URL will be resolved dynamically in the download service
let (filename, is_archive) = match (&os[..], &arch[..]) {
("windows", _) => (format!("brave-{version}.exe"), false),
("linux", "x64") => (format!("brave-browser-{version}-linux-amd64.zip"), true),
@@ -582,7 +580,7 @@ impl BrowserVersionService {
Ok(DownloadInfo {
url: format!(
"https://github.com/brave/brave-browser/releases/download/{version}/brave-placeholder"
"https://github.com/brave/brave-browser/releases/download/{version}/{filename}"
),
filename,
is_archive,
@@ -1521,10 +1519,10 @@ mod tests {
assert!(chromium_info.url.contains("chrome-mac.zip"));
assert!(chromium_info.is_archive);
// Test Brave
// Test Brave - Note: Brave uses dynamic URL resolution, so get_download_info provides a template URL
let brave_info = service.get_download_info("brave", "v1.81.9").unwrap();
assert_eq!(brave_info.filename, "Brave-Browser-universal.dmg");
assert!(brave_info.url.contains("brave-placeholder"));
assert_eq!(brave_info.url, "https://github.com/brave/brave-browser/releases/download/v1.81.9/Brave-Browser-universal.dmg");
assert!(brave_info.is_archive);
// Test unsupported browser
+5 -32
View File
@@ -195,40 +195,13 @@ impl Downloader {
})
}
"linux" => {
// For Linux, prefer ZIP files matching architecture (new format for stable releases)
// For Linux, be strict about architecture matching - same logic as has_compatible_brave_asset
let arch_pattern = if arch == "arm64" { "arm64" } else { "amd64" };
assets
.iter()
.find(|asset| {
let name = asset.name.to_lowercase();
name.contains("linux") && name.contains(arch_pattern) && name.ends_with(".zip")
})
.or_else(|| {
// Fallback to DEB packages
assets.iter().find(|asset| {
let name = asset.name.to_lowercase();
name.contains(arch_pattern) && name.ends_with(".deb")
})
})
.or_else(|| {
// Fallback to any ZIP
assets.iter().find(|asset| {
let name = asset.name.to_lowercase();
name.contains("linux") && name.ends_with(".zip")
})
})
.or_else(|| {
// Fallback to any DEB
assets.iter().find(|asset| asset.name.ends_with(".deb"))
})
.or_else(|| {
// Last fallback to RPM if no ZIP or DEB found
assets.iter().find(|asset| {
let name = asset.name.to_lowercase();
name.contains("x86_64") && name.ends_with(".rpm")
})
})
assets.iter().find(|asset| {
let name = asset.name.to_lowercase();
name.contains("linux") && name.contains(arch_pattern) && name.ends_with(".zip")
})
}
_ => None,
};
+1
View File
@@ -172,6 +172,7 @@ pub fn run() {
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_macos_permissions::init())
.setup(|app| {
// Create the main window programmatically
#[allow(unused_variables)]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Donut Browser",
"version": "0.3.1",
"version": "0.3.3",
"identifier": "com.donutbrowser",
"build": {
"beforeDevCommand": "pnpm dev",
+60
View File
@@ -3,6 +3,7 @@
import { ChangeVersionDialog } from "@/components/change-version-dialog";
import { CreateProfileDialog } from "@/components/create-profile-dialog";
import { ImportProfileDialog } from "@/components/import-profile-dialog";
import { PermissionDialog } from "@/components/permission-dialog";
import { ProfilesDataTable } from "@/components/profile-data-table";
import { ProfileSelectorDialog } from "@/components/profile-selector-dialog";
import { ProxySettingsDialog } from "@/components/proxy-settings-dialog";
@@ -21,6 +22,8 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useAppUpdateNotifications } from "@/hooks/use-app-update-notifications";
import { usePermissions } from "@/hooks/use-permissions";
import type { PermissionType } from "@/hooks/use-permissions";
import { useUpdateNotifications } from "@/hooks/use-update-notifications";
import { showErrorToast } from "@/lib/toast-utils";
import type { BrowserProfile, ProxySettings } from "@/types";
@@ -58,6 +61,11 @@ export default function Home() {
const [currentProfileForVersionChange, setCurrentProfileForVersionChange] =
useState<BrowserProfile | null>(null);
const [hasCheckedStartupPrompt, setHasCheckedStartupPrompt] = useState(false);
const [permissionDialogOpen, setPermissionDialogOpen] = useState(false);
const [currentPermissionType, setCurrentPermissionType] =
useState<PermissionType>("microphone");
const { isMicrophoneAccessGranted, isCameraAccessGranted, isInitialized } =
usePermissions();
// Simple profiles loader without updates check (for use as callback)
const loadProfiles = useCallback(async () => {
@@ -119,6 +127,13 @@ export default function Home() {
};
}, [loadProfilesWithUpdateCheck, checkForUpdates]);
// Check permissions when they are initialized
useEffect(() => {
if (isInitialized) {
void checkAllPermissions();
}
}, [isInitialized]);
const checkStartupPrompt = async () => {
// Only check once during app startup to prevent reopening after dismissing notifications
if (hasCheckedStartupPrompt) return;
@@ -137,6 +152,42 @@ export default function Home() {
}
};
const checkAllPermissions = async () => {
try {
// Wait for permissions to be initialized before checking
if (!isInitialized) {
return;
}
// Check if any permissions are not granted - prioritize missing permissions
if (!isMicrophoneAccessGranted) {
setCurrentPermissionType("microphone");
setPermissionDialogOpen(true);
} else if (!isCameraAccessGranted) {
setCurrentPermissionType("camera");
setPermissionDialogOpen(true);
}
} catch (error) {
console.error("Failed to check permissions:", error);
}
};
const checkNextPermission = () => {
try {
if (!isMicrophoneAccessGranted) {
setCurrentPermissionType("microphone");
setPermissionDialogOpen(true);
} else if (!isCameraAccessGranted) {
setCurrentPermissionType("camera");
setPermissionDialogOpen(true);
} else {
setPermissionDialogOpen(false);
}
} catch (error) {
console.error("Failed to check next permission:", error);
}
};
const checkStartupUrls = async () => {
try {
const hasStartupUrl = await invoke<boolean>(
@@ -533,6 +584,15 @@ export default function Home() {
runningProfiles={runningProfiles}
/>
))}
<PermissionDialog
isOpen={permissionDialogOpen}
onClose={() => {
setPermissionDialogOpen(false);
}}
permissionType={currentPermissionType}
onPermissionGranted={checkNextPermission}
/>
</div>
);
}
+1 -1
View File
@@ -293,7 +293,7 @@ export function CreateProfileDialog({
disabled={true}
className="opacity-50"
>
{displayName} (Not supported on this platform)
{displayName} (Not supported)
</SelectItem>
</TooltipTrigger>
<TooltipContent>
+15 -15
View File
@@ -116,31 +116,31 @@ type ToastProps =
function getToastIcon(type: ToastProps["type"], stage?: string) {
switch (type) {
case "success":
return <LuCheckCheck className="h-4 w-4 text-green-500 flex-shrink-0" />;
return <LuCheckCheck className="flex-shrink-0 w-4 h-4 text-green-500" />;
case "error":
return <LuTriangleAlert className="h-4 w-4 text-red-500 flex-shrink-0" />;
return <LuTriangleAlert className="flex-shrink-0 w-4 h-4 text-red-500" />;
case "download":
if (stage === "completed") {
return (
<LuCheckCheck className="h-4 w-4 text-green-500 flex-shrink-0" />
<LuCheckCheck className="flex-shrink-0 w-4 h-4 text-green-500" />
);
}
return <LuDownload className="h-4 w-4 text-blue-500 flex-shrink-0" />;
return <LuDownload className="flex-shrink-0 w-4 h-4 text-blue-500" />;
case "version-update":
return (
<LuRefreshCw className="h-4 w-4 text-blue-500 animate-spin flex-shrink-0" />
<LuRefreshCw className="flex-shrink-0 w-4 h-4 text-blue-500 animate-spin" />
);
case "fetching":
return (
<LuRefreshCw className="h-4 w-4 text-blue-500 animate-spin flex-shrink-0" />
<LuRefreshCw className="flex-shrink-0 w-4 h-4 text-blue-500 animate-spin" />
);
case "twilight-update":
return (
<LuRefreshCw className="h-4 w-4 text-purple-500 animate-spin flex-shrink-0" />
<LuRefreshCw className="flex-shrink-0 w-4 h-4 text-purple-500 animate-spin" />
);
default:
return (
<div className="animate-spin rounded-full h-4 w-4 border-2 border-blue-500 border-t-transparent flex-shrink-0" />
<div className="flex-shrink-0 w-4 h-4 rounded-full border-2 border-blue-500 animate-spin border-t-transparent" />
);
}
}
@@ -151,10 +151,10 @@ export function UnifiedToast(props: ToastProps) {
const progress = "progress" in props ? props.progress : undefined;
return (
<div className="flex items-start w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-3 shadow-lg">
<div className="flex items-start p-3 w-full bg-white rounded-lg border border-gray-200 shadow-lg dark:bg-gray-800 dark:border-gray-700">
<div className="mr-3 mt-0.5">{getToastIcon(type, stage)}</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900 dark:text-white leading-tight">
<p className="text-sm font-medium leading-tight text-gray-900 dark:text-white">
{title}
</p>
@@ -165,7 +165,7 @@ export function UnifiedToast(props: ToastProps) {
stage === "downloading" && (
<div className="mt-2 space-y-1">
<div className="flex justify-between items-center">
<p className="text-xs text-gray-600 dark:text-gray-300 min-w-0 flex-1">
<p className="flex-1 min-w-0 text-xs text-gray-600 dark:text-gray-300">
{progress.percentage.toFixed(1)}%
{progress.speed && `${progress.speed} MB/s`}
{progress.eta && `${progress.eta} remaining`}
@@ -195,7 +195,7 @@ export function UnifiedToast(props: ToastProps) {
}}
/>
</div>
<span className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap shrink-0 w-8 text-right">
<span className="w-8 text-xs text-right text-gray-500 whitespace-nowrap dark:text-gray-400 shrink-0">
{progress.current}/{progress.total}
</span>
</div>
@@ -211,7 +211,7 @@ export function UnifiedToast(props: ToastProps) {
: "Checking for twilight updates..."}
</p>
{props.browserName && (
<p className="text-xs text-purple-600 dark:text-purple-400 mt-1">
<p className="mt-1 text-xs text-purple-600 dark:text-purple-400">
{props.browserName} Rolling Release
</p>
)}
@@ -220,7 +220,7 @@ export function UnifiedToast(props: ToastProps) {
{/* Description */}
{description && (
<p className="mt-1 text-xs text-gray-600 dark:text-gray-300 leading-tight">
<p className="mt-1 text-xs leading-tight text-gray-600 dark:text-gray-300">
{description}
</p>
)}
@@ -235,7 +235,7 @@ export function UnifiedToast(props: ToastProps) {
)}
{stage === "verifying" && (
<p className="mt-1 text-xs text-gray-600 dark:text-gray-300">
Verifying installation...
Verifying browser files...
</p>
)}
{stage === "downloading (twilight rolling release)" && (
+2 -2
View File
@@ -458,7 +458,7 @@ export function ImportProfileDialog({
isLoading
}
>
Import Detected Profile
Import Profile
</LoadingButton>
) : (
<LoadingButton
@@ -472,7 +472,7 @@ export function ImportProfileDialog({
!manualProfileName.trim()
}
>
Import Manual Profile
Import Profile
</LoadingButton>
)}
</DialogFooter>
+182
View File
@@ -0,0 +1,182 @@
"use client";
import { LoadingButton } from "@/components/loading-button";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { usePermissions } from "@/hooks/use-permissions";
import type { PermissionType } from "@/hooks/use-permissions";
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
import { useEffect, useState } from "react";
import { BsCamera, BsMic } from "react-icons/bs";
interface PermissionDialogProps {
isOpen: boolean;
onClose: () => void;
permissionType: PermissionType;
onPermissionGranted?: () => void;
}
export function PermissionDialog({
isOpen,
onClose,
permissionType,
onPermissionGranted,
}: PermissionDialogProps) {
const [isRequesting, setIsRequesting] = useState(false);
const [isMacOS, setIsMacOS] = useState(false);
const {
requestPermission,
isMicrophoneAccessGranted,
isCameraAccessGranted,
} = usePermissions();
// Check if we're on macOS and close dialog if not
useEffect(() => {
const userAgent = navigator.userAgent;
const isMac = userAgent.includes("Mac");
setIsMacOS(isMac);
// If not macOS, close the dialog as permissions aren't needed
if (!isMac) {
onClose();
}
}, [onClose]);
// Get current permission status
const isCurrentPermissionGranted =
permissionType === "microphone"
? isMicrophoneAccessGranted
: isCameraAccessGranted;
// Auto-close dialog when permission is granted
useEffect(() => {
if (isCurrentPermissionGranted && isOpen) {
onPermissionGranted?.();
}
}, [isCurrentPermissionGranted, isOpen, onPermissionGranted]);
const getPermissionIcon = (type: PermissionType) => {
switch (type) {
case "microphone":
return <BsMic className="w-8 h-8" />;
case "camera":
return <BsCamera className="w-8 h-8" />;
}
};
const getPermissionTitle = (type: PermissionType) => {
switch (type) {
case "microphone":
return "Microphone Access Required";
case "camera":
return "Camera Access Required";
}
};
const getPermissionDescription = (type: PermissionType) => {
switch (type) {
case "microphone":
return "Donut Browser needs access to your microphone to enable microphone functionality in web browsers. Each website that wants to use your microphone will still ask for your permission individually.";
case "camera":
return "Donut Browser needs access to your camera to enable camera functionality in web browsers. Each website that wants to use your camera will still ask for your permission individually.";
}
};
const getStatusBadge = (isGranted: boolean) => {
if (isGranted) {
return (
<Badge variant="default" className="text-green-800 bg-green-100">
Granted
</Badge>
);
}
return <Badge variant="secondary">Not Granted</Badge>;
};
const handleRequestPermission = async () => {
setIsRequesting(true);
try {
await requestPermission(permissionType);
showSuccessToast(
`${getPermissionTitle(permissionType).replace(
" Required",
"",
)} permission requested`,
);
} catch (error) {
console.error("Failed to request permission:", error);
showErrorToast("Failed to request permission");
} finally {
setIsRequesting(false);
}
};
// Don't render if not macOS
if (!isMacOS) {
return null;
}
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-md">
<DialogHeader className="text-center">
<div className="flex justify-center items-center mx-auto mb-4 w-16 h-16 bg-blue-100 rounded-full dark:bg-blue-900">
{getPermissionIcon(permissionType)}
</div>
<DialogTitle className="text-xl">
{getPermissionTitle(permissionType)}
</DialogTitle>
<DialogDescription className="text-base">
{getPermissionDescription(permissionType)}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{isCurrentPermissionGranted && (
<div className="p-3 bg-green-50 rounded-lg dark:bg-green-900/20">
<p className="text-sm text-green-800 dark:text-green-200">
Permission granted! Browsers launched from Donut Browser can
now access your {permissionType}.
</p>
</div>
)}
{!isCurrentPermissionGranted && (
<div className="p-3 bg-amber-50 rounded-lg dark:bg-amber-900/20">
<p className="text-sm text-amber-800 dark:text-amber-200">
Permission not granted. Click the button below to request
access to your {permissionType}.
</p>
</div>
)}
</div>
<DialogFooter className="gap-2">
<Button variant="outline" onClick={onClose}>
{isCurrentPermissionGranted ? "Done" : "Cancel"}
</Button>
{!isCurrentPermissionGranted && (
<LoadingButton
isLoading={isRequesting}
onClick={() => {
handleRequestPermission().catch(console.error);
}}
className="min-w-24"
>
Grant Access
</LoadingButton>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
}
+207 -8
View File
@@ -19,10 +19,13 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { usePermissions } from "@/hooks/use-permissions";
import type { PermissionType } from "@/hooks/use-permissions";
import { showSuccessToast } from "@/lib/toast-utils";
import { invoke } from "@tauri-apps/api/core";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { BsCamera, BsMic } from "react-icons/bs";
interface AppSettings {
set_as_default_browser: boolean;
@@ -32,6 +35,12 @@ interface AppSettings {
auto_delete_unused_binaries: boolean;
}
interface PermissionInfo {
permission_type: PermissionType;
isGranted: boolean;
description: string;
}
interface SettingsDialogProps {
isOpen: boolean;
onClose: () => void;
@@ -57,18 +66,46 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
const [isSaving, setIsSaving] = useState(false);
const [isSettingDefault, setIsSettingDefault] = useState(false);
const [isClearingCache, setIsClearingCache] = useState(false);
const [permissions, setPermissions] = useState<PermissionInfo[]>([]);
const [isLoadingPermissions, setIsLoadingPermissions] = useState(false);
const [requestingPermission, setRequestingPermission] =
useState<PermissionType | null>(null);
const [isMacOS, setIsMacOS] = useState(false);
const { setTheme } = useTheme();
const {
requestPermission,
isMicrophoneAccessGranted,
isCameraAccessGranted,
} = usePermissions();
const getPermissionDescription = useCallback((type: PermissionType) => {
switch (type) {
case "microphone":
return "Access to microphone for browser applications";
case "camera":
return "Access to camera for browser applications";
}
}, []);
useEffect(() => {
if (isOpen) {
void loadSettings();
void checkDefaultBrowserStatus();
loadSettings().catch(console.error);
checkDefaultBrowserStatus().catch(console.error);
// Check if we're on macOS
const userAgent = navigator.userAgent;
const isMac = userAgent.includes("Mac");
setIsMacOS(isMac);
if (isMac) {
loadPermissions().catch(console.error);
}
// Set up interval to check default browser status
const intervalId = setInterval(() => {
void checkDefaultBrowserStatus();
}, 500); // Check every 2 seconds
checkDefaultBrowserStatus().catch(console.error);
}, 500); // Check every 500ms
// Cleanup interval on component unmount or dialog close
return () => {
@@ -77,6 +114,32 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
}
}, [isOpen]);
// Update permissions when the permission states change
useEffect(() => {
if (isMacOS) {
const permissionList: PermissionInfo[] = [
{
permission_type: "microphone",
isGranted: isMicrophoneAccessGranted,
description: getPermissionDescription("microphone"),
},
{
permission_type: "camera",
isGranted: isCameraAccessGranted,
description: getPermissionDescription("camera"),
},
];
setPermissions(permissionList);
} else {
setPermissions([]);
}
}, [
isMacOS,
isMicrophoneAccessGranted,
isCameraAccessGranted,
getPermissionDescription,
]);
const loadSettings = async () => {
setIsLoading(true);
try {
@@ -90,6 +153,36 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
}
};
const loadPermissions = async () => {
setIsLoadingPermissions(true);
try {
if (!isMacOS) {
// On non-macOS platforms, don't show permissions
setPermissions([]);
return;
}
const permissionList: PermissionInfo[] = [
{
permission_type: "microphone",
isGranted: isMicrophoneAccessGranted,
description: getPermissionDescription("microphone"),
},
{
permission_type: "camera",
isGranted: isCameraAccessGranted,
description: getPermissionDescription("camera"),
},
];
setPermissions(permissionList);
} catch (error) {
console.error("Failed to load permissions:", error);
} finally {
setIsLoadingPermissions(false);
}
};
const checkDefaultBrowserStatus = async () => {
try {
const isDefault = await invoke<boolean>("is_default_browser");
@@ -127,6 +220,49 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
}
};
const handleRequestPermission = async (permissionType: PermissionType) => {
setRequestingPermission(permissionType);
try {
await requestPermission(permissionType);
showSuccessToast(
`${getPermissionDisplayName(permissionType)} access requested`,
);
} catch (error) {
console.error("Failed to request permission:", error);
} finally {
setRequestingPermission(null);
}
};
const getPermissionIcon = (type: PermissionType) => {
switch (type) {
case "microphone":
return <BsMic className="h-4 w-4" />;
case "camera":
return <BsCamera className="h-4 w-4" />;
}
};
const getPermissionDisplayName = (type: PermissionType) => {
switch (type) {
case "microphone":
return "Microphone";
case "camera":
return "Camera";
}
};
const getStatusBadge = (isGranted: boolean) => {
if (isGranted) {
return (
<Badge variant="default" className="bg-green-100 text-green-800">
Granted
</Badge>
);
}
return <Badge variant="secondary">Not Granted</Badge>;
};
const handleSave = async () => {
setIsSaving(true);
try {
@@ -204,7 +340,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
<LoadingButton
isLoading={isSettingDefault}
onClick={() => {
void handleSetDefaultBrowser();
handleSetDefaultBrowser().catch(console.error);
}}
disabled={isDefaultBrowser}
variant={isDefaultBrowser ? "outline" : "default"}
@@ -284,6 +420,69 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
</p>
</div>
{/* Permissions Section - Only show on macOS */}
{isMacOS && (
<div className="space-y-4">
<Label className="text-base font-medium">
System Permissions
</Label>
{isLoadingPermissions ? (
<div className="text-sm text-muted-foreground">
Loading permissions...
</div>
) : (
<div className="space-y-3">
{permissions.map((permission) => (
<div
key={permission.permission_type}
className="flex items-center justify-between p-3 border rounded-lg"
>
<div className="flex items-center space-x-3">
{getPermissionIcon(permission.permission_type)}
<div>
<div className="text-sm font-medium">
{getPermissionDisplayName(
permission.permission_type,
)}
</div>
<div className="text-xs text-muted-foreground">
{permission.description}
</div>
</div>
</div>
<div className="flex items-center space-x-2">
{getStatusBadge(permission.isGranted)}
{!permission.isGranted && (
<LoadingButton
size="sm"
isLoading={
requestingPermission ===
permission.permission_type
}
onClick={() => {
handleRequestPermission(
permission.permission_type,
).catch(console.error);
}}
>
Grant
</LoadingButton>
)}
</div>
</div>
))}
</div>
)}
<p className="text-xs text-muted-foreground">
These permissions allow browsers launched from Donut Browser to
access system resources. Each website will still ask for your
permission individually.
</p>
</div>
)}
{/* Advanced Section */}
<div className="space-y-4">
<Label className="text-base font-medium">Advanced</Label>
@@ -291,7 +490,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
<LoadingButton
isLoading={isClearingCache}
onClick={() => {
void handleClearCache();
handleClearCache().catch(console.error);
}}
variant="outline"
className="w-full"
@@ -314,7 +513,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
<LoadingButton
isLoading={isSaving}
onClick={() => {
void handleSave();
handleSave().catch(console.error);
}}
disabled={isLoading || !hasChanges}
>
+174
View File
@@ -0,0 +1,174 @@
import { useCallback, useEffect, useRef, useState } from "react";
// Platform-specific imports
let macOSPermissions:
| typeof import("tauri-plugin-macos-permissions-api")
| null = null;
// Dynamically import macOS permissions only when needed
const loadMacOSPermissions = async () => {
if (macOSPermissions) return macOSPermissions;
try {
macOSPermissions = await import("tauri-plugin-macos-permissions-api");
return macOSPermissions;
} catch (error) {
console.warn("Failed to load macOS permissions API:", error);
return null;
}
};
export type PermissionType = "microphone" | "camera";
export interface UsePermissionsReturn {
requestPermission: (type: PermissionType) => Promise<void>;
isMicrophoneAccessGranted: boolean;
isCameraAccessGranted: boolean;
isInitialized: boolean;
}
export function usePermissions(): UsePermissionsReturn {
const [isMicrophoneAccessGranted, setIsMicrophoneAccessGranted] =
useState(false);
const [isCameraAccessGranted, setIsCameraAccessGranted] = useState(false);
const [currentPlatform, setCurrentPlatform] = useState<string | null>(null);
const [isInitialized, setIsInitialized] = useState(false);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
// Check permissions status
const checkPermissions = useCallback(async () => {
if (!currentPlatform) return;
if (currentPlatform !== "macos") {
// Windows/Linux - assume permissions are granted
setIsMicrophoneAccessGranted(true);
setIsCameraAccessGranted(true);
setIsInitialized(true);
return;
}
// macOS - use the permissions API
try {
const permissions = await loadMacOSPermissions();
if (permissions) {
const [micGranted, camGranted] = await Promise.all([
permissions.checkMicrophonePermission(),
permissions.checkCameraPermission(),
]);
setIsMicrophoneAccessGranted(micGranted);
setIsCameraAccessGranted(camGranted);
setIsInitialized(true);
}
} catch (error) {
console.error("Failed to check permissions on macOS:", error);
setIsInitialized(true);
}
}, [currentPlatform]);
// Request permission
const requestPermission = useCallback(
async (type: PermissionType): Promise<void> => {
if (!currentPlatform || currentPlatform !== "macos") return;
// macOS - use the permissions API
try {
const permissions = await loadMacOSPermissions();
if (!permissions) return;
if (type === "microphone") {
await permissions.requestMicrophonePermission();
// Poll for permission status change
const pollMicPermission = async () => {
const granted = await permissions.checkMicrophonePermission();
setIsMicrophoneAccessGranted(granted);
if (!granted) {
setTimeout(() => {
void pollMicPermission();
}, 1000);
}
};
await pollMicPermission();
}
if (type === "camera") {
await permissions.requestCameraPermission();
// Poll for permission status change
const pollCamPermission = async () => {
const granted = await permissions.checkCameraPermission();
setIsCameraAccessGranted(granted);
if (!granted) {
setTimeout(() => {
void pollCamPermission();
}, 1000);
}
};
await pollCamPermission();
}
} catch (error) {
console.error(`Failed to request ${type} permission on macOS:`, error);
}
},
[currentPlatform],
);
// Initialize platform detection and start interval checking
useEffect(() => {
const initializePlatform = async () => {
try {
// Detect platform - on macOS we need permissions, on others we don't
const userAgent = navigator.userAgent;
let platformName = "unknown";
if (userAgent.includes("Mac")) {
platformName = "macos";
} else if (userAgent.includes("Win")) {
platformName = "windows";
} else if (userAgent.includes("Linux")) {
platformName = "linux";
}
setCurrentPlatform(platformName);
} catch (error) {
console.error("Failed to detect platform:", error);
// Fallback - assume non-macOS
setCurrentPlatform("unknown");
}
};
initializePlatform().catch(console.error);
}, []);
// Set up interval checking when platform is determined
useEffect(() => {
if (!currentPlatform) return;
// Initial check
void checkPermissions();
// Set up 500ms interval for checking permissions
intervalRef.current = setInterval(() => {
void checkPermissions();
}, 500);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [currentPlatform, checkPermissions]);
return {
requestPermission,
isMicrophoneAccessGranted,
isCameraAccessGranted,
isInitialized,
};
}
+14
View File
@@ -40,3 +40,17 @@ export interface AppVersionInfo {
version: string;
is_nightly: boolean;
}
export type PermissionType = "microphone" | "camera" | "location";
export type PermissionStatus =
| "granted"
| "denied"
| "not_determined"
| "restricted";
export interface PermissionInfo {
permission_type: PermissionType;
status: PermissionStatus;
description: string;
}