diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index f7a245888..45b3078d7 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -150,6 +150,7 @@ export default class AssistManager { const now = +new Date() this.store.update({ assistStart: now }) + // @ts-ignore import('socket.io-client').then(({ default: io }) => { if (this.cleaned) { return } if (this.socket) { this.socket.close() } // TODO: single socket connection @@ -395,6 +396,8 @@ export default class AssistManager { // @ts-ignore const urlObject = new URL(window.env.API_EDP || window.location.origin) + + // @ts-ignore TODO: set module in ts settings return import('peerjs').then(({ default: Peer }) => { if (this.cleaned) {return Promise.reject("Already cleaned")} const peerOpts: Peer.PeerJSOption = { diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index a742c4fbd..925d7658b 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -12,6 +12,7 @@ import AnnotationCanvas from './AnnotationCanvas.js' import ConfirmWindow from './ConfirmWindow/ConfirmWindow.js' import { callConfirmDefault, } from './ConfirmWindow/defaults.js' import type { Options as ConfirmOptions, } from './ConfirmWindow/defaults.js' +import ScreenRecordingState, { RecordingState, } from './ScreenRecordingState' // TODO: fully specified strict check with no-any (everywhere) @@ -21,6 +22,7 @@ export interface Options { onAgentConnect: StartEndCallback; onCallStart: StartEndCallback; onRemoteControlStart: StartEndCallback; + onRecordingRequest?: (agentInfo: Record) => any; session_calling_peer_key: string; session_control_peer_key: string; callConfirm: ConfirmOptions; @@ -144,6 +146,7 @@ export default class Assist { private onStart() { const app = this.app const sessionId = app.getSessionID() + if (!sessionId) { return app.debug.error('No session ID') } @@ -199,6 +202,14 @@ export default class Assist { }, ) + const onAcceptRecording = () => { + socket.emit('recording_accepted') + } + const onDenyRecording = () => { + socket.emit('recording_denied') + } + const recordingState = new ScreenRecordingState(onAcceptRecording, onDenyRecording) + setTimeout(() => recordingState.requestRecording(), 5000) // TODO: check incoming args socket.on('request_control', this.remoteControl.requestControl) socket.on('release_control', this.remoteControl.releaseControl) @@ -231,7 +242,7 @@ export default class Assist { ids.forEach(id =>{ const agentInfo = this.agents[id]?.agentInfo this.agents[id] = { - agentInfo, + ...this.agents[id], onDisconnect: this.options.onAgentConnect?.(agentInfo), } }) @@ -269,6 +280,12 @@ export default class Assist { socket.on('videofeed', (id, feedState) => { callUI?.toggleVideoStream(feedState) }) + socket.on('request_recording', (id, agentData) => { + if (recordingState.status === RecordingState.Off) { + console.log('requested screen recording', this.agents[id].agentInfo, agentData) + this.options.onRecordingRequest?.(agentData) + } + }) const callingAgents: Map = new Map() // !! uses socket.io ID // TODO: merge peerId & socket.io id (simplest way - send peerId with the name) diff --git a/tracker/tracker-assist/src/ScreenRecordingState.ts b/tracker/tracker-assist/src/ScreenRecordingState.ts new file mode 100644 index 000000000..ad32a0c94 --- /dev/null +++ b/tracker/tracker-assist/src/ScreenRecordingState.ts @@ -0,0 +1,84 @@ +export enum RecordingState { + Off, + Requested, + Recording, +} + +const defaultStyles = '2px dashed red; position: fixed;' +const leftTop = 'left: 0; top: 0' +const bottomRight = 'right: 0; bottom: 0' + +const borderEmulationStyles = { + left: `${leftTop}; height: 100vh; width: 0; border-left: ${defaultStyles}`, + top: `${leftTop}; height: 0; width: 100vw; border-top: ${defaultStyles}`, + right: `${bottomRight}; height: 100vh; width: 0; border-right: ${defaultStyles}`, + bottom: `${bottomRight}; height: 0; width: 100vw; border-bottom: ${defaultStyles}`, +} + +export default class ScreenRecordingState { + public status = RecordingState.Off + + constructor( + private readonly onAccept: () => void, + private readonly onDeny: () => void, + ) {} + + public requestRecording = () => { + // mount recording window + if (this.status !== RecordingState.Off) return + this.status = RecordingState.Requested + + // todo: change timeout to deny after testing + setTimeout(() => { + console.log('starting recording') + this.acceptRecording() + }, 5000) + } + + private readonly acceptRecording = () => { + const borders = { + left: window.document.createElement('div'), + top: window.document.createElement('div'), + right: window.document.createElement('div'), + bottom: window.document.createElement('div'), + } + + const stopButton = window.document.createElement('div') + stopButton.onclick = this.denyRecording + const buttonStyle = 'position: fixed; bottom: 0; left: calc(50vw - 10px); padding: 4px; background: blue; border-radius: 6px; text-align: center;' + buttonStyle.split(';').forEach(styleEntry => { + console.log(styleEntry) + const styleKeyVal = styleEntry.split(':') + stopButton.style[styleKeyVal[0]] = styleKeyVal[1] + }) + stopButton.textContent = 'Stop Recording' + stopButton.id = 'or-recording-border' + + Object.entries(borderEmulationStyles).forEach(([key, style,]) => { + const styleEntries = style.split(';') + styleEntries.forEach(styleEntry => { + console.log(styleEntry) + const styleKeyVal = styleEntry.split(':') + borders[key].style[styleKeyVal[0]] = styleKeyVal[1] + }) + borders[key].style = style + borders[key].id = 'or-recording-border' + + window.document.appendChild(borders[key]) + }) + window.document.appendChild(stopButton) + + this.onAccept() + this.status = RecordingState.Recording + } + + private readonly denyRecording = () => { + this.onDeny() + this.status = RecordingState.Off + + const borders = window.document.querySelectorAll('#or-recording-border') + if (borders.length > 0) { + borders.forEach(border => border.parentElement?.removeChild(border)) + } + } +}