release 0.3.9 (#349)

This commit is contained in:
Aveline 2025-04-10 16:47:19 +02:00 committed by GitHub
commit 5452d7c721
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 172 additions and 53 deletions

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@ -59,6 +60,13 @@ var (
}, },
[]string{"type", "source"}, []string{"type", "source"},
) )
metricConnectionLastPingReceivedTimestamp = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "jetkvm_connection_last_ping_received_timestamp",
Help: "The timestamp when the last ping request was received",
},
[]string{"type", "source"},
)
metricConnectionLastPingDuration = promauto.NewGaugeVec( metricConnectionLastPingDuration = promauto.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "jetkvm_connection_last_ping_duration", Name: "jetkvm_connection_last_ping_duration",
@ -76,16 +84,23 @@ var (
}, },
[]string{"type", "source"}, []string{"type", "source"},
) )
metricConnectionTotalPingCount = promauto.NewCounterVec( metricConnectionTotalPingSentCount = promauto.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "jetkvm_connection_total_ping_count", Name: "jetkvm_connection_total_ping_sent",
Help: "The total number of pings sent to the connection", Help: "The total number of pings sent to the connection",
}, },
[]string{"type", "source"}, []string{"type", "source"},
) )
metricConnectionTotalPingReceivedCount = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "jetkvm_connection_total_ping_received",
Help: "The total number of pings received from the connection",
},
[]string{"type", "source"},
)
metricConnectionSessionRequestCount = promauto.NewCounterVec( metricConnectionSessionRequestCount = promauto.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "jetkvm_connection_session_total_request_count", Name: "jetkvm_connection_session_total_requests",
Help: "The total number of session requests received", Help: "The total number of session requests received",
}, },
[]string{"type", "source"}, []string{"type", "source"},
@ -131,6 +146,8 @@ func wsResetMetrics(established bool, sourceType string, source string) {
metricConnectionLastPingTimestamp.WithLabelValues(sourceType, source).Set(-1) metricConnectionLastPingTimestamp.WithLabelValues(sourceType, source).Set(-1)
metricConnectionLastPingDuration.WithLabelValues(sourceType, source).Set(-1) metricConnectionLastPingDuration.WithLabelValues(sourceType, source).Set(-1)
metricConnectionLastPingReceivedTimestamp.WithLabelValues(sourceType, source).Set(-1)
metricConnectionLastSessionRequestTimestamp.WithLabelValues(sourceType, source).Set(-1) metricConnectionLastSessionRequestTimestamp.WithLabelValues(sourceType, source).Set(-1)
metricConnectionLastSessionRequestDuration.WithLabelValues(sourceType, source).Set(-1) metricConnectionLastSessionRequestDuration.WithLabelValues(sourceType, source).Set(-1)
@ -275,18 +292,31 @@ func runWebsocketClient() error {
defer cancelDial() defer cancelDial()
c, _, err := websocket.Dial(dialCtx, wsURL.String(), &websocket.DialOptions{ c, _, err := websocket.Dial(dialCtx, wsURL.String(), &websocket.DialOptions{
HTTPHeader: header, HTTPHeader: header,
OnPingReceived: func(ctx context.Context, payload []byte) bool {
websocketLogger.Infof("ping frame received: %v, source: %s, sourceType: cloud", payload, wsURL.Host)
metricConnectionTotalPingReceivedCount.WithLabelValues("cloud", wsURL.Host).Inc()
metricConnectionLastPingReceivedTimestamp.WithLabelValues("cloud", wsURL.Host).SetToCurrentTime()
return true
},
}) })
// if the context is canceled, we don't want to return an error
if err != nil { if err != nil {
if errors.Is(err, context.Canceled) {
cloudLogger.Infof("websocket connection canceled")
return nil
}
return err return err
} }
defer c.CloseNow() //nolint:errcheck defer c.CloseNow() //nolint:errcheck
cloudLogger.Infof("websocket connected to %s", wsURL) cloudLogger.Infof("websocket connected to %s", wsURL)
// set the metrics when we successfully connect to the cloud. // set the metrics when we successfully connect to the cloud.
wsResetMetrics(true, "cloud", "") wsResetMetrics(true, "cloud", wsURL.Host)
// we don't have a source for the cloud connection // we don't have a source for the cloud connection
return handleWebRTCSignalWsMessages(c, true, "") return handleWebRTCSignalWsMessages(c, true, wsURL.Host)
} }
func authenticateSession(ctx context.Context, c *websocket.Conn, req WebRTCSessionRequest) error { func authenticateSession(ctx context.Context, c *websocket.Conn, req WebRTCSessionRequest) error {
@ -375,9 +405,6 @@ func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSess
func RunWebsocketClient() { func RunWebsocketClient() {
for { for {
// reset the metrics when we start the websocket client.
wsResetMetrics(false, "cloud", "")
// If the cloud token is not set, we don't need to run the websocket client. // If the cloud token is not set, we don't need to run the websocket client.
if config.CloudToken == "" { if config.CloudToken == "" {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)

View File

@ -1,3 +1,5 @@
#!/usr/bin/env bash
#
# Exit immediately if a command exits with a non-zero status # Exit immediately if a command exits with a non-zero status
set -e set -e
@ -16,7 +18,6 @@ show_help() {
echo "Example:" echo "Example:"
echo " $0 -r 192.168.0.17" echo " $0 -r 192.168.0.17"
echo " $0 -r 192.168.0.17 -u admin" echo " $0 -r 192.168.0.17 -u admin"
exit 0
} }
# Default values # Default values
@ -70,7 +71,7 @@ cd bin
ssh "${REMOTE_USER}@${REMOTE_HOST}" "killall jetkvm_app_debug || true" ssh "${REMOTE_USER}@${REMOTE_HOST}" "killall jetkvm_app_debug || true"
# Copy the binary to the remote host # Copy the binary to the remote host
cat jetkvm_app | ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > $REMOTE_PATH/jetkvm_app_debug" ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > ${REMOTE_PATH}/jetkvm_app_debug" < jetkvm_app
# Deploy and run the application on the remote host # Deploy and run the application on the remote host
ssh "${REMOTE_USER}@${REMOTE_HOST}" ash << EOF ssh "${REMOTE_USER}@${REMOTE_HOST}" ash << EOF
@ -84,13 +85,13 @@ killall jetkvm_app || true
killall jetkvm_app_debug || true killall jetkvm_app_debug || true
# Navigate to the directory where the binary will be stored # Navigate to the directory where the binary will be stored
cd "$REMOTE_PATH" cd "${REMOTE_PATH}"
# Make the new binary executable # Make the new binary executable
chmod +x jetkvm_app_debug chmod +x jetkvm_app_debug
# Run the application in the background # Run the application in the background
PION_LOG_TRACE=jetkvm,cloud ./jetkvm_app_debug PION_LOG_TRACE=jetkvm,cloud,websocket ./jetkvm_app_debug
EOF EOF
echo "Deployment complete." echo "Deployment complete."

2
go.mod
View File

@ -7,7 +7,7 @@ toolchain go1.21.1
require ( require (
github.com/Masterminds/semver/v3 v3.3.0 github.com/Masterminds/semver/v3 v3.3.0
github.com/beevik/ntp v1.3.1 github.com/beevik/ntp v1.3.1
github.com/coder/websocket v1.8.12 github.com/coder/websocket v1.8.13
github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-oidc/v3 v3.11.0
github.com/creack/pty v1.1.23 github.com/creack/pty v1.1.23
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1

2
go.sum
View File

@ -18,6 +18,8 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=

6
ota.go
View File

@ -126,12 +126,10 @@ func downloadFile(ctx context.Context, path string, url string, downloadProgress
return fmt.Errorf("error creating request: %w", err) return fmt.Errorf("error creating request: %w", err)
} }
// TODO: set a separate timeout for the download but keep the TLS handshake short
// use Transport here will cause CA certificate validation failure so we temporarily removed it
client := http.Client{ client := http.Client{
// allow a longer timeout for the download but keep the TLS handshake short
Timeout: 10 * time.Minute, Timeout: 10 * time.Minute,
Transport: &http.Transport{
TLSHandshakeTimeout: 1 * time.Minute,
},
} }
resp, err := client.Do(req) resp, err := client.Do(req)

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Check if a commit message was provided # Check if a commit message was provided
if [ -z "$1" ]; then if [ -z "$1" ]; then
@ -26,7 +26,7 @@ git checkout -b release-temp
if git ls-remote --heads public main | grep -q 'refs/heads/main'; then if git ls-remote --heads public main | grep -q 'refs/heads/main'; then
git reset --soft public/main git reset --soft public/main
else else
git reset --soft $(git rev-list --max-parents=0 HEAD) git reset --soft "$(git rev-list --max-parents=0 HEAD)"
fi fi
# Merge changes from main # Merge changes from main

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Check if an IP address was provided as an argument # Check if an IP address was provided as an argument
if [ -z "$1" ]; then if [ -z "$1" ]; then
@ -16,4 +16,4 @@ echo "└───────────────────────
# Set the environment variable and run Vite # Set the environment variable and run Vite
echo "Starting development server with JetKVM device at: $ip_address" echo "Starting development server with JetKVM device at: $ip_address"
sleep 1 sleep 1
JETKVM_PROXY_URL="http://$ip_address" npx vite dev --mode=device JETKVM_PROXY_URL="ws://$ip_address" npx vite dev --mode=device

View File

@ -15,12 +15,12 @@ const Modal = React.memo(function Modal({
onClose: () => void; onClose: () => void;
}) { }) {
return ( return (
<Dialog open={open} onClose={onClose} className="relative z-10"> <Dialog open={open} onClose={onClose} className="relative z-20">
<DialogBackdrop <DialogBackdrop
transition transition
className="fixed inset-0 bg-gray-500/75 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-500 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in dark:bg-slate-900/90" className="fixed inset-0 bg-gray-500/75 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-500 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in dark:bg-slate-900/90"
/> />
<div className="fixed inset-0 z-10 w-screen overflow-y-auto"> <div className="fixed inset-0 z-20 w-screen overflow-y-auto">
{/* TODO: This doesn't work well with other-sessions */} {/* TODO: This doesn't work well with other-sessions */}
<div className="flex min-h-full items-end justify-center p-4 text-center md:items-baseline md:p-4"> <div className="flex min-h-full items-end justify-center p-4 text-center md:items-baseline md:p-4">
<DialogPanel <DialogPanel

View File

@ -28,6 +28,7 @@ export default function WebRTCVideo() {
const videoElm = useRef<HTMLVideoElement>(null); const videoElm = useRef<HTMLVideoElement>(null);
const mediaStream = useRTCStore(state => state.mediaStream); const mediaStream = useRTCStore(state => state.mediaStream);
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const peerConnectionState = useRTCStore(state => state.peerConnectionState);
// Store hooks // Store hooks
const settings = useSettingsStore(); const settings = useSettingsStore();
@ -601,7 +602,10 @@ export default function WebRTCVideo() {
"cursor-none": "cursor-none":
settings.mouseMode === "absolute" && settings.mouseMode === "absolute" &&
settings.isCursorHidden, settings.isCursorHidden,
"opacity-0": isVideoLoading || hdmiError, "opacity-0":
isVideoLoading ||
hdmiError ||
peerConnectionState !== "connected",
"animate-slideUpFade border border-slate-800/30 opacity-0 shadow dark:border-slate-300/20": "animate-slideUpFade border border-slate-800/30 opacity-0 shadow dark:border-slate-300/20":
isPlaying, isPlaying,
}, },

View File

@ -243,7 +243,7 @@ export default function KvmIdRoute() {
{ {
heartbeat: true, heartbeat: true,
retryOnError: true, retryOnError: true,
reconnectAttempts: 5, reconnectAttempts: 15,
reconnectInterval: 1000, reconnectInterval: 1000,
onReconnectStop: () => { onReconnectStop: () => {
console.log("Reconnect stopped"); console.log("Reconnect stopped");
@ -398,11 +398,6 @@ export default function KvmIdRoute() {
setConnectionFailed(false); setConnectionFailed(false);
setLoadingMessage("Connecting to device..."); setLoadingMessage("Connecting to device...");
if (peerConnection?.signalingState === "stable") {
console.log("[setupPeerConnection] Peer connection already established");
return;
}
let pc: RTCPeerConnection; let pc: RTCPeerConnection;
try { try {
console.log("[setupPeerConnection] Creating peer connection"); console.log("[setupPeerConnection] Creating peer connection");
@ -499,7 +494,6 @@ export default function KvmIdRoute() {
cleanupAndStopReconnecting, cleanupAndStopReconnecting,
iceConfig?.iceServers, iceConfig?.iceServers,
legacyHTTPSignaling, legacyHTTPSignaling,
peerConnection?.signalingState,
sendWebRTCSignal, sendWebRTCSignal,
setDiskChannel, setDiskChannel,
setMediaMediaStream, setMediaMediaStream,
@ -791,6 +785,7 @@ export default function KvmIdRoute() {
<button className="absolute top-0" tabIndex={-1} id="videoFocusTrap" /> <button className="absolute top-0" tabIndex={-1} id="videoFocusTrap" />
</div> </div>
</FocusTrap> </FocusTrap>
<div className="grid h-full select-none grid-rows-headerBody"> <div className="grid h-full select-none grid-rows-headerBody">
<DashboardNavbar <DashboardNavbar
primaryLinks={isOnDevice ? [] : [{ title: "Cloud Devices", to: "/devices" }]} primaryLinks={isOnDevice ? [] : [{ title: "Cloud Devices", to: "/devices" }]}
@ -801,21 +796,23 @@ export default function KvmIdRoute() {
kvmName={deviceName || "JetKVM Device"} kvmName={deviceName || "JetKVM Device"}
/> />
<div className="flex h-full w-full overflow-hidden"> <div className="relative flex h-full w-full overflow-hidden">
<div className="pointer-events-none fixed inset-0 isolate z-20 flex h-full w-full items-center justify-center"> <WebRTCVideo />
<div className="my-2 h-full max-h-[720px] w-full max-w-[1280px] rounded-md"> <div
style={{ animationDuration: "500ms" }}
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center p-4 opacity-0"
>
<div className="relative h-full max-h-[720px] w-full max-w-[1280px] rounded-md">
{!!ConnectionStatusElement && ConnectionStatusElement} {!!ConnectionStatusElement && ConnectionStatusElement}
</div> </div>
</div> </div>
{peerConnectionState === "connected" && <WebRTCVideo />}
<SidebarContainer sidebarView={sidebarView} /> <SidebarContainer sidebarView={sidebarView} />
</div> </div>
</div> </div>
</div> </div>
<div <div
className="isolate" className="z-50"
onKeyUp={e => e.stopPropagation()} onKeyUp={e => e.stopPropagation()}
onKeyDown={e => { onKeyDown={e => {
e.stopPropagation(); e.stopPropagation();

120
web.go
View File

@ -1,9 +1,11 @@
package kvm package kvm
import ( import (
"bytes"
"context" "context"
"embed" "embed"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/fs" "io/fs"
"net/http" "net/http"
@ -99,6 +101,22 @@ func setupRouter() *gin.Engine {
protected := r.Group("/") protected := r.Group("/")
protected.Use(protectedMiddleware()) protected.Use(protectedMiddleware())
{ {
/*
* Legacy WebRTC session endpoint
*
* This endpoint is maintained for backward compatibility when users upgrade from a version
* using the legacy HTTP-based signaling method to the new WebSocket-based signaling method.
*
* During the upgrade process, when the "Rebooting device after update..." message appears,
* the browser still runs the previous JavaScript code which polls this endpoint to establish
* a new WebRTC session. Once the session is established, the page will automatically reload
* with the updated code.
*
* Without this endpoint, the stale JavaScript would fail to establish a connection,
* causing users to see the "Rebooting device after update..." message indefinitely
* until they manually refresh the page, leading to a confusing user experience.
*/
protected.POST("/webrtc/session", handleWebRTCSession)
protected.GET("/webrtc/signaling/client", handleLocalWebRTCSignal) protected.GET("/webrtc/signaling/client", handleLocalWebRTCSignal)
protected.POST("/cloud/register", handleCloudRegister) protected.POST("/cloud/register", handleCloudRegister)
protected.GET("/cloud/state", handleCloudState) protected.GET("/cloud/state", handleCloudState)
@ -126,11 +144,59 @@ func setupRouter() *gin.Engine {
// TODO: support multiple sessions? // TODO: support multiple sessions?
var currentSession *Session var currentSession *Session
func handleWebRTCSession(c *gin.Context) {
var req WebRTCSessionRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
session, err := newSession(SessionConfig{})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
return
}
sd, err := session.ExchangeOffer(req.Sd)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
return
}
if currentSession != nil {
writeJSONRPCEvent("otherSessionConnected", nil, currentSession)
peerConn := currentSession.peerConnection
go func() {
time.Sleep(1 * time.Second)
_ = peerConn.Close()
}()
}
currentSession = session
c.JSON(http.StatusOK, gin.H{"sd": sd})
}
var (
pingMessage = []byte("ping")
pongMessage = []byte("pong")
)
func handleLocalWebRTCSignal(c *gin.Context) { func handleLocalWebRTCSignal(c *gin.Context) {
cloudLogger.Infof("new websocket connection established") cloudLogger.Infof("new websocket connection established")
// get the source from the request
source := c.ClientIP()
// Create WebSocket options with InsecureSkipVerify to bypass origin check // Create WebSocket options with InsecureSkipVerify to bypass origin check
wsOptions := &websocket.AcceptOptions{ wsOptions := &websocket.AcceptOptions{
InsecureSkipVerify: true, // Allow connections from any origin InsecureSkipVerify: true, // Allow connections from any origin
OnPingReceived: func(ctx context.Context, payload []byte) bool {
websocketLogger.Infof("ping frame received: %v, source: %s, sourceType: local", payload, source)
metricConnectionTotalPingReceivedCount.WithLabelValues("local", source).Inc()
metricConnectionLastPingReceivedTimestamp.WithLabelValues("local", source).SetToCurrentTime()
return true
},
} }
wsCon, err := websocket.Accept(c.Writer, c.Request, wsOptions) wsCon, err := websocket.Accept(c.Writer, c.Request, wsOptions)
@ -139,9 +205,6 @@ func handleLocalWebRTCSignal(c *gin.Context) {
return return
} }
// get the source from the request
source := c.ClientIP()
// Now use conn for websocket operations // Now use conn for websocket operations
defer wsCon.Close(websocket.StatusNormalClosure, "") defer wsCon.Close(websocket.StatusNormalClosure, "")
@ -164,7 +227,6 @@ func handleWebRTCSignalWsMessages(wsCon *websocket.Conn, isCloudConnection bool,
// Add connection tracking to detect reconnections // Add connection tracking to detect reconnections
connectionID := uuid.New().String() connectionID := uuid.New().String()
cloudLogger.Infof("new websocket connection established with ID: %s", connectionID)
// connection type // connection type
var sourceType string var sourceType string
@ -176,29 +238,40 @@ func handleWebRTCSignalWsMessages(wsCon *websocket.Conn, isCloudConnection bool,
// probably we can use a better logging framework here // probably we can use a better logging framework here
logInfof := func(format string, args ...interface{}) { logInfof := func(format string, args ...interface{}) {
args = append(args, source, sourceType) args = append(args, source, sourceType, connectionID)
websocketLogger.Infof(format+", source: %s, sourceType: %s", args...) websocketLogger.Infof(format+", source: %s, sourceType: %s, id: %s", args...)
} }
logWarnf := func(format string, args ...interface{}) { logWarnf := func(format string, args ...interface{}) {
args = append(args, source, sourceType) args = append(args, source, sourceType, connectionID)
websocketLogger.Warnf(format+", source: %s, sourceType: %s", args...) websocketLogger.Warnf(format+", source: %s, sourceType: %s, id: %s", args...)
} }
logTracef := func(format string, args ...interface{}) { logTracef := func(format string, args ...interface{}) {
args = append(args, source, sourceType) args = append(args, source, sourceType, connectionID)
websocketLogger.Tracef(format+", source: %s, sourceType: %s", args...) websocketLogger.Tracef(format+", source: %s, sourceType: %s, id: %s", args...)
} }
logInfof("new websocket connection established")
go func() { go func() {
for { for {
time.Sleep(WebsocketPingInterval) time.Sleep(WebsocketPingInterval)
if ctxErr := runCtx.Err(); ctxErr != nil {
if !errors.Is(ctxErr, context.Canceled) {
logWarnf("websocket connection closed: %v", ctxErr)
} else {
logTracef("websocket connection closed as the context was canceled: %v")
}
return
}
// set the timer for the ping duration // set the timer for the ping duration
timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) {
metricConnectionLastPingDuration.WithLabelValues(sourceType, source).Set(v) metricConnectionLastPingDuration.WithLabelValues(sourceType, source).Set(v)
metricConnectionPingDuration.WithLabelValues(sourceType, source).Observe(v) metricConnectionPingDuration.WithLabelValues(sourceType, source).Observe(v)
})) }))
logInfof("pinging websocket") logTracef("sending ping frame")
err := wsCon.Ping(runCtx) err := wsCon.Ping(runCtx)
if err != nil { if err != nil {
@ -208,10 +281,12 @@ func handleWebRTCSignalWsMessages(wsCon *websocket.Conn, isCloudConnection bool,
} }
// dont use `defer` here because we want to observe the duration of the ping // dont use `defer` here because we want to observe the duration of the ping
timer.ObserveDuration() duration := timer.ObserveDuration()
metricConnectionTotalPingCount.WithLabelValues(sourceType, source).Inc() metricConnectionTotalPingSentCount.WithLabelValues(sourceType, source).Inc()
metricConnectionLastPingTimestamp.WithLabelValues(sourceType, source).SetToCurrentTime() metricConnectionLastPingTimestamp.WithLabelValues(sourceType, source).SetToCurrentTime()
logTracef("received pong frame, duration: %v", duration)
} }
}() }()
@ -249,6 +324,20 @@ func handleWebRTCSignalWsMessages(wsCon *websocket.Conn, isCloudConnection bool,
Data json.RawMessage `json:"data"` Data json.RawMessage `json:"data"`
} }
if bytes.Equal(msg, pingMessage) {
logInfof("ping message received: %s", string(msg))
err = wsCon.Write(context.Background(), websocket.MessageText, pongMessage)
if err != nil {
logWarnf("unable to write pong message: %v", err)
return err
}
metricConnectionTotalPingReceivedCount.WithLabelValues(sourceType, source).Inc()
metricConnectionLastPingReceivedTimestamp.WithLabelValues(sourceType, source).SetToCurrentTime()
continue
}
err = json.Unmarshal(msg, &message) err = json.Unmarshal(msg, &message)
if err != nil { if err != nil {
logWarnf("unable to parse ws message: %v", err) logWarnf("unable to parse ws message: %v", err)
@ -264,8 +353,9 @@ func handleWebRTCSignalWsMessages(wsCon *websocket.Conn, isCloudConnection bool,
continue continue
} }
logInfof("new session request: %v", req.OidcGoogle) if req.OidcGoogle != "" {
logTracef("session request info: %v", req) logInfof("new session request with OIDC Google: %v", req.OidcGoogle)
}
metricConnectionSessionRequestCount.WithLabelValues(sourceType, source).Inc() metricConnectionSessionRequestCount.WithLabelValues(sourceType, source).Inc()
metricConnectionLastSessionRequestTimestamp.WithLabelValues(sourceType, source).SetToCurrentTime() metricConnectionLastSessionRequestTimestamp.WithLabelValues(sourceType, source).SetToCurrentTime()