diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index fa51d92a..c73385bf 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -35,6 +35,7 @@ "@supabase/supabase-js": "^2.47.7", "@tauri-apps/api": "2.1.1", "@tauri-apps/plugin-cli": "^2.2.0", + "@tauri-apps/plugin-deep-link": "^2.2.0", "@tauri-apps/plugin-dialog": "^2.2.0", "@tauri-apps/plugin-fs": "^2.2.0", "@tauri-apps/plugin-http": "^2.2.0", diff --git a/apps/readest-app/src-tauri/Cargo.lock b/apps/readest-app/src-tauri/Cargo.lock index 8be729f0..ac82fb43 100644 --- a/apps/readest-app/src-tauri/Cargo.lock +++ b/apps/readest-app/src-tauri/Cargo.lock @@ -15,6 +15,7 @@ dependencies = [ "tauri", "tauri-build 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "tauri-plugin-cli", + "tauri-plugin-deep-link", "tauri-plugin-devtools", "tauri-plugin-dialog", "tauri-plugin-fs", @@ -891,6 +892,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -1033,6 +1054,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -1277,6 +1304,15 @@ dependencies = [ "syn 2.0.91", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "document-features" version = "0.2.10" @@ -2016,6 +2052,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.2" @@ -3224,6 +3266,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -4004,7 +4056,7 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "windows-registry", + "windows-registry 0.2.0", ] [[package]] @@ -4084,6 +4136,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + [[package]] name = "rust_decimal" version = "1.36.0" @@ -4958,6 +5021,26 @@ dependencies = [ "thiserror 2.0.9", ] +[[package]] +name = "tauri-plugin-deep-link" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35d51ffd286073414d26353bcfc9e83e3cd63f96fa7f7a912f92f2118e5de5a6" +dependencies = [ + "dunce", + "rust-ini", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-utils 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 2.0.9", + "tracing", + "url", + "windows-registry 0.3.0", + "windows-result", +] + [[package]] name = "tauri-plugin-devtools" version = "2.0.0" @@ -5422,6 +5505,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -5783,6 +5875,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "try-lock" version = "0.2.5" @@ -6332,7 +6430,7 @@ dependencies = [ "windows-implement", "windows-interface", "windows-result", - "windows-strings", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] @@ -6365,7 +6463,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", - "windows-strings", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafa604f2104cf5ae2cc2db1dee84b7e6a5d11b05f737b60def0ffdc398cbc0a" +dependencies = [ + "windows-result", + "windows-strings 0.2.0", "windows-targets 0.52.6", ] @@ -6388,6 +6497,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978d65aedf914c664c510d9de43c8fd85ca745eaff1ed53edf409b479e441663" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/apps/readest-app/src-tauri/Cargo.toml b/apps/readest-app/src-tauri/Cargo.toml index 0f00c2a2..1efe0330 100644 --- a/apps/readest-app/src-tauri/Cargo.toml +++ b/apps/readest-app/src-tauri/Cargo.toml @@ -34,6 +34,7 @@ tauri-plugin-shell = "2" tauri-plugin-process = "2" tauri-plugin-oauth = "2" tauri-plugin-opener = "2.2.2" +tauri-plugin-deep-link = "2" [patch.crates-io] tauri = { path = "../../../packages/tauri/crates/tauri" } diff --git a/apps/readest-app/src-tauri/capabilities/default.json b/apps/readest-app/src-tauri/capabilities/default.json index 969cda50..3d6e97f9 100644 --- a/apps/readest-app/src-tauri/capabilities/default.json +++ b/apps/readest-app/src-tauri/capabilities/default.json @@ -60,6 +60,7 @@ "process:allow-restart", "oauth:allow-start", "oauth:allow-cancel", - "opener:default" + "opener:default", + "deep-link:default" ] } diff --git a/apps/readest-app/src-tauri/src/lib.rs b/apps/readest-app/src-tauri/src/lib.rs index 056d49a9..3a5465d0 100644 --- a/apps/readest-app/src-tauri/src/lib.rs +++ b/apps/readest-app/src-tauri/src/lib.rs @@ -74,6 +74,7 @@ async fn start_server(window: Window) -> Result { pub fn run() { let builder = tauri::Builder::default() .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_oauth::init()) .invoke_handler(tauri::generate_handler![start_server]) .plugin(tauri_plugin_shell::init()) diff --git a/apps/readest-app/src-tauri/tauri.conf.json b/apps/readest-app/src-tauri/tauri.conf.json index 5809cd47..177ffb0f 100644 --- a/apps/readest-app/src-tauri/tauri.conf.json +++ b/apps/readest-app/src-tauri/tauri.conf.json @@ -130,6 +130,12 @@ } ] }, + "deep-link": { + "mobile": [{ "host": "web.readest.com" }], + "desktop": { + "schemes": ["readest"] + } + }, "updater": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU0OTAxMURGQkUzQjFENTQKUldSVUhUdSszeEdRNUExdmFkWnlvYWNYNG5wamkxMmUxRk5SejlMOTJVd28yNXlVTDh6Wld4OC8K", "endpoints": ["https://github.com/readest/readest/releases/latest/download/latest.json"] diff --git a/apps/readest-app/src/app/auth/page.tsx b/apps/readest-app/src/app/auth/page.tsx index e3e439ae..6372dd3c 100644 --- a/apps/readest-app/src/app/auth/page.tsx +++ b/apps/readest-app/src/app/auth/page.tsx @@ -17,6 +17,7 @@ import { useTheme } from '@/hooks/useTheme'; import { useEnv } from '@/context/EnvContext'; import { useSettingsStore } from '@/store/settingsStore'; import { isTauriAppPlatform } from '@/services/environment'; +import { onOpenUrl } from '@tauri-apps/plugin-deep-link'; import { start, cancel, onUrl, onInvalidUrl } from '@fabianlars/tauri-plugin-oauth'; import { openUrl } from '@tauri-apps/plugin-opener'; import { handleAuthCallback } from '@/helpers/auth'; @@ -64,7 +65,10 @@ export default function AuthPage() { provider, options: { skipBrowserRedirect: true, - redirectTo: `http://localhost:${port}`, + redirectTo: + process.env.NODE_ENV === 'production' + ? 'readest://auth/callback' + : `http://localhost:${port}`, }, }); @@ -75,34 +79,45 @@ export default function AuthPage() { openUrl(data.url); }; - const startOAuthServer = async () => { + const handleOAuthUrl = async (url: string) => { + console.log('Received OAuth URL:', url); + const hashMatch = url.match(/#(.*)/); + if (hashMatch) { + const hash = hashMatch[1]; + const params = new URLSearchParams(hash); + const accessToken = params.get('access_token'); + const refreshToken = params.get('refresh_token'); + const next = params.get('next') ?? '/'; + if (accessToken) { + handleAuthCallback({ accessToken, refreshToken, next, login, navigate: router.push }); + } + } + }; + + const startTauriOAuth = async () => { try { - const port = await start(); - setPort(port); - console.log(`OAuth server started on port ${port}`); + if (process.env.NODE_ENV === 'production') { + await onOpenUrl((urls) => { + urls.forEach((url) => { + handleOAuthUrl(url); + }); + }); + } else { + const port = await start(); + setPort(port); + console.log(`OAuth server started on port ${port}`); - await onUrl((url) => { - console.log('Received OAuth URL:', url); - const hashMatch = url.match(/#(.*)/); - if (hashMatch) { - const hash = hashMatch[1]; - const params = new URLSearchParams(hash); - const accessToken = params.get('access_token'); - const refreshToken = params.get('refresh_token'); - const next = params.get('next') ?? '/'; - handleAuthCallback({ accessToken, refreshToken, next, login, navigate: router.push }); - } - }); - - await onInvalidUrl((url) => { - console.log('Received invalid OAuth URL:', url); - }); + await onUrl(handleOAuthUrl); + await onInvalidUrl((url) => { + console.log('Received invalid OAuth URL:', url); + }); + } } catch (error) { console.error('Error starting OAuth server:', error); } }; - const stopOAuthServer = async () => { + const stopTauriOAuth = async () => { try { if (port) { await cancel(port); @@ -126,10 +141,10 @@ export default function AuthPage() { if (isOAuthServerRunning.current) return; isOAuthServerRunning.current = true; - startOAuthServer(); + startTauriOAuth(); return () => { isOAuthServerRunning.current = false; - stopOAuthServer(); + stopTauriOAuth(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -156,6 +171,9 @@ export default function AuthPage() { return null; } + // For tauri app development, use a custom OAuth server to handle the OAuth callback + // For tauri app production, use deeplink to handle the OAuth callback + // For web app, use the built-in OAuth callback page /auth/callback return isTauriAppPlatform() ? (