import Mouse from './Mouse.js' import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js' import { controlConfirmDefault, } from './ConfirmWindow/defaults.js' import type { Options as AssistOptions, } from './Assist.js' export enum RCStatus { Disabled, Requesting, Enabled, } let setInputValue = function(this: HTMLInputElement | HTMLTextAreaElement, value: string) { this.value = value } const nativeInputValueDescriptor = typeof window !== 'undefined' && Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value') if (nativeInputValueDescriptor && nativeInputValueDescriptor.set) { setInputValue = nativeInputValueDescriptor.set } export default class RemoteControl { private mouse: Mouse | null = null public status: RCStatus = RCStatus.Disabled private agentID: string | null = null constructor( private readonly options: AssistOptions, private readonly onGrand: (id: string) => string | undefined, private readonly onRelease: (id?: string | null, isDenied?: boolean) => void, private readonly onBusy: (id?: string) => void) {} reconnect(ids: string[]) { const storedID = sessionStorage.getItem(this.options.session_control_peer_key) if (storedID !== null && ids.indexOf(storedID) !== -1) { this.grantControl(storedID) } else { sessionStorage.removeItem(this.options.session_control_peer_key) } } private confirm: ConfirmWindow | null = null requestControl = (id: string) => { if (this.status === RCStatus.Enabled) { return this.onBusy(id) } if (this.agentID !== null) { this.releaseControl() return } setTimeout(() => { if (this.status === RCStatus.Requesting) { this.releaseControl() } }, 30000) this.agentID = id this.status = RCStatus.Requesting this.confirm = new ConfirmWindow(controlConfirmDefault(this.options.controlConfirm)) this.confirm.mount().then(allowed => { if (allowed) { this.grantControl(id) } else { this.confirm?.remove() this.releaseControl(true) } }) .then(() => { this.confirm?.remove() }) .catch(e => { this.confirm?.remove() console.error(e) }) } releaseControl = (isDenied?: boolean, keepId?: boolean) => { if (this.confirm) { this.confirm.remove() this.confirm = null } this.resetMouse() this.status = RCStatus.Disabled if (!keepId) { sessionStorage.removeItem(this.options.session_control_peer_key) } this.onRelease(this.agentID, isDenied) this.agentID = null } grantControl = (id: string) => { this.agentID = id this.status = RCStatus.Enabled sessionStorage.setItem(this.options.session_control_peer_key, id) const agentName = this.onGrand(id) if (this.mouse) { this.resetMouse() } this.mouse = new Mouse(agentName, this.options.onDragCamera) this.mouse.mount() document.addEventListener('visibilitychange', () => { if (document.hidden) this.releaseControl(false, true) else { if (this.status === RCStatus.Disabled) { this.reconnect([id,]) } } }) } resetMouse = () => { this.mouse?.remove() this.mouse = null } scroll = (id, d) => { id === this.agentID && this.mouse?.scroll(d) } move = (id, xy) => { return id === this.agentID && this.mouse?.move(xy) } private focused: HTMLElement | SVGElement | null = null click = (id, xy) => { if (id !== this.agentID || !this.mouse) { return } this.focused = this.mouse.click(xy) } focus = (id, el: HTMLElement) => { this.focused = el } startDrag = (id, xy) => { this.mouse?.startDrag(xy) } drag = (id, xydxdy) => { this.mouse?.drag(xydxdy); } stopDrag = (id) => { this.mouse?.stopDrag(); } input = (id, value: string) => { if (id !== this.agentID || !this.mouse || !this.focused) { return } if (this.focused instanceof HTMLTextAreaElement || this.focused instanceof HTMLInputElement || this.focused.tagName === 'INPUT' || this.focused.tagName === 'TEXTAREA') { setInputValue.call(this.focused, value) const ev = new Event('input', { bubbles: true,}) this.focused.dispatchEvent(ev) // @ts-ignore } else if (this.focused.isContentEditable) { // @ts-ignore this.focused.innerText = value } } }