mirror of https://github.com/jetkvm/kvm.git
feat: Add app version header and update WebRTC signaling endpoint
This commit is contained in:
parent
b9c4f4a24b
commit
443cf5d029
1
cloud.go
1
cloud.go
|
@ -268,6 +268,7 @@ func runWebsocketClient() error {
|
|||
|
||||
header := http.Header{}
|
||||
header.Set("X-Device-ID", GetDeviceID())
|
||||
header.Set("X-App-Version", builtAppVersion)
|
||||
header.Set("Authorization", "Bearer "+config.CloudToken)
|
||||
dialCtx, cancelDial := context.WithTimeout(context.Background(), CloudWebSocketConnectTimeout)
|
||||
|
||||
|
|
|
@ -44,16 +44,16 @@ import UpdateInProgressStatusCard from "../components/UpdateInProgressStatusCard
|
|||
import api from "../api";
|
||||
import Modal from "../components/Modal";
|
||||
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
|
||||
import { FeatureFlagProvider } from "../providers/FeatureFlagProvider";
|
||||
import notifications from "../notifications";
|
||||
import {
|
||||
ConnectionFailedOverlay,
|
||||
LoadingConnectionOverlay,
|
||||
PeerConnectionDisconnectedOverlay,
|
||||
} from "../components/VideoOverlay";
|
||||
import { FeatureFlagProvider } from "../providers/FeatureFlagProvider";
|
||||
import notifications from "../notifications";
|
||||
|
||||
import { SystemVersionInfo } from "./devices.$id.settings.general.update";
|
||||
import { DeviceStatus } from "./welcome-local";
|
||||
import { SystemVersionInfo } from "./devices.$id.settings.general.update";
|
||||
|
||||
interface LocalLoaderResp {
|
||||
authMode: "password" | "noPassword" | null;
|
||||
|
@ -140,6 +140,8 @@ export default function KvmIdRoute() {
|
|||
const setTransceiver = useRTCStore(state => state.setTransceiver);
|
||||
const location = useLocation();
|
||||
|
||||
const isLegacySignalingEnabled = useRef(false);
|
||||
|
||||
const [connectionFailed, setConnectionFailed] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
@ -234,11 +236,10 @@ export default function KvmIdRoute() {
|
|||
|
||||
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
|
||||
console.log("isondevice", isOnDevice);
|
||||
const { sendMessage } = useWebSocket(
|
||||
const { sendMessage, getWebSocket } = useWebSocket(
|
||||
isOnDevice
|
||||
? `${wsProtocol}//${window.location.host}/webrtc/signaling`
|
||||
: `${CLOUD_API.replace("http", "ws")}/webrtc/signaling?id=${params.id}`,
|
||||
? `${wsProtocol}//${window.location.host}/webrtc/signaling/client`
|
||||
: `${CLOUD_API.replace("http", "ws")}/webrtc/signaling/client?id=${params.id}`,
|
||||
{
|
||||
heartbeat: true,
|
||||
retryOnError: true,
|
||||
|
@ -266,15 +267,45 @@ export default function KvmIdRoute() {
|
|||
},
|
||||
onOpen() {
|
||||
console.log("[Websocket] onOpen");
|
||||
setupPeerConnection();
|
||||
},
|
||||
|
||||
onMessage: message => {
|
||||
if (message.data === "pong") return;
|
||||
if (!peerConnection) return;
|
||||
|
||||
/*
|
||||
Currently the signaling process is as follows:
|
||||
After open, the other side will send a `device-metadata` message with the device version
|
||||
If the device version is not set, we can assume the device is using the legacy signaling
|
||||
Otherwise, we can assume the device is using the new signaling
|
||||
|
||||
If the device is using the legacy signaling, we close the websocket connection
|
||||
and use the legacy HTTPSignaling function to get the remote session description
|
||||
|
||||
If the device is using the new signaling, we don't need to do anything special, but continue to use the websocket connection
|
||||
to chat with the other peer about the connection
|
||||
*/
|
||||
|
||||
const parsedMessage = JSON.parse(message.data);
|
||||
if (parsedMessage.type === "device-metadata") {
|
||||
const { deviceVersion } = parsedMessage.data;
|
||||
console.log("[Websocket] Received device-metadata message");
|
||||
console.log("[Websocket] Device version", deviceVersion);
|
||||
// If the device version is not set, we can assume the device is using the legacy signaling
|
||||
if (!deviceVersion) {
|
||||
console.log("[Websocket] Device is using legacy signaling");
|
||||
|
||||
// Now we don't need the websocket connection anymore, as we've established that we need to use the legacy signaling
|
||||
// which does everything over HTTP(at least from the perspective of the client)
|
||||
isLegacySignalingEnabled.current = true;
|
||||
getWebSocket()?.close();
|
||||
} else {
|
||||
console.log("[Websocket] Device is using new signaling");
|
||||
isLegacySignalingEnabled.current = false;
|
||||
}
|
||||
setupPeerConnection();
|
||||
}
|
||||
|
||||
if (!peerConnection) return;
|
||||
if (parsedMessage.type === "answer") {
|
||||
console.log("[Websocket] Received answer");
|
||||
const readyForOffer =
|
||||
|
@ -314,7 +345,7 @@ export default function KvmIdRoute() {
|
|||
},
|
||||
|
||||
// Don't even retry once we declare failure
|
||||
!connectionFailed,
|
||||
!connectionFailed && isLegacySignalingEnabled.current === false,
|
||||
);
|
||||
|
||||
const sendWebRTCSignal = useCallback(
|
||||
|
@ -326,6 +357,42 @@ export default function KvmIdRoute() {
|
|||
[sendMessage],
|
||||
);
|
||||
|
||||
const legacyHTTPSignaling = useCallback(
|
||||
async (pc: RTCPeerConnection) => {
|
||||
const sd = btoa(JSON.stringify(pc.localDescription));
|
||||
|
||||
// Legacy mode == UI in cloud with updated code connecting to older device version.
|
||||
// In device mode, old devices wont server this JS, and on newer devices legacy mode wont be enabled
|
||||
const sessionUrl = `${CLOUD_API}/webrtc/session`;
|
||||
|
||||
console.log("Trying to get remote session description");
|
||||
setLoadingMessage(
|
||||
`Getting remote session description... ${signalingAttempts.current > 0 ? `(attempt ${signalingAttempts.current + 1})` : ""}`,
|
||||
);
|
||||
const res = await api.POST(sessionUrl, {
|
||||
sd,
|
||||
// When on device, we don't need to specify the device id, as it's already known
|
||||
...(isOnDevice ? {} : { id: params.id }),
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
if (res.status === 401) return navigate(isOnDevice ? "/login-local" : "/login");
|
||||
if (!res.ok) {
|
||||
console.error("Error getting SDP", { status: res.status, json });
|
||||
cleanupAndStopReconnecting();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Successfully got Remote Session Description. Setting.");
|
||||
setLoadingMessage("Setting remote session description...");
|
||||
|
||||
const decodedSd = atob(json.sd);
|
||||
const parsedSd = JSON.parse(decodedSd);
|
||||
setRemoteSessionDescription(pc, new RTCSessionDescription(parsedSd));
|
||||
},
|
||||
[cleanupAndStopReconnecting, navigate, params.id, setRemoteSessionDescription],
|
||||
);
|
||||
|
||||
const setupPeerConnection = useCallback(async () => {
|
||||
console.log("[setupPeerConnection] Setting up peer connection");
|
||||
setConnectionFailed(false);
|
||||
|
@ -346,6 +413,7 @@ export default function KvmIdRoute() {
|
|||
? { iceServers: [iceConfig?.iceServers] }
|
||||
: {}),
|
||||
});
|
||||
|
||||
setPeerConnectionState(pc.connectionState);
|
||||
console.log("[setupPeerConnection] Peer connection created", pc);
|
||||
setLoadingMessage("Setting up connection to device...");
|
||||
|
@ -371,7 +439,12 @@ export default function KvmIdRoute() {
|
|||
const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
const sd = btoa(JSON.stringify(pc.localDescription));
|
||||
sendWebRTCSignal("offer", { sd: sd });
|
||||
const isNewSignalingEnabled = isLegacySignalingEnabled.current === false;
|
||||
if (isNewSignalingEnabled) {
|
||||
sendWebRTCSignal("offer", { sd: sd });
|
||||
} else {
|
||||
console.log("Legacy signanling. Waiting for ICE Gathering to complete...");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[setupPeerConnection] Error creating offer: ${e}`,
|
||||
|
@ -389,6 +462,22 @@ export default function KvmIdRoute() {
|
|||
sendWebRTCSignal("new-ice-candidate", candidate);
|
||||
};
|
||||
|
||||
pc.onicegatheringstatechange = event => {
|
||||
const pc = event.currentTarget as RTCPeerConnection;
|
||||
if (pc.iceGatheringState === "complete") {
|
||||
console.log("ICE Gathering completed");
|
||||
setLoadingMessage("ICE Gathering completed");
|
||||
|
||||
if (isLegacySignalingEnabled.current) {
|
||||
// We can now start the https/ws connection to get the remote session description from the KVM device
|
||||
legacyHTTPSignaling(pc);
|
||||
}
|
||||
} else if (pc.iceGatheringState === "gathering") {
|
||||
console.log("ICE Gathering Started");
|
||||
setLoadingMessage("Gathering ICE candidates...");
|
||||
}
|
||||
};
|
||||
|
||||
pc.ontrack = function (event) {
|
||||
setMediaMediaStream(event.streams[0]);
|
||||
};
|
||||
|
@ -409,6 +498,7 @@ export default function KvmIdRoute() {
|
|||
}, [
|
||||
cleanupAndStopReconnecting,
|
||||
iceConfig?.iceServers,
|
||||
legacyHTTPSignaling,
|
||||
peerConnection?.signalingState,
|
||||
sendWebRTCSignal,
|
||||
setDiskChannel,
|
||||
|
@ -552,10 +642,6 @@ export default function KvmIdRoute() {
|
|||
});
|
||||
}, [rpcDataChannel?.readyState, send, setHdmiState]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
window.send = send;
|
||||
|
||||
// When the update is successful, we need to refresh the client javascript and show a success modal
|
||||
useEffect(() => {
|
||||
if (queryParams.get("updateSuccess")) {
|
||||
|
@ -654,8 +740,6 @@ export default function KvmIdRoute() {
|
|||
|
||||
if (isOtherSession) return null;
|
||||
if (peerConnectionState === "connected") return null;
|
||||
|
||||
console.log("isDisconnected", isDisconnected);
|
||||
if (isDisconnected) {
|
||||
return <PeerConnectionDisconnectedOverlay show={true} />;
|
||||
}
|
||||
|
|
6
web.go
6
web.go
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/coder/websocket/wsjson"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/webrtc/v4"
|
||||
|
@ -98,7 +99,7 @@ func setupRouter() *gin.Engine {
|
|||
protected := r.Group("/")
|
||||
protected.Use(protectedMiddleware())
|
||||
{
|
||||
protected.GET("/webrtc/signaling", handleLocalWebRTCSignal)
|
||||
protected.GET("/webrtc/signaling/client", handleLocalWebRTCSignal)
|
||||
protected.POST("/cloud/register", handleCloudRegister)
|
||||
protected.GET("/cloud/state", handleCloudState)
|
||||
protected.GET("/device", handleDevice)
|
||||
|
@ -143,6 +144,9 @@ func handleLocalWebRTCSignal(c *gin.Context) {
|
|||
|
||||
// Now use conn for websocket operations
|
||||
defer wsCon.Close(websocket.StatusNormalClosure, "")
|
||||
|
||||
wsjson.Write(context.Background(), wsCon, gin.H{"type": "device-metadata", "data": gin.H{"deviceVersion": builtAppVersion}})
|
||||
|
||||
err = handleWebRTCSignalWsMessages(wsCon, false, source)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
|
|
Loading…
Reference in New Issue