Skip to content

Commit 0fefe8a

Browse files
authored
chore: check server version on sign-in and launch (#43)
Updates CredentialModel to have an additional state `Unknown` during startup while credentials are validated in the background asynchronously (15s timeout). While loading credentials on startup, the tray app shows a loading message. While updating credentials, we now check that the server version falls in our expected range. The sign in page now uses a WinUI 3 Dialog to show errors. Closes #42
1 parent be51a7b commit 0fefe8a

31 files changed

+1098
-152
lines changed

.github/workflows/ci.yaml

+26-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
jobs:
1212
fmt:
1313
runs-on: windows-latest
14+
timeout-minutes: 10
1415
steps:
1516
- uses: actions/checkout@v4
1617
- name: Setup dotnet
@@ -26,6 +27,7 @@ jobs:
2627

2728
test:
2829
runs-on: windows-latest
30+
timeout-minutes: 10
2931
steps:
3032
- uses: actions/checkout@v4
3133
- name: Setup dotnet
@@ -34,13 +36,36 @@ jobs:
3436
dotnet-version: 8.0.x
3537
cache: true
3638
cache-dependency-path: '**/packages.lock.json'
39+
- name: Install Windows App SDK Runtime
40+
shell: pwsh
41+
run: |
42+
$ErrorActionPreference = "Stop"
43+
44+
$filename = ".\WindowsAppRuntimeInstall-x64.exe"
45+
$url = "https://download.microsoft.com/download/7a3a6a44-b07e-4ca5-8b63-2de185769dbc/WindowsAppRuntimeInstall-x64.exe" # 1.6.5 (1.6.250205002)
46+
& curl.exe --progress-bar --show-error --fail --location --output $filename $url
47+
if ($LASTEXITCODE -ne 0) { throw "Failed to download Windows App SDK" }
48+
49+
$process = Start-Process -FilePath $filename -ArgumentList "--quiet --force" -NoNewWindow -Wait -PassThru
50+
if ($process.ExitCode -ne 0) { throw "Failed to install Windows App SDK: exit code is $($process.ExitCode)" }
3751
- name: dotnet restore
3852
run: dotnet restore --locked-mode
3953
- name: dotnet test
40-
run: dotnet test --no-restore
54+
run: dotnet test --no-restore --blame-hang --blame-hang-dump-type full --blame-hang-timeout 2m -p:Platform=x64
55+
- name: Upload test binaries and TestResults
56+
if: failure()
57+
uses: actions/upload-artifact@v4
58+
with:
59+
name: test-results
60+
retention-days: 1
61+
path: |
62+
./**/bin
63+
./**/obj
64+
./**/TestResults
4165
4266
build:
4367
runs-on: windows-latest
68+
timeout-minutes: 10
4469
steps:
4570
- uses: actions/checkout@v4
4671
- name: Setup dotnet

.github/workflows/release.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ permissions:
1818
jobs:
1919
release:
2020
runs-on: ${{ github.repository_owner == 'coder' && 'windows-latest-16-cores' || 'windows-latest' }}
21+
timeout-minutes: 15
2122

2223
steps:
2324
- uses: actions/checkout@v4

App/App.xaml.cs

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.Threading;
23
using System.Threading.Tasks;
4+
using Coder.Desktop.App.Models;
35
using Coder.Desktop.App.Services;
46
using Coder.Desktop.App.ViewModels;
57
using Coder.Desktop.App.Views;
@@ -26,6 +28,7 @@ public App()
2628
services.AddTransient<SignInWindow>();
2729

2830
// TrayWindow views and view models
31+
services.AddTransient<TrayWindowLoadingPage>();
2932
services.AddTransient<TrayWindowDisconnectedViewModel>();
3033
services.AddTransient<TrayWindowDisconnectedPage>();
3134
services.AddTransient<TrayWindowLoginRequiredViewModel>();
@@ -45,17 +48,29 @@ public async Task ExitApplication()
4548
{
4649
_handleWindowClosed = false;
4750
Exit();
48-
var rpcManager = _services.GetRequiredService<IRpcController>();
51+
var rpcController = _services.GetRequiredService<IRpcController>();
4952
// TODO: send a StopRequest if we're connected???
50-
await rpcManager.DisposeAsync();
53+
await rpcController.DisposeAsync();
5154
Environment.Exit(0);
5255
}
5356

5457
protected override void OnLaunched(LaunchActivatedEventArgs args)
5558
{
56-
var trayWindow = _services.GetRequiredService<TrayWindow>();
59+
// Start connecting to the manager in the background.
60+
var rpcController = _services.GetRequiredService<IRpcController>();
61+
if (rpcController.GetState().RpcLifecycle == RpcLifecycle.Disconnected)
62+
// Passing in a CT with no cancellation is desired here, because
63+
// the named pipe open will block until the pipe comes up.
64+
_ = rpcController.Reconnect(CancellationToken.None);
65+
66+
// Load the credentials in the background. Even though we pass a CT
67+
// with no cancellation, the method itself will impose a timeout on the
68+
// HTTP portion.
69+
var credentialManager = _services.GetRequiredService<ICredentialManager>();
70+
_ = credentialManager.LoadCredentials(CancellationToken.None);
5771

5872
// Prevent the TrayWindow from closing, just hide it.
73+
var trayWindow = _services.GetRequiredService<TrayWindow>();
5974
trayWindow.Closed += (sender, args) =>
6075
{
6176
if (!_handleWindowClosed) return;

App/Models/CredentialModel.cs

+12-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@ namespace Coder.Desktop.App.Models;
22

33
public enum CredentialState
44
{
5+
// Unknown means "we haven't checked yet"
6+
Unknown,
7+
8+
// Invalid means "we checked and there's either no saved credentials or they are not valid"
59
Invalid,
10+
11+
// Valid means "we checked and there are saved credentials and they are valid"
612
Valid,
713
}
814

915
public class CredentialModel
1016
{
11-
public CredentialState State { get; set; } = CredentialState.Invalid;
17+
public CredentialState State { get; init; } = CredentialState.Unknown;
18+
19+
public string? CoderUrl { get; init; }
20+
public string? ApiToken { get; init; }
1221

13-
public string? CoderUrl { get; set; }
14-
public string? ApiToken { get; set; }
22+
public string? Username { get; init; }
1523

1624
public CredentialModel Clone()
1725
{
@@ -20,6 +28,7 @@ public CredentialModel Clone()
2028
State = State,
2129
CoderUrl = CoderUrl,
2230
ApiToken = ApiToken,
31+
Username = Username,
2332
};
2433
}
2534
}

0 commit comments

Comments
 (0)