fix: solve windows deeplink error

This commit is contained in:
Leafd 2025-10-09 10:31:51 -04:00
parent fa6acaf41f
commit ad3e27c859
No known key found for this signature in database
GPG key ID: D44AE7A3699406BE
6 changed files with 87 additions and 11 deletions

16
src-tauri/Cargo.lock generated
View file

@ -836,6 +836,7 @@ dependencies = [
"tauri-plugin-deep-link",
"tauri-plugin-opener",
"tauri-plugin-process",
"tauri-plugin-single-instance",
"tauri-plugin-updater",
"tokio",
"urlencoding",
@ -4915,6 +4916,21 @@ dependencies = [
"tauri-plugin",
]
[[package]]
name = "tauri-plugin-single-instance"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb9cac815bf11c4a80fb498666bcdad66d65b89e3ae24669e47806febb76389c"
dependencies = [
"serde",
"serde_json",
"tauri",
"thiserror 2.0.17",
"tracing",
"windows-sys 0.60.2",
"zbus",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.9.0"

View file

@ -10,6 +10,20 @@
],
"permissions": [
"core:default",
{
"identifier": "opener:allow-open-url",
"allow": [
{
"url": "https://*"
},
{
"url": "http://*"
},
{
"url": "hackatime://*"
}
]
},
"opener:default",
"deep-link:default",
"updater:default",

View file

@ -82,8 +82,8 @@ pub async fn get_auth_state(
pub async fn authenticate_with_rails(
api_config: crate::config::ApiConfig,
pkce_state: State<'_, Arc<tauri::async_runtime::Mutex<Option<PkceState>>>>,
_app_handle: tauri::AppHandle,
) -> Result<(), String> {
app_handle: tauri::AppHandle,
) -> Result<String, String> {
let callback_url = "hackatime://auth/callback";
@ -106,12 +106,16 @@ pub async fn authenticate_with_rails(
urlencoding::encode(&code_challenge)
);
if let Err(e) = open::that(&auth_url) {
return Err(format!("Failed to open authentication URL: {}", e));
// Use Tauri's opener plugin for better cross-platform support
use tauri_plugin_opener::OpenerExt;
if let Err(e) = app_handle.opener().open_url(&auth_url, None::<&str>) {
push_log("error", "backend", format!("Failed to open authentication URL: {}", e));
// Return the URL so the frontend can show a fallback button
return Ok(auth_url);
}
push_log("info", "backend", "OAuth authentication URL opened in browser. Waiting for callback...".to_string());
Ok(())
Ok(auth_url)
}
#[tauri::command]

View file

@ -61,7 +61,6 @@
]
}
},
"single-instance": {},
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdERjg4QTFCNTJFMDk0MUQKUldRZGxPQlNHNHI0ZlRkMDN0MGI1MnllY1dUVStZalV3dVdhcTFuREx5SGtBc0txQ2xnTWs3WU4K",
"endpoints": [

View file

@ -58,6 +58,7 @@ const sessionStats = ref<any>(null);
const presenceData = ref<any>(null);
const presenceRefreshInterval = ref<number | null>(null);
const presenceFetchInProgress = ref(false);
const oauthUrl = ref<string | null>(null);
const nextPresenceFetchAllowedAt = ref<number>(0);
const lastPresenceFetchAt = ref<number>(0);
@ -343,10 +344,11 @@ function stopPresenceRefresh() {
async function authenticate() {
isLoading.value = true;
oauthUrl.value = null;
try {
await invoke("authenticate_with_rails", { apiConfig: apiConfig.value });
alert(`OAuth authentication opened in browser!\n\nInstructions:\n1. Complete the OAuth flow in your browser\n2. The app will automatically handle the callback\n3. If the callback doesn't work, you can manually paste the authorization code from the URL\n\nFor manual entry:\n- Copy the 'code' parameter from the callback URL\n- Use the "Direct OAuth" field below to paste it`);
const url = await invoke("authenticate_with_rails", { apiConfig: apiConfig.value });
oauthUrl.value = url as string;
console.log("OAuth URL:", url);
} catch (error) {
console.error("Authentication failed:", error);
alert("Authentication failed: " + (error instanceof Error ? error.message : String(error)));
@ -355,6 +357,23 @@ async function authenticate() {
}
}
async function openOAuthUrlManually() {
if (oauthUrl.value) {
try {
const { openUrl } = await import("@tauri-apps/plugin-opener");
await openUrl(oauthUrl.value);
} catch (error) {
console.error("Failed to open URL manually:", error);
try {
await navigator.clipboard.writeText(oauthUrl.value);
alert("Failed to open link. URL copied to clipboard!");
} catch (clipError) {
alert(`Failed to open link. Please visit: ${oauthUrl.value}`);
}
}
}
}
async function logout() {
try {
stopPresenceRefresh();
@ -580,9 +599,11 @@ async function checkForUpdatesAndInstall() {
:weeklyChartData="weeklyChartData"
:isLoading="isLoading"
:isDevMode="isDevMode"
:oauthUrl="oauthUrl"
v-model:directOAuthToken="directOAuthToken"
@authenticate="authenticate"
@handleDirectOAuthAuth="handleDirectOAuthAuth"
@openOAuthUrlManually="openOAuthUrlManually"
/>
</div>

View file

@ -3,7 +3,7 @@
<div v-if="!authState.is_authenticated" class="flex items-center justify-center min-h-96">
<div class="text-center max-w-md">
<h3 class="text-2xl mb-4 text-text-primary">Welcome to Hackatime</h3>
<p class="text-text-secondary mb-8 leading-relaxed">Connect to your KubeTime account to start tracking your coding time.</p>
<p class="text-text-secondary mb-8 leading-relaxed">Connect to your Hackatime account to start tracking your coding time.</p>
<!-- Production authentication (deep link) -->
<template v-if="!isDevMode">
@ -12,9 +12,17 @@
:disabled="isLoading"
class="bg-accent-primary text-white border-0 px-8 py-4 rounded-xl text-base font-medium cursor-pointer transition-all duration-200 my-4 w-full hover:bg-accent-secondary hover:shadow-card-hover disabled:bg-text-muted disabled:cursor-not-allowed disabled:transform-none"
>
{{ isLoading ? 'Opening Login...' : 'Login with KubeTime' }}
{{ isLoading ? 'Opening Login...' : 'Login with Hackatime' }}
</button>
<p class="text-text-secondary text-sm mt-2">This will open your browser for OAuth authentication.</p>
<button
v-if="oauthUrl && !isLoading"
@click="openOAuthUrlManually"
class="bg-bg-secondary text-text-primary border border-border-secondary px-6 py-3 rounded-xl text-sm font-medium cursor-pointer transition-all duration-200 mt-4 w-full hover:bg-bg-tertiary hover:border-accent-primary"
>
Link didn't open? Click here
</button>
</template>
<!-- Development authentication options -->
@ -28,6 +36,14 @@
{{ isLoading ? 'Opening Login...' : 'Open Browser Login' }}
</button>
<p class="text-text-secondary text-sm mt-2">This will open your browser for OAuth authentication.</p>
<button
v-if="oauthUrl && !isLoading"
@click="openOAuthUrlManually"
class="bg-bg-secondary text-text-primary border border-border-secondary px-6 py-3 rounded-xl text-sm font-medium cursor-pointer transition-all duration-200 mt-4 w-full hover:bg-bg-tertiary hover:border-accent-primary"
>
Link didn't open? Click here
</button>
</div>
<div class="mt-8 pt-8 border-t border-border-primary text-left">
@ -190,11 +206,13 @@ defineProps<{
isLoading: boolean;
isDevMode: boolean;
directOAuthToken: string;
oauthUrl: string | null;
}>();
const emit = defineEmits<{
authenticate: [];
handleDirectOAuthAuth: [];
openOAuthUrlManually: [];
'update:directOAuthToken': [value: string];
}>();
@ -230,6 +248,10 @@ async function handleDirectOAuthAuth() {
emit('handleDirectOAuthAuth');
}
async function openOAuthUrlManually() {
emit('openOAuthUrlManually');
}
function getWeeklyCardStyle(percentage: number): string {
const positiveColor = { r: 52, g: 148, b: 230 };
const negativeColor = { r: 236, g: 110, b: 173 };