Skip to content

Commit

Permalink
feat(imperativeHandles): implemented noVNC RFB methods (#28)
Browse files Browse the repository at this point in the history
* feat(rfbOptions): added rfbOptions in props

* feat(imperativehandles): implemented sendCredentials,
        sendKey,
        sendCtrlAltDel,
        focus,
        blur,
        machineShutDown,
        machineReboot,
        machineReset,
        clipboardPaste

* refactor: machineShutDown -> machineShutdown

* feat(eventListeners): added and exposed eventListeners object

* docs(README): update eventListeners

* chore(lint): spacing and line-endings

* chore(lint): spacing and line-endings
  • Loading branch information
roerohan authored Feb 27, 2022
1 parent 464b1c6 commit 1c8c234
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 13 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ The only `required` parameter is `url`, which must be a `ws://` or a `wss://` (w
interface Props {
url: string;
style?: object;
className?: string;
viewOnly?: boolean;
rfbOptions?: Partial<RFBOptions>;
focusOnClick?: boolean;
clipViewport?: boolean;
dragViewport?: boolean;
Expand All @@ -152,7 +154,23 @@ interface Props {
onConnect?: (rfb?: RFB) => void;
onDisconnect?: (rfb?: RFB) => void;
onCredentialsRequired?: (rfb?: RFB) => void;
onSecurityFailure?: (e?: { detail: { status: number, reason: string } }) => void;
onClipboard?: (e?: { detail: { text: string } }) => void;
onBell?: () => void;
onDesktopName?: (e?: { detail: { name: string } }) => void;
onCapabilities?: (e?: { detail: { capabilities: RFB["capabilities"] } }) => void;
}

// The rfbOptions object in Props is of type Partial<RFBOptions>
interface RFBOptions {
shared: boolean;
credentials: {
username?: string;
password?: string;
target?: string;
};
repeaterID: string;
wsProtocols: string;
}
```

Expand Down
135 changes: 122 additions & 13 deletions src/lib/VncScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ import React, {
} from 'react';
import RFB from '../noVNC/core/rfb';

export interface RFBOptions {
shared: boolean;
credentials: {
username?: string;
password?: string;
target?: string;
};
repeaterID: string;
wsProtocols: string;
}

export interface Props {
url: string;
style?: object;
className?: string;
viewOnly?: boolean;
rfbOptions?: Partial<RFBOptions>;
focusOnClick?: boolean;
clipViewport?: boolean;
dragViewport?: boolean;
Expand All @@ -28,20 +40,48 @@ export interface Props {
onConnect?: (rfb?: RFB) => void;
onDisconnect?: (rfb?: RFB) => void;
onCredentialsRequired?: (rfb?: RFB) => void;
onSecurityFailure?: (e?: { detail: { status: number, reason: string } }) => void;
onClipboard?: (e?: { detail: { text: string } }) => void;
onBell?: () => void;
onDesktopName?: (e?: { detail: { name: string } }) => void;
onCapabilities?: (e?: { detail: { capabilities: RFB["capabilities"] } }) => void;
}

export enum Events {
connect,
disconnect,
credentialsrequired,
securityfailure,
clipboard,
bell,
desktopname,
capabilities,
}

export type EventListeners = { -readonly [key in keyof typeof Events]?: (e?: any) => void };

export type VncScreenHandle = {
connect: () => void;
disconnect: () => void;
connected: boolean;
sendCredentials: (credentials: RFBOptions["credentials"]) => void;
sendKey: (keysym: number, code: string, down?: boolean) => void;
sendCtrlAltDel: () => void;
focus: () => void;
blur: () => void;
machineShutdown: () => void;
machineReboot: () => void;
machineReset: () => void;
clipboardPaste: (text: string) => void;
rfb: RFB | null;
eventListeners: EventListeners;
};

const VncScreen: React.ForwardRefRenderFunction<VncScreenHandle, Props> = (props, ref) => {
const rfb = useRef<RFB | null>(null);
const connected = useRef<boolean>(props.autoConnect ?? true);
const timeouts = useRef<Array<NodeJS.Timeout>>([]);
const eventListeners = useRef<EventListeners>({});
const screen = useRef<HTMLDivElement>(null);
const [loading, setLoading] = useState<boolean>(true);

Expand All @@ -50,6 +90,7 @@ const VncScreen: React.ForwardRefRenderFunction<VncScreenHandle, Props> = (props
style,
className,
viewOnly,
rfbOptions,
focusOnClick,
clipViewport,
dragViewport,
Expand All @@ -66,7 +107,11 @@ const VncScreen: React.ForwardRefRenderFunction<VncScreenHandle, Props> = (props
onConnect,
onDisconnect,
onCredentialsRequired,
onSecurityFailure,
onClipboard,
onBell,
onDesktopName,
onCapabilities,
} = props;

const logger = {
Expand Down Expand Up @@ -129,7 +174,7 @@ const VncScreen: React.ForwardRefRenderFunction<VncScreenHandle, Props> = (props
return;
}

const password = prompt("Password Required:");
const password = rfbOptions?.credentials?.password ?? prompt("Password Required:");
rfb?.sendCredentials({ password: password });
};

Expand All @@ -150,10 +195,12 @@ const VncScreen: React.ForwardRefRenderFunction<VncScreenHandle, Props> = (props
}

timeouts.current.forEach(clearTimeout);
rfb.removeEventListener('connected', _onConnect);
rfb.removeEventListener('disconnect', _onDisconnect);
rfb.removeEventListener('credentialsrequired', _onCredentialsRequired);
rfb.removeEventListener('desktopname', _onDesktopName);
(Object.keys(eventListeners.current) as (keyof typeof Events)[]).forEach((event) => {
if (eventListeners.current[event]) {
rfb.removeEventListener(event, eventListeners.current[event]);
eventListeners.current[event] = undefined;
}
});
rfb.disconnect();
setRfb(null);
setConnected(false);
Expand Down Expand Up @@ -181,7 +228,7 @@ const VncScreen: React.ForwardRefRenderFunction<VncScreenHandle, Props> = (props

screen.current.innerHTML = '';

const _rfb = new RFB(screen.current, url);
const _rfb = new RFB(screen.current, url, rfbOptions);

_rfb.viewOnly = viewOnly ?? false;
_rfb.focusOnClick = focusOnClick ?? false;
Expand All @@ -195,25 +242,87 @@ const VncScreen: React.ForwardRefRenderFunction<VncScreenHandle, Props> = (props
_rfb.compressionLevel = compressionLevel ?? 2;
setRfb(_rfb);

_rfb.addEventListener('connect', _onConnect);

_rfb.addEventListener('disconnect', _onDisconnect);

_rfb.addEventListener('credentialsrequired', _onCredentialsRequired);

_rfb.addEventListener('desktopname', _onDesktopName);
eventListeners.current.connect = _onConnect;
eventListeners.current.disconnect = _onDisconnect;
eventListeners.current.credentialsrequired = _onCredentialsRequired;
eventListeners.current.securityfailure = onSecurityFailure;
eventListeners.current.clipboard = onClipboard;
eventListeners.current.bell = onBell;
eventListeners.current.desktopname = _onDesktopName;
eventListeners.current.capabilities = onCapabilities;

(Object.keys(eventListeners.current) as (keyof typeof Events)[]).forEach((event) => {
if (eventListeners.current[event]) {
_rfb.addEventListener(event, eventListeners.current[event]);
}
});

setConnected(true);
} catch (err) {
logger.error(err);
}
};

const sendCredentials = (credentials: RFBOptions["credentials"]) => {
const rfb = getRfb();
rfb?.sendCredentials(credentials);
};

const sendKey = (keysym: number, code: string, down?: boolean) => {
const rfb = getRfb();
rfb?.sendKey(keysym, code, down);
};

const sendCtrlAltDel = () => {
const rfb = getRfb();
rfb?.sendCtrlAltDel();
};

const focus = () => {
const rfb = getRfb();
rfb?.focus();
};

const blur = () => {
const rfb = getRfb();
rfb?.blur();
};

const machineShutdown = () => {
const rfb = getRfb();
rfb?.machineShutdown();
};

const machineReboot = () => {
const rfb = getRfb();
rfb?.machineReboot();
};

const machineReset = () => {
const rfb = getRfb();
rfb?.machineReset();
};

const clipboardPaste = (text: string) => {
const rfb = getRfb();
rfb?.clipboardPasteFrom(text);
};

useImperativeHandle(ref, () => ({
connect,
disconnect,
connected: connected.current,
sendCredentials,
sendKey,
sendCtrlAltDel,
focus,
blur,
machineShutdown,
machineReboot,
machineReset,
clipboardPaste,
rfb: rfb.current,
eventListeners: eventListeners.current,
}));

useEffect(() => {
Expand Down

0 comments on commit 1c8c234

Please sign in to comment.