Sync: keep the remembered sign-in when the provider is merely down, and say why SYNC NOW found the machine offline
ci / desktop nightly (push) Canceled after 0s
ci / api image (push) Canceled after 0s
ci / android head (push) Canceled after 4m10s
ci / build and test (push) Canceled after 4m12s

This commit is contained in:
jaap-jan committed 2026-10-07 11:11:09 +02:00
1 parent 5b32c76c95
commit 509937c4b8
5 files changed
+89 -6

No files matched your search

@@ -3227,6 +3227,9 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
if (token is null
|| !Uri.TryCreate(vault.Session.Profile.ServerUrl, UriKind.Absolute, out var server))
{
vault.OfflineReason = "This machine has no remembered sign-in. Press SIGN IN in "
+ "Preferences to start syncing.";
return null;
}
@@ -3234,6 +3237,7 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
connection = resumed;
rememberedToken = token;
vault.OfflineReason = null;
OnPropertyChanged(nameof(IsOnline));
RaiseSyncState();
@@ -3244,15 +3248,18 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
return resumed;
}
catch (OidcException exception)
catch (OidcException exception) when (IsRefusedGrant(exception))
{
// The provider answered and said no: the session was revoked, or the token was rotated and
// this machine kept the old one. Retrying costs a round trip a minute and can only ever get
// the same answer, so the token goes and the user is told the one thing that fixes it.
await ForgetSignInAsync(cancellationToken).ConfigureAwait(true);
Announce($"Your sign-in has expired, so this machine is offline: {exception.Message} "
+ "Sign in again from Preferences to start syncing.");
var message = $"Your sign-in has expired, so this machine is offline: {exception.Message} "
+ "Sign in again from Preferences to start syncing.";
vault.OfflineReason = message;
Announce(message);
return null;
}
@@ -3260,11 +3267,29 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
{
// Everything else is a machine with no network, a server that is down, or a vault that was
// locked mid-attempt — all of which are ordinary and all of which resolve themselves. The
// titlebar already says OFFLINE; a socket error once a minute would say nothing more.
// titlebar already says OFFLINE; a socket error once a minute would say nothing more. That
// includes an OidcException with no refusal in it: discovery answering 502, or a token endpoint
// whose body is a proxy's error page, is the identity provider restarting. Forgetting the token
// for that used to strand a machine offline for good once the provider came back.
vault.OfflineReason = $"The server could not be reached: {exception.Message} Changes are "
+ "queued and will be sent once it can.";
return null;
}
}
/// <summary>
/// Whether a failed resume was the provider refusing the token, rather than failing to answer.
/// </summary>
/// <remarks>
/// <c>invalid_grant</c> is what RFC 6749 §5.2 has a token endpoint say about a refresh token that is
/// expired, revoked or already rotated — the one answer that a retry cannot change. Every other
/// <see cref="OidcException"/> on this path is discovery or the token endpoint failing, which is a
/// provider that is down and will be back.
/// </remarks>
private static bool IsRefusedGrant(OidcException exception) =>
string.Equals(exception.ErrorCode, "invalid_grant", StringComparison.Ordinal);
/// <summary>
/// Writes the connection's current refresh token into the vault, if it has changed.
/// </summary>