Used definitions from https://gist.github.com/ovcharik/35979d45af808c443e844fc1e6740b87
Created
December 2, 2021 10:31
-
-
Save ovcharik/890c81ef620b4736c155bc901acbec40 to your computer and use it in GitHub Desktop.
rxjs wrapper for @novnc/novnc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable @typescript-eslint/triple-slash-reference */ | |
/// <reference path="./types/input-keysym.d.ts" /> | |
/// <reference path="./types/input-utl.d.ts" /> | |
/// <reference path="./types/util-browser.d.ts" /> | |
/// <reference path="./types/rfb.d.ts" /> | |
import * as NovncBrowserUtils from '@novnc/novnc/core/util/browser'; | |
import * as NovncInputUtils from '@novnc/novnc/core/input/util'; | |
import NovncKeysym from '@novnc/novnc/core/input/keysym'; | |
import NovncClient from '@novnc/novnc/core/rfb'; | |
export { NovncClient, NovncBrowserUtils, NovncInputUtils, NovncKeysym }; | |
export { NovncCredentials, NovncOptions } from '@novnc/novnc/core/rfb'; | |
export { NovncEvents, NovncEventType, NovncEvent } from '@novnc/novnc/core/rfb'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { isNil } from 'lodash'; | |
import { from, of, Subject } from 'rxjs'; | |
import { bufferCount, bufferTime, concatAll, concatMap, delay, switchMap } from 'rxjs/operators'; | |
import { NovncKeyboardKey, NovncKeyboardLayout } from './novnc-keyboard'; | |
import { NovncKeysym, NovncClient } from './novnc-core'; | |
type SendKeyArgShort = [number]; | |
type SendKeyArgMiddle = [number, string | null]; | |
type SendKeyArgFull = [number, string | null, boolean]; | |
type SendKeyArgsAll = SendKeyArgShort | SendKeyArgMiddle | SendKeyArgFull; | |
type ShortcutKey = keyof typeof SHORTCUT_TO_KEYSYM; | |
/** | |
* For some reason QEMU's VNC implementation can't | |
* properly process symbols like «!» and we need to | |
* emulate shift presses. Partly related link: | |
* lists.gnu.org/archive/html/qemu-devel/2012-03/msg01109.html | |
*/ | |
// prettier-ignore | |
const SHIFTED_KEYSYMS = [ | |
// Shifted 0-9: ) ! @ # $ % ^ & * ( | |
0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, | |
// Other shifted keys: + < _ > ? ~ { | } " | |
0x3a, 0x2b, 0x3c, 0x5f, 0x3e, 0x3f, 0x7e, 0x7b, 0x7c, 0x7d, 0x22, | |
]; | |
// https://github.com/novnc/noVNC/blob/master/core/input/keysym.js | |
const SHORTCUT_TO_KEYSYM = { | |
ctrl: NovncKeysym.XK_Control_L, | |
shift: NovncKeysym.XK_Shift_L, | |
alt: NovncKeysym.XK_Alt_L, | |
delete: NovncKeysym.XK_Delete, | |
sysrq: NovncKeysym.XK_Sys_Req, | |
windows: NovncKeysym.XK_Super_L, | |
tab: NovncKeysym.XK_Tab, | |
f1: NovncKeysym.XK_F1, | |
f24: NovncKeysym.XK_F24, | |
enter: NovncKeysym.XK_Return, | |
}; | |
export class NovncInputSubject extends Subject<SendKeyArgFull> { | |
protected connectedClients: NovncClient[] = []; | |
protected shiftKey = new NovncKeyboardKey('Shift', 'ShiftLeft', 'modifier'); | |
protected ctrlKey = new NovncKeyboardKey('Control', 'ControlLeft', 'modifier'); | |
protected altKey = new NovncKeyboardKey('Alt', 'AltLeft', 'modifier'); | |
protected metaKey = new NovncKeyboardKey('Meta', 'MetaLeft', 'modifier'); | |
constructor(protected options: { wrapText?: boolean } = {}) { | |
super(); | |
} | |
next(key?: SendKeyArgsAll): void { | |
if (!key) return; | |
if (key.length === 1) { | |
super.next([...key, null, true]); | |
super.next([...key, null, false]); | |
return; | |
} | |
if (key.length === 2) { | |
super.next([...key, true]); | |
super.next([...key, false]); | |
return; | |
} | |
super.next(key); | |
return; | |
} | |
nextShiftKey(down?: boolean) { | |
const { keysym, code } = this.shiftKey; | |
if (isNil(down) || down === true) this.next([keysym, code, true]); | |
if (isNil(down) || down === false) this.next([keysym, code, false]); | |
} | |
nextKeyCode(keyCode: number) { | |
const shiftPressed = SHIFTED_KEYSYMS.includes(keyCode); | |
if (shiftPressed) this.nextShiftKey(true); | |
switch (keyCode) { | |
case 0x0a: | |
this.next([NovncKeysym.XK_Return]); | |
break; | |
default: | |
this.next([keyCode]); | |
} | |
if (shiftPressed) this.nextShiftKey(false); | |
} | |
nextKeyboardKey(keyboardKey: NovncKeyboardKey) { | |
const { keysym, code, shiftKey } = keyboardKey; | |
if (shiftKey) this.nextShiftKey(true); | |
this.next([keysym, code, true]); | |
this.next([keysym, code, false]); | |
if (shiftKey) this.nextShiftKey(false); | |
} | |
nextChar(char: string) { | |
const keyboardKey = NovncKeyboardLayout.get(char); | |
if (keyboardKey) return this.nextKeyboardKey(keyboardKey); | |
return this.nextKeyCode(char.charCodeAt(0)); | |
} | |
nextText(text: string = '') { | |
// if (this.options.wrapText) | |
// this.nextCharCodes(STATE_KEYSYMS); | |
// this.nextModifierKey(this.ctrlKey, false); | |
// this.nextModifierKey(this.metaKey, false); | |
const chars = text.split(''); | |
for (const char of chars) this.nextChar(char); | |
} | |
nextShortcut(shortcut: ShortcutKey[] = []) { | |
const reverse = shortcut.slice(0).reverse(); | |
for (const key of shortcut) this.next([this.shortcutToKeysym(key), null, true]); | |
for (const key of reverse) this.next([this.shortcutToKeysym(key), null, false]); | |
} | |
nextShortcutState(shortcut: ShortcutKey[] = [], pressed: boolean) { | |
for (const key of shortcut) this.next([this.shortcutToKeysym(key), null, pressed]); | |
} | |
private shortcutToKeysym(key: ShortcutKey) { | |
return SHORTCUT_TO_KEYSYM[key]; | |
} | |
/** | |
* We need to split sending keys into ~100 symbols | |
* chunks because QEMU's VNC implementation lose keys | |
* if we sending them too fast (OPENSTACK-930). | |
* @param input | |
* @returns | |
*/ | |
asInputObservable(chunkSize = 20, chunkTimeout = 8) { | |
return this.asObservable().pipe( | |
bufferTime(chunkTimeout), | |
switchMap((line) => from(line).pipe(bufferCount(chunkSize))), | |
concatMap((chunk) => of(chunk).pipe(delay(chunkTimeout))), | |
concatAll() | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { chain } from 'lodash'; | |
import { NovncInputUtils } from './novnc-core'; | |
/** | |
* Базовая раскладка получена следующим образом: | |
* `navigator.keyboard.getLayoutMap().then(x => Array.from(x.entries()))` | |
* | |
* https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API | |
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardLayoutMap | |
*/ | |
const normalCodeKeyPairs = [ | |
['KeyE', 'e'], | |
['KeyD', 'd'], | |
['Minus', '-'], | |
['KeyH', 'h'], | |
['KeyZ', 'z'], | |
['Equal', '='], | |
['KeyN', 'n'], | |
['KeyP', 'p'], | |
['BracketRight', ']'], | |
['BracketLeft', '['], | |
['Digit8', '8'], | |
['Digit9', '9'], | |
['KeyS', 's'], | |
['Semicolon', ';'], | |
['Digit5', '5'], | |
['KeyQ', 'q'], | |
['KeyO', 'o'], | |
['Period', '.'], | |
['Digit6', '6'], | |
['KeyV', 'v'], | |
['Digit3', '3'], | |
['KeyL', 'l'], | |
['Backquote', '`'], | |
['KeyG', 'g'], | |
['KeyJ', 'j'], | |
['KeyT', 't'], | |
['Quote', "'"], | |
['KeyY', 'y'], | |
['IntlBackslash', '\\'], | |
['KeyR', 'r'], | |
['Backslash', '\\'], | |
['KeyU', 'u'], | |
['KeyK', 'k'], | |
['Slash', '/'], | |
['KeyF', 'f'], | |
['KeyI', 'i'], | |
['KeyX', 'x'], | |
['KeyA', 'a'], | |
['Digit2', '2'], | |
['Digit7', '7'], | |
['KeyM', 'm'], | |
['Digit4', '4'], | |
['KeyW', 'w'], | |
['Digit1', '1'], | |
['Digit0', '0'], | |
['KeyB', 'b'], | |
['KeyC', 'c'], | |
['Comma', ','], | |
]; | |
/** | |
* Символы с раскладками из библиотеки [`Mottie/Keyboard`](https://github.com/Mottie/Keyboard) | |
* https://github.com/Mottie/Keyboard/blob/master/layouts/_layout_template.js | |
* https://github.com/Mottie/Keyboard/blob/master/layouts/russian.js | |
*/ | |
type KeyboardLayoutType = 'normal' | 'shift' | 'alt' | 'alt-shift' | 'modifier'; | |
type KeyboardLayoutMap = Partial<Record<KeyboardLayoutType, string[]>>; | |
const normalKeyLayout = [ | |
'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}', | |
'{tab} q w e r t y u i o p [ ] \\', | |
"a s d f g h j k l ; ' {enter}", | |
'{shift} z x c v b n m , . / {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
]; | |
const englishLayout: KeyboardLayoutMap = { | |
normal: [ | |
'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}', | |
'{tab} q w e r t y u i o p [ ] \\', | |
"a s d f g h j k l ; ' {enter}", | |
'{shift} z x c v b n m , . / {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
], | |
shift: [ | |
'~ ! @ # $ % ^ & * ( ) _ + {bksp}', | |
'{tab} Q W E R T Y U I O P { } |', | |
'A S D F G H J K L : " {enter}', | |
'{shift} Z X C V B N M < > ? {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
], | |
alt: [ | |
'~ \u00a1 \u00b2 \u00b3 \u00a4 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00d7 {bksp}', | |
'{tab} \u00e4 \u00e5 \u00e9 \u00ae \u00fe \u00fc \u00fa \u00ed \u00f3 \u00f6 \u00ab \u00bb \u00ac', | |
'\u00e1 \u00df \u00f0 f g h j k \u00f8 \u00b6 \u00b4 {enter}', | |
'{shift} \u00e6 x \u00a9 v b \u00f1 \u00b5 \u00e7 > \u00bf {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
], | |
'alt-shift': [ | |
'~ \u00b9 \u00b2 \u00b3 \u00a3 \u20ac \u00bc \u00bd \u00be \u2018 \u2019 \u00a5 \u00f7 {bksp}', | |
'{tab} \u00c4 \u00c5 \u00c9 \u00ae \u00de \u00dc \u00da \u00cd \u00d3 \u00d6 \u00ab \u00bb \u00a6', | |
'\u00c4 \u00a7 \u00d0 F G H J K \u00d8 \u00b0 \u00a8 {enter}', | |
'{shift} \u00c6 X \u00a2 V B \u00d1 \u00b5 \u00c7 . \u00bf {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
], | |
}; | |
const russianLayout: KeyboardLayoutMap = { | |
normal: [ | |
'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}', | |
'{tab} q w e r t y u i o p [ ] \\', | |
"a s d f g h j k l ; ' {enter}", | |
'{shift} z x c v b n m , . / {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
], | |
shift: [ | |
'~ ! @ # $ % ^ & * ( ) _ + {bksp}', | |
'{tab} Q W E R T Y U I O P { } |', | |
'A S D F G H J K L : " {enter}', | |
'{shift} Z X C V B N M < > ? {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
], | |
alt: [ | |
'\u0451 1 2 3 4 5 6 7 8 9 0 - = {bksp}', | |
'{tab} \u0439 \u0446 \u0443 \u043a \u0435 \u043d \u0433 \u0448 \u0449 \u0437 \u0445 \u044a \\', | |
'\u0444 \u044b \u0432 \u0430 \u043f \u0440 \u043e \u043b \u0434 \u0436 \u044d {enter}', | |
'{shift} \u044f \u0447 \u0441 \u043c \u0438 \u0442 \u044c \u0431 \u044e . {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
], | |
'alt-shift': [ | |
'\u0401 ! " \u2116 ; \u20ac : ? * ( ) _ + {bksp}', | |
'{tab} \u0419 \u0426 \u0423 \u041a \u0415 \u041d \u0413 \u0428 \u0429 \u0417 \u0425 \u042a /', | |
'\u0424 \u042b \u0412 \u0410 \u041f \u0420 \u041e \u041b \u0414 \u0416 \u042d {enter}', | |
'{shift} \u042f \u0427 \u0421 \u041c \u0418 \u0422 \u042c \u0411 \u042e , {shift}', | |
'{accept} {alt} {space} {alt} {cancel}', | |
], | |
}; | |
const getLayoutPairs = (layout: string[]) => { | |
return layout | |
.join(' ') | |
.split(' ') | |
.map((key, index) => [key, index]); | |
}; | |
const getKeyToIndexMap = (layout: string[]) => { | |
const keyToIndexPairs = getLayoutPairs(layout); | |
return Object.fromEntries(keyToIndexPairs); | |
}; | |
const getIndexToKeyMap = (layout: string[]) => { | |
const indexToKeyPairs = getLayoutPairs(layout).map(([key, index]) => [index, key]); | |
return Object.fromEntries(indexToKeyPairs); | |
}; | |
const parseKeyboardLayouts = (lang: string, layouts: KeyboardLayoutMap) => { | |
return Object.entries(layouts).map(([type, layout]) => { | |
const indexToKeyMap = getIndexToKeyMap(layout); | |
return { lang, type: type as KeyboardLayoutType, indexToKeyMap }; | |
}); | |
}; | |
const normalKeyToIndexMap = getKeyToIndexMap(normalKeyLayout); | |
const parsedLayouts = [ | |
...parseKeyboardLayouts('en', englishLayout), | |
...parseKeyboardLayouts('ru', russianLayout), | |
]; | |
type KeyboardEventType = 'keydown' | 'keyup'; | |
export class NovncKeyboardKey { | |
protected static readonly modifiersMap = { | |
shift: { key: 'Shift', code: 'ShiftLeft', location: 1, keyCode: 16, which: 16 }, | |
ctrl: { key: 'Control', code: 'ControlLeft', location: 0, keyCode: 17, which: 17 }, | |
alt: { key: 'Alt', code: 'AltLeft', location: 1, keyCode: 18, which: 18 }, | |
meta: { key: 'Meta', code: 'MetaLeft', location: 1, keyCode: 91, which: 91 }, | |
} as const; | |
public readonly altKey = ['alt', 'alt-shift'].includes(this.type); | |
public readonly shiftKey = ['alt-shift', 'shift'].includes(this.type); | |
public readonly ctrlKey = false; | |
public readonly metaKey = false; | |
public readonly weight = { normal: 0, shift: 1, alt: 2, 'alt-shift': 3, modifier: 4 }[this.type]; | |
protected readonly eventOptions = { | |
key: this.key, | |
code: this.code, | |
location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD, | |
altKey: this.altKey, | |
shiftKey: this.shiftKey, | |
ctrlKey: this.ctrlKey, | |
}; | |
public readonly keysym = NovncInputUtils.getKeysym(this.eventOptions); | |
constructor( | |
public readonly key: string, | |
public readonly code: string, | |
public readonly type: KeyboardLayoutType, | |
public readonly options?: Record<string, unknown> | |
) {} | |
protected getModifierEvent(key: 'shift' | 'ctrl' | 'alt' | 'meta', type: KeyboardEventType) { | |
const base = NovncKeyboardKey.modifiersMap[key]; | |
const altKey = key === 'alt' ? type === 'keydown' : this.altKey; | |
const ctrlKey = key === 'ctrl' ? type === 'keydown' : false; | |
const shiftKey = key === 'shift' ? type === 'keydown' : false; | |
const metaKey = key === 'meta' ? type === 'keydown' : false; | |
return new KeyboardEvent(type, { ...base, altKey, ctrlKey, shiftKey, metaKey }); | |
} | |
public getShiftEvent(type: KeyboardEventType) { | |
return this.getModifierEvent('shift', type); | |
} | |
public getCtrlEvent(type: KeyboardEventType) { | |
return this.getModifierEvent('ctrl', type); | |
} | |
public getAltEvent(type: KeyboardEventType) { | |
return this.getModifierEvent('alt', type); | |
} | |
public getMetaEvent(type: KeyboardEventType) { | |
return this.getModifierEvent('meta', type); | |
} | |
public getKeyEvent(type: KeyboardEventType) { | |
return new KeyboardEvent(type, this.eventOptions); | |
} | |
public getEventSequence() { | |
const events: KeyboardEvent[] = []; | |
if (this.altKey) events.push(this.getAltEvent('keydown')); | |
if (this.shiftKey) events.push(this.getShiftEvent('keydown')); | |
events.push(this.getKeyEvent('keydown')); | |
events.push(this.getKeyEvent('keyup')); | |
if (this.shiftKey) events.push(this.getShiftEvent('keyup')); | |
if (this.altKey) events.push(this.getAltEvent('keyup')); | |
return events; | |
} | |
} | |
const keyboardLayoutPairs = chain(normalCodeKeyPairs) | |
.flatMap(([code, defaultKey]) => { | |
const index = normalKeyToIndexMap[defaultKey]; | |
return parsedLayouts.map(({ lang, type, indexToKeyMap }) => { | |
const key = indexToKeyMap[index]; | |
const options = { lang, defaultKey, index }; | |
const keyboardKey = new NovncKeyboardKey(key, code, type, options); | |
return keyboardKey; | |
}); | |
}) | |
.orderBy((key) => key.weight) | |
.uniqBy((key) => key.key) | |
.map((key) => [key.key, key] as [string, NovncKeyboardKey]) | |
.value(); | |
export const NovncKeyboardLayout = new Map(keyboardLayoutPairs); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { from, fromEvent, of, pipe } from 'rxjs'; | |
import { Observable, Subscriber } from 'rxjs'; | |
import { map, mergeMap, share, startWith, switchMap } from 'rxjs/operators'; | |
import { takeUntil, filter } from 'rxjs/operators'; | |
import { NovncClient, NovncOptions, NovncEventType, NovncBrowserUtils } from './novnc-core'; | |
/** | |
* Создать NoVNC клиент. | |
* @param element | |
* @param url | |
* @param options | |
* @returns | |
*/ | |
export const createNovncClient = (element: Element, url?: string, options?: NovncOptions) => { | |
return new Observable<NovncClient>((subscriber) => { | |
if (!url) throw new Error('Url is undefined'); | |
const state = { disconnected: false }; | |
const complete = () => { | |
state.disconnected = true; | |
subscriber.complete(); | |
}; | |
const unsubscribe = () => { | |
if (!state.disconnected) { | |
state.disconnected = true; | |
try { client.disconnect(); } catch (e) { /* */ } // prettier-ignore | |
} | |
}; | |
const client = new NovncClient(element, url, options); | |
subscriber.next(client); | |
subscriber.add(fromEvent(client, 'disconnect').subscribe(complete)); | |
subscriber.add({ unsubscribe }); | |
return subscriber; | |
}); | |
}; | |
/** | |
* Подписаться на события NoVNC клиента. | |
* @param client | |
* @returns | |
*/ | |
export const listenNovncEvents = () => { | |
const eventTypeNames: NovncEventType[] = [ | |
'bell', | |
'capabilities', | |
'clipboard', | |
'connect', | |
'credentialsrequired', | |
'desktopname', | |
'disconnect', | |
'securityfailure', | |
]; | |
return switchMap((client: NovncClient) => { | |
return from(eventTypeNames).pipe( | |
mergeMap((type) => fromEvent(client, type)), | |
takeUntil(fromEvent(client, 'disconnected')), | |
share() | |
); | |
}); | |
}; | |
export const listenNovncStatus = () => { | |
type Status = 'loading' | 'initializing' | 'connecting' | 'connected' | 'disconnected'; | |
const getStatus = switchMap( | |
(client: NovncClient): Observable<Status> => { | |
return of(client).pipe( | |
listenNovncEvents(), | |
map((event): Status | undefined => { | |
if (event.type === 'desktopname') return 'connecting'; | |
if (event.type === 'connect') return 'connected'; | |
if (event.type === 'disconnect') return 'disconnected'; | |
return undefined; | |
}), | |
filter(<T>(status: T): status is NonNullable<T> => !!status), | |
startWith('initializing' as Status), | |
takeUntil(fromEvent(client, 'disconnected')), | |
share() | |
); | |
} | |
); | |
return pipe(getStatus, startWith('loading' as Status)); | |
}; | |
/** | |
* Подписаться на событие вставки из буфера обмена. | |
* @param client | |
* @param target | |
* @returns | |
*/ | |
export const listenNovncClipboardPaste = () => { | |
const listenDomEvents = (client: NovncClient, subscriber: Subscriber<string>) => { | |
const body = client._target?.closest('body'); | |
const canvas = client._target?.querySelector('canvas'); | |
if (!canvas || !body) return false; | |
let wasForceBlurred: KeyboardEvent | null = null; | |
const onDocumentPaste = (event: ClipboardEvent) => { | |
if (!wasForceBlurred) return; | |
if (!event.clipboardData) return; | |
canvas.dispatchEvent(new KeyboardEvent('keyup', wasForceBlurred)); | |
subscriber.next(event.clipboardData?.getData('Text')); | |
}; | |
const onDocumentKey = (event: KeyboardEvent) => { | |
if (!wasForceBlurred) return; | |
if (event.code === 'KeyV') return; | |
canvas.dispatchEvent(new KeyboardEvent(event.type, event)); | |
client.focus(); | |
}; | |
const onCanvasKeydown = (event: KeyboardEvent) => { | |
const isMac = NovncBrowserUtils.isMac() || NovncBrowserUtils.isIOS(); | |
if (isMac && !event.metaKey) return; | |
if (!isMac && !event.ctrlKey) return; | |
client.blur(); | |
wasForceBlurred = event; | |
}; | |
const onCanvasFocus = () => { | |
wasForceBlurred = null; | |
}; | |
subscriber.add(fromEvent<ClipboardEvent>(body, 'paste').subscribe(onDocumentPaste)); | |
subscriber.add(fromEvent<KeyboardEvent>(body, 'keydown').subscribe(onDocumentKey)); | |
subscriber.add(fromEvent<KeyboardEvent>(body, 'keyup').subscribe(onDocumentKey)); | |
subscriber.add(fromEvent<KeyboardEvent>(canvas, 'keydown').subscribe(onCanvasKeydown)); | |
subscriber.add(fromEvent<FocusEvent>(canvas, 'focus').subscribe(onCanvasFocus)); | |
return true; | |
}; | |
const listenClient = (client: NovncClient) => | |
new Observable<string>((subscriber) => { | |
const start$ = fromEvent(client, 'connect'); | |
const finish$ = fromEvent(client, 'disconnect'); | |
if (!listenDomEvents(client, subscriber)) { | |
subscriber.add(start$.subscribe(() => listenDomEvents(client, subscriber))); | |
} | |
subscriber.add(finish$.subscribe(() => () => subscriber.complete())); | |
return subscriber; | |
}); | |
return switchMap((client: NovncClient) => listenClient(client).pipe(share())); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment