mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-02 17:28:43 +02:00
Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6836d73ffa | ||
|
|
63b890d47f | ||
|
|
aeb6a08fc8 | ||
|
|
c698fff101 | ||
|
|
ccfd1f81f6 | ||
|
|
4c42099661 | ||
|
|
4c4aa10d8c | ||
|
|
a1a6ef63e4 | ||
|
|
bd7b9f1d9f | ||
|
|
f93b5daa9b | ||
|
|
f7f45bdc90 | ||
|
|
48067ee3a7 | ||
|
|
87bd75aa21 | ||
|
|
cf443061b6 | ||
|
|
ca662d91a1 | ||
|
|
2963dbc0f9 | ||
|
|
225ed05d08 | ||
|
|
97de246ac6 | ||
|
|
b00f62ebec | ||
|
|
2025a2a690 | ||
|
|
2f1faa02e4 | ||
|
|
7a5b807828 | ||
|
|
d0a5c16ce9 | ||
|
|
e2e1ad1582 | ||
|
|
cb61861503 | ||
|
|
1950ef0098 | ||
|
|
814875c28e | ||
|
|
b06ca4f11e |
+46
-4
@@ -1,28 +1,70 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
# Enable version updates for Node.js dependencies
|
# Frontend dependencies (root package.json)
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "09:00"
|
||||||
allow:
|
allow:
|
||||||
- dependency-type: "all"
|
- dependency-type: "all"
|
||||||
groups:
|
groups:
|
||||||
all:
|
frontend-dependencies:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: "eslint"
|
- dependency-name: "eslint"
|
||||||
versions: ">= 9"
|
versions: ">= 9"
|
||||||
|
commit-message:
|
||||||
|
prefix: "deps"
|
||||||
|
include: "scope"
|
||||||
|
|
||||||
# Enable version updates for rust
|
# Nodecar dependencies
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/nodecar"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "09:00"
|
||||||
|
allow:
|
||||||
|
- dependency-type: "all"
|
||||||
|
groups:
|
||||||
|
nodecar-dependencies:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
commit-message:
|
||||||
|
prefix: "deps(nodecar)"
|
||||||
|
include: "scope"
|
||||||
|
|
||||||
|
# Rust dependencies
|
||||||
- package-ecosystem: "cargo"
|
- package-ecosystem: "cargo"
|
||||||
directory: "/src-tauri"
|
directory: "/src-tauri"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "09:00"
|
||||||
allow:
|
allow:
|
||||||
- dependency-type: "all"
|
- dependency-type: "all"
|
||||||
groups:
|
groups:
|
||||||
all:
|
rust-dependencies:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
|
commit-message:
|
||||||
|
prefix: "deps(rust)"
|
||||||
|
include: "scope"
|
||||||
|
|
||||||
|
# GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "09:00"
|
||||||
|
groups:
|
||||||
|
github-actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
commit-message:
|
||||||
|
prefix: "ci"
|
||||||
|
include: "scope"
|
||||||
|
|||||||
@@ -1,34 +1,60 @@
|
|||||||
# Automatically squashes and merges Dependabot dependency upgrades if tests pass
|
name: Dependabot Automerge
|
||||||
|
|
||||||
name: Dependabot Auto-merge
|
on:
|
||||||
|
pull_request_target:
|
||||||
on: pull_request_target
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
contents: write
|
contents: write
|
||||||
|
checks: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
dependabot:
|
security-scan:
|
||||||
runs-on: ubuntu-latest
|
name: Security Vulnerability Scan
|
||||||
|
|
||||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
|
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2
|
||||||
|
with:
|
||||||
|
scan-args: |-
|
||||||
|
-r
|
||||||
|
--skip-git
|
||||||
|
--lockfile=pnpm-lock.yaml
|
||||||
|
--lockfile=src-tauri/Cargo.lock
|
||||||
|
--lockfile=nodecar/pnpm-lock.yaml
|
||||||
|
./
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
|
||||||
|
lint-js:
|
||||||
|
name: Lint JavaScript/TypeScript
|
||||||
|
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
|
uses: ./.github/workflows/lint-js.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
lint-rust:
|
||||||
|
name: Lint Rust
|
||||||
|
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
|
uses: ./.github/workflows/lint-rs.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
dependabot-automerge:
|
||||||
|
name: Dependabot Automerge
|
||||||
|
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
|
needs: [security-scan, lint-js, lint-rust]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Fetch Dependabot metadata
|
- name: Dependabot metadata
|
||||||
id: dependabot-metadata
|
id: metadata
|
||||||
uses: dependabot/fetch-metadata@v2
|
uses: dependabot/fetch-metadata@v2
|
||||||
with:
|
with:
|
||||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|
||||||
- name: Approve Dependabot PR
|
- name: Auto-merge minor and patch updates
|
||||||
run: gh pr review --approve "$PR_URL"
|
uses: ridedott/merge-me-action@v2
|
||||||
env:
|
with:
|
||||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Auto-merge (squash) Dependabot PR
|
|
||||||
if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }}
|
|
||||||
run: gh pr merge --auto --squash "$PR_URL"
|
|
||||||
env:
|
|
||||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PRESET: DEPENDABOT_MINOR
|
||||||
|
MERGE_METHOD: SQUASH
|
||||||
|
timeout-minutes: 10
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ on:
|
|||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "src-tauri/**"
|
- "src-tauri/**"
|
||||||
- "README.md"
|
- "README.md"
|
||||||
|
- ".github/workflows/lint-rs.yml"
|
||||||
|
- ".github/workflows/osv.yml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -12,11 +12,17 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "src/**"
|
- "src/**"
|
||||||
|
- "nodecar/**"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "package-lock.json"
|
|
||||||
- "yarn.lock"
|
|
||||||
- "pnpm-lock.yaml"
|
- "pnpm-lock.yaml"
|
||||||
|
- "yarn.lock"
|
||||||
- "README.md"
|
- "README.md"
|
||||||
|
- ".github/workflows/lint-js.yml"
|
||||||
|
- ".github/workflows/osv.yml"
|
||||||
|
- "next.config.js"
|
||||||
|
- "tailwind.config.js"
|
||||||
|
- "tsconfig.json"
|
||||||
|
- "biome.json"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -61,7 +67,7 @@ jobs:
|
|||||||
- name: Install nodecar dependencies
|
- name: Install nodecar dependencies
|
||||||
working-directory: ./nodecar
|
working-directory: ./nodecar
|
||||||
run: |
|
run: |
|
||||||
pnpm install --ignore-workspace --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build nodecar binary
|
- name: Build nodecar binary
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
# A sample workflow which sets up periodic OSV-Scanner scanning for vulnerabilities,
|
||||||
|
# in addition to a PR check which fails if new vulnerabilities are introduced.
|
||||||
|
#
|
||||||
|
# For more examples and options, including how to ignore specific vulnerabilities,
|
||||||
|
# see https://google.github.io/osv-scanner/github-action/
|
||||||
|
|
||||||
|
# Security vulnerability scanning for Donut Browser
|
||||||
|
# Scans dependencies in package managers (npm/pnpm, Cargo) for known vulnerabilities
|
||||||
|
# Runs on schedule and when dependencies change
|
||||||
|
|
||||||
|
name: Security Vulnerability Scan
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
paths:
|
||||||
|
- "package.json"
|
||||||
|
- "pnpm-lock.yaml"
|
||||||
|
- "src-tauri/Cargo.toml"
|
||||||
|
- "src-tauri/Cargo.lock"
|
||||||
|
- "nodecar/package.json"
|
||||||
|
- "nodecar/pnpm-lock.yaml"
|
||||||
|
- ".github/workflows/osv.yml"
|
||||||
|
merge_group:
|
||||||
|
branches: ["main"]
|
||||||
|
schedule:
|
||||||
|
# Run weekly on Tuesdays at 2:20 PM UTC
|
||||||
|
- cron: "20 14 * * 2"
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
paths:
|
||||||
|
- "package.json"
|
||||||
|
- "pnpm-lock.yaml"
|
||||||
|
- "src-tauri/Cargo.toml"
|
||||||
|
- "src-tauri/Cargo.lock"
|
||||||
|
- "nodecar/package.json"
|
||||||
|
- "nodecar/pnpm-lock.yaml"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
scan-scheduled:
|
||||||
|
name: Scheduled Security Scan
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }}
|
||||||
|
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2
|
||||||
|
with:
|
||||||
|
scan-args: |-
|
||||||
|
-r
|
||||||
|
--skip-git
|
||||||
|
--lockfile=pnpm-lock.yaml
|
||||||
|
--lockfile=src-tauri/Cargo.lock
|
||||||
|
--lockfile=nodecar/pnpm-lock.yaml
|
||||||
|
./
|
||||||
|
|
||||||
|
scan-pr:
|
||||||
|
name: PR Security Scan
|
||||||
|
if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
|
||||||
|
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2
|
||||||
|
with:
|
||||||
|
scan-args: |-
|
||||||
|
-r
|
||||||
|
--skip-git
|
||||||
|
--lockfile=pnpm-lock.yaml
|
||||||
|
--lockfile=src-tauri/Cargo.lock
|
||||||
|
--lockfile=nodecar/pnpm-lock.yaml
|
||||||
|
./
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
name: Pull Request Checks
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
merge_group:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-js:
|
||||||
|
name: Lint JavaScript/TypeScript
|
||||||
|
uses: ./.github/workflows/lint-js.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
lint-rust:
|
||||||
|
name: Lint Rust
|
||||||
|
uses: ./.github/workflows/lint-rs.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
security-scan:
|
||||||
|
name: Security Vulnerability Scan
|
||||||
|
if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
|
||||||
|
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2
|
||||||
|
with:
|
||||||
|
scan-args: |-
|
||||||
|
-r
|
||||||
|
--skip-git
|
||||||
|
--lockfile=pnpm-lock.yaml
|
||||||
|
--lockfile=nodecar/pnpm-lock.yaml
|
||||||
|
--lockfile=src-tauri/Cargo.lock
|
||||||
|
./
|
||||||
|
|
||||||
|
pr-status:
|
||||||
|
name: PR Status Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [lint-js, lint-rust, security-scan]
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Check all jobs succeeded
|
||||||
|
run: |
|
||||||
|
if [[ "${{ needs.lint-js.result }}" != "success" || "${{ needs.lint-rust.result }}" != "success" || "${{ needs.security-scan.result }}" != "success" ]]; then
|
||||||
|
echo "One or more checks failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "All checks passed!"
|
||||||
@@ -11,6 +11,22 @@ env:
|
|||||||
STABLE_RELEASE: "true"
|
STABLE_RELEASE: "true"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
security-scan:
|
||||||
|
name: Security Vulnerability Scan
|
||||||
|
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2
|
||||||
|
with:
|
||||||
|
scan-args: |-
|
||||||
|
-r
|
||||||
|
--skip-git
|
||||||
|
--lockfile=pnpm-lock.yaml
|
||||||
|
--lockfile=src-tauri/Cargo.lock
|
||||||
|
--lockfile=nodecar/pnpm-lock.yaml
|
||||||
|
./
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
|
||||||
lint-js:
|
lint-js:
|
||||||
name: Lint JavaScript/TypeScript
|
name: Lint JavaScript/TypeScript
|
||||||
uses: ./.github/workflows/lint-js.yml
|
uses: ./.github/workflows/lint-js.yml
|
||||||
@@ -22,7 +38,7 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: [lint-js, lint-rust]
|
needs: [security-scan, lint-js, lint-rust]
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
strategy:
|
strategy:
|
||||||
@@ -101,7 +117,7 @@ jobs:
|
|||||||
- name: Install nodecar dependencies
|
- name: Install nodecar dependencies
|
||||||
working-directory: ./nodecar
|
working-directory: ./nodecar
|
||||||
run: |
|
run: |
|
||||||
pnpm install --ignore-workspace --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build nodecar sidecar
|
- name: Build nodecar sidecar
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -10,6 +10,22 @@ env:
|
|||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
security-scan:
|
||||||
|
name: Security Vulnerability Scan
|
||||||
|
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2
|
||||||
|
with:
|
||||||
|
scan-args: |-
|
||||||
|
-r
|
||||||
|
--skip-git
|
||||||
|
--lockfile=pnpm-lock.yaml
|
||||||
|
--lockfile=src-tauri/Cargo.lock
|
||||||
|
--lockfile=nodecar/pnpm-lock.yaml
|
||||||
|
./
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
|
||||||
lint-js:
|
lint-js:
|
||||||
name: Lint JavaScript/TypeScript
|
name: Lint JavaScript/TypeScript
|
||||||
uses: ./.github/workflows/lint-js.yml
|
uses: ./.github/workflows/lint-js.yml
|
||||||
@@ -21,7 +37,7 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
rolling-release:
|
rolling-release:
|
||||||
needs: [lint-js, lint-rust]
|
needs: [security-scan, lint-js, lint-rust]
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
strategy:
|
strategy:
|
||||||
@@ -69,7 +85,7 @@ jobs:
|
|||||||
- name: Install nodecar dependencies
|
- name: Install nodecar dependencies
|
||||||
working-directory: ./nodecar
|
working-directory: ./nodecar
|
||||||
run: |
|
run: |
|
||||||
pnpm install --ignore-workspace --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build nodecar sidecar
|
- name: Build nodecar sidecar
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
+6
-2
@@ -5,6 +5,10 @@
|
|||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
# npm/yarn lock files (project uses pnpm only)
|
||||||
|
**/package-lock.json
|
||||||
|
**/yarn.lock
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
@@ -30,8 +34,8 @@ yarn-error.log*
|
|||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# nodecar
|
# nodecar
|
||||||
nodecar/dist
|
**/dist
|
||||||
nodecar/node_modules
|
**/node_modules
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env*.local
|
.env*.local
|
||||||
|
|||||||
Vendored
+27
-1
@@ -1,23 +1,49 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"applescript",
|
||||||
|
"autoconfig",
|
||||||
"autologin",
|
"autologin",
|
||||||
|
"biomejs",
|
||||||
|
"cdylib",
|
||||||
"CFURL",
|
"CFURL",
|
||||||
|
"checkin",
|
||||||
"clippy",
|
"clippy",
|
||||||
|
"codegen",
|
||||||
"donutbrowser",
|
"donutbrowser",
|
||||||
|
"dtolnay",
|
||||||
|
"elif",
|
||||||
|
"esbuild",
|
||||||
|
"gifs",
|
||||||
"launchservices",
|
"launchservices",
|
||||||
"mountpoint",
|
"mountpoint",
|
||||||
|
"Mullvad",
|
||||||
"nodecar",
|
"nodecar",
|
||||||
"ntlm",
|
"ntlm",
|
||||||
|
"objc",
|
||||||
|
"osascript",
|
||||||
|
"plasmohq",
|
||||||
"propertylist",
|
"propertylist",
|
||||||
|
"reqwest",
|
||||||
"rlib",
|
"rlib",
|
||||||
"rustc",
|
"rustc",
|
||||||
|
"SARIF",
|
||||||
"serde",
|
"serde",
|
||||||
"shadcn",
|
"shadcn",
|
||||||
"signon",
|
"signon",
|
||||||
|
"sonner",
|
||||||
"sspi",
|
"sspi",
|
||||||
"staticlib",
|
"staticlib",
|
||||||
|
"swatinem",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"systempreferences",
|
"systempreferences",
|
||||||
"turbopack"
|
"tauri",
|
||||||
|
"titlebar",
|
||||||
|
"Torbrowser",
|
||||||
|
"turbopack",
|
||||||
|
"unlisten",
|
||||||
|
"unrs",
|
||||||
|
"wiremock",
|
||||||
|
"xattr",
|
||||||
|
"zhom"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ After having the above dependencies installed, proceed through the following ste
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd nodecar
|
cd nodecar
|
||||||
pnpm install --ignore-workspace --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
cd ..
|
cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ Have questions or want to contribute? We'd love to hear from you!
|
|||||||
- **Issues**: [GitHub Issues](https://github.com/zhom/donutbrowser/issues)
|
- **Issues**: [GitHub Issues](https://github.com/zhom/donutbrowser/issues)
|
||||||
- **Discussions**: [GitHub Discussions](https://github.com/zhom/donutbrowser/discussions)
|
- **Discussions**: [GitHub Discussions](https://github.com/zhom/donutbrowser/discussions)
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
Have an urgent question or want to report a security vulnerability? Send an email to contact at donutbrowser dot com and we'll get back to you as fast as possible.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the AGPL-3.0 License - see the [LICENSE](LICENSE) file for details.
|
This project is licensed under the AGPL-3.0 License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
Thanks for helping make Donut Browser safe for everyone! ❤️
|
||||||
|
|
||||||
|
We take the security of Donut Browser seriously. If you believe you have found a security vulnerability in Donut Browser, please report it to us through coordinated disclosure.
|
||||||
|
|
||||||
|
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
|
||||||
|
|
||||||
|
Instead, please send an email to **contact at donutbrowser dot com** with the subject line "Security Vulnerability Report".
|
||||||
|
|
||||||
|
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
|
||||||
|
|
||||||
|
- The type of issue (e.g., buffer overflow, injection attack, privilege escalation, or cross-site scripting)
|
||||||
|
- Full paths of source file(s) related to the manifestation of the issue
|
||||||
|
- The location of the affected source code (tag/branch/commit or direct URL)
|
||||||
|
- Any special configuration required to reproduce the issue
|
||||||
|
- Step-by-step instructions to reproduce the issue
|
||||||
|
- Proof-of-concept or exploit code (if possible)
|
||||||
|
- Impact of the issue, including how an attacker might exploit the issue
|
||||||
|
- Your assessment of the severity level
|
||||||
|
|
||||||
|
This information will help us triage your report more quickly.
|
||||||
|
|
||||||
|
## What to Expect
|
||||||
|
|
||||||
|
- **Response Time**: We will acknowledge receipt of your vulnerability report within 72 hours.
|
||||||
|
- **Investigation**: We will investigate the issue and provide you with updates on our progress.
|
||||||
|
- **Resolution**: We aim to resolve critical security issues as fast as possible, but no longer than in 30 days after the initial report.
|
||||||
|
- **Disclosure**: We will coordinate with you on the timing of any public disclosure.
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
For urgent security matters, please contact us at **contact at donutbrowser dot com**.
|
||||||
|
|
||||||
|
For general questions about this security policy, you can also reach out through:
|
||||||
|
|
||||||
|
- [GitHub Issues](https://github.com/zhom/donutbrowser/issues) (for non-security questions only)
|
||||||
|
- [GitHub Discussions](https://github.com/zhom/donutbrowser/discussions)
|
||||||
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
"rsc": true,
|
"rsc": true,
|
||||||
"tsx": true,
|
"tsx": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
"config": "tailwind.config.js",
|
||||||
"css": "src/styles/globals.css",
|
"css": "src/styles/globals.css",
|
||||||
"baseColor": "zinc",
|
"baseColor": "zinc",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
@@ -18,4 +18,4 @@
|
|||||||
"hooks": "@/hooks"
|
"hooks": "@/hooks"
|
||||||
},
|
},
|
||||||
"iconLibrary": "lucide"
|
"iconLibrary": "lucide"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "AGPL-3.0",
|
||||||
"packageManager": "pnpm@10.6.1",
|
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^22.15.17",
|
"@types/node": "^22.15.29",
|
||||||
"@yao-pkg/pkg": "^6.4.1",
|
"@yao-pkg/pkg": "^6.5.1",
|
||||||
"commander": "^13.1.0",
|
"commander": "^14.0.0",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"proxy-chain": "^2.5.8",
|
"proxy-chain": "^2.5.9",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
-1304
File diff suppressed because it is too large
Load Diff
@@ -48,8 +48,8 @@ program
|
|||||||
ignoreProxyCertificate: options.ignoreCertificate,
|
ignoreProxyCertificate: options.ignoreCertificate,
|
||||||
});
|
});
|
||||||
console.log(JSON.stringify(config));
|
console.log(JSON.stringify(config));
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error(`Failed to start proxy: ${error.message}`);
|
console.error(`Failed to start proxy: ${JSON.stringify(error)}`);
|
||||||
}
|
}
|
||||||
} else if (action === "stop") {
|
} else if (action === "stop") {
|
||||||
if (options.id) {
|
if (options.id) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { spawn } from "child_process";
|
import { spawn } from "node:child_process";
|
||||||
import path from "path";
|
import path from "node:path";
|
||||||
import getPort from "get-port";
|
import getPort from "get-port";
|
||||||
import {
|
import {
|
||||||
type ProxyConfig,
|
type ProxyConfig,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import fs from "fs";
|
import fs from "node:fs";
|
||||||
import path from "path";
|
import path from "node:path";
|
||||||
import os from "os";
|
import os from "node:os";
|
||||||
|
|
||||||
// Define the proxy configuration type
|
// Define the proxy configuration type
|
||||||
export interface ProxyConfig {
|
export interface ProxyConfig {
|
||||||
|
|||||||
+18
-17
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "donutbrowser",
|
"name": "donutbrowser",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.4",
|
"license": "AGPL-3.0",
|
||||||
|
"version": "0.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
@@ -36,36 +37,36 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"next": "^15.3.2",
|
"next": "^15.3.3",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"sonner": "^2.0.3",
|
"sonner": "^2.0.5",
|
||||||
"tailwind-merge": "^3.3.0"
|
"tailwind-merge": "^3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@eslint/js": "^9.27.0",
|
"@eslint/js": "^9.28.0",
|
||||||
"@next/eslint-plugin-next": "^15.3.2",
|
"@next/eslint-plugin-next": "^15.3.3",
|
||||||
"@tailwindcss/postcss": "^4.1.7",
|
"@tailwindcss/postcss": "^4.1.8",
|
||||||
"@tauri-apps/cli": "^2.5.0",
|
"@tauri-apps/cli": "^2.5.0",
|
||||||
"@types/node": "^22.15.21",
|
"@types/node": "^22.15.29",
|
||||||
"@types/react": "^19.1.5",
|
"@types/react": "^19.1.6",
|
||||||
"@types/react-dom": "^19.1.5",
|
"@types/react-dom": "^19.1.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
"@typescript-eslint/eslint-plugin": "^8.33.1",
|
||||||
"@typescript-eslint/parser": "^8.32.1",
|
"@typescript-eslint/parser": "^8.33.1",
|
||||||
"@vitejs/plugin-react": "^4.5.0",
|
"@vitejs/plugin-react": "^4.5.1",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.28.0",
|
||||||
"eslint-config-next": "^15.3.2",
|
"eslint-config-next": "^15.3.3",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^15.3.0",
|
"lint-staged": "^16.1.0",
|
||||||
"tailwindcss": "^4.1.7",
|
"tailwindcss": "^4.1.8",
|
||||||
"tw-animate-css": "^1.3.0",
|
"tw-animate-css": "^1.3.3",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.32.1"
|
"typescript-eslint": "^8.33.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977",
|
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977",
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|||||||
Generated
+1846
-982
File diff suppressed because it is too large
Load Diff
+6
-2
@@ -1,5 +1,9 @@
|
|||||||
|
packages:
|
||||||
|
- "nodecar"
|
||||||
|
|
||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
- '@biomejs/biome'
|
- "@biomejs/biome"
|
||||||
- '@tailwindcss/oxide'
|
- "@tailwindcss/oxide"
|
||||||
- esbuild
|
- esbuild
|
||||||
- sharp
|
- sharp
|
||||||
|
- unrs-resolver
|
||||||
|
|||||||
Generated
+9
-7
@@ -458,9 +458,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "camino"
|
name = "camino"
|
||||||
version = "1.1.9"
|
version = "1.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
|
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -963,7 +963,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "donutbrowser"
|
name = "donutbrowser"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -971,6 +971,8 @@ dependencies = [
|
|||||||
"directories",
|
"directories",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"objc2 0.6.1",
|
||||||
|
"objc2-app-kit",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3393,9 +3395,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.18"
|
version = "0.12.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5"
|
checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -4729,9 +4731,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.6.4"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
|
checksum = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "donutbrowser"
|
name = "donutbrowser"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
description = "Browser Orchestrator"
|
description = "Browser Orchestrator"
|
||||||
authors = ["zhom@github"]
|
authors = ["zhom@github"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -37,6 +37,8 @@ futures-util = "0.3"
|
|||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
core-foundation="0.10"
|
core-foundation="0.10"
|
||||||
|
objc2 = "0.6.1"
|
||||||
|
objc2-app-kit = { version = "0.3.1", features = ["NSWindow"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.13.0"
|
tempfile = "3.13.0"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.2.4</string>
|
<string>0.2.5</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"core:event:default",
|
"core:event:default",
|
||||||
|
"core:window:default",
|
||||||
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-minimize",
|
||||||
|
"core:window:allow-toggle-maximize",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"fs:default",
|
"fs:default",
|
||||||
"shell:allow-execute",
|
"shell:allow-execute",
|
||||||
|
|||||||
+707
-598
File diff suppressed because it is too large
Load Diff
@@ -239,7 +239,7 @@ impl BrowserVersionService {
|
|||||||
} else {
|
} else {
|
||||||
BrowserVersionInfo {
|
BrowserVersionInfo {
|
||||||
version: version.clone(),
|
version: version.clone(),
|
||||||
is_prerelease: version.contains("alpha") || version.contains("beta"),
|
is_prerelease: false, // Zen Browser releases are usually stable
|
||||||
date: "".to_string(),
|
date: "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,60 +356,148 @@ impl BrowserVersionService {
|
|||||||
version: &str,
|
version: &str,
|
||||||
) -> Result<DownloadInfo, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<DownloadInfo, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
match browser {
|
match browser {
|
||||||
"firefox" => Ok(DownloadInfo {
|
"firefox" => {
|
||||||
url: format!("https://download.mozilla.org/?product=firefox-{version}&os=osx&lang=en-US"),
|
#[cfg(target_os = "macos")]
|
||||||
filename: format!("firefox-{version}.dmg"),
|
return Ok(DownloadInfo {
|
||||||
is_archive: true,
|
url: format!("https://download.mozilla.org/?product=firefox-{version}&os=osx&lang=en-US"),
|
||||||
}),
|
filename: format!("firefox-{version}.dmg"),
|
||||||
"firefox-developer" => Ok(DownloadInfo {
|
is_archive: true,
|
||||||
url: format!("https://download.mozilla.org/?product=devedition-{version}&os=osx&lang=en-US"),
|
});
|
||||||
filename: format!("firefox-developer-{version}.dmg"),
|
|
||||||
is_archive: true,
|
#[cfg(target_os = "windows")]
|
||||||
}),
|
return Ok(DownloadInfo {
|
||||||
"mullvad-browser" => Ok(DownloadInfo {
|
url: format!("https://download.mozilla.org/?product=firefox-{version}&os=win64&lang=en-US"),
|
||||||
url: format!(
|
filename: format!("firefox-{version}.exe"),
|
||||||
"https://github.com/mullvad/mullvad-browser/releases/download/{version}/mullvad-browser-macos-{version}.dmg"
|
is_archive: false,
|
||||||
),
|
});
|
||||||
filename: format!("mullvad-browser-{version}.dmg"),
|
|
||||||
is_archive: true,
|
#[cfg(target_os = "linux")]
|
||||||
}),
|
return Ok(DownloadInfo {
|
||||||
"zen" => Ok(DownloadInfo {
|
url: format!("https://download.mozilla.org/?product=firefox-{version}&os=linux64&lang=en-US"),
|
||||||
url: format!(
|
filename: format!("firefox-{version}.tar.bz2"),
|
||||||
"https://github.com/zen-browser/desktop/releases/download/{version}/zen.macos-universal.dmg"
|
is_archive: true,
|
||||||
),
|
});
|
||||||
filename: format!("zen-{version}.dmg"),
|
|
||||||
is_archive: true,
|
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||||
}),
|
return Err("Unsupported platform for Firefox".into());
|
||||||
"brave" => {
|
}
|
||||||
// For Brave, we use a placeholder URL since we need to resolve the actual asset URL dynamically
|
"firefox-developer" => {
|
||||||
// The actual URL will be resolved in the download service using the GitHub API
|
#[cfg(target_os = "macos")]
|
||||||
Ok(DownloadInfo {
|
return Ok(DownloadInfo {
|
||||||
url: format!(
|
url: format!("https://download.mozilla.org/?product=devedition-{version}&os=osx&lang=en-US"),
|
||||||
"https://github.com/brave/brave-browser/releases/download/{version}/Brave-Browser-universal.dmg"
|
filename: format!("firefox-developer-{version}.dmg"),
|
||||||
),
|
is_archive: true,
|
||||||
filename: format!("brave-{version}.dmg"),
|
});
|
||||||
is_archive: true,
|
|
||||||
})
|
#[cfg(target_os = "windows")]
|
||||||
}
|
return Ok(DownloadInfo {
|
||||||
"chromium" => {
|
url: format!("https://download.mozilla.org/?product=devedition-{version}&os=win64&lang=en-US"),
|
||||||
let arch = if cfg!(target_arch = "aarch64") { "Mac_Arm" } else { "Mac" };
|
filename: format!("firefox-developer-{version}.exe"),
|
||||||
Ok(DownloadInfo {
|
is_archive: false,
|
||||||
url: format!(
|
});
|
||||||
"https://commondatastorage.googleapis.com/chromium-browser-snapshots/{arch}/{version}/chrome-mac.zip"
|
|
||||||
),
|
#[cfg(target_os = "linux")]
|
||||||
filename: format!("chromium-{version}.zip"),
|
return Ok(DownloadInfo {
|
||||||
is_archive: true,
|
url: format!("https://download.mozilla.org/?product=devedition-{version}&os=linux64&lang=en-US"),
|
||||||
})
|
filename: format!("firefox-developer-{version}.tar.bz2"),
|
||||||
}
|
is_archive: true,
|
||||||
"tor-browser" => Ok(DownloadInfo {
|
});
|
||||||
url: format!(
|
|
||||||
"https://archive.torproject.org/tor-package-archive/torbrowser/{version}/tor-browser-macos-{version}.dmg"
|
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||||
),
|
return Err("Unsupported platform for Firefox Developer".into());
|
||||||
filename: format!("tor-browser-{version}.dmg"),
|
}
|
||||||
is_archive: true,
|
"mullvad-browser" => {
|
||||||
}),
|
#[cfg(target_os = "macos")]
|
||||||
_ => Err(format!("Unsupported browser: {browser}").into()),
|
return Ok(DownloadInfo {
|
||||||
}
|
url: format!(
|
||||||
|
"https://github.com/mullvad/mullvad-browser/releases/download/{version}/mullvad-browser-macos-{version}.dmg"
|
||||||
|
),
|
||||||
|
filename: format!("mullvad-browser-{version}.dmg"),
|
||||||
|
is_archive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return Ok(DownloadInfo {
|
||||||
|
url: format!(
|
||||||
|
"https://github.com/mullvad/mullvad-browser/releases/download/{version}/mullvad-browser-windows-{version}.exe"
|
||||||
|
),
|
||||||
|
filename: format!("mullvad-browser-{version}.exe"),
|
||||||
|
is_archive: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
return Ok(DownloadInfo {
|
||||||
|
url: format!(
|
||||||
|
"https://github.com/mullvad/mullvad-browser/releases/download/{version}/mullvad-browser-linux-{version}.tar.xz"
|
||||||
|
),
|
||||||
|
filename: format!("mullvad-browser-{version}.tar.xz"),
|
||||||
|
is_archive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||||
|
return Err("Unsupported platform for Mullvad Browser".into());
|
||||||
|
}
|
||||||
|
"zen" => {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
return Ok(DownloadInfo {
|
||||||
|
url: format!(
|
||||||
|
"https://github.com/zen-browser/desktop/releases/download/{version}/zen.macos-universal.dmg"
|
||||||
|
),
|
||||||
|
filename: format!("zen-{version}.dmg"),
|
||||||
|
is_archive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return Ok(DownloadInfo {
|
||||||
|
url: format!(
|
||||||
|
"https://github.com/zen-browser/desktop/releases/download/{version}/zen.win.x64.zip"
|
||||||
|
),
|
||||||
|
filename: format!("zen-{version}.zip"),
|
||||||
|
is_archive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
return Ok(DownloadInfo {
|
||||||
|
url: format!(
|
||||||
|
"https://github.com/zen-browser/desktop/releases/download/{version}/zen.linux-x86_64.AppImage"
|
||||||
|
),
|
||||||
|
filename: format!("zen-{version}.AppImage"),
|
||||||
|
is_archive: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||||
|
return Err("Unsupported platform for Zen Browser".into());
|
||||||
|
}
|
||||||
|
"brave" => {
|
||||||
|
// For Brave, we use a placeholder URL since we need to resolve the actual asset URL dynamically
|
||||||
|
// The actual URL will be resolved in the download service using the GitHub API
|
||||||
|
Ok(DownloadInfo {
|
||||||
|
url: format!(
|
||||||
|
"https://github.com/brave/brave-browser/releases/download/{version}/Brave-Browser-universal.dmg"
|
||||||
|
),
|
||||||
|
filename: format!("brave-{version}.dmg"),
|
||||||
|
is_archive: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"chromium" => {
|
||||||
|
let arch = if cfg!(target_arch = "aarch64") { "Mac_Arm" } else { "Mac" };
|
||||||
|
Ok(DownloadInfo {
|
||||||
|
url: format!(
|
||||||
|
"https://commondatastorage.googleapis.com/chromium-browser-snapshots/{arch}/{version}/chrome-mac.zip"
|
||||||
|
),
|
||||||
|
filename: format!("chromium-{version}.zip"),
|
||||||
|
is_archive: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"tor-browser" => Ok(DownloadInfo {
|
||||||
|
url: format!(
|
||||||
|
"https://archive.torproject.org/tor-package-archive/torbrowser/{version}/tor-browser-macos-{version}.dmg"
|
||||||
|
),
|
||||||
|
filename: format!("tor-browser-{version}.dmg"),
|
||||||
|
is_archive: true,
|
||||||
|
}),
|
||||||
|
_ => Err(format!("Unsupported browser: {browser}").into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private helper methods for each browser type
|
// Private helper methods for each browser type
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ pub async fn open_url_with_profile(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn smart_open_url(
|
pub async fn smart_open_url(
|
||||||
_app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
_url: String,
|
url: String,
|
||||||
_is_startup: Option<bool>,
|
_is_startup: Option<bool>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
use crate::browser_runner::BrowserRunner;
|
use crate::browser_runner::BrowserRunner;
|
||||||
@@ -171,10 +171,75 @@ pub async fn smart_open_url(
|
|||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"URL opening - Total profiles: {}, showing profile selector",
|
"URL opening - Total profiles: {}, checking for running profiles",
|
||||||
profiles.len()
|
profiles.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Always show the profile selector so the user can choose
|
// Check for running profiles and find the first one that can handle URLs
|
||||||
|
for profile in &profiles {
|
||||||
|
// Check if this profile is running
|
||||||
|
let is_running = runner
|
||||||
|
.check_browser_status(app_handle.clone(), profile)
|
||||||
|
.await
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if is_running {
|
||||||
|
println!(
|
||||||
|
"Found running profile '{}', attempting to open URL",
|
||||||
|
profile.name
|
||||||
|
);
|
||||||
|
|
||||||
|
// For TOR browser: Check if any other TOR browser is running
|
||||||
|
if profile.browser == "tor-browser" {
|
||||||
|
let mut other_tor_running = false;
|
||||||
|
for p in &profiles {
|
||||||
|
if p.browser == "tor-browser"
|
||||||
|
&& p.name != profile.name
|
||||||
|
&& runner
|
||||||
|
.check_browser_status(app_handle.clone(), p)
|
||||||
|
.await
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
other_tor_running = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if other_tor_running {
|
||||||
|
continue; // Skip this one, can't have multiple TOR instances
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Mullvad browser: skip if running (can't open URLs in running Mullvad)
|
||||||
|
if profile.browser == "mullvad-browser" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to open the URL with this running profile
|
||||||
|
match runner
|
||||||
|
.launch_or_open_url(app_handle.clone(), profile, Some(url.clone()))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
println!(
|
||||||
|
"Successfully opened URL '{}' with running profile '{}'",
|
||||||
|
url, profile.name
|
||||||
|
);
|
||||||
|
return Ok(format!("opened_with_profile:{}", profile.name));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!(
|
||||||
|
"Failed to open URL with running profile '{}': {}",
|
||||||
|
profile.name, e
|
||||||
|
);
|
||||||
|
// Continue to try other profiles or show selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("No suitable running profiles found, showing profile selector");
|
||||||
|
|
||||||
|
// No suitable running profile found, show the profile selector
|
||||||
Err("show_selector".to_string())
|
Err("show_selector".to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
+119
-1
@@ -1,7 +1,7 @@
|
|||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tauri::{Emitter, Manager};
|
use tauri::{Emitter, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
|
|
||||||
// Store pending URLs that need to be handled when the window is ready
|
// Store pending URLs that need to be handled when the window is ready
|
||||||
@@ -58,6 +58,51 @@ use app_auto_updater::{
|
|||||||
get_app_version_info,
|
get_app_version_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Trait to extend WebviewWindow with transparent titlebar functionality
|
||||||
|
pub trait WindowExt {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn set_transparent_titlebar(&self, transparent: bool) -> Result<(), String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Runtime> WindowExt for WebviewWindow<R> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn set_transparent_titlebar(&self, transparent: bool) -> Result<(), String> {
|
||||||
|
use objc2::rc::Retained;
|
||||||
|
use objc2_app_kit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ns_window: Retained<NSWindow> =
|
||||||
|
Retained::retain(self.ns_window().unwrap().cast()).unwrap();
|
||||||
|
|
||||||
|
if transparent {
|
||||||
|
// Hide the title text
|
||||||
|
ns_window.setTitleVisibility(NSWindowTitleVisibility(2)); // NSWindowTitleHidden
|
||||||
|
|
||||||
|
// Make titlebar transparent
|
||||||
|
ns_window.setTitlebarAppearsTransparent(true);
|
||||||
|
|
||||||
|
// Set full size content view
|
||||||
|
let current_mask = ns_window.styleMask();
|
||||||
|
let new_mask = NSWindowStyleMask(current_mask.0 | (1 << 15)); // NSFullSizeContentViewWindowMask
|
||||||
|
ns_window.setStyleMask(new_mask);
|
||||||
|
} else {
|
||||||
|
// Show the title text
|
||||||
|
ns_window.setTitleVisibility(NSWindowTitleVisibility(0)); // NSWindowTitleVisible
|
||||||
|
|
||||||
|
// Make titlebar opaque
|
||||||
|
ns_window.setTitlebarAppearsTransparent(false);
|
||||||
|
|
||||||
|
// Remove full size content view
|
||||||
|
let current_mask = ns_window.styleMask();
|
||||||
|
let new_mask = NSWindowStyleMask(current_mask.0 & !(1 << 15));
|
||||||
|
ns_window.setStyleMask(new_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn greet() -> String {
|
fn greet() -> String {
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
@@ -124,6 +169,61 @@ async fn check_and_handle_startup_url(app_handle: tauri::AppHandle) -> Result<bo
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn set_window_background_color(
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
|
is_dark_mode: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
use objc2::rc::Retained;
|
||||||
|
use objc2_app_kit::{NSColor, NSWindow};
|
||||||
|
|
||||||
|
let ns_window: Retained<NSWindow> =
|
||||||
|
unsafe { Retained::retain(window.ns_window().unwrap().cast()).unwrap() };
|
||||||
|
|
||||||
|
let bg_color = if is_dark_mode {
|
||||||
|
// Dark mode - pure black background
|
||||||
|
unsafe { NSColor::colorWithRed_green_blue_alpha(0.0, 0.0, 0.0, 1.0) }
|
||||||
|
} else {
|
||||||
|
// Light mode - pure white background
|
||||||
|
unsafe { NSColor::colorWithRed_green_blue_alpha(1.0, 1.0, 1.0, 1.0) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure this runs on the main thread for immediate visual update
|
||||||
|
unsafe {
|
||||||
|
// Set the window background color
|
||||||
|
ns_window.setBackgroundColor(Some(&bg_color));
|
||||||
|
|
||||||
|
// Force immediate visual updates using multiple refresh methods
|
||||||
|
ns_window.invalidateShadow();
|
||||||
|
ns_window.display();
|
||||||
|
|
||||||
|
// Ensure the window content is redrawn
|
||||||
|
if let Some(content_view) = ns_window.contentView() {
|
||||||
|
content_view.setNeedsDisplay(true);
|
||||||
|
content_view.displayIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger a window update
|
||||||
|
ns_window.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also emit an event to the frontend to ensure synchronization
|
||||||
|
let _ = app_handle.emit("window-background-updated", is_dark_mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
// For non-macOS platforms, we can't change the native window background
|
||||||
|
let _ = (app_handle, is_dark_mode); // Suppress unused variable warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
@@ -132,6 +232,23 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
// Create the main window programmatically
|
||||||
|
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||||
|
.title("Donut Browser")
|
||||||
|
.inner_size(900.0, 600.0)
|
||||||
|
.resizable(false)
|
||||||
|
.fullscreen(false);
|
||||||
|
|
||||||
|
let window = win_builder.build().unwrap();
|
||||||
|
|
||||||
|
// Set transparent titlebar for macOS
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if let Err(e) = window.set_transparent_titlebar(true) {
|
||||||
|
eprintln!("Failed to set transparent titlebar: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set up deep link handler
|
// Set up deep link handler
|
||||||
let handle = app.handle().clone();
|
let handle = app.handle().clone();
|
||||||
|
|
||||||
@@ -264,6 +381,7 @@ pub fn run() {
|
|||||||
check_for_app_updates_manual,
|
check_for_app_updates_manual,
|
||||||
download_and_install_app_update,
|
download_and_install_app_update,
|
||||||
get_app_version_info,
|
get_app_version_info,
|
||||||
|
set_window_background_color,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Donut Browser",
|
"productName": "Donut Browser",
|
||||||
"version": "0.2.4",
|
"version": "0.2.5",
|
||||||
"identifier": "com.donutbrowser",
|
"identifier": "com.donutbrowser",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
@@ -10,15 +10,7 @@
|
|||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [],
|
||||||
{
|
|
||||||
"title": "Donut Browser",
|
|
||||||
"width": 900,
|
|
||||||
"height": 600,
|
|
||||||
"resizable": false,
|
|
||||||
"fullscreen": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import "@/styles/globals.css";
|
|||||||
import { CustomThemeProvider } from "@/components/theme-provider";
|
import { CustomThemeProvider } from "@/components/theme-provider";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
|
import { WindowDragArea } from "@/components/window-drag-area";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@@ -26,6 +27,7 @@ export default function RootLayout({
|
|||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
<CustomThemeProvider>
|
<CustomThemeProvider>
|
||||||
|
<WindowDragArea />
|
||||||
<TooltipProvider>{children}</TooltipProvider>
|
<TooltipProvider>{children}</TooltipProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</CustomThemeProvider>
|
</CustomThemeProvider>
|
||||||
|
|||||||
+18
-14
@@ -48,6 +48,7 @@ export default function Home() {
|
|||||||
useState<BrowserProfile | null>(null);
|
useState<BrowserProfile | null>(null);
|
||||||
const [currentProfileForVersionChange, setCurrentProfileForVersionChange] =
|
const [currentProfileForVersionChange, setCurrentProfileForVersionChange] =
|
||||||
useState<BrowserProfile | null>(null);
|
useState<BrowserProfile | null>(null);
|
||||||
|
const [hasCheckedStartupPrompt, setHasCheckedStartupPrompt] = useState(false);
|
||||||
|
|
||||||
// Simple profiles loader without updates check (for use as callback)
|
// Simple profiles loader without updates check (for use as callback)
|
||||||
const loadProfiles = useCallback(async () => {
|
const loadProfiles = useCallback(async () => {
|
||||||
@@ -110,6 +111,9 @@ export default function Home() {
|
|||||||
}, [loadProfilesWithUpdateCheck, checkForUpdates]);
|
}, [loadProfilesWithUpdateCheck, checkForUpdates]);
|
||||||
|
|
||||||
const checkStartupPrompt = async () => {
|
const checkStartupPrompt = async () => {
|
||||||
|
// Only check once during app startup to prevent reopening after dismissing notifications
|
||||||
|
if (hasCheckedStartupPrompt) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldShow = await invoke<boolean>(
|
const shouldShow = await invoke<boolean>(
|
||||||
"should_show_settings_on_startup",
|
"should_show_settings_on_startup",
|
||||||
@@ -117,8 +121,10 @@ export default function Home() {
|
|||||||
if (shouldShow) {
|
if (shouldShow) {
|
||||||
setSettingsDialogOpen(true);
|
setSettingsDialogOpen(true);
|
||||||
}
|
}
|
||||||
|
setHasCheckedStartupPrompt(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check startup prompt:", error);
|
console.error("Failed to check startup prompt:", error);
|
||||||
|
setHasCheckedStartupPrompt(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,10 +152,7 @@ export default function Home() {
|
|||||||
// Listen for show profile selector events
|
// Listen for show profile selector events
|
||||||
await listen<string>("show-profile-selector", (event) => {
|
await listen<string>("show-profile-selector", (event) => {
|
||||||
console.log("Received show profile selector request:", event.payload);
|
console.log("Received show profile selector request:", event.payload);
|
||||||
setPendingUrls((prev) => [
|
setPendingUrls([{ id: Date.now().toString(), url: event.payload }]);
|
||||||
...prev,
|
|
||||||
{ id: Date.now().toString(), url: event.payload },
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for show create profile dialog events
|
// Listen for show create profile dialog events
|
||||||
@@ -175,7 +178,7 @@ export default function Home() {
|
|||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
console.log("Smart URL opening succeeded:", result);
|
console.log("Smart URL opening succeeded:", result);
|
||||||
// URL was handled successfully
|
// URL was handled successfully, no need to show selector
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.log(
|
console.log(
|
||||||
"Smart URL opening failed or requires profile selection:",
|
"Smart URL opening failed or requires profile selection:",
|
||||||
@@ -183,7 +186,8 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Show profile selector for manual selection
|
// Show profile selector for manual selection
|
||||||
setPendingUrls((prev) => [...prev, { id: Date.now().toString(), url }]);
|
// Replace any existing pending URL with the new one
|
||||||
|
setPendingUrls([{ id: Date.now().toString(), url }]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -396,13 +400,13 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 gap-8 sm:p-12 font-[family-name:var(--font-geist-sans)]">
|
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 gap-8 sm:p-12 font-[family-name:var(--font-geist-sans)] bg-white dark:bg-black">
|
||||||
<main className="flex flex-col gap-8 row-start-2 items-center w-full max-w-3xl">
|
<main className="flex flex-col row-start-2 gap-8 items-center w-full max-w-3xl">
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex justify-between items-center">
|
||||||
<CardTitle>Profiles</CardTitle>
|
<CardTitle>Profiles</CardTitle>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex gap-2 items-center">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -411,9 +415,9 @@ export default function Home() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSettingsDialogOpen(true);
|
setSettingsDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex gap-2 items-center"
|
||||||
>
|
>
|
||||||
<GoGear className="h-4 w-4" />
|
<GoGear className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Settings</TooltipContent>
|
<TooltipContent>Settings</TooltipContent>
|
||||||
@@ -425,9 +429,9 @@ export default function Home() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCreateProfileDialogOpen(true);
|
setCreateProfileDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex gap-2 items-center"
|
||||||
>
|
>
|
||||||
<GoPlus className="h-4 w-4" />
|
<GoPlus className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Create a new profile</TooltipContent>
|
<TooltipContent>Create a new profile</TooltipContent>
|
||||||
|
|||||||
@@ -69,16 +69,29 @@ export function ProfileSelectorDialog({
|
|||||||
|
|
||||||
// Auto-select first available profile for link opening
|
// Auto-select first available profile for link opening
|
||||||
if (profileList.length > 0) {
|
if (profileList.length > 0) {
|
||||||
// Find the first profile that can be used for opening links
|
// First, try to find a running profile that can be used for opening links
|
||||||
const availableProfile = profileList.find((profile) => {
|
const runningAvailableProfile = profileList.find((profile) => {
|
||||||
return canUseProfileForLinks(profile, profileList, runningProfiles);
|
const isRunning = runningProfiles.has(profile.name);
|
||||||
|
return (
|
||||||
|
isRunning &&
|
||||||
|
canUseProfileForLinks(profile, profileList, runningProfiles)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (availableProfile) {
|
if (runningAvailableProfile) {
|
||||||
setSelectedProfile(availableProfile.name);
|
setSelectedProfile(runningAvailableProfile.name);
|
||||||
} else {
|
} else {
|
||||||
// If no suitable profile found, still select the first one to show UI
|
// If no running profile is suitable, find the first profile that can be used for opening links
|
||||||
setSelectedProfile(profileList[0].name);
|
const availableProfile = profileList.find((profile) => {
|
||||||
|
return canUseProfileForLinks(profile, profileList, runningProfiles);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (availableProfile) {
|
||||||
|
setSelectedProfile(availableProfile.name);
|
||||||
|
} else {
|
||||||
|
// If no suitable profile found, still select the first one to show UI
|
||||||
|
setSelectedProfile(profileList[0].name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -277,7 +290,7 @@ export function ProfileSelectorDialog({
|
|||||||
!canUseForLinks ? "opacity-50" : ""
|
!canUseForLinks ? "opacity-50" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 p-3 rounded-lg hover:bg-accent cursor-pointer">
|
<div className="flex items-center gap-3 py-1 px-2 rounded-lg hover:bg-accent cursor-pointer">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{(() => {
|
{(() => {
|
||||||
const IconComponent = getBrowserIcon(
|
const IconComponent = getBrowserIcon(
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
|||||||
<DialogTitle>Settings</DialogTitle>
|
<DialogTitle>Settings</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="grid gap-6 py-4 overflow-y-auto flex-1 min-h-0">
|
<div className="grid overflow-y-auto flex-1 gap-6 py-4 min-h-0">
|
||||||
{/* Appearance Section */}
|
{/* Appearance Section */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Label className="text-base font-medium">Appearance</Label>
|
<Label className="text-base font-medium">Appearance</Label>
|
||||||
@@ -172,7 +172,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
|||||||
|
|
||||||
{/* Default Browser Section */}
|
{/* Default Browser Section */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex justify-between items-center">
|
||||||
<Label className="text-base font-medium">Default Browser</Label>
|
<Label className="text-base font-medium">Default Browser</Label>
|
||||||
<Badge variant={isDefaultBrowser ? "default" : "secondary"}>
|
<Badge variant={isDefaultBrowser ? "default" : "secondary"}>
|
||||||
{isDefaultBrowser ? "Active" : "Inactive"}
|
{isDefaultBrowser ? "Active" : "Inactive"}
|
||||||
|
|||||||
@@ -15,8 +15,15 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|||||||
"--normal-bg": "var(--popover)",
|
"--normal-bg": "var(--popover)",
|
||||||
"--normal-text": "var(--popover-foreground)",
|
"--normal-text": "var(--popover-foreground)",
|
||||||
"--normal-border": "var(--border)",
|
"--normal-border": "var(--border)",
|
||||||
|
zIndex: 99999,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
|
toastOptions={{
|
||||||
|
style: {
|
||||||
|
zIndex: 99999,
|
||||||
|
pointerEvents: "auto",
|
||||||
|
},
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,17 +47,17 @@ export function UpdateNotificationComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 p-4 max-w-md bg-background border border-border rounded-lg shadow-lg">
|
<div className="flex flex-col gap-3 p-4 max-w-md rounded-lg border shadow-lg bg-background border-border">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex gap-2 justify-between items-start">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex gap-2 items-center">
|
||||||
<span className="font-semibold text-foreground">
|
<span className="font-semibold text-foreground">
|
||||||
{browserDisplayName} Update Available
|
{browserDisplayName} Update Available
|
||||||
</span>
|
</span>
|
||||||
<Badge
|
<Badge
|
||||||
variant={notification.is_stable_update ? "default" : "secondary"}
|
variant={notification.is_stable_update ? "default" : "secondary"}
|
||||||
>
|
>
|
||||||
{notification.is_stable_update ? "Stable" : "Beta"}
|
{notification.is_stable_update ? "Stable" : "Nightly"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
@@ -71,20 +71,20 @@ export function UpdateNotificationComponent({
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await onDismiss(notification.id);
|
await onDismiss(notification.id);
|
||||||
}}
|
}}
|
||||||
className="h-6 w-6 p-0 shrink-0"
|
className="p-0 w-6 h-6 shrink-0"
|
||||||
>
|
>
|
||||||
<FaTimes className="h-3 w-3" />
|
<FaTimes className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex gap-2 items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleUpdateClick}
|
onClick={handleUpdateClick}
|
||||||
disabled={isUpdating}
|
disabled={isUpdating}
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-2"
|
className="flex gap-2 items-center"
|
||||||
>
|
>
|
||||||
<FaDownload className="h-3 w-3" />
|
<FaDownload className="w-3 h-3" />
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function WindowDragArea() {
|
||||||
|
const [isMacOS, setIsMacOS] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if we're on macOS using user agent detection
|
||||||
|
const checkPlatform = () => {
|
||||||
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
setIsMacOS(userAgent.includes("mac"));
|
||||||
|
};
|
||||||
|
|
||||||
|
checkPlatform();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
|
// Only handle left mouse button
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
|
||||||
|
// Start dragging asynchronously
|
||||||
|
const startDrag = async () => {
|
||||||
|
try {
|
||||||
|
const window = getCurrentWindow();
|
||||||
|
await window.startDragging();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to start window dragging:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void startDrag();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only render on macOS
|
||||||
|
if (!isMacOS) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed top-0 right-0 left-0 z-50 h-8 cursor-move"
|
||||||
|
style={{
|
||||||
|
// Ensure it's above all other content
|
||||||
|
zIndex: 9999,
|
||||||
|
// Make it transparent but still capture mouse events
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
// Prevent text selection during drag
|
||||||
|
userSelect: "none",
|
||||||
|
WebkitUserSelect: "none",
|
||||||
|
}}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
// Prevent context menu
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -135,6 +135,10 @@ export function useAppUpdateNotifications() {
|
|||||||
id: "app-update",
|
id: "app-update",
|
||||||
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
||||||
position: "top-left",
|
position: "top-left",
|
||||||
|
style: {
|
||||||
|
zIndex: 99999, // Ensure app updates appear above dialogs
|
||||||
|
pointerEvents: "auto", // Ensure app updates remain interactive
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -232,8 +232,10 @@ export function useUpdateNotifications(
|
|||||||
id: notification.id,
|
id: notification.id,
|
||||||
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
||||||
position: "top-right",
|
position: "top-right",
|
||||||
// Remove transparent styling to fix background issue
|
style: {
|
||||||
style: undefined,
|
zIndex: 99999, // Ensure notifications appear above dialogs
|
||||||
|
pointerEvents: "auto", // Ensure notifications remain interactive
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ export function showToast(props: ToastProps & { id?: string }) {
|
|||||||
border: "none",
|
border: "none",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
zIndex: 99999,
|
||||||
|
pointerEvents: "auto",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (props.type === "error") {
|
} else if (props.type === "error") {
|
||||||
@@ -127,6 +129,8 @@ export function showToast(props: ToastProps & { id?: string }) {
|
|||||||
border: "none",
|
border: "none",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
zIndex: 99999,
|
||||||
|
pointerEvents: "auto",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -138,6 +142,8 @@ export function showToast(props: ToastProps & { id?: string }) {
|
|||||||
border: "none",
|
border: "none",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
zIndex: 99999,
|
||||||
|
pointerEvents: "auto",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,3 +123,23 @@
|
|||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure Sonner toasts appear above all dialogs and remain interactive */
|
||||||
|
.toaster,
|
||||||
|
[data-sonner-toaster] {
|
||||||
|
z-index: 99999 !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast] {
|
||||||
|
z-index: 99999 !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure toast buttons and interactive elements work */
|
||||||
|
[data-sonner-toast] button,
|
||||||
|
[data-sonner-toast] [role="button"],
|
||||||
|
[data-sonner-toast] input,
|
||||||
|
[data-sonner-toast] select {
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
black: "#000000",
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
dark: "#000000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user