refactor(edge): Server 协议统一与 wsclient 收敛(Phase 3 Batch 3)

- openflare/agent/relay/flared 协议类型改为 pkg/protocol 别名,删除重复 struct
- 新增 edge/wsclient Preset 表,三组件 wsclient 改为薄包装
- 更新设计文档、计划与 changelog
This commit is contained in:
ryan
2026-06-19 15:00:46 +08:00
parent db9a9f98fd
commit 0b34792709
16 changed files with 328 additions and 484 deletions
+11 -58
View File
@@ -5,78 +5,31 @@ import (
"time"
"github.com/Rain-kl/Wavelet/internal/apps/agent/protocol"
shared "github.com/Rain-kl/Wavelet/pkg/wsclient"
edgews "github.com/Rain-kl/Wavelet/internal/apps/edge/wsclient"
)
type WSMessage = shared.WSMessage
type MessageHandler = shared.MessageHandler
type WSMessage = edgews.WSMessage
type MessageHandler = edgews.MessageHandler
type Connection = edgews.AgentConnection
type Client struct {
sharedClient *shared.Client
inner *edgews.Client
}
type Connection struct {
sharedConn *shared.Connection
}
func New(baseURL string, token string, timeout time.Duration) *Client {
func New(baseURL, token string, timeout time.Duration) *Client {
return &Client{
sharedClient: shared.New(shared.Config{
BaseURL: baseURL,
Token: token,
Timeout: timeout,
HeaderKey: "X-Agent-Token",
WSPath: "/api/v1/agent/ws",
}),
inner: edgews.New(edgews.PresetAgent, baseURL, token, timeout),
}
}
func (c *Client) SetToken(token string) {
c.sharedClient.SetToken(token)
c.inner.SetToken(token)
}
func (c *Client) URL() string {
return c.sharedClient.URL()
return c.inner.URL()
}
func (c *Client) Connect(ctx context.Context) (protocol.WebSocketConnection, error) {
conn, err := c.sharedClient.Connect(ctx)
if err != nil {
return nil, err
}
return &Connection{sharedConn: conn}, nil
}
func (conn *Connection) URL() string {
if conn == nil || conn.sharedConn == nil {
return ""
}
return conn.sharedConn.URL
}
func (conn *Connection) SendStatus(payload protocol.NodePayload) error {
return conn.sharedConn.SendMessage(protocol.WSMessageTypeStatus, payload)
}
func (conn *Connection) SendPong() error {
return conn.sharedConn.SendMessage(protocol.WSMessageTypePong, nil)
}
func (conn *Connection) Receive() (protocol.WSMessage, error) {
var message protocol.WSMessage
if err := conn.sharedConn.Receive(&message); err != nil {
return message, err
}
return message, nil
}
func (conn *Connection) RunReceiveLoop(ctx context.Context, handler shared.MessageHandler) error {
return conn.sharedConn.RunReceiveLoop(ctx, handler)
}
func (conn *Connection) Close() error {
if conn == nil || conn.sharedConn == nil {
return nil
}
return conn.sharedConn.Close()
}
return c.inner.ConnectAgent(ctx)
}
+130
View File
@@ -0,0 +1,130 @@
package wsclient
import (
"context"
"time"
pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol"
shared "github.com/Rain-kl/Wavelet/pkg/wsclient"
)
type WSMessage = shared.WSMessage
type MessageHandler = shared.MessageHandler
type Preset int
const (
PresetAgent Preset = iota
PresetRelay
PresetFlared
)
type presetConfig struct {
HeaderKey string
WSPath string
}
var presets = map[Preset]presetConfig{
PresetAgent: {HeaderKey: "X-Agent-Token", WSPath: "/api/v1/agent/ws"},
PresetRelay: {HeaderKey: "X-Agent-Token", WSPath: "/api/v1/relay/ws"},
PresetFlared: {HeaderKey: "X-Tunnel-Token", WSPath: "/api/v1/tunnel/ws"},
}
func PresetHeaderKey(preset Preset) string {
return presets[preset].HeaderKey
}
func PresetWSPath(preset Preset) string {
return presets[preset].WSPath
}
type Client struct {
sharedClient *shared.Client
}
func New(preset Preset, baseURL, token string, timeout time.Duration) *Client {
cfg := presets[preset]
return &Client{
sharedClient: shared.New(shared.Config{
BaseURL: baseURL,
Token: token,
Timeout: timeout,
HeaderKey: cfg.HeaderKey,
WSPath: cfg.WSPath,
}),
}
}
func (c *Client) SetToken(token string) {
c.sharedClient.SetToken(token)
}
func (c *Client) URL() string {
return c.sharedClient.URL()
}
type Connection struct {
sharedConn *shared.Connection
}
func (c *Client) Connect(ctx context.Context) (*Connection, error) {
conn, err := c.sharedClient.Connect(ctx)
if err != nil {
return nil, err
}
return &Connection{sharedConn: conn}, nil
}
type AgentConnection struct {
Connection
}
func (c *Client) ConnectAgent(ctx context.Context) (*AgentConnection, error) {
conn, err := c.Connect(ctx)
if err != nil {
return nil, err
}
return &AgentConnection{Connection: *conn}, nil
}
func (conn *Connection) URL() string {
if conn == nil || conn.sharedConn == nil {
return ""
}
return conn.sharedConn.URL
}
func (conn *Connection) SendPing() error {
return conn.sharedConn.SendMessage(pkgprotocol.WSMessageTypePing, nil)
}
func (conn *Connection) SendPong() error {
return conn.sharedConn.SendMessage(pkgprotocol.WSMessageTypePong, nil)
}
func (conn *Connection) SendMessage(msgType string, payload any) error {
return conn.sharedConn.SendMessage(msgType, payload)
}
func (conn *Connection) Receive() (pkgprotocol.WSMessage, error) {
var message pkgprotocol.WSMessage
if err := conn.sharedConn.Receive(&message); err != nil {
return message, err
}
return message, nil
}
func (conn *Connection) RunReceiveLoop(ctx context.Context, handler MessageHandler) error {
return conn.sharedConn.RunReceiveLoop(ctx, handler)
}
func (conn *Connection) Close() error {
if conn == nil || conn.sharedConn == nil {
return nil
}
return conn.sharedConn.Close()
}
func (conn *AgentConnection) SendStatus(payload pkgprotocol.NodePayload) error {
return conn.sharedConn.SendMessage(pkgprotocol.WSMessageTypeStatus, payload)
}
@@ -0,0 +1,66 @@
package wsclient
import (
"testing"
"time"
)
func TestPresetConfig(t *testing.T) {
tests := []struct {
preset Preset
headerKey string
wsPath string
}{
{preset: PresetAgent, headerKey: "X-Agent-Token", wsPath: "/api/v1/agent/ws"},
{preset: PresetRelay, headerKey: "X-Agent-Token", wsPath: "/api/v1/relay/ws"},
{preset: PresetFlared, headerKey: "X-Tunnel-Token", wsPath: "/api/v1/tunnel/ws"},
}
for _, tt := range tests {
t.Run(tt.wsPath, func(t *testing.T) {
if got := PresetHeaderKey(tt.preset); got != tt.headerKey {
t.Fatalf("PresetHeaderKey() = %q, want %q", got, tt.headerKey)
}
if got := PresetWSPath(tt.preset); got != tt.wsPath {
t.Fatalf("PresetWSPath() = %q, want %q", got, tt.wsPath)
}
})
}
}
func TestClientURL(t *testing.T) {
tests := []struct {
name string
preset Preset
baseURL string
want string
}{
{
name: "agent https",
preset: PresetAgent,
baseURL: "https://example.com",
want: "wss://example.com/api/v1/agent/ws",
},
{
name: "relay http with path prefix",
preset: PresetRelay,
baseURL: "http://example.com/api",
want: "ws://example.com/api/api/v1/relay/ws",
},
{
name: "flared wss",
preset: PresetFlared,
baseURL: "wss://edge.example.com",
want: "wss://edge.example.com/api/v1/tunnel/ws",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := New(tt.preset, tt.baseURL, "token", time.Second)
if got := client.URL(); got != tt.want {
t.Fatalf("URL() = %q, want %q", got, tt.want)
}
})
}
}
+10 -55
View File
@@ -2,74 +2,29 @@ package wsclient
import (
"context"
"encoding/json"
"time"
service "github.com/Rain-kl/Wavelet/pkg/protocol"
shared "github.com/Rain-kl/Wavelet/pkg/wsclient"
edgews "github.com/Rain-kl/Wavelet/internal/apps/edge/wsclient"
)
type WSMessage = shared.WSMessage
type MessageHandler = shared.MessageHandler
type WSMessage = edgews.WSMessage
type MessageHandler = edgews.MessageHandler
type Connection = edgews.Connection
type Client struct {
sharedClient *shared.Client
inner *edgews.Client
}
type Connection struct {
sharedConn *shared.Connection
}
func New(baseURL string, token string, timeout time.Duration) *Client {
func New(baseURL, token string, timeout time.Duration) *Client {
return &Client{
sharedClient: shared.New(shared.Config{
BaseURL: baseURL,
Token: token,
Timeout: timeout,
HeaderKey: "X-Tunnel-Token",
WSPath: "/api/v1/tunnel/ws",
}),
inner: edgews.New(edgews.PresetFlared, baseURL, token, timeout),
}
}
func (c *Client) SetToken(token string) {
c.sharedClient.SetToken(token)
c.inner.SetToken(token)
}
func (c *Client) Connect(ctx context.Context) (*Connection, error) {
conn, err := c.sharedClient.Connect(ctx)
if err != nil {
return nil, err
}
return &Connection{sharedConn: conn}, nil
}
func (conn *Connection) SendPing() error {
return conn.sharedConn.SendMessage("ping", nil)
}
func (conn *Connection) SendPong() error {
return conn.sharedConn.SendMessage("pong", nil)
}
func (conn *Connection) Receive() (service.WSMessage, error) {
var raw struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload,omitempty"`
}
if err := conn.sharedConn.Receive(&raw); err != nil {
return service.WSMessage{}, err
}
return service.WSMessage{
Type: raw.Type,
Payload: raw.Payload,
}, nil
}
func (conn *Connection) RunReceiveLoop(ctx context.Context, handler shared.MessageHandler) error {
return conn.sharedConn.RunReceiveLoop(ctx, handler)
}
func (conn *Connection) Close() error {
return conn.sharedConn.Close()
}
return c.inner.Connect(ctx)
}
@@ -29,82 +29,6 @@ const (
accessLogPathMaxLength = 100
)
// NodeSystemProfile is the agent-reported system profile.
type NodeSystemProfile struct {
Hostname string `json:"hostname"`
OSName string `json:"os_name"`
OSVersion string `json:"os_version"`
KernelVersion string `json:"kernel_version"`
Architecture string `json:"architecture"`
CPUModel string `json:"cpu_model"`
CPUCores int `json:"cpu_cores"`
TotalMemoryBytes int64 `json:"total_memory_bytes"`
TotalDiskBytes int64 `json:"total_disk_bytes"`
UptimeSeconds int64 `json:"uptime_seconds"`
ReportedAtUnix int64 `json:"reported_at_unix"`
}
// NodeMetricSnapshot is the agent-reported capacity snapshot.
type NodeMetricSnapshot struct {
CapturedAtUnix int64 `json:"captured_at_unix"`
CPUUsagePercent float64 `json:"cpu_usage_percent"`
MemoryUsedBytes int64 `json:"memory_used_bytes"`
MemoryTotalBytes int64 `json:"memory_total_bytes"`
StorageUsedBytes int64 `json:"storage_used_bytes"`
StorageTotalBytes int64 `json:"storage_total_bytes"`
DiskReadBytes int64 `json:"disk_read_bytes"`
DiskWriteBytes int64 `json:"disk_write_bytes"`
NetworkRxBytes int64 `json:"network_rx_bytes"`
NetworkTxBytes int64 `json:"network_tx_bytes"`
}
// NodeOpenrestyObservation is the agent-reported openresty network observation.
type NodeOpenrestyObservation struct {
CapturedAtUnix int64 `json:"captured_at_unix"`
OpenrestyRxBytes int64 `json:"openresty_rx_bytes"`
OpenrestyTxBytes int64 `json:"openresty_tx_bytes"`
OpenrestyConnections int64 `json:"openresty_connections"`
}
// NodeTrafficReport is the agent-reported traffic window.
type NodeTrafficReport struct {
WindowStartedAtUnix int64 `json:"window_started_at_unix"`
WindowEndedAtUnix int64 `json:"window_ended_at_unix"`
RequestCount int64 `json:"request_count"`
ErrorCount int64 `json:"error_count"`
UniqueVisitorCount int64 `json:"unique_visitor_count"`
StatusCodes map[string]int64 `json:"status_codes"`
TopDomains map[string]int64 `json:"top_domains"`
SourceCountries map[string]int64 `json:"source_countries"`
}
// NodeAccessLog is a single access log row from the agent.
type NodeAccessLog struct {
LoggedAtUnix int64 `json:"logged_at_unix"`
RemoteAddr string `json:"remote_addr"`
Host string `json:"host"`
Path string `json:"path"`
StatusCode int `json:"status_code"`
}
// BufferedObservabilityRecord is a buffered observability window from the agent.
type BufferedObservabilityRecord struct {
WindowStartedAtUnix int64 `json:"window_started_at_unix"`
Snapshot *NodeMetricSnapshot `json:"snapshot,omitempty"`
OpenrestyObservation *NodeOpenrestyObservation `json:"openresty_observation,omitempty"`
TrafficReport *NodeTrafficReport `json:"traffic_report,omitempty"`
AccessLogs []NodeAccessLog `json:"access_logs,omitempty"`
}
// NodeHealthEvent is an agent-reported health event.
type NodeHealthEvent struct {
EventType string `json:"event_type"`
Severity string `json:"severity"`
Message string `json:"message"`
TriggeredAtUnix int64 `json:"triggered_at_unix"`
Metadata map[string]string `json:"metadata"`
}
// PersistHeartbeatObservability stores profile, snapshots, traffic, access logs, and health events.
func PersistHeartbeatObservability(ctx context.Context, nodeID string, payload NodePayload, reportedAt time.Time) {
if strings.TrimSpace(nodeID) == "" {
@@ -0,0 +1,26 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package agent
import pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol"
type NodePayload = pkgprotocol.NodePayload
type NodeSystemProfile = pkgprotocol.NodeSystemProfile
type NodeMetricSnapshot = pkgprotocol.NodeMetricSnapshot
type NodeOpenrestyObservation = pkgprotocol.NodeOpenrestyObservation
type NodeTrafficReport = pkgprotocol.NodeTrafficReport
type NodeAccessLog = pkgprotocol.NodeAccessLog
type BufferedObservabilityRecord = pkgprotocol.BufferedObservabilityRecord
type NodeHealthEvent = pkgprotocol.NodeHealthEvent
type ApplyLogPayload = pkgprotocol.ApplyLogPayload
type Settings = pkgprotocol.AgentSettings
type ActiveConfigMeta = pkgprotocol.ActiveConfigMeta
type SupportFile = pkgprotocol.SupportFile
type WAFIPGroup = pkgprotocol.WAFIPGroup
type WAFIPGroupSyncRequest = pkgprotocol.WAFIPGroupSyncRequest
type WAFIPGroupSyncResponse = pkgprotocol.WAFIPGroupSyncResponse
// Backward-compatible names used by server routers and handlers.
type WAFIPGroupSyncInput = WAFIPGroupSyncRequest
type WAFIPGroupSyncResult = WAFIPGroupSyncResponse
+3 -79
View File
@@ -16,71 +16,16 @@ const (
applyResultFailed = "failed"
)
// NodePayload is the agent register/heartbeat payload.
type NodePayload struct {
NodeID string `json:"node_id"`
Name string `json:"name"`
IP string `json:"ip"`
Version string `json:"version"`
ExtVersion string `json:"ext_version"`
CurrentVersion string `json:"current_version"`
LastError string `json:"last_error"`
OpenrestyStatus string `json:"openresty_status"`
OpenrestyMessage string `json:"openresty_message"`
Profile *NodeSystemProfile `json:"profile,omitempty"`
Snapshot *NodeMetricSnapshot `json:"snapshot,omitempty"`
OpenrestyObservation *NodeOpenrestyObservation `json:"openresty_observation,omitempty"`
TrafficReport *NodeTrafficReport `json:"traffic_report,omitempty"`
AccessLogs []NodeAccessLog `json:"access_logs,omitempty"`
BufferedObservability []BufferedObservabilityRecord `json:"buffered_observability,omitempty"`
HealthEvents []NodeHealthEvent `json:"health_events"`
WAFIPGroupChecksums map[string]string `json:"waf_ip_group_checksums,omitempty"`
}
// ApplyLogPayload is the agent apply log report payload.
type ApplyLogPayload struct {
NodeID string `json:"node_id"`
Version string `json:"version"`
Result string `json:"result"`
Message string `json:"message"`
Checksum string `json:"checksum"`
MainConfigChecksum string `json:"main_config_checksum"`
RouteConfigChecksum string `json:"route_config_checksum"`
SupportFileCount int `json:"support_file_count"`
}
// RegistrationResponse is returned after agent registration.
// Server uses access_token; the agent client expects agent_token via RegisterNodeResponse.
type RegistrationResponse struct {
NodeID string `json:"node_id"`
AccessToken string `json:"access_token"`
Name string `json:"name"`
}
// Settings carries remote agent control flags.
type Settings struct {
HeartbeatInterval int `json:"heartbeat_interval"`
WebsocketUpgradeEnabled bool `json:"websocket_upgrade_enabled"`
AutoUpdate bool `json:"auto_update"`
UpdateRepo string `json:"update_repo"`
UpdateNow bool `json:"update_now"`
UpdateChannel string `json:"update_channel"`
UpdateTag string `json:"update_tag"`
RestartOpenrestyNow bool `json:"restart_openresty_now"`
}
// ActiveConfigMeta summarizes the active configuration version.
type ActiveConfigMeta struct {
Version string `json:"version"`
Checksum string `json:"checksum"`
}
// SupportFile is a configuration support artifact shipped to agents.
type SupportFile struct {
Path string `json:"path"`
Content string `json:"content"`
}
// ConfigResponse is the full active config payload for agents.
// Server uses time.Time for CreatedAt; the agent client uses string via ActiveConfigResponse.
type ConfigResponse struct {
Version string `json:"version"`
Checksum string `json:"checksum"`
@@ -89,31 +34,10 @@ type ConfigResponse struct {
CreatedAt time.Time `json:"created_at"`
}
// WAFIPGroup is a WAF IP group snapshot for agents.
type WAFIPGroup struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Enabled bool `json:"enabled"`
IPList []string `json:"ip_list"`
Checksum string `json:"checksum"`
}
// WAFIPGroupSyncInput requests changed WAF IP groups.
type WAFIPGroupSyncInput struct {
IDs []uint `json:"ids"`
Checksums map[string]string `json:"checksums"`
}
// WAFIPGroupSyncResult returns synced WAF IP groups.
type WAFIPGroupSyncResult struct {
Groups []WAFIPGroup `json:"groups"`
}
// HeartbeatResponse is the heartbeat handler result.
type HeartbeatResponse struct {
Node *model.OpenFlareNode `json:"node"`
AgentSettings *Settings `json:"agent_settings"`
ActiveConfig *ActiveConfigMeta `json:"active_config"`
WAFIPGroups []WAFIPGroup `json:"waf_ip_groups,omitempty"`
}
}
-68
View File
@@ -11,7 +11,6 @@ import (
"time"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/agent"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/relay"
"github.com/Rain-kl/Wavelet/internal/db"
"github.com/Rain-kl/Wavelet/internal/model"
"gorm.io/gorm"
@@ -24,73 +23,6 @@ const (
applyResultFail = "failed"
)
// HeartbeatPayload is sent by OpenFlared on each heartbeat.
type HeartbeatPayload struct {
ClientVersion string `json:"client_version"`
FrpVersion string `json:"frp_version"`
IP string `json:"ip"`
TunnelStatus string `json:"tunnel_status"`
ConnectedRelays []ConnectedRelay `json:"connected_relays"`
CurrentVersion string `json:"current_version"`
CurrentChecksum string `json:"current_checksum"`
}
// ConnectedRelay describes relay connection status from the client.
type ConnectedRelay struct {
RelayNodeID string `json:"relay_node_id"`
Status string `json:"status"`
ProxyCount int `json:"proxy_count"`
}
// ActiveConfigMeta summarizes the active config version.
type ActiveConfigMeta struct {
Version string `json:"version"`
Checksum string `json:"checksum"`
}
// HeartbeatResponse is returned to the OpenFlared client.
type HeartbeatResponse struct {
ActiveConfig *ActiveConfigMeta `json:"active_config"`
TunnelSettings *relay.Settings `json:"tunnel_settings"`
}
// TunnelConfigResponse is the full tunnel routing config sent to the client.
type TunnelConfigResponse struct {
Version string `json:"version"`
Checksum string `json:"checksum"`
Relays []RelayInfo `json:"relays"`
Proxies []ProxyEntry `json:"proxies"`
}
// RelayInfo describes a relay the client should connect to.
type RelayInfo struct {
RelayNodeID string `json:"relay_node_id"`
Address string `json:"address"`
AuthToken string `json:"auth_token"`
ProxyURL string `json:"proxy_url"`
}
// ProxyEntry describes one frpc proxy definition.
type ProxyEntry struct {
Name string `json:"name"`
Type string `json:"type"`
LocalAddr string `json:"local_addr"`
LocalPort int `json:"local_port"`
CustomDomains []string `json:"custom_domains"`
}
// ApplyLogPayload is the apply result reported by OpenFlared.
type ApplyLogPayload struct {
NodeID string `json:"node_id"`
Version string `json:"version"`
Result string `json:"result"`
Message string `json:"message"`
Checksum string `json:"checksum"`
MainConfigChecksum string `json:"main_config_checksum"`
RouteConfigChecksum string `json:"route_config_checksum"`
SupportFileCount int `json:"support_file_count"`
}
// Heartbeat processes an OpenFlared heartbeat and returns runtime settings.
func Heartbeat(ctx context.Context, node *model.OpenFlareNode, payload HeartbeatPayload) (*HeartbeatResponse, error) {
if node == nil {
@@ -0,0 +1,15 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package flared
import pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol"
type HeartbeatPayload = pkgprotocol.FlaredHeartbeatPayload
type ConnectedRelay = pkgprotocol.FlaredConnectedRelay
type ActiveConfigMeta = pkgprotocol.ActiveConfigMeta
type HeartbeatResponse = pkgprotocol.FlaredHeartbeatResponse
type TunnelConfigResponse = pkgprotocol.FlaredTunnelConfigResponse
type RelayInfo = pkgprotocol.FlaredRelayInfo
type ProxyEntry = pkgprotocol.FlaredProxyEntry
type ApplyLogPayload = pkgprotocol.ApplyLogPayload
-53
View File
@@ -16,59 +16,6 @@ import (
const nodeStatusOnline = "online"
// ProxyStat describes a single frps proxy reported by the relay.
type ProxyStat struct {
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
ClientVersion string `json:"client_version"`
LastStartTime string `json:"last_start_time"`
LastCloseTime string `json:"last_close_time"`
ClientAddr string `json:"client_addr"`
}
// HeartbeatPayload is sent by OpenFlareRelay on each heartbeat.
type HeartbeatPayload struct {
Version string `json:"version"`
ExtVersion string `json:"frp_version"`
RelayStatus string `json:"relay_status"`
FrpsConnCount int `json:"frps_connections"`
FrpsProxyCount int `json:"frps_proxy_count"`
FrpsClientCount int `json:"frps_client_count"`
FrpsProxies []ProxyStat `json:"frps_proxies,omitempty"`
Name string `json:"name"`
IP string `json:"ip"`
Profile *agent.NodeSystemProfile `json:"profile,omitempty"`
Snapshot *agent.NodeMetricSnapshot `json:"snapshot,omitempty"`
HealthEvents []agent.NodeHealthEvent `json:"health_events,omitempty"`
}
// Config is the frps configuration sent to the relay.
type Config struct {
BindPort int `json:"bind_port"`
VhostHTTPPort int `json:"vhost_http_port"`
AuthToken string `json:"auth_token"`
LogLevel string `json:"log_level"`
WebServerEnabled bool `json:"web_server_enabled"`
}
// Settings contains runtime settings for relay and flared clients.
type Settings struct {
HeartbeatInterval int `json:"heartbeat_interval"`
WebsocketUpgradeEnabled bool `json:"websocket_upgrade_enabled"`
AutoUpdate bool `json:"auto_update"`
UpdateRepo string `json:"update_repo"`
UpdateNow bool `json:"update_now"`
UpdateChannel string `json:"update_channel"`
UpdateTag string `json:"update_tag"`
}
// HeartbeatResponse is returned from a relay heartbeat.
type HeartbeatResponse struct {
RelayConfig *Config `json:"relay_config"`
RelaySettings *Settings `json:"relay_settings"`
}
// Heartbeat processes a relay heartbeat, updates node status, and returns config.
func Heartbeat(ctx context.Context, node *model.OpenFlareNode, payload HeartbeatPayload) (*HeartbeatResponse, error) {
if node == nil {
@@ -0,0 +1,12 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package relay
import pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol"
type ProxyStat = pkgprotocol.RelayProxyStat
type HeartbeatPayload = pkgprotocol.RelayHeartbeatPayload
type Config = pkgprotocol.RelayConfig
type Settings = pkgprotocol.RelaySettings
type HeartbeatResponse = pkgprotocol.RelayHeartbeatResponse
+10 -55
View File
@@ -2,74 +2,29 @@ package wsclient
import (
"context"
"encoding/json"
"time"
service "github.com/Rain-kl/Wavelet/pkg/protocol"
shared "github.com/Rain-kl/Wavelet/pkg/wsclient"
edgews "github.com/Rain-kl/Wavelet/internal/apps/edge/wsclient"
)
type WSMessage = shared.WSMessage
type MessageHandler = shared.MessageHandler
type WSMessage = edgews.WSMessage
type MessageHandler = edgews.MessageHandler
type Connection = edgews.Connection
type Client struct {
sharedClient *shared.Client
inner *edgews.Client
}
type Connection struct {
sharedConn *shared.Connection
}
func New(baseURL string, token string, timeout time.Duration) *Client {
func New(baseURL, token string, timeout time.Duration) *Client {
return &Client{
sharedClient: shared.New(shared.Config{
BaseURL: baseURL,
Token: token,
Timeout: timeout,
HeaderKey: "X-Agent-Token",
WSPath: "/api/v1/relay/ws",
}),
inner: edgews.New(edgews.PresetRelay, baseURL, token, timeout),
}
}
func (c *Client) SetToken(token string) {
c.sharedClient.SetToken(token)
c.inner.SetToken(token)
}
func (c *Client) Connect(ctx context.Context) (*Connection, error) {
conn, err := c.sharedClient.Connect(ctx)
if err != nil {
return nil, err
}
return &Connection{sharedConn: conn}, nil
}
func (conn *Connection) SendPing() error {
return conn.sharedConn.SendMessage("ping", nil)
}
func (conn *Connection) SendPong() error {
return conn.sharedConn.SendMessage("pong", nil)
}
func (conn *Connection) Receive() (service.WSMessage, error) {
var raw struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload,omitempty"`
}
if err := conn.sharedConn.Receive(&raw); err != nil {
return service.WSMessage{}, err
}
return service.WSMessage{
Type: raw.Type,
Payload: raw.Payload,
}, nil
}
func (conn *Connection) RunReceiveLoop(ctx context.Context, handler shared.MessageHandler) error {
return conn.sharedConn.RunReceiveLoop(ctx, handler)
}
func (conn *Connection) Close() error {
return conn.sharedConn.Close()
}
return c.inner.Connect(ctx)
}