From afaab71bf4793f3dc4c5be2b3d066a2b2274afbc Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Thu, 23 Sep 2021 19:16:04 +0200 Subject: [PATCH] feat(tracker-assist):3.2.0: maintaining call resume on page reload --- tracker/tracker-assist/package-lock.json | 2 +- tracker/tracker-assist/package.json | 6 +- tracker/tracker-assist/src/CallWindow.ts | 159 +++++++++++--------- tracker/tracker-assist/src/ConfirmWindow.ts | 28 ++-- tracker/tracker-assist/src/_slim.ts | 8 + tracker/tracker-assist/src/index.ts | 55 +++++-- 6 files changed, 153 insertions(+), 105 deletions(-) create mode 100644 tracker/tracker-assist/src/_slim.ts diff --git a/tracker/tracker-assist/package-lock.json b/tracker/tracker-assist/package-lock.json index d797a86d4..9c778051a 100644 --- a/tracker/tracker-assist/package-lock.json +++ b/tracker/tracker-assist/package-lock.json @@ -1,6 +1,6 @@ { "name": "@openreplay/tracker-assist", - "version": "3.1.0", + "version": "3.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tracker/tracker-assist/package.json b/tracker/tracker-assist/package.json index 683c9c322..fec7cf002 100644 --- a/tracker/tracker-assist/package.json +++ b/tracker/tracker-assist/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-assist", "description": "Tracker plugin for screen assistance through the WebRTC", - "version": "3.1.1", + "version": "3.2.0", "keywords": [ "WebRTC", "assistance", @@ -24,10 +24,10 @@ "peerjs": "^1.3.2" }, "peerDependencies": { - "@openreplay/tracker": "^3.3.0" + "@openreplay/tracker": "^3.4.0" }, "devDependencies": { - "@openreplay/tracker": "^3.3.0", + "@openreplay/tracker": "^3.4.0", "prettier": "^1.18.2", "replace-in-files-cli": "^1.0.0", "typescript": "^3.6.4" diff --git a/tracker/tracker-assist/src/CallWindow.ts b/tracker/tracker-assist/src/CallWindow.ts index 860753aa7..995c36e76 100644 --- a/tracker/tracker-assist/src/CallWindow.ts +++ b/tracker/tracker-assist/src/CallWindow.ts @@ -7,6 +7,7 @@ export default class CallWindow { private audioBtn: HTMLAnchorElement | null = null; private videoBtn: HTMLAnchorElement | null = null; private userNameSpan: HTMLSpanElement | null = null; + private vPlaceholder: HTMLParagraphElement | null = null; private tsInterval: ReturnType; constructor(endCall: () => void) { @@ -23,84 +24,84 @@ export default class CallWindow { height: "200px", width: "200px", }); - //iframe.src = "//static.openreplay.com/tracker-assist/index.html"; - iframe.onload = () => { - const doc = iframe.contentDocument; - if (!doc) { - console.error("OpenReplay: CallWindow iframe document is not reachable.") - return; - } - fetch("https://static.openreplay.com/tracker-assist/index.html") - //fetch("file:///Users/shikhu/work/asayer-tester/dist/assist/index.html") - .then(r => r.text()) - .then((text) => { - iframe.onload = () => { - doc.body.removeChild(doc.body.children[0]); //?!!>R# - const assistSection = doc.getElementById("or-assist") - assistSection && assistSection.removeAttribute("style"); - iframe.style.height = doc.body.scrollHeight + 'px'; - iframe.style.width = doc.body.scrollWidth + 'px'; - iframe.onload = null; - } - - text = text.replace(/href="css/g, "href=\"https://static.openreplay.com/tracker-assist/css") - doc.open(); - doc.write(text); - doc.close(); - - - this.vLocal = doc.getElementById("video-local") as HTMLVideoElement; - this.vRemote = doc.getElementById("video-remote") as HTMLVideoElement; - this._trySetStreams(); - // - this.vLocal.parentElement && this.vLocal.parentElement.classList.add("d-none"); - - this.audioBtn = doc.getElementById("audio-btn") as HTMLAnchorElement; - this.audioBtn.onclick = () => this.toggleAudio(); - this.videoBtn = doc.getElementById("video-btn") as HTMLAnchorElement; - this.videoBtn.onclick = () => this.toggleVideo(); - - this.userNameSpan = doc.getElementById("username") as HTMLSpanElement; - this._trySetAssistentName(); - - const endCallBtn = doc.getElementById("end-call-btn") as HTMLAnchorElement; - endCallBtn.onclick = endCall; - - const tsText = doc.getElementById("time-stamp"); - const startTs = Date.now(); - if (tsText) { - this.tsInterval = setInterval(() => { - const ellapsed = Date.now() - startTs; - const secsFull = ~~(ellapsed / 1000); - const mins = ~~(secsFull / 60); - const secs = secsFull - mins * 60 - tsText.innerText = `${mins}:${secs < 10 ? 0 : ''}${secs}`; - }, 500); - } - - // TODO: better D'n'D - doc.body.setAttribute("draggable", "true"); - doc.body.ondragstart = (e) => { - if (!e.dataTransfer || !e.target) { return; } - //@ts-ignore - if (!e.target.classList || !e.target.classList.contains("card-header")) { return; } - e.dataTransfer.setDragImage(doc.body, e.clientX, e.clientY); - }; - doc.body.ondragend = e => { - Object.assign(iframe.style, { - left: `${e.clientX}px`, - top: `${e.clientY}px`, - bottom: 'auto', - right: 'auto', - }) - } - }); - } - document.body.appendChild(iframe); + const doc = iframe.contentDocument; + if (!doc) { + console.error("OpenReplay: CallWindow iframe document is not reachable.") + return; + } + fetch("https://static.openreplay.com/tracker-assist/index.html") + //fetch("file:///Users/shikhu/work/asayer-tester/dist/assist/index.html") + .then(r => r.text()) + .then((text) => { + iframe.onload = () => { + doc.body.removeChild(doc.body.children[0]); //?!!>R# + const assistSection = doc.getElementById("or-assist") + assistSection && assistSection.removeAttribute("style"); + iframe.style.height = doc.body.scrollHeight + 'px'; + iframe.style.width = doc.body.scrollWidth + 'px'; + iframe.onload = null; + } + + text = text.replace(/href="css/g, "href=\"https://static.openreplay.com/tracker-assist/css") + doc.open(); + doc.write(text); + doc.close(); + + + this.vLocal = doc.getElementById("video-local") as HTMLVideoElement; + this.vRemote = doc.getElementById("video-remote") as HTMLVideoElement; + + // + this.vLocal.parentElement && this.vLocal.parentElement.classList.add("d-none"); + + this.audioBtn = doc.getElementById("audio-btn") as HTMLAnchorElement; + this.audioBtn.onclick = () => this.toggleAudio(); + this.videoBtn = doc.getElementById("video-btn") as HTMLAnchorElement; + this.videoBtn.onclick = () => this.toggleVideo(); + + this.userNameSpan = doc.getElementById("username") as HTMLSpanElement; + this.vPlaceholder = doc.querySelector("#remote-stream p") + this._trySetAssistentName(); + this._trySetStreams(); + + const endCallBtn = doc.getElementById("end-call-btn") as HTMLAnchorElement; + endCallBtn.onclick = endCall; + + const tsText = doc.getElementById("time-stamp"); + const startTs = Date.now(); + if (tsText) { + this.tsInterval = setInterval(() => { + const ellapsed = Date.now() - startTs; + const secsFull = ~~(ellapsed / 1000); + const mins = ~~(secsFull / 60); + const secs = secsFull - mins * 60 + tsText.innerText = `${mins}:${secs < 10 ? 0 : ''}${secs}`; + }, 500); + } + + // TODO: better D'n'D + doc.body.setAttribute("draggable", "true"); + doc.body.ondragstart = (e) => { + if (!e.dataTransfer || !e.target) { return; } + //@ts-ignore + if (!e.target.classList || !e.target.classList.contains("card-header")) { return; } + e.dataTransfer.setDragImage(doc.body, e.clientX, e.clientY); + }; + doc.body.ondragend = e => { + Object.assign(iframe.style, { + left: `${e.clientX}px`, // TODO: fix in case e is inside the iframe + top: `${e.clientY}px`, + bottom: 'auto', + right: 'auto', + }) + } + }); } + // TODO: load(): Promise + private aRemote: HTMLAudioElement | null = null; private localStream: MediaStream | null = null; private remoteStream: MediaStream | null = null; @@ -109,7 +110,11 @@ export default class CallWindow { private _trySetStreams() { if (this.vRemote && !this.vRemote.srcObject && this.remoteStream) { this.vRemote.srcObject = this.remoteStream; - // Hack for audio (doesen't work in iframe because of some magical reasons) + + if (this.vPlaceholder) { + this.vPlaceholder.innerText = "Video has been paused. Click anywhere to resume."; + } + // Hack for audio (doesen't work in iframe because of some magical reasons (check if it is connected to autoplay?)) this.aRemote = document.createElement("audio"); this.aRemote.autoplay = true; this.aRemote.style.display = "none" @@ -133,6 +138,10 @@ export default class CallWindow { this._trySetStreams(); } + playRemote() { + this.vRemote && this.vRemote.play() + } + // TODO: determined workflow _trySetAssistentName() { diff --git a/tracker/tracker-assist/src/ConfirmWindow.ts b/tracker/tracker-assist/src/ConfirmWindow.ts index 64dc98a28..6257f4376 100644 --- a/tracker/tracker-assist/src/ConfirmWindow.ts +++ b/tracker/tracker-assist/src/ConfirmWindow.ts @@ -67,26 +67,32 @@ export default class ConfirmWindow { this.wrapper = wrapper; answerBtn.onclick = () => { - this.remove(); - this.callback(true); + this._remove(); + this.resolve(true); } declineBtn.onclick = () => { - this.remove(); - this.callback(false); + this._remove(); + this.resolve(false); } } - mount() { + private resolve: (result: boolean) => void = ()=>{}; + private reject: ()=>void = ()=>{}; + + mount(): Promise { document.body.appendChild(this.wrapper); + return new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); } - private callback: (result: boolean) => void = ()=>{}; - onAnswer(callback: (result: boolean) => void) { - this.callback = callback; - } - - remove() { + private _remove() { if (!this.wrapper.parentElement) { return; } document.body.removeChild(this.wrapper); } + remove() { + this._remove(); + this.reject(); + } } diff --git a/tracker/tracker-assist/src/_slim.ts b/tracker/tracker-assist/src/_slim.ts new file mode 100644 index 000000000..cf8d2205e --- /dev/null +++ b/tracker/tracker-assist/src/_slim.ts @@ -0,0 +1,8 @@ + +/** + * Hach for the issue of peerjs compilation on angular + * Mor info here: https://github.com/peers/peerjs/issues/552 + */ + +// @ts-ignore +window.parcelRequire = window.parcelRequire || undefined; diff --git a/tracker/tracker-assist/src/index.ts b/tracker/tracker-assist/src/index.ts index 89d233e70..a5c6510fa 100644 --- a/tracker/tracker-assist/src/index.ts +++ b/tracker/tracker-assist/src/index.ts @@ -1,3 +1,4 @@ +import './_slim'; import Peer, { MediaConnection } from 'peerjs'; import type { DataConnection } from 'peerjs'; import { App, Messages } from '@openreplay/tracker'; @@ -11,6 +12,7 @@ import ConfirmWindow from './ConfirmWindow'; export interface Options { confirmText: string, confirmStyle: Object, // Styles object + session_calling_peer_key: string, } @@ -25,6 +27,7 @@ export default function(opts: Partial = {}) { { confirmText: "You have a call. Do you want to answer?", confirmStyle: {}, + session_calling_peer_key: "__openreplay_calling_peer", }, opts, ); @@ -104,25 +107,42 @@ export default function(opts: Partial = {}) { return; } + function setCallingState(newState: CallingState) { + if (newState === CallingState.True) { + sessionStorage.setItem(options.session_calling_peer_key, call.peer); + } else if (newState === CallingState.False) { + sessionStorage.removeItem(options.session_calling_peer_key); + } + callingState = newState; + } + const notifyCallEnd = () => { dataConn.open && dataConn.send("call_end"); } - callingState = CallingState.Requesting; - const confirm = new ConfirmWindow(options.confirmText, options.confirmStyle); - dataConn.on('data', (data) => { // if call closed by a caller before confirm - if (data === "call_end") { - //console.log('OpenReplay tracker-assist: receiving callend onconfirm') - callingState = CallingState.False; - confirm.remove(); - } - }); - confirm.mount(); - confirm.onAnswer(agreed => { + + let confirmAnswer: Promise + const peerOnCall = sessionStorage.getItem(options.session_calling_peer_key) + if (peerOnCall === call.peer) { + confirmAnswer = Promise.resolve(true) + } else { + setCallingState(CallingState.Requesting); + const confirm = new ConfirmWindow(options.confirmText, options.confirmStyle); + confirmAnswer = confirm.mount(); + dataConn.on('data', (data) => { // if call closed by a caller before confirm + if (data === "call_end") { + //console.log('OpenReplay tracker-assist: receiving callend onconfirm') + setCallingState(CallingState.False); + confirm.remove(); + } + }); + } + + confirmAnswer.then(agreed => { if (!agreed || !dataConn.open) { call.close(); notifyCallEnd(); - callingState = CallingState.False; + setCallingState(CallingState.False); return; } @@ -131,11 +151,10 @@ export default function(opts: Partial = {}) { const onCallConnect = lStream => { const onCallEnd = () => { - //console.log("on callend", call.open) mouse.remove(); callUI?.remove(); lStream.getTracks().forEach(t => t.stop()); - callingState = CallingState.False; + setCallingState(CallingState.False); } const initiateCallEnd = () => { //console.log("callend initiated") @@ -145,6 +164,7 @@ export default function(opts: Partial = {}) { } call.answer(lStream); + setCallingState(CallingState.True) dataConn.on("close", onCallEnd); @@ -176,6 +196,11 @@ export default function(opts: Partial = {}) { }); call.on('stream', function(rStream) { callUI.setRemoteStream(rStream); + const onInteraction = () => { + callUI.playRemote() + document.removeEventListener("click", onInteraction) + } + document.addEventListener("click", onInteraction) }); dataConn.on('data', (data: any) => { if (data === "call_end") { @@ -200,7 +225,7 @@ export default function(opts: Partial = {}) { .then(onCallConnect) .catch(e => console.log("OpenReplay tracker-assist: cant reach media devices. ", e)); }); - }); + }).catch(); // in case of Confirm.remove() without any confirmation }); }); }