mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
57e5ab7a87
* fix(apps): surface native action failures Make iOS gateway reconnect attempts return visible closed outcomes, route macOS gateway deep links through primary setup confirmation, and expose pending/sent/error notification test results across native and Dashboard settings. * chore(apps): refresh native i18n inventory * test(macos): isolate gateway cancel state * fix(ui): accept permission-only notification status * test(gateway): drain accepted agent run before fixture reset * fix(ui): notify clients after service worker claim * test(gateway): wait for accepted root work to drain * test(ui): isolate widget theme observer window * test(ui): await durable attachment draft before teardown * test(agents): use race-safe MCP process cleanup * test(ui): await cloud startup runtime before error probe * test(ui): align startup and teardown probes with current owners
162 lines
5.8 KiB
Swift
162 lines
5.8 KiB
Swift
import AppKit
|
|
import Foundation
|
|
import OpenClawKit
|
|
import WebKit
|
|
|
|
enum DashboardGatewaysRequest: Equatable {
|
|
case select(DashboardGatewayTarget)
|
|
case openWindow(DashboardGatewayTarget)
|
|
case setPrimary(DashboardGatewayTarget)
|
|
case openSettings
|
|
}
|
|
|
|
@MainActor
|
|
final class DashboardGatewaysMessageHandler: NSObject, WKScriptMessageHandler {
|
|
weak var owner: DashboardWindowController?
|
|
|
|
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
self.owner?.receiveGatewaysMessage(message)
|
|
}
|
|
}
|
|
|
|
extension DashboardWindowController {
|
|
static let gatewaysMessageHandlerName = "openclawGateways"
|
|
|
|
func hasTLSParams(_ params: GatewayTLSParams?) -> Bool {
|
|
self.tlsParams == params
|
|
}
|
|
|
|
func webView(
|
|
_ webView: WKWebView,
|
|
didReceive challenge: URLAuthenticationChallenge,
|
|
completionHandler: @escaping @MainActor @Sendable (
|
|
URLSession.AuthChallengeDisposition,
|
|
URLCredential?) -> Void)
|
|
{
|
|
guard webView === self.webView,
|
|
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
|
|
else {
|
|
completionHandler(.performDefaultHandling, nil)
|
|
return
|
|
}
|
|
guard let params = self.tlsParams else {
|
|
completionHandler(.performDefaultHandling, nil)
|
|
return
|
|
}
|
|
guard Self.isExpectedTLSAuthority(
|
|
host: challenge.protectionSpace.host,
|
|
port: challenge.protectionSpace.port,
|
|
dashboardURL: self.currentURL)
|
|
else {
|
|
completionHandler(.performDefaultHandling, nil)
|
|
return
|
|
}
|
|
guard let trust = challenge.protectionSpace.serverTrust else {
|
|
completionHandler(.cancelAuthenticationChallenge, nil)
|
|
return
|
|
}
|
|
switch GatewayTLSServerTrust.evaluate(
|
|
trust: trust,
|
|
host: challenge.protectionSpace.host,
|
|
port: challenge.protectionSpace.port,
|
|
params: params)
|
|
{
|
|
case .accept:
|
|
completionHandler(.useCredential, URLCredential(trust: trust))
|
|
case .reject:
|
|
completionHandler(.cancelAuthenticationChallenge, nil)
|
|
}
|
|
}
|
|
|
|
static func isExpectedTLSAuthority(host: String, port: Int, dashboardURL: URL) -> Bool {
|
|
GatewayTLSAuthority(url: dashboardURL)?.matches(host: host, port: port) == true
|
|
}
|
|
|
|
static func gatewaysRequest(from body: Any) -> DashboardGatewaysRequest? {
|
|
guard let payload = body as? [String: Any], let type = payload["type"] as? String else {
|
|
return nil
|
|
}
|
|
if type == "open-settings" { return .openSettings }
|
|
guard let id = payload["id"] as? String,
|
|
let target = DashboardGatewayTarget(bridgeID: id)
|
|
else {
|
|
return nil
|
|
}
|
|
return switch type {
|
|
case "select": .select(target)
|
|
case "open-window": .openWindow(target)
|
|
case "set-primary": .setPrimary(target)
|
|
default: nil
|
|
}
|
|
}
|
|
|
|
func receiveGatewaysMessage(_ message: WKScriptMessage) {
|
|
guard message.name == Self.gatewaysMessageHandlerName,
|
|
message.webView === self.webView,
|
|
message.frameInfo.isMainFrame,
|
|
Self.isTrustedLinkSource(message.frameInfo.request.url, dashboardURL: self.currentURL),
|
|
let request = Self.gatewaysRequest(from: message.body)
|
|
else {
|
|
return
|
|
}
|
|
DashboardManager.shared.handleGatewayRequest(request, from: self)
|
|
}
|
|
|
|
func updateGatewaySnapshot(_ snapshot: DashboardGatewaySnapshot) {
|
|
self.gatewaySnapshot = snapshot
|
|
let controller = self.webView.configuration.userContentController
|
|
controller.removeAllUserScripts()
|
|
Self.installNativeChromeScript(into: controller)
|
|
Self.installNativeGatewaysScript(into: controller, snapshot: snapshot)
|
|
Self.installNativeAuthScript(into: controller, url: self.currentURL, auth: self.auth)
|
|
self.webView.evaluateJavaScript(Self.nativeGatewaysScriptSource(snapshot: snapshot, dispatch: true))
|
|
}
|
|
|
|
static func installNativeGatewaysScript(
|
|
into userContentController: WKUserContentController,
|
|
snapshot: DashboardGatewaySnapshot?)
|
|
{
|
|
guard let snapshot else { return }
|
|
userContentController.addUserScript(WKUserScript(
|
|
source: self.nativeGatewaysScriptSource(snapshot: snapshot, dispatch: false),
|
|
injectionTime: .atDocumentStart,
|
|
forMainFrameOnly: true))
|
|
}
|
|
|
|
static func nativeGatewaysScriptSource(
|
|
snapshot: DashboardGatewaySnapshot,
|
|
dispatch: Bool) -> String
|
|
{
|
|
guard let data = try? JSONEncoder().encode(snapshot),
|
|
let json = String(data: data, encoding: .utf8)
|
|
else {
|
|
return ""
|
|
}
|
|
let event = dispatch
|
|
? "window.dispatchEvent(new CustomEvent('openclaw:native-gateways-changed'," +
|
|
"{detail:window.__OPENCLAW_NATIVE_GATEWAYS__}));"
|
|
: ""
|
|
return "window.__OPENCLAW_NATIVE_GATEWAYS__=\(json);\(event)"
|
|
}
|
|
|
|
static func makeSetPrimaryAlert(gatewayName: String) -> NSAlert {
|
|
let alert = NSAlert()
|
|
alert.messageText = "Set \(gatewayName) as primary?"
|
|
alert.informativeText =
|
|
"This changes the Mac app's primary Gateway and resets Talk Mode, canvas, and chat connections."
|
|
alert.addButton(withTitle: "Set as Primary")
|
|
alert.addButton(withTitle: "Cancel")
|
|
return alert
|
|
}
|
|
|
|
static func makeGatewaySetupAlert(title: String, message: String) -> NSAlert {
|
|
let alert = NSAlert()
|
|
alert.messageText = title
|
|
alert.informativeText = message
|
|
alert.addButton(withTitle: "Change Gateway")
|
|
alert.addButton(withTitle: "Cancel")
|
|
alert.alertStyle = .warning
|
|
return alert
|
|
}
|
|
}
|