71 lines
4.0 KiB
C#
71 lines
4.0 KiB
C#
using AppKit;
|
|||
|
|
using CoreGraphics;
|
||
|
|
using Foundation;
|
||
|
|
using VoiceCat.Core;
|
||
|
|
using VoiceCat.Crypto;
|
||
|
|
|
||
|
|
namespace VoiceCat.Mac;
|
||
|
|
|
||
|
|
internal sealed class ConnectWindowController : NSWindowController
|
||
|
|
{
|
||
|
|
private readonly NSTextField host = new(new CGRect(120, 180, 280, 26)) { StringValue = "127.0.0.1:8384", PlaceholderString = "Host:port" };
|
||
|
|
private readonly NSTextField nickname = new(new CGRect(120, 140, 280, 26)) { StringValue = NSProcessInfo.ProcessInfo.UserName, PlaceholderString = "Nickname" };
|
||
|
|
private readonly NSTextField status = NSTextField.CreateLabel("Ready to connect");
|
||
|
|
private readonly NSButton connect = new(new CGRect(300, 55, 100, 32)) { Title = "Connect", BezelStyle = NSBezelStyle.Rounded };
|
||
|
|
private VoiceCatClient? client;
|
||
|
|
private MainWindowController? main;
|
||
|
|
|
||
|
|
internal ConnectWindowController() : base(new NSWindow(new CGRect(0, 0, 520, 260), NSWindowStyle.Titled | NSWindowStyle.Closable,
|
||
|
|
NSBackingStore.Buffered, false))
|
||
|
|
{
|
||
|
|
Window!.Title = "Connect to VoiceCat"; Window.Center();
|
||
|
|
var view = Window.ContentView!;
|
||
|
|
var hostLabel = NSTextField.CreateLabel("Server"); hostLabel.Frame = new CGRect(30, 185, 80, 20); view.AddSubview(hostLabel); view.AddSubview(host);
|
||
|
|
var nicknameLabel = NSTextField.CreateLabel("Nickname"); nicknameLabel.Frame = new CGRect(30, 145, 80, 20); view.AddSubview(nicknameLabel); view.AddSubview(nickname);
|
||
|
|
status.Frame = new CGRect(30, 95, 370, 22); status.AccessibilityLabel = "Connection status"; view.AddSubview(status);
|
||
|
|
connect.AccessibilityLabel = "Connect to server"; connect.Activated += Connect; view.AddSubview(connect);
|
||
|
|
Window.DefaultButtonCell = connect.Cell;
|
||
|
|
}
|
||
|
|
|
||
|
|
private async void Connect(object? sender, EventArgs args)
|
||
|
|
{
|
||
|
|
if (client is not null) return;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
connect.Enabled = false; status.StringValue = "Connecting…";
|
||
|
|
(string serverHost, ushort port) = ParseEndpoint(host.StringValue);
|
||
|
|
string pins = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VoiceCat", "tofu.txt");
|
||
|
|
client = new("VoiceCat macOS", "0.1.0", pins);
|
||
|
|
await client.ConnectAsync(serverHost, port, ConfirmIdentity);
|
||
|
|
var auth = await client.AuthenticateGuestAsync(nickname.StringValue);
|
||
|
|
if (!auth.Ok) throw new InvalidOperationException(auth.Error);
|
||
|
|
main = new(client, auth.Self.Id, nickname.StringValue);
|
||
|
|
client = null; main.ShowWindow(this); Window.Close();
|
||
|
|
}
|
||
|
|
catch (Exception exception)
|
||
|
|
{
|
||
|
|
if (client is not null) await client.DisposeAsync(); client = null;
|
||
|
|
status.StringValue = exception.Message; connect.Enabled = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private ValueTask<bool> ConfirmIdentity(ServerIdentityChallenge challenge, CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
var completion = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||
|
|
NSApplication.SharedApplication.InvokeOnMainThread(() =>
|
||
|
|
{
|
||
|
|
var alert = new NSAlert { MessageText = challenge.Status == TofuStatus.FirstConnect ? "Trust this VoiceCat server?" : "Server identity changed",
|
||
|
|
InformativeText = $"{challenge.Host}:{challenge.Port}\n\nCertificate SHA-256:\n{challenge.CertificateFingerprint}", AlertStyle = challenge.Status == TofuStatus.Mismatch ? NSAlertStyle.Critical : NSAlertStyle.Informational };
|
||
|
|
alert.AddButton("Trust and connect"); alert.AddButton("Cancel"); completion.TrySetResult(alert.RunModal() == 1000);
|
||
|
|
});
|
||
|
|
return new(completion.Task.WaitAsync(cancellationToken));
|
||
|
|
}
|
||
|
|
|
||
|
|
private static (string Host, ushort Port) ParseEndpoint(string value)
|
||
|
|
{
|
||
|
|
int separator = value.LastIndexOf(':');
|
||
|
|
if (separator <= 0 || !ushort.TryParse(value[(separator + 1)..], out ushort port) || port == 0) throw new ArgumentException("Enter a server as host:port.");
|
||
|
|
return (value[..separator].Trim('[', ']'), port);
|
||
|
|
}
|
||
|
|
}
|