Improve web chat security UX and bump to 0.4.2
Some checks failed
Build and Test / test (push) Has been cancelled
Build and Test / build (win-arm64) (push) Has been cancelled
Build and Test / build (win-x64) (push) Has been cancelled
Build and Test / build-msix (ARM64, win-arm64) (push) Has been cancelled
Build and Test / build-msix (x64, win-x64) (push) Has been cancelled
Build and Test / build-extension (arm64) (push) Has been cancelled
Build and Test / build-extension (x64) (push) Has been cancelled
Build and Test / release (push) Has been cancelled
Some checks failed
Build and Test / test (push) Has been cancelled
Build and Test / build (win-arm64) (push) Has been cancelled
Build and Test / build (win-x64) (push) Has been cancelled
Build and Test / build-msix (ARM64, win-arm64) (push) Has been cancelled
Build and Test / build-msix (x64, win-x64) (push) Has been cancelled
Build and Test / build-extension (arm64) (push) Has been cancelled
Build and Test / build-extension (x64) (push) Has been cancelled
Build and Test / release (push) Has been cancelled
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
c5e5a291e9
commit
4d8d43bcfe
@ -22,7 +22,7 @@ This monorepo contains three projects:
|
||||
|
||||
### Prerequisites
|
||||
- Windows 10 (20H2+) or Windows 11
|
||||
- .NET 10.0 SDK (preview) - https://dotnet.microsoft.com/download/dotnet/10.0
|
||||
- .NET 10.0 SDK - https://dotnet.microsoft.com/download/dotnet/10.0
|
||||
- Windows 10 SDK (for WinUI build) - install via Visual Studio or standalone
|
||||
- WebView2 Runtime - pre-installed on modern Windows, or get from https://developer.microsoft.com/microsoft-edge/webview2
|
||||
- PowerToys (optional, for Command Palette extension)
|
||||
@ -212,6 +212,8 @@ When Node Mode is enabled in Settings, your Windows PC becomes a **node** that t
|
||||
|
||||
> 🔒 **Exec Policy**: `system.run` is gated by an approval policy (saved to `exec-policy.json`). Default rules allow read-only commands (echo, Get-*, hostname, etc.) and deny destructive operations (rm, shutdown, registry edits). Use `system.execApprovals.get/set` to view/modify rules remotely.
|
||||
|
||||
> 🔐 **Web Chat secure context**: Remote web chat requires `https://` (or localhost). If using a self-signed cert, trust it in Windows (Trusted Root Certification Authorities) or use an SSH tunnel to localhost.
|
||||
|
||||
#### Node Status in Tray Menu
|
||||
|
||||
The tray menu shows node connection status:
|
||||
|
||||
@ -19,7 +19,7 @@ internal sealed partial class OpenClawPage : ListPage
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return [
|
||||
new ListItem(new OpenUrlCommand("http://localhost:18789"))
|
||||
new ListItem(new OpenUrlCommand("openclaw://dashboard"))
|
||||
{
|
||||
Title = "🦞 Open Dashboard",
|
||||
Subtitle = "Open OpenClaw web dashboard"
|
||||
|
||||
@ -514,7 +514,7 @@ public partial class App : Application
|
||||
case "webchat": ShowWebChat(); break;
|
||||
case "quicksend": ShowQuickSend(); break;
|
||||
case "history": ShowNotificationHistory(); break;
|
||||
case "healthcheck": _ = RunHealthCheckAsync(); break;
|
||||
case "healthcheck": _ = RunHealthCheckAsync(userInitiated: true); break;
|
||||
case "settings": ShowSettings(); break;
|
||||
case "autostart": ToggleAutoStart(); break;
|
||||
case "log": OpenLogFile(); break;
|
||||
@ -801,7 +801,7 @@ public partial class App : Application
|
||||
flyout.Items.Add(historyItem);
|
||||
|
||||
var healthCheckItem = new MenuFlyoutItem { Text = "🔄 Run Health Check" };
|
||||
healthCheckItem.Click += async (s, e) => await RunHealthCheckAsync();
|
||||
healthCheckItem.Click += async (s, e) => await RunHealthCheckAsync(userInitiated: true);
|
||||
flyout.Items.Add(healthCheckItem);
|
||||
|
||||
flyout.Items.Add(new MenuFlyoutSeparator());
|
||||
@ -1106,18 +1106,42 @@ public partial class App : Application
|
||||
_ = RunHealthCheckAsync();
|
||||
}
|
||||
|
||||
private async Task RunHealthCheckAsync()
|
||||
private async Task RunHealthCheckAsync(bool userInitiated = false)
|
||||
{
|
||||
if (_gatewayClient == null) return;
|
||||
if (_gatewayClient == null)
|
||||
{
|
||||
if (userInitiated)
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText("Health Check")
|
||||
.AddText("Gateway is not connected yet.")
|
||||
.Show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_lastCheckTime = DateTime.Now;
|
||||
await _gatewayClient.CheckHealthAsync();
|
||||
if (userInitiated)
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText("Health Check")
|
||||
.AddText("Health check request sent.")
|
||||
.Show();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn($"Health check failed: {ex.Message}");
|
||||
if (userInitiated)
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText("Health Check Failed")
|
||||
.AddText(ex.Message)
|
||||
.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1282,11 +1306,18 @@ public partial class App : Application
|
||||
|
||||
var baseUrl = _settings.GatewayUrl
|
||||
.Replace("ws://", "http://")
|
||||
.Replace("wss://", "https://");
|
||||
|
||||
var url = string.IsNullOrEmpty(path)
|
||||
? $"{baseUrl}?token={Uri.EscapeDataString(_settings.Token)}"
|
||||
: $"{baseUrl}/{path}?token={Uri.EscapeDataString(_settings.Token)}";
|
||||
.Replace("wss://", "https://")
|
||||
.TrimEnd('/');
|
||||
|
||||
var url = string.IsNullOrEmpty(path)
|
||||
? baseUrl
|
||||
: $"{baseUrl}/{path.TrimStart('/')}";
|
||||
|
||||
if (!string.IsNullOrEmpty(_settings.Token))
|
||||
{
|
||||
var separator = url.Contains('?') ? "&" : "?";
|
||||
url = $"{url}{separator}token={Uri.EscapeDataString(_settings.Token)}";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
@ -124,6 +125,10 @@ public sealed class QuickSendDialog : WindowEx
|
||||
{
|
||||
await _client.SendChatMessageAsync(message);
|
||||
Logger.Info($"Quick send: {message}");
|
||||
new ToastContentBuilder()
|
||||
.AddText("Message Sent")
|
||||
.AddText("Your message was sent to OpenClaw.")
|
||||
.Show();
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<ApplicationIcon>Assets\openclaw.ico</ApplicationIcon>
|
||||
<RootNamespace>OpenClawTray</RootNamespace>
|
||||
<Version>0.4.1</Version>
|
||||
<Version>0.4.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Unpackaged (default): traditional EXE distribution via Inno Setup -->
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="OpenClaw.Tray"
|
||||
Publisher="CN=Scott Hanselman, O=Scott Hanselman, L=Forest Grove, S=Oregon, C=US"
|
||||
Version="0.4.1.0" />
|
||||
Version="0.4.2.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>OpenClaw Tray</DisplayName>
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
<!-- Error display (hidden by default) -->
|
||||
<ScrollViewer x:Name="ErrorPanel" Grid.Row="1" Visibility="Collapsed" Padding="20">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="WebView2 Failed to Initialize" FontSize="18" FontWeight="SemiBold"
|
||||
<TextBlock Text="Web Chat Unavailable" FontSize="18" FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"/>
|
||||
<TextBlock x:Name="ErrorText" TextWrapping="Wrap" IsTextSelectionEnabled="True"
|
||||
FontFamily="Consolas" FontSize="12"/>
|
||||
|
||||
@ -99,14 +99,25 @@ public sealed partial class WebChatWindow : WindowEx
|
||||
e.WebErrorStatus == CoreWebView2WebErrorStatus.ServerUnreachable))
|
||||
{
|
||||
Logger.Info("WebChatWindow: Gateway unreachable, showing friendly error");
|
||||
WebView.Visibility = Visibility.Collapsed;
|
||||
ErrorPanel.Visibility = Visibility.Visible;
|
||||
ErrorText.Text = "Can't reach OpenClaw Gateway\n\n" +
|
||||
ShowErrorMessage("Can't reach OpenClaw Gateway\n\n" +
|
||||
$"The gateway at {_gatewayUrl} is not responding.\n\n" +
|
||||
"To connect:\n" +
|
||||
"• Make sure your OpenClaw gateway is running\n" +
|
||||
"• If remote, connect via VPN to your home network\n" +
|
||||
"• Or use SSH tunnel: ssh -N -L 18789:localhost:18789 your-server";
|
||||
"• Or use SSH tunnel: ssh -N -L 18789:localhost:18789 your-server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.IsSuccess &&
|
||||
e.WebErrorStatus.ToString().Contains("Certificate", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.Info("WebChatWindow: TLS certificate issue detected");
|
||||
ShowErrorMessage(
|
||||
"The gateway HTTPS certificate is not trusted.\n\n" +
|
||||
"To connect securely:\n" +
|
||||
"• Use an HTTPS gateway URL (for example: https://host.tailnet.ts.net)\n" +
|
||||
"• If self-signed, import the cert into Windows Trusted Root Certification Authorities\n" +
|
||||
"• Or use SSH tunnel to localhost and keep using localhost URLs");
|
||||
}
|
||||
};
|
||||
WebView.CoreWebView2.NavigationCompleted += _navigationCompletedHandler;
|
||||
@ -159,6 +170,59 @@ public sealed partial class WebChatWindow : WindowEx
|
||||
|
||||
// Set to a test URL to bypass gateway (e.g., "https://www.bing.com"), or null for normal operation
|
||||
private const string? DEBUG_TEST_URL = null;
|
||||
|
||||
private static bool IsLocalHost(Uri uri)
|
||||
{
|
||||
return uri.IsLoopback || string.Equals(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private bool TryBuildChatUrl(out string url, out string errorMessage)
|
||||
{
|
||||
url = string.Empty;
|
||||
errorMessage = string.Empty;
|
||||
|
||||
if (!GatewayUrlHelper.TryNormalizeWebSocketUrl(_gatewayUrl, out var normalizedGatewayUrl) ||
|
||||
!Uri.TryCreate(normalizedGatewayUrl, UriKind.Absolute, out var gatewayUri))
|
||||
{
|
||||
errorMessage = $"Invalid gateway URL: {_gatewayUrl}";
|
||||
return false;
|
||||
}
|
||||
|
||||
var webScheme = gatewayUri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase)
|
||||
? "https"
|
||||
: "http";
|
||||
|
||||
if (webScheme == "http" && !IsLocalHost(gatewayUri))
|
||||
{
|
||||
errorMessage =
|
||||
"Web chat requires a secure context.\n\n" +
|
||||
"There is no safe bypass for remote plain HTTP: browsers and WebView enforce this.\n\n" +
|
||||
"Use one of these options:\n" +
|
||||
"• Use a trusted HTTPS/WSS endpoint (Let's Encrypt, Tailscale Serve, Caddy)\n" +
|
||||
"• If self-signed, import your gateway CA/cert into Windows Trusted Root (certmgr.msc)\n" +
|
||||
"• Or tunnel to localhost: ssh -N -L 18789:localhost:18789 <mac>";
|
||||
return false;
|
||||
}
|
||||
|
||||
var builder = new UriBuilder(gatewayUri)
|
||||
{
|
||||
Scheme = webScheme,
|
||||
Port = gatewayUri.Port
|
||||
};
|
||||
|
||||
var baseUrl = builder.Uri.GetLeftPart(UriPartial.Authority);
|
||||
url = $"{baseUrl}?token={Uri.EscapeDataString(_token)}";
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ShowErrorMessage(string message)
|
||||
{
|
||||
LoadingRing.IsActive = false;
|
||||
LoadingRing.Visibility = Visibility.Collapsed;
|
||||
WebView.Visibility = Visibility.Collapsed;
|
||||
ErrorPanel.Visibility = Visibility.Visible;
|
||||
ErrorText.Text = message;
|
||||
}
|
||||
|
||||
private void NavigateToChat()
|
||||
{
|
||||
@ -172,12 +236,15 @@ public sealed partial class WebChatWindow : WindowEx
|
||||
return;
|
||||
}
|
||||
|
||||
var baseUrl = _gatewayUrl
|
||||
.Replace("ws://", "http://")
|
||||
.Replace("wss://", "https://");
|
||||
|
||||
var url = $"{baseUrl}?token={Uri.EscapeDataString(_token)}";
|
||||
Logger.Info($"WebChatWindow: Navigating to {baseUrl} (token hidden)");
|
||||
if (!TryBuildChatUrl(out var url, out var errorMessage))
|
||||
{
|
||||
Logger.Warn($"WebChatWindow: {errorMessage}");
|
||||
ShowErrorMessage(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
var safeBaseUrl = url.Split('?')[0];
|
||||
Logger.Info($"WebChatWindow: Navigating to {safeBaseUrl} (token hidden)");
|
||||
WebView.CoreWebView2.Navigate(url);
|
||||
}
|
||||
|
||||
@ -193,10 +260,12 @@ public sealed partial class WebChatWindow : WindowEx
|
||||
|
||||
private void OnPopout(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var baseUrl = _gatewayUrl
|
||||
.Replace("ws://", "http://")
|
||||
.Replace("wss://", "https://");
|
||||
var url = $"{baseUrl}?token={Uri.EscapeDataString(_token)}";
|
||||
if (!TryBuildChatUrl(out var url, out var errorMessage))
|
||||
{
|
||||
Logger.Warn($"WebChatWindow: {errorMessage}");
|
||||
ShowErrorMessage(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<AssemblyCompany>Scott Hanselman</AssemblyCompany>
|
||||
<AssemblyProduct>OpenClaw Tray</AssemblyProduct>
|
||||
<Copyright>Copyright © 2026 Scott Hanselman</Copyright>
|
||||
<Version>0.4.1</Version>
|
||||
<Version>0.4.2</Version>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<!-- RuntimeIdentifier set at publish time: win-x64 or win-arm64 -->
|
||||
|
||||
Loading…
Reference in New Issue
Block a user