fix: properly handle urls added by system events

This commit is contained in:
zhom
2025-06-14 02:11:40 +04:00
parent 9ac662aee8
commit 8e7982bdf8
9 changed files with 893 additions and 748 deletions
+1
View File
@@ -69,6 +69,7 @@
"Torbrowser", "Torbrowser",
"turbopack", "turbopack",
"unlisten", "unlisten",
"unminimize",
"unrs", "unrs",
"urlencoding", "urlencoding",
"vercel", "vercel",
+2 -2
View File
@@ -21,7 +21,7 @@
"author": "", "author": "",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@types/node": "^22.15.30", "@types/node": "^22.15.31",
"@yao-pkg/pkg": "^6.5.1", "@yao-pkg/pkg": "^6.5.1",
"commander": "^14.0.0", "commander": "^14.0.0",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
@@ -30,6 +30,6 @@
"proxy-chain": "^2.5.9", "proxy-chain": "^2.5.9",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.33.1" "typescript-eslint": "^8.34.0"
} }
} }
+12 -11
View File
@@ -35,6 +35,7 @@
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"@tauri-apps/api": "^2.5.0", "@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-deep-link": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.2.2", "@tauri-apps/plugin-dialog": "^2.2.2",
"@tauri-apps/plugin-fs": "~2.3.0", "@tauri-apps/plugin-fs": "~2.3.0",
"@tauri-apps/plugin-opener": "^2.2.7", "@tauri-apps/plugin-opener": "^2.2.7",
@@ -48,31 +49,31 @@
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"sonner": "^2.0.5", "sonner": "^2.0.5",
"tailwind-merge": "^3.3.0", "tailwind-merge": "^3.3.1",
"tauri-plugin-macos-permissions-api": "^2.3.0" "tauri-plugin-macos-permissions-api": "^2.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.28.0", "@eslint/js": "^9.29.0",
"@next/eslint-plugin-next": "^15.3.3", "@next/eslint-plugin-next": "^15.3.3",
"@tailwindcss/postcss": "^4.1.8", "@tailwindcss/postcss": "^4.1.10",
"@tauri-apps/cli": "^2.5.0", "@tauri-apps/cli": "^2.5.0",
"@types/node": "^22.15.30", "@types/node": "^22.15.31",
"@types/react": "^19.1.6", "@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^8.33.1", "@typescript-eslint/eslint-plugin": "^8.34.0",
"@typescript-eslint/parser": "^8.33.1", "@typescript-eslint/parser": "^8.34.0",
"@vitejs/plugin-react": "^4.5.1", "@vitejs/plugin-react": "^4.5.2",
"eslint": "^9.28.0", "eslint": "^9.29.0",
"eslint-config-next": "^15.3.3", "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": "^16.1.0", "lint-staged": "^16.1.0",
"tailwindcss": "^4.1.8", "tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4", "tw-animate-css": "^1.3.4",
"typescript": "~5.8.3", "typescript": "~5.8.3",
"typescript-eslint": "^8.33.1" "typescript-eslint": "^8.34.0"
}, },
"packageManager": "pnpm@10.11.1", "packageManager": "pnpm@10.11.1",
"lint-staged": { "lint-staged": {
+762 -712
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -26,5 +26,33 @@
<string>public.app-category.productivity</string> <string>public.app-category.productivity</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright © 2025 Donut Browser</string> <string>Copyright © 2025 Donut Browser</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>HTML document</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>public.html</string>
<string>public.xhtml</string>
</array>
</dict>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Web site URL</string>
<key>CFBundleURLSchemes</key>
<array>
<string>http</string>
<string>https</string>
</array>
</dict>
</array>
</dict> </dict>
</plist> </plist>
+3
View File
@@ -20,6 +20,9 @@
"shell:allow-stdin-write", "shell:allow-stdin-write",
"deep-link:default", "deep-link:default",
"deep-link:allow-register", "deep-link:allow-register",
"deep-link:allow-unregister",
"deep-link:allow-is-registered",
"deep-link:allow-get-current",
"dialog:default", "dialog:default",
"dialog:allow-open", "dialog:allow-open",
"macos-permissions:default", "macos-permissions:default",
+67 -20
View File
@@ -1,4 +1,5 @@
// 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::env;
use std::sync::Mutex; use std::sync::Mutex;
use tauri::{Emitter, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder}; use tauri::{Emitter, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_deep_link::DeepLinkExt;
@@ -111,20 +112,16 @@ async fn handle_url_open(app: tauri::AppHandle, url: String) -> Result<(), Strin
// Check if the main window exists and is ready // Check if the main window exists and is ready
if let Some(window) = app.get_webview_window("main") { if let Some(window) = app.get_webview_window("main") {
if window.is_visible().unwrap_or(false) { println!("Main window exists");
// Window is visible, emit event directly
println!("Main window is visible, emitting show-profile-selector event"); // Try to show and focus the window first
app let _ = window.show();
.emit("show-profile-selector", url.clone()) let _ = window.set_focus();
.map_err(|e| format!("Failed to emit URL open event: {e}"))?; let _ = window.unminimize();
let _ = window.show();
let _ = window.set_focus(); app
} else { .emit("show-profile-selector", url.clone())
// Window not visible yet - add to pending URLs .map_err(|e| format!("Failed to emit URL open event: {e}"))?;
println!("Main window not visible, adding URL to pending list");
let mut pending = PENDING_URLS.lock().unwrap();
pending.push(url);
}
} else { } else {
// Window doesn't exist yet - add to pending URLs // Window doesn't exist yet - add to pending URLs
println!("Main window doesn't exist, adding URL to pending list"); println!("Main window doesn't exist, adding URL to pending list");
@@ -137,6 +134,8 @@ async fn handle_url_open(app: tauri::AppHandle, url: String) -> Result<(), Strin
#[tauri::command] #[tauri::command]
async fn check_and_handle_startup_url(app_handle: tauri::AppHandle) -> Result<bool, String> { async fn check_and_handle_startup_url(app_handle: tauri::AppHandle) -> Result<bool, String> {
println!("check_and_handle_startup_url called");
let pending_urls = { let pending_urls = {
let mut pending = PENDING_URLS.lock().unwrap(); let mut pending = PENDING_URLS.lock().unwrap();
let urls = pending.clone(); let urls = pending.clone();
@@ -144,12 +143,24 @@ async fn check_and_handle_startup_url(app_handle: tauri::AppHandle) -> Result<bo
urls urls
}; };
println!("Found {} pending URLs", pending_urls.len());
if !pending_urls.is_empty() { if !pending_urls.is_empty() {
println!( println!(
"Handling {} pending URLs from frontend request", "Handling {} pending URLs from frontend request",
pending_urls.len() pending_urls.len()
); );
// Ensure the main window is visible and focused
if let Some(window) = app_handle.get_webview_window("main") {
let _ = window.show();
let _ = window.set_focus();
let _ = window.unminimize();
// Give the window a moment to become visible
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
}
for url in pending_urls { for url in pending_urls {
println!("Emitting show-profile-selector event for URL: {url}"); println!("Emitting show-profile-selector event for URL: {url}");
if let Err(e) = app_handle.emit("show-profile-selector", url.clone()) { if let Err(e) = app_handle.emit("show-profile-selector", url.clone()) {
@@ -166,11 +177,23 @@ async fn check_and_handle_startup_url(app_handle: tauri::AppHandle) -> Result<bo
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
let args: Vec<String> = env::args().collect();
let startup_url = args.iter().find(|arg| arg.starts_with("http")).cloned();
if let Some(url) = startup_url.clone() {
println!("Found startup URL in command line: {url}");
let mut pending = PENDING_URLS.lock().unwrap();
pending.push(url.clone());
}
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|_, args, _cwd| {
println!("Single instance triggered with args: {args:?}");
}))
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_macos_permissions::init()) .plugin(tauri_plugin_macos_permissions::init())
.setup(|app| { .setup(|app| {
@@ -180,7 +203,10 @@ pub fn run() {
.title("Donut Browser") .title("Donut Browser")
.inner_size(900.0, 600.0) .inner_size(900.0, 600.0)
.resizable(false) .resizable(false)
.fullscreen(false); .fullscreen(false)
.center()
.focused(true)
.visible(true);
#[allow(unused_variables)] #[allow(unused_variables)]
let window = win_builder.build().unwrap(); let window = win_builder.build().unwrap();
@@ -199,16 +225,27 @@ pub fn run() {
#[cfg(any(windows, target_os = "linux"))] #[cfg(any(windows, target_os = "linux"))]
{ {
// For Windows and Linux, register all deep links at runtime for development // For Windows and Linux, register all deep links at runtime for development
app.deep_link().register_all()?; if let Err(e) = app.deep_link().register_all() {
eprintln!("Failed to register deep links: {e}");
}
}
#[cfg(target_os = "macos")]
{
// On macOS, try to register deep links for development builds
if let Err(e) = app.deep_link().register_all() {
eprintln!(
"Note: Deep link registration failed on macOS (this is normal for production): {e}"
);
}
} }
// Handle deep links - this works for both scenarios:
// 1. App is running and URL is opened
// 2. App is not running and URL causes app to launch
app.deep_link().on_open_url({ app.deep_link().on_open_url({
let handle = handle.clone(); let handle = handle.clone();
move |event| { move |event| {
let urls = event.urls(); let urls = event.urls();
println!("Deep link event received with {} URLs", urls.len());
for url in urls { for url in urls {
let url_string = url.to_string(); let url_string = url.to_string();
println!("Deep link received: {url_string}"); println!("Deep link received: {url_string}");
@@ -226,6 +263,16 @@ pub fn run() {
} }
}); });
if let Some(startup_url) = startup_url {
let handle_clone = handle.clone();
tauri::async_runtime::spawn(async move {
println!("Processing startup URL from command line: {startup_url}");
if let Err(e) = handle_url_open(handle_clone, startup_url.clone()).await {
eprintln!("Failed to handle startup URL: {e}");
}
});
}
// Initialize and start background version updater // Initialize and start background version updater
let app_handle = app.handle().clone(); let app_handle = app.handle().clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
+3 -2
View File
@@ -61,8 +61,9 @@
}, },
"plugins": { "plugins": {
"deep-link": { "deep-link": {
"schemes": ["http", "https"], "desktop": {
"domains": [] "schemes": ["http", "https"]
}
} }
} }
} }
+15 -1
View File
@@ -29,6 +29,7 @@ import { showErrorToast } from "@/lib/toast-utils";
import type { BrowserProfile, ProxySettings } from "@/types"; import type { BrowserProfile, ProxySettings } from "@/types";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { getCurrent } from "@tauri-apps/plugin-deep-link";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { FaDownload } from "react-icons/fa"; import { FaDownload } from "react-icons/fa";
import { GoGear, GoKebabHorizontal, GoPlus } from "react-icons/go"; import { GoGear, GoKebabHorizontal, GoPlus } from "react-icons/go";
@@ -102,6 +103,18 @@ export default function Home() {
useAppUpdateNotifications(); useAppUpdateNotifications();
// For some reason, app.deep_link().get_current() is not working properly
const checkCurrentUrl = useCallback(async () => {
try {
const currentUrl = await getCurrent();
if (currentUrl && currentUrl.length > 0) {
void handleUrlOpen(currentUrl[0]);
}
} catch (error) {
console.error("Failed to check current URL:", error);
}
}, []);
useEffect(() => { useEffect(() => {
void loadProfilesWithUpdateCheck(); void loadProfilesWithUpdateCheck();
@@ -113,6 +126,7 @@ export default function Home() {
// Check for startup URLs (when app was launched as default browser) // Check for startup URLs (when app was launched as default browser)
void checkStartupUrls(); void checkStartupUrls();
void checkCurrentUrl();
// Set up periodic update checks (every 30 minutes) // Set up periodic update checks (every 30 minutes)
const updateInterval = setInterval( const updateInterval = setInterval(
@@ -125,7 +139,7 @@ export default function Home() {
return () => { return () => {
clearInterval(updateInterval); clearInterval(updateInterval);
}; };
}, [loadProfilesWithUpdateCheck, checkForUpdates]); }, [loadProfilesWithUpdateCheck, checkForUpdates, checkCurrentUrl]);
// Check permissions when they are initialized // Check permissions when they are initialized
useEffect(() => { useEffect(() => {