Skip to content

Start up error handling #369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions crates/sage-api/src/types/error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ pub enum ErrorKind {
NotFound,
Unauthorized,
Internal,
DatabaseMigration,
Nfc,
}
1 change: 1 addition & 0 deletions crates/sage-cli/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ impl_endpoints! {
Self::Start => {
let mut sage = Sage::new(&path);
let mut receiver = sage.initialize().await?;
sage.switch_wallet().await?;
tokio::spawn(async move { while let Some(_message) = receiver.recv().await {} });
start_rpc(Arc::new(Mutex::new(sage))).await
},
Expand Down
7 changes: 4 additions & 3 deletions crates/sage-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ where
ErrorKind::Api => StatusCode::BAD_REQUEST,
ErrorKind::NotFound => StatusCode::NOT_FOUND,
ErrorKind::Unauthorized => StatusCode::UNAUTHORIZED,
ErrorKind::Wallet | ErrorKind::Internal | ErrorKind::Nfc => {
StatusCode::INTERNAL_SERVER_ERROR
}
ErrorKind::DatabaseMigration
| ErrorKind::Wallet
| ErrorKind::Internal
| ErrorKind::Nfc => StatusCode::INTERNAL_SERVER_ERROR,
};
(status, error.to_string()).into_response()
}
Expand Down
2 changes: 1 addition & 1 deletion crates/sage/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,11 @@ impl Error {
| KeychainError::Bip39(..)
| KeychainError::Argon2(..) => ErrorKind::Internal,
},
Self::SqlxMigration(..) => ErrorKind::DatabaseMigration,
Self::Send(..)
| Self::Io(..)
| Self::Client(..)
| Self::Sqlx(..)
| Self::SqlxMigration(..)
| Self::Bip39(..)
| Self::TomlDe(..)
| Self::TomlSer(..)
Expand Down
1 change: 0 additions & 1 deletion crates/sage/src/sage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ impl Sage {
self.setup_logging()?;

let receiver = self.setup_sync_manager()?;
self.switch_wallet().await?;
self.setup_peers().await?;

info!("Sage wallet initialized");
Expand Down
7 changes: 7 additions & 0 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ pub async fn set_rpc_run_on_startup(
Ok(())
}

#[command]
#[specta]
pub async fn switch_wallet(state: State<'_, AppState>) -> Result<()> {
state.lock().await.switch_wallet().await?;
Ok(())
}

#[command]
#[specta]
pub async fn move_key(state: State<'_, AppState>, fingerprint: u32, index: u32) -> Result<()> {
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub fn run() {
commands::stop_rpc_server,
commands::get_rpc_run_on_startup,
commands::set_rpc_run_on_startup,
commands::switch_wallet,
commands::move_key,
commands::download_cni_offercode,
])
Expand Down
7 changes: 5 additions & 2 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ async getRpcRunOnStartup() : Promise<boolean> {
async setRpcRunOnStartup(runOnStartup: boolean) : Promise<null> {
return await TAURI_INVOKE("set_rpc_run_on_startup", { runOnStartup });
},
async switchWallet() : Promise<null> {
return await TAURI_INVOKE("switch_wallet");
},
async moveKey(fingerprint: number, index: number) : Promise<null> {
return await TAURI_INVOKE("move_key", { fingerprint, index });
},
Expand Down Expand Up @@ -363,7 +366,7 @@ export type DerivationMode = { mode: "default" } |
export type DerivationRecord = { index: number; public_key: string; address: string }
export type DidRecord = { launcher_id: string; name: string | null; visible: boolean; coin_id: string; address: string; amount: Amount; recovery_hash: string | null; created_height: number | null; create_transaction_id: string | null }
export type Error = { kind: ErrorKind; reason: string }
export type ErrorKind = "wallet" | "api" | "not_found" | "unauthorized" | "internal" | "nfc"
export type ErrorKind = "wallet" | "api" | "not_found" | "unauthorized" | "internal" | "database_migration" | "nfc"
export type FilterUnlockedCoins = { coin_ids: string[] }
export type FilterUnlockedCoinsResponse = { coin_ids: string[] }
export type GenerateMnemonic = { use_24_words: boolean }
Expand Down Expand Up @@ -427,7 +430,7 @@ export type GetXchCoinsResponse = { coins: CoinRecord[]; total: number }
export type ImportKey = { name: string; key: string; derivation_index?: number; save_secrets?: boolean; login?: boolean }
export type ImportKeyResponse = { fingerprint: number }
export type ImportOffer = { offer: string }
export type ImportOfferResponse = Record<string, never>
export type ImportOfferResponse = { offer_id: string }
export type IncreaseDerivationIndex = { hardened?: boolean | null; index: number }
export type IncreaseDerivationIndexResponse = Record<string, never>
export type InheritedNetwork = "mainnet" | "testnet11"
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/ErrorContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default function ErrorDialog({ error, setError }: ErrorDialogProps) {
<DialogContent>
<DialogHeader>
<DialogTitle>{kind ? `${kind} ` : ''}Error</DialogTitle>
<DialogDescription className='break-all'>
<DialogDescription className='break-words'>
{error?.reason}
</DialogDescription>
</DialogHeader>
Expand Down
30 changes: 23 additions & 7 deletions src/hooks/useInitialization.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import { commands } from '@/bindings';
import { useCallback, useEffect, useState } from 'react';
import { useErrors } from './useErrors';
import { logoutAndUpdateState } from '@/state';

export default function useInitialization() {
const { addError } = useErrors();

const [initialized, setInitialized] = useState(false);

const initialize = useCallback(async () => {
commands
.initialize()
.then(() => setInitialized(true))
.catch(addError);
const onInitialize = useCallback(async () => {
try {
await commands.initialize();
setInitialized(true);
await commands.switchWallet();
} catch (error: any) {
// Always add the error to be displayed
addError(error);

// Check if this is a database migration, which is recoverable
if (error.kind === 'database_migration') {
try {
await logoutAndUpdateState();
} catch (logoutError) {
console.error('Error during logout:', logoutError);
}
} else {
console.error('Unrecoverable initialization error', error);
}
}
}, [addError]);

useEffect(() => {
if (!initialized) {
initialize();
onInitialize();
}
}, [initialized, initialize]);
}, [initialized, onInitialize]);

return initialized;
}
28 changes: 19 additions & 9 deletions src/pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,14 @@ export default function Login() {
useEffect(() => {
commands
.getKey({})
.then((data) => data.key !== null && navigate('/wallet'))
.catch(addError);
.then((data) => {
if (data.key !== null) {
navigate('/wallet');
}
})
.catch((error) => {
addError(error);
});
}, [navigate, addError]);

const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
Expand Down Expand Up @@ -333,13 +339,17 @@ function WalletItem({ draggable, info, keys, setKeys }: WalletItemProps) {
const loginSelf = (explicit: boolean) => {
if (isMenuOpen && !explicit) return;

loginAndUpdateState(info.fingerprint).then(() => {
commands
.getKey({})
.then((data) => setWallet(data.key))
.then(() => navigate('/wallet'))
.catch(addError);
});
loginAndUpdateState(info.fingerprint, addError)
.then(() => {
commands
.getKey({})
.then((data) => setWallet(data.key))
.then(() => navigate('/wallet'))
.catch(addError);
})
.catch((error) => {
addError(error);
});
};

useEffect(() => {
Expand Down
19 changes: 16 additions & 3 deletions src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
GetSyncStatusResponse,
KeyInfo,
} from './bindings';
import { CustomError } from './contexts/ErrorContext';

export interface WalletState {
sync: GetSyncStatusResponse;
Expand Down Expand Up @@ -85,9 +86,21 @@ events.syncEvent.listen((event) => {
}
});

export async function loginAndUpdateState(fingerprint: number): Promise<void> {
await commands.login({ fingerprint });
await fetchState();
export async function loginAndUpdateState(
fingerprint: number,
onError?: (error: CustomError) => void,
): Promise<void> {
try {
await commands.login({ fingerprint });
await fetchState();
} catch (error) {
if (onError) {
onError(error as CustomError);
} else {
console.error(error);
}
throw error;
}
}

// Create a separate function to handle wallet state updates
Expand Down
Loading