From 2cdfb01d474d2d6553d698b8adfea1e70a2eb7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric-S=C3=A9bastien=20Lachance?= Date: Sat, 22 Nov 2025 05:12:34 -0500 Subject: [PATCH] feat(vncscreen.tsx): supports a websocket instance as prop which may be pre-authenticated Currently, `react-vnc` requires a WebSocket URL and handles the connection internally. However, many VNC servers require custom authentication flows (cookie-based auth, token-based auth, MessagePack handshakes, etc.) that must be completed before the VNC protocol can begin. --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++-- src/lib/VncScreen.tsx | 13 +++++++-- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ffaa8e0..c9862e4 100644 --- a/README.md +++ b/README.md @@ -131,13 +131,76 @@ function App() { export default App; ``` -The only `required` parameter is `url`, which must be a `ws://` or a `wss://` (websocket) URL for the library to function properly. noVNC can display only websocket URLs. All other props to `VncScreen` are optional. The following is a list (an interface) of all props along with their types. +### Using Pre-Authenticated WebSocket + +If you need to handle authentication or perform a custom handshake before establishing the VNC connection, you can pass a pre-authenticated WebSocket instance instead of a URL: + +```ts +import React, { useEffect, useState } from 'react'; +import { VncScreen } from 'react-vnc'; + +function App() { + const [websocket, setWebsocket] = useState(null); + + useEffect(() => { + // Create WebSocket and handle authentication + const ws = new WebSocket('ws://your-vnc-server.com'); + + ws.addEventListener('open', () => { + // Perform custom authentication or handshake + ws.send(JSON.stringify({ token: 'your-auth-token' })); + }); + + ws.addEventListener('message', (event) => { + const response = JSON.parse(event.data); + if (response.authenticated) { + // Once authenticated, pass the WebSocket to VncScreen + setWebsocket(ws); + } + }); + + return () => { + ws.close(); + }; + }, []); + + if (!websocket) { + return
Authenticating...
; + } + + return ( + + ); +} + +export default App; +``` + +This approach is particularly useful for: +- Cookie-based authentication +- Custom authentication protocols +- Connection pooling or reuse +- Advanced WebSocket configuration + +Either `url` or `websocket` is required: +- **`url`**: A `ws://` or `wss://` websocket URL to connect to the VNC server +- **`websocket`**: A pre-authenticated WebSocket instance (useful for custom authentication flows) + +All other props to `VncScreen` are optional. The following is a list (an interface) of all props along with their types. ```ts type EventListeners = { [T in NoVncEventType]?: (event: NoVncEvents[T]) => void }; interface Props { - url: string; + url?: string; + websocket?: WebSocket; style?: object; className?: string; viewOnly?: boolean; diff --git a/src/lib/VncScreen.tsx b/src/lib/VncScreen.tsx index 3e84ff9..59222bd 100644 --- a/src/lib/VncScreen.tsx +++ b/src/lib/VncScreen.tsx @@ -10,7 +10,8 @@ import RFB, { NoVncEventType, NoVncEvents, NoVncOptions } from '@novnc/novnc/lib type EventListeners = { [T in NoVncEventType]?: (event: NoVncEvents[T]) => void }; export interface Props { - url: string; + url?: string; + websocket?: WebSocket; style?: object; className?: string; viewOnly?: boolean; @@ -66,6 +67,7 @@ const VncScreen: React.ForwardRefRenderFunction = (props const { url, + websocket, style, className, viewOnly, @@ -134,7 +136,7 @@ const VncScreen: React.ForwardRefRenderFunction = (props } const connected = getConnected(); - if (connected) { + if (connected && !websocket) { logger.info(`Unexpectedly disconnected from remote VNC, retrying in ${retryDuration / 1000} seconds.`); timeouts.current.push(setTimeout(connect, retryDuration)); @@ -205,9 +207,14 @@ const VncScreen: React.ForwardRefRenderFunction = (props return; } + if (!url && !websocket) { + logger.error('Either url or websocket must be provided'); + return; + } + screen.current.innerHTML = ''; - const _rfb = new RFB(screen.current, url, rfbOptions); + const _rfb = new RFB(screen.current, websocket || url!, rfbOptions); _rfb.viewOnly = viewOnly ?? false; _rfb.focusOnClick = focusOnClick ?? false;