Public Access
Sync: keep the remembered sign-in when the provider is merely down, and say why SYNC NOW found the machine offline
This commit is contained in:
1 parent
5b32c76c95
commit
509937c4b8
5 files changed
+89
-6
No files matched your search
@@ -87,6 +87,11 @@
|
||||
<TextBlock Classes="settingsrowtitle" Text="Synchronise" />
|
||||
<TextBlock Classes="settingsrowcaption"
|
||||
Text="Runs a pass now. One runs on its own when the keychain opens, straight after any change, and every minute while it stays open." />
|
||||
<!-- What SYNC NOW said. The status bar that carries it elsewhere is hidden in settings
|
||||
mode, so without this the button ran, failed or found the machine offline in silence. -->
|
||||
<TextBlock Classes="settingsrowcaption"
|
||||
IsVisible="{Binding Vault.Status, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=False}"
|
||||
Text="{Binding Vault.Status}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
|
||||
<Button Classes="ghost" Height="38" Content="SIGN IN" Command="{Binding SignInCommand}"
|
||||
|
||||
@@ -70,6 +70,13 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- What SYNC NOW said. The status bar that carries it elsewhere is hidden in settings mode, so
|
||||
without this the button ran, failed or found the machine offline in perfect silence. -->
|
||||
<TextBlock Margin="0,10,0,0" FontSize="11.5" TextWrapping="Wrap" HorizontalAlignment="Right"
|
||||
Foreground="{StaticResource TextFaint}"
|
||||
IsVisible="{Binding Vault.Status, Converter={x:Static StringConverters.IsNotNullOrEmpty}, FallbackValue=False}"
|
||||
Text="{Binding Vault.Status}" />
|
||||
|
||||
<!-- The name-a-vault and rename-a-vault forms, in place — this window has no modal idiom for a bare
|
||||
text field, and the members panel below is a different kind of overlay: over one vault's people,
|
||||
not over a form. -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6199,8 +6199,10 @@ internal sealed partial class VaultViewModel(
|
||||
if (await ResolveServerAsync(cancellationToken).ConfigureAwait(true) is not { } server)
|
||||
{
|
||||
LastSyncFailed = true;
|
||||
Status = "Offline. Changes are queued and will be sent as soon as this machine "
|
||||
+ "can reach the server again.";
|
||||
Status = OfflineReason is { } reason
|
||||
? $"Offline. {reason}"
|
||||
: "Offline. Changes are queued and will be sent as soon as this machine "
|
||||
+ "can reach the server again.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6214,6 +6216,14 @@ internal sealed partial class VaultViewModel(
|
||||
}).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
/// <summary>Why the last attempt to get online failed, or null when nothing has said.</summary>
|
||||
/// <remarks>
|
||||
/// Set by the shell, which is the thing that knows: <see cref="ServerReconnectHandler" /> answers null
|
||||
/// for every reason on purpose, so that a background pass stays quiet. A press of SYNC NOW is somebody
|
||||
/// asking, though, and "offline" with no why is what left a machine stuck with no way to find out.
|
||||
/// </remarks>
|
||||
internal string? OfflineReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Finds a server to sync against, getting this machine online if it is not.
|
||||
/// </summary>
|
||||
|
||||
Reference in new issue
Block a user