поправлен агент
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
Hockey vMix Agent 1.5.1
|
||||
Hockey vMix Agent 1.5.2
|
||||
|
||||
Changes:
|
||||
- vMix 29 tally now reads Overlay channels 1 through 8.
|
||||
- Existing agent_config.json is NOT included. Keep your current config next to agent.exe.
|
||||
- More reliable Agent ↔ Hockey Server reconnect after WebSocket drops.
|
||||
- After a working session the Agent retries the last healthy server first, then falls back to the other configured server.
|
||||
- WebSocket TCP keep-alive is enabled and the connection handshake timeout is increased to 12 seconds.
|
||||
- The Agent is marked ONLINE only after the server accepts the hello/credentials, not immediately after TCP/WebSocket upgrade.
|
||||
- A failed command/ACK write now immediately tears down the broken WebSocket and starts reconnect instead of continuing to send into a dead connection.
|
||||
- Reverse-proxy HTTP errors such as 502/503 now include a short diagnostic response in agent.log.
|
||||
- Server heartbeat ACK is sent before the PostgreSQL status update, so a slow database cannot make a healthy Agent connection look dead.
|
||||
|
||||
Update:
|
||||
1. Close Agent.
|
||||
1. Completely close the current Agent through tray → Exit.
|
||||
2. Replace only agent.exe.
|
||||
3. Keep the existing agent_config.json.
|
||||
3. Keep the existing agent_config.json next to agent.exe.
|
||||
4. Start Agent again.
|
||||
|
||||
Device ID, Device Secret and account pairing remain in agent_config.json and are not changed by this update.
|
||||
|
||||
BIN
agent/agent.exe
BIN
agent/agent.exe
Binary file not shown.
435060
agent/agent.log.1
435060
agent/agent.log.1
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
agentVersion = "1.5.0"
|
||||
agentVersion = "1.5.2"
|
||||
protocolVersion = 1
|
||||
websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
heartbeatInterval = 8 * time.Second
|
||||
@@ -445,6 +445,7 @@ func suffixVersion(v string) string {
|
||||
|
||||
func (a *Agent) connectionLoop() {
|
||||
backoff := time.Second
|
||||
preferredServer := ""
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
@@ -459,6 +460,19 @@ outer:
|
||||
if len(candidates) == 0 {
|
||||
candidates = []string{"http://127.0.0.1:8000"}
|
||||
}
|
||||
// Once a server has carried a real session, try that server first after
|
||||
// a disconnect. This avoids wasting the 12-second local/remote dial
|
||||
// timeout on a known-unavailable address every time the live socket drops.
|
||||
if preferredServer != "" {
|
||||
for index, candidate := range candidates {
|
||||
if candidate == preferredServer {
|
||||
if index > 0 {
|
||||
candidates[0], candidates[index] = candidates[index], candidates[0]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
var lastErr error
|
||||
for _, serverBase := range candidates {
|
||||
a.mu.Lock()
|
||||
@@ -467,7 +481,12 @@ outer:
|
||||
a.mu.Unlock()
|
||||
connectionStarted := time.Now()
|
||||
err := a.runConnection(serverBase)
|
||||
if time.Since(connectionStarted) >= 5*time.Second {
|
||||
connectionDuration := time.Since(connectionStarted)
|
||||
if connectionDuration >= 5*time.Second {
|
||||
// A socket that survived at least a few seconds is a useful
|
||||
// signal that this is currently the healthy route. Prefer it
|
||||
// on the next reconnect, but still fall back if it is unavailable.
|
||||
preferredServer = serverBase
|
||||
backoff = time.Second
|
||||
}
|
||||
a.mu.Lock()
|
||||
@@ -526,20 +545,23 @@ func (a *Agent) runConnection(serverBase string) error {
|
||||
return err
|
||||
}
|
||||
a.logf("Connecting to %s", target)
|
||||
ws, err := dialWebSocket(target, 7*time.Second)
|
||||
ws, err := dialWebSocket(target, 12*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ws.Close()
|
||||
a.mu.Lock()
|
||||
a.currentWS = ws
|
||||
a.state.ServerConnected = true
|
||||
a.state.ServerConnecting = false
|
||||
// TCP/TLS/WebSocket upgrade is not enough to call the Agent online: the
|
||||
// server still has to accept hello + device credentials. The UI therefore
|
||||
// becomes ONLINE only after hello.ok arrives in handleMessage.
|
||||
a.state.ServerConnected = false
|
||||
a.state.ServerConnecting = true
|
||||
a.state.ServerError = ""
|
||||
a.state.LastServerAt = time.Now()
|
||||
a.state.ConnectedServer = serverBase
|
||||
a.mu.Unlock()
|
||||
a.logf("Server connected: %s", serverBase)
|
||||
a.logf("WebSocket established: %s; waiting for server hello", serverBase)
|
||||
|
||||
vmix, inventory := scanVmix(cfg.VmixURL)
|
||||
a.mu.Lock()
|
||||
@@ -642,6 +664,10 @@ func (a *Agent) runConnection(serverBase string) error {
|
||||
a.mu.Unlock()
|
||||
if err := a.handleMessage(ws, msg); err != nil {
|
||||
a.logf("Message error: %v", err)
|
||||
// A message handler error is normally a failed ACK/response write.
|
||||
// Continuing to consume a socket that has already failed only queues
|
||||
// more commands into a dead connection. Reconnect immediately instead.
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -650,6 +676,12 @@ func (a *Agent) handleMessage(ws *wsConn, msg map[string]any) error {
|
||||
typ := stringValue(msg["type"])
|
||||
switch typ {
|
||||
case "hello.ok":
|
||||
a.mu.Lock()
|
||||
a.state.ServerConnected = true
|
||||
a.state.ServerConnecting = false
|
||||
a.state.ServerError = ""
|
||||
a.state.LastServerAt = time.Now()
|
||||
a.mu.Unlock()
|
||||
a.logf("Device registered on server")
|
||||
if dev, ok := msg["device"].(map[string]any); ok {
|
||||
a.mu.Lock()
|
||||
@@ -1126,7 +1158,7 @@ func dialWebSocket(target string, timeout time.Duration) (*wsConn, error) {
|
||||
}
|
||||
}
|
||||
address := net.JoinHostPort(host, port)
|
||||
d := net.Dialer{Timeout: timeout}
|
||||
d := net.Dialer{Timeout: timeout, KeepAlive: 15 * time.Second}
|
||||
var conn net.Conn
|
||||
if u.Scheme == "wss" {
|
||||
conn, err = tls.DialWithDialer(&d, "tcp", address, &tls.Config{ServerName: host, MinVersion: tls.VersionTLS12})
|
||||
@@ -1159,7 +1191,25 @@ func dialWebSocket(target string, timeout time.Duration) (*wsConn, error) {
|
||||
return nil, err
|
||||
}
|
||||
if !strings.Contains(statusLine, " 101 ") {
|
||||
// Keep a small diagnostic preview from reverse proxies (502/503/504).
|
||||
// This makes intermittent public-server failures distinguishable from
|
||||
// a local TCP/DNS problem without dumping an HTML error page to the log.
|
||||
preview := ""
|
||||
if statusLine != "" {
|
||||
_ = conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
||||
buf := make([]byte, 2048)
|
||||
if n, readErr := reader.Read(buf); readErr == nil || n > 0 {
|
||||
preview = strings.TrimSpace(strings.ReplaceAll(string(buf[:n]), "\r", " "))
|
||||
preview = strings.Join(strings.Fields(preview), " ")
|
||||
}
|
||||
}
|
||||
conn.Close()
|
||||
if len(preview) > 300 {
|
||||
preview = preview[:300] + "…"
|
||||
}
|
||||
if preview != "" {
|
||||
return nil, fmt.Errorf("websocket upgrade failed: %s · %s", strings.TrimSpace(statusLine), preview)
|
||||
}
|
||||
return nil, fmt.Errorf("websocket upgrade failed: %s", strings.TrimSpace(statusLine))
|
||||
}
|
||||
headers := map[string]string{}
|
||||
|
||||
Reference in New Issue
Block a user