Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
refactor(deep-link): streamline OAuth handling and skill setup process
- Removed the RPC call for persisting setup completion, now handled directly in the preferences store.
- Updated comments in the deep link handler to clarify the sequence of operations during OAuth completion.
- Enhanced the `set_setup_complete` function to automatically enable skills upon setup completion, improving user experience during skill activation.

This refactor simplifies the OAuth deep link handling and ensures skills are automatically enabled after setup, enhancing the overall flow.
  • Loading branch information
senamakel committed Mar 31, 2026
commit e26b887f53bd0c1f9fa5eefc1bac8515dd9c3e9a
16 changes: 6 additions & 10 deletions app/src/utils/desktopDeepLinkListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getCurrent, onOpenUrl } from '@tauri-apps/plugin-deep-link';

import { skillManager } from '../lib/skills/manager';
import { emitSkillStateChange } from '../lib/skills/skillEvents';
import { setSetupComplete as rpcSetSetupComplete, startSkill } from '../lib/skills/skillsApi';
import { startSkill } from '../lib/skills/skillsApi';
import { consumeLoginToken } from '../services/api/authApi';
import { store } from '../store';
import { setToken } from '../store/authSlice';
Expand Down Expand Up @@ -122,29 +122,25 @@ const handleOAuthDeepLink = async (parsed: URL) => {

console.log(`[DeepLink] OAuth success for skill=${skillId} integration=${integrationId}`);

// 1. Persist setup completion
await rpcSetSetupComplete(skillId, true).catch(err =>
console.warn('[DeepLink] Failed to persist setup_complete via RPC:', err)
);
emitSkillStateChange(skillId);

// 2. Start the skill in the core QuickJS runtime (if not already running)
// 1. Start the skill in the core QuickJS runtime (if not already running).
// This also sets enabled=true via the preferences store.
try {
await startSkill(skillId);
console.log(`[DeepLink] Skill '${skillId}' started in core runtime`);
} catch (startErr) {
console.warn(`[DeepLink] Could not start skill '${skillId}' in runtime:`, startErr);
}

// 3. Send oauth/complete to the running skill with the credential
// 2. Notify the running skill of the OAuth credential, mark setup_complete,
// and activate (list tools, sync to backend).
try {
await skillManager.notifyOAuthComplete(skillId, integrationId);
console.log(`[DeepLink] OAuth complete sent to skill '${skillId}'`);
} catch (runtimeErr) {
console.warn('[DeepLink] Runtime notify failed:', runtimeErr);
}

// 4. Trigger initial data sync
// 3. Trigger initial data sync
try {
await skillManager.triggerSync(skillId);
} catch {
Expand Down
9 changes: 8 additions & 1 deletion src/openhuman/skills/preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,15 @@ impl PreferencesStore {
}

/// Set the setup completion flag for a skill. Persists immediately.
/// When marking setup as complete, also sets `enabled = true` so the skill
/// auto-starts on subsequent app launches.
pub fn set_setup_complete(&self, skill_id: &str, complete: bool) {
self.update(skill_id, |p| p.setup_complete = complete);
self.update(skill_id, |p| {
p.setup_complete = complete;
if complete {
p.enabled = true;
}
});
log::info!(
"[preferences] setup_complete for '{}' set to {}",
skill_id,
Expand Down
15 changes: 15 additions & 0 deletions src/openhuman/skills/quickjs_libs/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,21 @@ globalThis.data = {
console.log('[oauth.fetch] ' + method + ' ' + proxyUrl + ' (credentialId=' + globalThis.__oauthCredential.credentialId + ')');
var result = await net.fetch(proxyUrl, fetchOpts);
console.log('[oauth.fetch] response status=' + result.status + ' body_len=' + (result.body ? result.body.length : 0));

// Auto-clear invalid/expired credentials so the user is prompted to re-auth
if (result.status === 401 || result.status === 403) {
console.warn('[oauth.fetch] Got ' + result.status + ' — clearing invalid credential for re-auth');
globalThis.__oauthCredential = null;
if (typeof globalThis.state !== 'undefined' && globalThis.state.set) {
globalThis.state.set('__oauth_credential', '');
globalThis.state.setPartial({
connection_status: 'error',
connection_error: 'Integration token expired or invalid. Please reconnect.',
auth_status: 'not_authenticated',
});
}
}

return result;
},

Expand Down
35 changes: 35 additions & 0 deletions src/openhuman/skills/quickjs_libs/qjs_ops/ops_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ use std::time::{Duration, Instant};

use super::types::{TimerEntry, TimerState, ALLOWED_ENV_VARS};

/// Read the session JWT from the on-disk credentials store.
///
/// Returns `None` on any failure so the caller can fall back to env vars.
fn token_from_credentials_store() -> Option<String> {
use crate::openhuman::credentials::{AuthService, APP_SESSION_PROVIDER};

let home = directories::UserDirs::new()?.home_dir().to_path_buf();
let default_dir = home.join(".openhuman");

let state_dir = match std::env::var("OPENHUMAN_WORKSPACE") {
Ok(ws) if !ws.is_empty() => {
let ws_path = std::path::PathBuf::from(&ws);
if ws_path.join("config.toml").exists() {
ws_path
} else {
default_dir
}
}
_ => default_dir,
};

if !state_dir.exists() {
return None;
}

let auth = AuthService::new(&state_dir, true);
let profile = auth.get_profile(APP_SESSION_PROVIDER, None).ok()??;
profile.token.filter(|t| !t.trim().is_empty())
}

pub fn register<'js>(
ctx: &Ctx<'js>,
ops: &Object<'js>,
Expand Down Expand Up @@ -115,6 +145,11 @@ pub fn register<'js>(
ops.set(
"get_session_token",
Function::new(ctx.clone(), || -> String {
// Try the on-disk credentials store first (where login actually persists
// the JWT), then fall back to the legacy JWT_TOKEN env var.
if let Some(token) = token_from_credentials_store() {
return token;
}
std::env::var("JWT_TOKEN").unwrap_or_default()
}),
)?;
Expand Down