fix(slint): sign-in flow no longer lifts the offline session up front

login_via_system_browser's start-of-flow offline_session pre-clear (added
when the gate still blocked login) unlocked an empty shell while the
browser OAuth was pending. The pre-clear is gone: the auth endpoints are
gate-exempt since c207f232, runtime activation is purely local, and the
one remaining gated dependency - the COLD bundle fetch in try_init_api -
is handled by the new ensure_api_initialized helper, which lifts the flag
only around that init and restores it immediately (no-op on the normal
warm-token path). The flag now ends up false exclusively on a completed
login; restore_saved_session goes through the same helper, fixing a latent
cold-bundle recovery failure. Handler wiring rides the next commit.

cc
This commit is contained in:
vicrodh committed 2026-06-10 09:04:27 -06:00
1 parent 952846b6a0
commit d5bd43aca3
1 file changed
+47 -20
+47 -20
View File
@@ -33,23 +33,16 @@ where
{ {
let core = runtime.core(); let core = runtime.core();
// Belt-and-suspenders: signing in is an explicit intent to reach Qobuz. // NOTE: no offline_session pre-clear here. The sign-in endpoints are
// If a session-scoped offline session is still active (e.g. invoked from // EXEMPT from the offline gate (qbz-qobuz raw-client auth methods since
// the login screen after a logout that raced the engine reset), end it so // c207f232), so a live offline session never blocks the login itself —
// the still-gated parts of the pipeline (cold bundle init, post-login // and session activation (`runtime.activate`) is purely local. The old
// activation) work. The persisted `induced` preference is NOT touched — // upfront clear ended the offline session the moment the attempt
// a successful login reloads it via init_for_user and the app correctly // STARTED, which unlocked the shell (empty Discover/Library) while the
// lands back in induced offline. // browser OAuth was still pending. The flag now drops on SUCCESS only
let offline_engine = crate::offline_mode::engine(); // (below); the one still-gated dependency — a cold bundle-token init —
if offline_engine.status().offline_session { // is scoped inside ensure_api_initialized.
log::info!("[qbz-slint] sign-in requested with an offline session active — ending it"); ensure_api_initialized(core).await?;
offline_engine.set_offline_session(false);
}
// Ensure the Qobuz client is initialized (bundle tokens).
if !core.is_api_initialized().await {
core.try_init_api().await.map_err(|e| e.to_string())?;
}
let app_id = { let app_id = {
let client_lock = core.client(); let client_lock = core.client();
@@ -134,6 +127,40 @@ where
}) })
} }
/// Make sure the Qobuz client holds bundle tokens before a sign-in call.
///
/// The sign-in POSTs are gate-exempt, but `try_init_api`'s cold bundle
/// fetch is a network SERVICE request and stays gated on purpose. When an
/// unauthenticated offline session holds the gate closed (sign-in from the
/// offline shell's badge flyout / recovery path), lift the session flag
/// ONLY around this init and put it back immediately after, success or
/// failure — the offline session must end exclusively on a COMPLETED
/// login (the callers' success paths), never as a side effect of merely
/// starting an attempt. No-op in the normal case: an offline session boots
/// from cached bundle tokens, so the client is already initialized and the
/// flag is never touched.
async fn ensure_api_initialized<A>(core: &qbz_core::QbzCore<A>) -> Result<(), String>
where
A: FrontendAdapter + Send + Sync + 'static,
{
if core.is_api_initialized().await {
return Ok(());
}
let offline_engine = crate::offline_mode::engine();
let lifted = offline_engine.status().offline_session;
if lifted {
log::info!(
"[qbz-slint] cold bundle init with an offline session active — lifting the flag for the init only"
);
offline_engine.set_offline_session(false);
}
let result = core.try_init_api().await.map_err(|e| e.to_string());
if lifted {
offline_engine.set_offline_session(true);
}
result
}
/// True only for an EXPLICIT auth rejection from Qobuz: a 401 on the token /// True only for an EXPLICIT auth rejection from Qobuz: a 401 on the token
/// login (`AuthenticationError`) or an ineligible-account verdict. Network /// login (`AuthenticationError`) or an ineligible-account verdict. Network
/// failures, the offline gate, 5xx, rate limiting, parse errors and unknown /// failures, the offline gate, 5xx, rate limiting, parse errors and unknown
@@ -172,9 +199,9 @@ where
}; };
let core = runtime.core(); let core = runtime.core();
if !core.is_api_initialized().await { // Same scoped handling of the gated cold bundle init as the browser
core.try_init_api().await.map_err(|e| e.to_string())?; // flow (no-op when the client already holds tokens).
} ensure_api_initialized(core).await?;
match core.login_with_token(&token).await { match core.login_with_token(&token).await {
Ok(session) => { Ok(session) => {