Linking a device is one flow in the core: sync::link
The desktop's sync_link and the ffi's link_with_password/link_with_token were the same steps written out twice: probe, refuse an incompatible server before any credential is sent, log in or verify a pasted token, keep the link, and adopt the server's trash retention. link::authenticate(url, Credential) does the network half and link::store(conn, ..., seal) keeps it, sealed when the client has a seal. Each client now only reads its input and picks its seal. The desktop checks for a missing email/password before probing rather than after. Same error, sooner. DRY pass #2, batch 1, F2 (#5372). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ use inkwell_core::sync::client::{self, Identity, ProbeResult};
|
||||
use inkwell_core::sync::client::{Directory, NoteShare, ShareTarget};
|
||||
use inkwell_core::sync::compat::Compatibility;
|
||||
use inkwell_core::sync::engine;
|
||||
use inkwell_core::sync::link;
|
||||
use inkwell_core::sync::push;
|
||||
use inkwell_core::sync::sharing;
|
||||
use inkwell_core::sync::state;
|
||||
@@ -63,55 +64,44 @@ fn trimmed(value: &Option<String>) -> Option<&str> {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sync_link(input: LinkInput, db: State<'_, Db>) -> Result<LinkResult, String> {
|
||||
// 1. Handshake FIRST. Never hand credentials to a server we've established we
|
||||
// can't sync with — and an incompatible server is exactly the case where a
|
||||
// later failure would be hardest to attribute.
|
||||
let probe = client::probe(&input.url).await?;
|
||||
if let Compatibility::Incompatible { reason, .. } = &probe.compatibility {
|
||||
return Err(reason.clone());
|
||||
}
|
||||
let base_url = probe.base_url;
|
||||
|
||||
// 2. Obtain a credential.
|
||||
let (token, identity) = match trimmed(&input.token) {
|
||||
Some(token) => {
|
||||
// Verify before storing: an unverified paste turns a copy/paste slip
|
||||
// into a failure that only surfaces at the next sync.
|
||||
let identity = client::fetch_identity(&base_url, token).await?;
|
||||
(token.to_string(), identity)
|
||||
}
|
||||
let name = trimmed(&input.name)
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(default_device_name);
|
||||
let credential = match trimmed(&input.token) {
|
||||
Some(token) => link::Credential::Token(token),
|
||||
None => {
|
||||
let (Some(email), Some(password)) = (trimmed(&input.email), trimmed(&input.password))
|
||||
else {
|
||||
return Err("Enter your email and password, or paste a device token.".to_string());
|
||||
};
|
||||
let name = trimmed(&input.name)
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(default_device_name);
|
||||
client::device_login(&base_url, email, password, &name).await?
|
||||
link::Credential::Password {
|
||||
email,
|
||||
password,
|
||||
device_name: &name,
|
||||
}
|
||||
}
|
||||
};
|
||||
let granted = link::authenticate(&input.url, credential).await?;
|
||||
|
||||
// 3. Persist. The lock is taken only now, for two reasons: a std MutexGuard
|
||||
// isn't Send so it cannot be held across an await, and holding the store
|
||||
// locked for a network round-trip would freeze every note operation in the UI.
|
||||
// The lock is taken only now: a std MutexGuard isn't Send so it cannot be held
|
||||
// across an await, and holding the store locked for a network round-trip would
|
||||
// freeze every note operation in the UI.
|
||||
let status = {
|
||||
let conn = db.conn()?;
|
||||
state::set_link(&conn, &base_url, &token).map_err(|e| e.to_string())?;
|
||||
// Adopt the server's trash-retention window immediately, so the Trash view
|
||||
// stops counting down against this device's offline default the moment it's
|
||||
// no longer the policy in force.
|
||||
if let Some(days) = probe.server.trash_retention_days {
|
||||
state::set_server_retention(&conn, days as i64).map_err(|e| e.to_string())?;
|
||||
}
|
||||
link::store(
|
||||
&conn,
|
||||
&granted.base_url,
|
||||
&granted.token,
|
||||
granted.retention_days,
|
||||
None,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
state::status(&conn).map_err(|e| e.to_string())?
|
||||
};
|
||||
|
||||
log::info!("linked to {} as {}", base_url, identity.email);
|
||||
Ok(LinkResult {
|
||||
status,
|
||||
identity,
|
||||
compatibility: probe.compatibility,
|
||||
identity: granted.identity,
|
||||
compatibility: granted.compatibility,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user