diff --git a/tracker/tracker-assist/.gitignore b/tracker/tracker-assist/.gitignore index 6ddaccbb5..b5c5ddbce 100644 --- a/tracker/tracker-assist/.gitignore +++ b/tracker/tracker-assist/.gitignore @@ -1,6 +1,8 @@ node_modules npm-debug.log +yarn-error.log lib cjs .cache -*.DS_Store \ No newline at end of file +*.cache +*.DS_Store diff --git a/tracker/tracker-assist/.npmignore b/tracker/tracker-assist/.npmignore index 038eacb24..a8af1c889 100644 --- a/tracker/tracker-assist/.npmignore +++ b/tracker/tracker-assist/.npmignore @@ -1,4 +1,6 @@ src +npm-debug.log +yarn-error.log tsconfig-cjs.json tsconfig.json .prettierrc.json diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index c71031189..f9e010559 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -1,12 +1,14 @@ import type { Socket } from 'socket.io-client'; import io from 'socket.io-client'; import Peer from 'peerjs'; +import type { Properties } from 'csstype'; import { App } from '@openreplay/tracker'; +import RequestLocalStream from './LocalStream.js'; import Mouse from './Mouse.js'; import CallWindow from './CallWindow.js'; -import ConfirmWindow from './ConfirmWindow.js'; -import RequestLocalStream from './LocalStream.js'; +import ConfirmWindow, { callConfirmDefault, controlConfirmDefault } from './ConfirmWindow.js'; +import type { Options as ConfirmOptions } from './ConfirmWindow.js'; //@ts-ignore peerjs hack for webpack5 (?!) TODO: ES/node modules; @@ -15,9 +17,13 @@ Peer = Peer.default || Peer; export interface Options { onAgentConnect: () => ((()=>{}) | void), onCallStart: () => ((()=>{}) | void), - confirmText: string, - confirmStyle: Object, // Styles object session_calling_peer_key: string, + callConfirm: ConfirmOptions, + controlConfirm: ConfirmOptions, + + confirmText?: string, // @depricated + confirmStyle?: Properties, // @depricated + config: RTCConfiguration, } @@ -44,10 +50,22 @@ export default class Assist { private callingState: CallingState = CallingState.False private agents: Record = {} + private readonly options: Options constructor( private readonly app: App, - private readonly options: Options, - private readonly noSecureMode: boolean = false) { + options?: Partial, + private readonly noSecureMode: boolean = false, + ) { + this.options = Object.assign({ + session_calling_peer_key: "__openreplay_calling_peer", + config: null, + onCallStart: ()=>{}, + onAgentConnect: ()=>{}, + callConfirm: {}, + controlConfirm: {}, // TODO: clear options passing/merging/overriting + }, + options, + ); app.attachStartCallback(() => { if (this.assistDemandedRestart) { return; } this.onStart() @@ -123,7 +141,7 @@ export default class Assist { return } controllingAgent = id - confirmRC = new ConfirmWindow("Allow remote control?") + confirmRC = new ConfirmWindow(controlConfirmDefault(this.options.controlConfirm)) confirmRC.mount().then(allowed => { if (allowed) { // TODO: per agent id mouse.mount() @@ -206,7 +224,10 @@ export default class Assist { confirmAnswer = Promise.resolve(true) } else { setCallingState(CallingState.Requesting) - confirmCall = new ConfirmWindow(this.options.confirmText, this.options.confirmStyle) + confirmCall = new ConfirmWindow(callConfirmDefault(this.options.callConfirm || { + text: this.options.confirmText, + style: this.options.confirmStyle, + })) confirmAnswer = confirmCall.mount() this.onRemoteCallEnd = () => { // if call cancelled by a caller before confirmation app.debug.log("Received call_end during confirm window opened") diff --git a/tracker/tracker-assist/src/BufferingConnection.ts b/tracker/tracker-assist/src/BufferingConnection.ts deleted file mode 100644 index 5fb3b7349..000000000 --- a/tracker/tracker-assist/src/BufferingConnection.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { DataConnection } from 'peerjs'; - -// TODO: proper Message type export from tracker in 3.5.0 -interface Message { - encode(w: any): boolean; -} - -// 16kb should be max according to specification -// 64kb chrome -const crOrFf: boolean = - typeof navigator !== "undefined" && - (navigator.userAgent.indexOf("Chrom") !== -1 || // Chrome && Chromium - navigator.userAgent.indexOf("Firefox") !== -1); - -const MESSAGES_PER_SEND = crOrFf ? 200 : 50 - -// Bffering required in case of webRTC -export default class BufferingConnection { - private readonly buffer: Message[][] = [] - private buffering: boolean = false - - constructor(readonly conn: DataConnection, - private readonly msgsPerSend: number = MESSAGES_PER_SEND){} - private sendNext() { - if (this.buffer.length) { - setTimeout(() => { - this.conn.send(this.buffer.shift()) - this.sendNext() - }, 15) - } else { - this.buffering = false - } - } - - send(messages: Message[]) { - if (!this.conn.open) { return; } - let i = 0; - //@ts-ignore - messages=messages.filter(m => m._id !== 39) - while (i < messages.length) { - - this.buffer.push(messages.slice(i, i+=this.msgsPerSend)) - } - if (!this.buffering) { - this.buffering = true - this.sendNext(); - } - } -} \ No newline at end of file diff --git a/tracker/tracker-assist/src/ConfirmWindow.ts b/tracker/tracker-assist/src/ConfirmWindow.ts index 6257f4376..7a1238508 100644 --- a/tracker/tracker-assist/src/ConfirmWindow.ts +++ b/tracker/tracker-assist/src/ConfirmWindow.ts @@ -1,36 +1,83 @@ +import type { Properties } from 'csstype'; -const declineIcon = ``; +import { declineCall, acceptCall, cross, remoteControl } from './icons.js' + +type ButtonOptions = HTMLButtonElement | string | { + innerHTML: string, + style?: Properties, +} + + +// TODO: common strategy for InputOptions/defaultOptions merging +interface ConfirmWindowOptions { + text: string, + style?: Properties, + confirmBtn: ButtonOptions, + declineBtn: ButtonOptions, +} + +export type Options = string | Partial + +function confirmDefault( + opts: Options, + confirmBtn: ButtonOptions, + declineBtn: ButtonOptions, + text: string, +): ConfirmWindowOptions { + const isStr = typeof opts === "string" + return Object.assign({ + text: isStr ? opts : text, + confirmBtn, + declineBtn, + }, isStr ? undefined : opts) +} + +export const callConfirmDefault = (opts: Options) => + confirmDefault(opts, acceptCall, declineCall, "You have an incoming call. Do you want to answer?") +export const controlConfirmDefault = (opts: Options) => + confirmDefault(opts, remoteControl, cross, "Allow remote control?") + +function makeButton(options: ButtonOptions): HTMLButtonElement { + if (options instanceof HTMLButtonElement) { + return options + } + const btn = document.createElement('button') + Object.assign(btn.style, { + background: "transparent", + padding: 0, + margin: 0, + border: 0, + cursor: "pointer", + borderRadius: "50%", + width: "22px", + height: "22px", + color: "white", // TODO: nice text button in case when only text is passed + }) + if (typeof options === "string") { + btn.innerHTML = options + } else { + btn.innerHTML = options.innerHTML + Object.assign(btn.style, options.style) + } + return btn +} export default class ConfirmWindow { private wrapper: HTMLDivElement; - constructor(text: string, styles?: Object) { + constructor(options: ConfirmWindowOptions) { const wrapper = document.createElement('div'); const popup = document.createElement('div'); const p = document.createElement('p'); - p.innerText = text; + p.innerText = options.text; const buttons = document.createElement('div'); - const answerBtn = document.createElement('button'); - answerBtn.innerHTML = declineIcon.replace('fill="#ef5261"', 'fill="green"'); - const declineBtn = document.createElement('button'); - declineBtn.innerHTML = declineIcon; - buttons.appendChild(answerBtn); + const confirmBtn = makeButton(options.confirmBtn); + const declineBtn = makeButton(options.declineBtn); + buttons.appendChild(confirmBtn); buttons.appendChild(declineBtn); popup.appendChild(p); popup.appendChild(buttons); - const btnStyles = { - borderRadius: "50%", - width: "22px", - height: "22px", - background: "transparent", - padding: 0, - margin: 0, - border: 0, - cursor: "pointer", - } - Object.assign(answerBtn.style, btnStyles); - Object.assign(declineBtn.style, btnStyles); Object.assign(buttons.style, { marginTop: "10px", display: "flex", @@ -51,7 +98,7 @@ export default class ConfirmWindow { textAlign: "center", borderRadius: ".25em .25em .4em .4em", boxShadow: "0 0 20px rgb(0 0 0 / 20%)", - }, styles); + }, options.style); Object.assign(wrapper.style, { position: "fixed", @@ -66,7 +113,7 @@ export default class ConfirmWindow { wrapper.appendChild(popup); this.wrapper = wrapper; - answerBtn.onclick = () => { + confirmBtn.onclick = () => { this._remove(); this.resolve(true); } diff --git a/tracker/tracker-assist/src/icons.ts b/tracker/tracker-assist/src/icons.ts new file mode 100644 index 000000000..724d94248 --- /dev/null +++ b/tracker/tracker-assist/src/icons.ts @@ -0,0 +1,14 @@ + + +// TODO: something with these big strings in bundle? + +export const declineCall = ``; + +export const acceptCall = declineCall.replace('fill="#ef5261"', 'fill="green"') + +export const cross = ` + + +` + +export const remoteControl = `` diff --git a/tracker/tracker-assist/src/index.ts b/tracker/tracker-assist/src/index.ts index 2ba0d6cd4..2a3161089 100644 --- a/tracker/tracker-assist/src/index.ts +++ b/tracker/tracker-assist/src/index.ts @@ -5,18 +5,7 @@ import type { Options } from './Assist.js' import Assist from './Assist.js' -export default function(opts?: Partial) { - const options: Options = Object.assign( - { - confirmText: "You have an incoming call. Do you want to answer?", - confirmStyle: {}, - session_calling_peer_key: "__openreplay_calling_peer", - config: null, - onCallStart: ()=>{}, - onAgentConnect: ()=>{}, - }, - opts, - ); +export default function(opts?: Partial) { return function(app: App | null, appOptions: { __DISABLE_SECURE_MODE?: boolean } = {}) { // @ts-ignore if (app === null || !navigator?.mediaDevices?.getUserMedia) { // 93.04% browsers @@ -27,7 +16,7 @@ export default function(opts?: Partial) { return } app.notify.log("OpenReplay Assist initializing.") - const assist = new Assist(app, options, appOptions.__DISABLE_SECURE_MODE) + const assist = new Assist(app, opts, appOptions.__DISABLE_SECURE_MODE) app.debug.log(assist) return assist