Add a relay ping and backend status to the iOS app
The relay now answers a stateless ping with pong. The Relay card auto-checks on appear and address changes, showing latency or the connection error; this verifies the endpoint speaks the share protocol, not just that a TCP port is open.
This commit is contained in:
@@ -11,10 +11,13 @@ package share
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tzgyn/share/src/comm"
|
||||
"github.com/tzgyn/share/src/swarm"
|
||||
"github.com/tzgyn/share/src/utils"
|
||||
)
|
||||
@@ -75,6 +78,97 @@ func RelayDefault() string {
|
||||
return DefaultRelay
|
||||
}
|
||||
|
||||
// relayStatus is the JSON result of CheckRelay. Swift shows it as a small
|
||||
// "backend reachable" indicator in the Relay card.
|
||||
type relayStatus struct {
|
||||
OK bool `json:"ok"`
|
||||
Relay string `json:"relay"`
|
||||
LatencyMs int64 `json:"latencyMs"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CheckRelay probes the configured relay with the protocol's stateless
|
||||
// ping/pong exchange. A plain TCP connect would only prove that something
|
||||
// is listening on the port; this also proves the service actually speaks
|
||||
// the share relay protocol.
|
||||
//
|
||||
// The UI calls this in the background. The five-second dial and read
|
||||
// timeouts keep a dead backend from leaving the indicator spinning.
|
||||
func CheckRelay(address string) string {
|
||||
relay := normalizeRelay(address)
|
||||
result := relayStatus{Relay: relay}
|
||||
start := time.Now()
|
||||
|
||||
c, err := comm.NewConnection(relay, 5*time.Second)
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return marshalRelayStatus(result)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if err := c.Send([]byte(`{"type":"ping"}`)); err != nil {
|
||||
result.Error = err.Error()
|
||||
return marshalRelayStatus(result)
|
||||
}
|
||||
|
||||
// comm's normal read deadline is measured in hours. Run the read in a
|
||||
// goroutine so a relay that accepts but never answers cannot leave the
|
||||
// UI spinning; the deferred Close below unblocks the goroutine.
|
||||
type readResult struct {
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
read := make(chan readResult, 1)
|
||||
go func() {
|
||||
data, err := c.Receive()
|
||||
read <- readResult{data: data, err: err}
|
||||
}()
|
||||
|
||||
var response []byte
|
||||
select {
|
||||
case r := <-read:
|
||||
if r.err != nil {
|
||||
result.Error = r.err.Error()
|
||||
return marshalRelayStatus(result)
|
||||
}
|
||||
response = r.data
|
||||
case <-time.After(5 * time.Second):
|
||||
result.Error = "timed out waiting for relay response"
|
||||
return marshalRelayStatus(result)
|
||||
}
|
||||
|
||||
var pong struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
if err := json.Unmarshal(response, &pong); err != nil || pong.Type != "pong" {
|
||||
result.Error = "relay did not answer with pong"
|
||||
return marshalRelayStatus(result)
|
||||
}
|
||||
|
||||
result.OK = true
|
||||
result.LatencyMs = time.Since(start).Milliseconds()
|
||||
return marshalRelayStatus(result)
|
||||
}
|
||||
|
||||
func normalizeRelay(address string) string {
|
||||
if address == "" {
|
||||
address = DefaultRelay
|
||||
}
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return net.JoinHostPort(address, "9009")
|
||||
}
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
func marshalRelayStatus(result relayStatus) string {
|
||||
b, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return `{"ok":false,"error":"could not encode relay status"}`
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Start joins one room. filesJSON is a JSON array of absolute paths inside
|
||||
// the app sandbox; pass "[]" to be a pure receiver. outDir is created if
|
||||
// needed and must also be absolute. oneTime stops the room as soon as every
|
||||
|
||||
Reference in New Issue
Block a user