Skip to content

Commit 7ad8a8d

Browse files
createpjfclaude
andcommitted
feat: gateway mode switcher + cloud token Keychain separation
- Add cloud_api_token Keychain key separate from local api_token - Add store/get/delete_cloud_token Tauri commands - Add GatewayMode (local|cloud) to constants with separate token getters - Settings: segmented Local/Cloud switcher with mode-specific panels - Local panel: gateway controls + ProviderKeyManager + auth token - Cloud panel: login/register/logout + account info - ProviderKeyManager: Add Custom Provider form (name + baseUrl + apiKey) - AccountPage: simplified to cloud account management only - App.tsx: mode-aware init loads correct token from Keychain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fd9ab29 commit 7ad8a8d

File tree

8 files changed

+673
-323
lines changed

8 files changed

+673
-323
lines changed

apps/desktop/src-tauri/src/commands.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ pub async fn delete_token() -> Result<(), String> {
2626
keychain::delete_token()
2727
}
2828

29+
#[tauri::command]
30+
pub async fn store_cloud_token(token: String) -> Result<(), String> {
31+
keychain::store_cloud_token(&token)
32+
}
33+
34+
#[tauri::command]
35+
pub async fn get_cloud_token() -> Result<Option<String>, String> {
36+
keychain::get_cloud_token()
37+
}
38+
39+
#[tauri::command]
40+
pub async fn delete_cloud_token() -> Result<(), String> {
41+
keychain::delete_cloud_token()
42+
}
43+
2944
#[tauri::command]
3045
pub async fn copy_to_clipboard(text: String) -> Result<(), String> {
3146
let mut clipboard = Clipboard::new().map_err(|e| e.to_string())?;

apps/desktop/src-tauri/src/keychain.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use keyring::Entry;
22

33
const SERVICE_NAME: &str = "com.routebox.app";
44
const TOKEN_KEY: &str = "api_token";
5+
const CLOUD_TOKEN_KEY: &str = "cloud_api_token";
56

67
pub fn store_token(token: &str) -> Result<(), String> {
78
let entry = Entry::new(SERVICE_NAME, TOKEN_KEY).map_err(|e| e.to_string())?;
@@ -25,3 +26,26 @@ pub fn delete_token() -> Result<(), String> {
2526
Err(e) => Err(e.to_string()),
2627
}
2728
}
29+
30+
pub fn store_cloud_token(token: &str) -> Result<(), String> {
31+
let entry = Entry::new(SERVICE_NAME, CLOUD_TOKEN_KEY).map_err(|e| e.to_string())?;
32+
entry.set_password(token).map_err(|e| e.to_string())
33+
}
34+
35+
pub fn get_cloud_token() -> Result<Option<String>, String> {
36+
let entry = Entry::new(SERVICE_NAME, CLOUD_TOKEN_KEY).map_err(|e| e.to_string())?;
37+
match entry.get_password() {
38+
Ok(token) => Ok(Some(token)),
39+
Err(keyring::Error::NoEntry) => Ok(None),
40+
Err(e) => Err(e.to_string()),
41+
}
42+
}
43+
44+
pub fn delete_cloud_token() -> Result<(), String> {
45+
let entry = Entry::new(SERVICE_NAME, CLOUD_TOKEN_KEY).map_err(|e| e.to_string())?;
46+
match entry.delete_credential() {
47+
Ok(()) => Ok(()),
48+
Err(keyring::Error::NoEntry) => Ok(()),
49+
Err(e) => Err(e.to_string()),
50+
}
51+
}

apps/desktop/src-tauri/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ pub fn run() {
111111
commands::store_token,
112112
commands::get_token,
113113
commands::delete_token,
114+
commands::store_cloud_token,
115+
commands::get_cloud_token,
116+
commands::delete_cloud_token,
114117
commands::copy_to_clipboard,
115118
commands::show_notification,
116119
commands::toggle_panel,

apps/desktop/src/App.tsx

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { AlertBanner } from "@/components/AlertBanner";
1616
import { ToastContainer } from "@/components/ToastContainer";
1717
import { useRealtimeStats } from "@/hooks/useRealtimeStats";
1818
import { useToast } from "@/hooks/useToast";
19-
import { getGatewayUrl, setGatewayUrl, setAuthToken, getPortFromUrl } from "@/lib/constants";
19+
import { getGatewayUrl, setGatewayUrl, setAuthToken, setCloudAuthToken, setGatewayMode, getGatewayMode, getPortFromUrl, ROUTEBOX_CLOUD_URL } from "@/lib/constants";
2020
import { checkGatewayHealth, waitForGateway, isLocalGatewayUrl } from "@/lib/gateway-health";
2121
import type { RealtimeStats, RequestLogEntry } from "@/types/stats";
2222

@@ -45,27 +45,48 @@ export function App() {
4545

4646
// Load persisted settings before any API calls
4747
useEffect(() => {
48-
import("@tauri-apps/plugin-store")
49-
.then(({ load }) => load("settings.json", { defaults: {} }))
50-
.then(async (store) => {
48+
async function loadSettings() {
49+
try {
50+
const { load } = await import("@tauri-apps/plugin-store");
51+
const store = await load("settings.json", { defaults: {} });
52+
53+
const mode = await store.get<string>("gatewayMode");
54+
const savedMode = mode === "cloud" ? "cloud" : "local";
55+
setGatewayMode(savedMode);
56+
5157
const url = await store.get<string>("gatewayUrl");
52-
if (url) setGatewayUrl(url);
58+
if (url) {
59+
setGatewayUrl(url);
60+
} else {
61+
// Apply default URL based on mode
62+
setGatewayUrl(savedMode === "cloud" ? ROUTEBOX_CLOUD_URL : "http://localhost:3001");
63+
}
5364

5465
const dismissed = await store.get<boolean>("onboardingDismissed");
5566
setOnboardingDismissed(!!dismissed);
56-
})
57-
.catch(() => {
58-
// Not in Tauri — check if we should show onboarding anyway
67+
68+
// Load the appropriate token based on mode
69+
const { invoke } = await import("@tauri-apps/api/core");
70+
if (savedMode === "cloud") {
71+
try {
72+
const t = await invoke<string>("get_cloud_token");
73+
if (t) { setCloudAuthToken(t); setToken(t); }
74+
} catch {}
75+
} else {
76+
try {
77+
const t = await invoke<string>("get_token");
78+
if (t) { setAuthToken(t); setToken(t); }
79+
} catch {}
80+
}
81+
} catch {
82+
// Not in Tauri
5983
setOnboardingDismissed(false);
60-
})
61-
.finally(() => {
84+
} finally {
6285
setSettingsLoaded(true);
63-
});
86+
}
87+
}
6488

65-
import("@tauri-apps/api/core")
66-
.then(({ invoke }) => invoke<string>("get_token"))
67-
.then((t) => { if (t) { setAuthToken(t); setToken(t); } })
68-
.catch(() => {});
89+
loadSettings();
6990
}, []);
7091

7192
// Gateway auto-start — waits for settings to be loaded first
@@ -89,7 +110,8 @@ export function App() {
89110
}
90111

91112
const url = getGatewayUrl();
92-
const isLocal = isLocalGatewayUrl(url);
113+
const currentMode = getGatewayMode();
114+
const isLocal = currentMode === "local" && isLocalGatewayUrl(url);
93115
setGatewayState("checking");
94116

95117
// Already running / reachable?

0 commit comments

Comments
 (0)