diff --git a/internal/acp/gateway_runtime.go b/internal/acp/gateway_runtime.go index 4b260db..d49417c 100644 --- a/internal/acp/gateway_runtime.go +++ b/internal/acp/gateway_runtime.go @@ -1,6 +1,7 @@ package acp import ( + "net/url" "strings" "time" @@ -52,6 +53,7 @@ func handleGatewayConnect( Password: strings.TrimSpace(shared.StringArg(asMap(params["auth"]), "password", "")), }, } + request.ReportedRemoteAddress = resolveGatewayReportedRemoteAddress(server, request) result := server.gateway.Connect(request, notify) return map[string]any{ "ok": result.OK, @@ -157,3 +159,55 @@ func parsePositiveInt(value any) int { } return 0 } + +func resolveGatewayReportedRemoteAddress( + server *Server, + request gatewayruntime.ConnectRequest, +) string { + if strings.TrimSpace(strings.ToLower(request.Mode)) != "remote" { + return "" + } + if !shouldOverrideGatewayReportedRemoteAddress(request.Endpoint.Host) { + return "" + } + if server != nil { + if provider, ok := server.syncedProviderByID("openclaw"); ok { + if reported := publicEndpointAddressLabel(provider.Endpoint); reported != "" { + return reported + } + } + } + return publicEndpointAddressLabel( + shared.EnvOrDefault("OPENCLAW_URL", "wss://openclaw.svc.plus"), + ) +} + +func shouldOverrideGatewayReportedRemoteAddress(host string) bool { + switch strings.TrimSpace(strings.ToLower(host)) { + case "127.0.0.1", "localhost", "::1", "xworkmate-bridge.svc.plus": + return true + default: + return false + } +} + +func publicEndpointAddressLabel(raw string) string { + parsed, err := url.Parse(strings.TrimSpace(raw)) + if err != nil || strings.TrimSpace(parsed.Hostname()) == "" { + return "" + } + host := strings.TrimSpace(parsed.Hostname()) + port := strings.TrimSpace(parsed.Port()) + if port == "" { + switch strings.TrimSpace(strings.ToLower(parsed.Scheme)) { + case "https", "wss": + port = "443" + case "http", "ws": + port = "80" + } + } + if port == "" { + return host + } + return host + ":" + port +} diff --git a/internal/acp/gateway_runtime_test.go b/internal/acp/gateway_runtime_test.go new file mode 100644 index 0000000..e5541a4 --- /dev/null +++ b/internal/acp/gateway_runtime_test.go @@ -0,0 +1,54 @@ +package acp + +import ( + "testing" + + "xworkmate-bridge/internal/gatewayruntime" +) + +func TestResolveGatewayReportedRemoteAddressUsesSyncedOpenClawEndpoint(t *testing.T) { + t.Parallel() + + server := NewServer() + server.syncProviders([]syncedProvider{ + { + ProviderID: "openclaw", + Label: "OpenClaw", + Endpoint: "wss://gateway.example.com", + Enabled: true, + }, + }) + + got := resolveGatewayReportedRemoteAddress(server, gatewayruntime.ConnectRequest{ + Mode: "remote", + Endpoint: gatewayruntime.Endpoint{ + Host: "127.0.0.1", + Port: 18789, + TLS: false, + }, + }) + + const want = "gateway.example.com:443" + if got != want { + t.Fatalf("resolveGatewayReportedRemoteAddress() = %q, want %q", got, want) + } +} + +func TestResolveGatewayReportedRemoteAddressPreservesExplicitPublicRemoteHost(t *testing.T) { + t.Parallel() + + server := NewServer() + + got := resolveGatewayReportedRemoteAddress(server, gatewayruntime.ConnectRequest{ + Mode: "remote", + Endpoint: gatewayruntime.Endpoint{ + Host: "openclaw.svc.plus", + Port: 443, + TLS: true, + }, + }) + + if got != "" { + t.Fatalf("expected explicit public remote host to bypass override, got %q", got) + } +} diff --git a/internal/gatewayruntime/runtime.go b/internal/gatewayruntime/runtime.go index dd64878..50fa361 100644 --- a/internal/gatewayruntime/runtime.go +++ b/internal/gatewayruntime/runtime.go @@ -292,11 +292,7 @@ func (s *session) connect() ConnectResult { s.updateSnapshot(func(snapshot *runtimeSnapshot) { snapshot.Status = "connecting" snapshot.StatusText = "Connecting…" - snapshot.RemoteAddress = fmt.Sprintf( - "%s:%d", - s.config.Endpoint.Host, - s.config.Endpoint.Port, - ) + snapshot.RemoteAddress = s.reportedRemoteAddress() snapshot.LastError = "" snapshot.LastErrorCode = "" snapshot.LastErrorDetailCode = "" @@ -382,11 +378,7 @@ func (s *session) connectAttempt() (ConnectResult, *GatewayError) { snapshot.Status = "connected" snapshot.StatusText = "Connected" snapshot.ServerName = strings.TrimSpace(stringValue(server["host"])) - snapshot.RemoteAddress = fmt.Sprintf( - "%s:%d", - s.config.Endpoint.Host, - s.config.Endpoint.Port, - ) + snapshot.RemoteAddress = s.reportedRemoteAddress() snapshot.MainSessionKey = strings.TrimSpace( stringValue(sessionDefaults["mainSessionKey"]), ) @@ -421,6 +413,14 @@ func (s *session) connectAttempt() (ConnectResult, *GatewayError) { }, nil } +func (s *session) reportedRemoteAddress() string { + reported := strings.TrimSpace(s.config.ReportedRemoteAddress) + if reported != "" { + return reported + } + return fmt.Sprintf("%s:%d", s.config.Endpoint.Host, s.config.Endpoint.Port) +} + func (s *session) request( method string, params map[string]any, diff --git a/internal/gatewayruntime/runtime_reported_address_test.go b/internal/gatewayruntime/runtime_reported_address_test.go new file mode 100644 index 0000000..70e61ac --- /dev/null +++ b/internal/gatewayruntime/runtime_reported_address_test.go @@ -0,0 +1,60 @@ +package gatewayruntime + +import ( + "testing" + "time" +) + +func TestManagerConnectUsesReportedRemoteAddressInSnapshot(t *testing.T) { + t.Parallel() + + server := newFakeGatewayServer(t) + defer server.Close() + + manager := NewManager() + manager.ReconnectDelay = 20 * time.Millisecond + + result := manager.Connect(ConnectRequest{ + RuntimeID: "runtime-1", + Mode: "remote", + ClientID: "openclaw-macos", + Locale: "en_US", + UserAgent: "XWorkmate/1.0.0", + Endpoint: Endpoint{ + Host: "127.0.0.1", + Port: server.Port(), + TLS: false, + }, + ReportedRemoteAddress: "openclaw.svc.plus:443", + ConnectAuthMode: "shared-token", + ConnectAuthFields: []string{"token"}, + ConnectAuthSources: []string{"shared:form"}, + HasSharedAuth: true, + HasDeviceToken: false, + PackageInfo: PackageInfo{ + AppName: "XWorkmate", + Version: "1.0.0", + }, + DeviceInfo: DeviceInfo{ + Platform: "macos", + PlatformVersion: "14.0", + DeviceFamily: "Mac", + ModelIdentifier: "Mac14,5", + }, + Identity: DeviceIdentity{ + DeviceID: "device-1", + PublicKeyBase64URL: "tl4fnKW7VLD0Cl4lQTu2CEgHPs4PWAX7eVgWfWQWk2Q", + PrivateKeyBase64URL: "dr7GfMKoO-lJBtgA0dE5m6f_X4kEFsxChDc7mW8mkXu2Xh-cpbsUsPQKXiVBO7YISAc-zg9YBft5WBZ9ZBaTZA", + }, + Auth: AuthConfig{ + Token: "shared-token", + }, + }, func(map[string]any) {}) + if !result.OK { + t.Fatalf("expected connect success, got %#v", result.Error) + } + + if got := result.Snapshot["remoteAddress"]; got != "openclaw.svc.plus:443" { + t.Fatalf("expected reported remote address, got %#v", got) + } +} diff --git a/internal/gatewayruntime/types.go b/internal/gatewayruntime/types.go index 8656cb7..90eda12 100644 --- a/internal/gatewayruntime/types.go +++ b/internal/gatewayruntime/types.go @@ -58,21 +58,22 @@ type AuthConfig struct { } type ConnectRequest struct { - RuntimeID string - Mode string - ClientID string - Locale string - UserAgent string - Endpoint Endpoint - ConnectAuthMode string - ConnectAuthFields []string - ConnectAuthSources []string - HasSharedAuth bool - HasDeviceToken bool - PackageInfo PackageInfo - DeviceInfo DeviceInfo - Identity DeviceIdentity - Auth AuthConfig + RuntimeID string + Mode string + ClientID string + Locale string + UserAgent string + Endpoint Endpoint + ReportedRemoteAddress string + ConnectAuthMode string + ConnectAuthFields []string + ConnectAuthSources []string + HasSharedAuth bool + HasDeviceToken bool + PackageInfo PackageInfo + DeviceInfo DeviceInfo + Identity DeviceIdentity + Auth AuthConfig } type ConnectResult struct {