diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index b3f16664c..f42a40bb1 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -21,17 +21,14 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus const [ incomeStream, setIncomeStream ] = useState(null); const [ localStream, setLocalStream ] = useState(null); const [ endCall, setEndCall ] = useState<()=>void>(()=>{}); - const [ disconnected, setDisconnected ] = useState(false); useEffect(() => { return endCall }, []) useEffect(() => { - console.log('peerConnectionStatus', peerConnectionStatus) - if (peerConnectionStatus == 4) { - toast.info(`Live session is closed.`); - setDisconnected(true) + if (peerConnectionStatus == ConnectionStatus.Disconnected) { + toast.info(`Live session was closed.`); } }, [peerConnectionStatus]) @@ -61,7 +58,7 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus }).catch(onError); } - const inCall = calling == 0 || calling == 1 + const inCall = calling !== CallingState.False; return (
@@ -72,10 +69,10 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus cn( 'cursor-pointer p-2 mr-2 flex items-center', {[stl.inCall] : inCall }, - {[stl.disabled]: disconnected} + {[stl.disabled]: peerConnectionStatus !== ConnectionStatus.Connected} ) } - onClick={inCall ? endCall : call} + onClick={ inCall ? endCall : call} role="button" >
)); @@ -19,10 +20,11 @@ const ScreenWrapper = withOverlay()(React.memo(() =>
({ //session: state.getIn([ 'sessions', 'current' ]), @@ -108,6 +110,7 @@ export default class Player extends React.PureComponent { autoplay, nextId, live, + liveStatusText, } = this.props; return ( @@ -134,7 +137,10 @@ export default class Player extends React.PureComponent { className={ stl.overlay } onClick={ disabled ? null : this.togglePlay } > - + { live && liveStatusText + ? {liveStatusText} + : + }
{ + peer.on('error', e => { if (e.type === 'peer-unavailable') { if (this.peer && this.connectionAttempts++ < MAX_RECONNECTION_COUNT) { update({ peerConnectionStatus: ConnectionStatus.Connecting }) this.connectToPeer(); } else { - update({ peerConnectionStatus: ConnectionStatus.Disconnected }) - + update({ peerConnectionStatus: ConnectionStatus.Disconnected }); + this.dataCheckIntervalID && clearInterval(this.dataCheckIntervalID); } } else { console.error(`PeerJS error (on peer). Type ${e.type}`, e); @@ -134,27 +150,26 @@ export default class AssistManager { } }) peer.on("open", me => { - console.log("peer opened", me); this.connectToPeer(); }); }); } + private dataCheckIntervalID: ReturnType | undefined; private connectToPeer() { if (!this.peer) { return; } + update({ peerConnectionStatus: ConnectionStatus.Connecting }) const id = this.peerID; console.log("trying to connect to", id) const conn = this.peer.connect(id, { serialization: 'json', reliable: true}); conn.on('open', () => { - update({ peerConnectionStatus: ConnectionStatus.Inactive }); console.log("peer connected") let i = 0; let firstMessage = true; conn.on('data', (data) => { - if (typeof data === 'string') { return this.handleCommand(data); } - if (!Array.isArray(data)) { return; } + if (!Array.isArray(data)) { return this.handleCommand(data); } if (firstMessage) { firstMessage = false; this.md.setMessagesLoading(false); @@ -204,25 +219,21 @@ export default class AssistManager { }); }); - const intervalID = setInterval(() => { - if (!conn.open) { - this.md.setMessagesLoading(true); - this.assistentCallEnd(); - update({ peerConnectionStatus: ConnectionStatus.Disconnected }) - clearInterval(intervalID); + + const onDataClose = () => { + this.initiateCallEnd(); + this.md.setMessagesLoading(true); + update({ peerConnectionStatus: ConnectionStatus.Connecting }); + console.log('closed peer conn. Reconnecting...') + this.connectToPeer(); + } + + this.dataCheckIntervalID = setInterval(() => { + if (!this.dataConnection && getState().peerConnectionStatus === ConnectionStatus.Connected) { + onDataClose(); } - }, 5000); - conn.on('close', () => this.onDataClose());// Doesn't work ? - } - - - - private onDataClose() { - this.md.setMessagesLoading(true); - this.assistentCallEnd(); - console.log('closed peer conn. Reconnecting...') - update({ peerConnectionStatus: ConnectionStatus.Connecting }) - setTimeout(() => this.connectToPeer(), 300); // reconnect + }, 3000); + conn.on('close', onDataClose);// Does it work ? } @@ -234,23 +245,34 @@ export default class AssistManager { return this.peer?.connections[this.peerID]?.find(c => c.type === 'media' && c.open); } + private send(data: any) { + this.dataConnection?.send(data); + } + private onCallEnd: null | (()=>void) = null; - private assistentCallEnd = () => { - console.log('assistentCallEnd') - const conn = this.callConnection?.close(); + private onReject: null | (()=>void) = null; + private forceCallEnd() { + this.callConnection?.close(); + } + private notifyCallEnd() { const dataConn = this.dataConnection; if (dataConn) { - console.log("call_end send") + console.log("notifyCallEnd send") dataConn.send("call_end"); } + } + private initiateCallEnd = () => { + console.log('initiateCallEnd') + this.forceCallEnd(); + this.notifyCallEnd(); this.onCallEnd?.(); } private onTrackerCallEnd = () => { - const conn = this.callConnection; - if (conn && conn.open) { - conn.close(); + this.forceCallEnd(); + if (getState().calling === CallingState.Requesting) { + this.onReject?.(); } this.onCallEnd?.(); } @@ -258,6 +280,10 @@ export default class AssistManager { private handleCommand(command: string) { switch (command) { + case "unload": + this.onTrackerCallEnd(); + this.dataConnection?.close(); + return; case "call_end": this.onTrackerCallEnd(); return; @@ -268,28 +294,25 @@ export default class AssistManager { } } - //private blocked: boolean = false; private onMouseMove = (e: MouseEvent ): void => { - //if (this.blocked) { return; } - //this.blocked = true; - //setTimeout(() => this.blocked = false, 200); const conn = this.dataConnection; - if (!conn || !conn.open) { return; } + if (!conn) { return; } // @ts-ignore ??? const data = this.md.getInternalCoordinates(e); conn.send({ x: Math.round(data.x), y: Math.round(data.y) }); } - call(localStream: MediaStream, onStream: (s: MediaStream)=>void, onClose: () => void, onReject: () => void, onError?: ()=> void): null | Function { + call(localStream: MediaStream, onStream: (s: MediaStream)=>void, onCallEnd: () => void, onReject: () => void, onError?: ()=> void): null | Function { if (!this.peer || getState().calling !== CallingState.False) { return null; } update({ calling: CallingState.Requesting }); - console.log('calling...') + console.log('calling...') + const call = this.peer.call(this.peerID, localStream); call.on('stream', stream => { update({ calling: CallingState.True }); onStream(stream); - this.dataConnection?.send({ + this.send({ name: store.getState().getIn([ 'user', 'account', 'name']), }); @@ -298,38 +321,37 @@ export default class AssistManager { }); this.onCallEnd = () => { - if (getState().calling === CallingState.Requesting) { - onReject(); - } - onClose(); - + onCallEnd(); // @ts-ignore ?? this.md.overlay.removeEventListener("mousemove", this.onMouseMove); update({ calling: CallingState.False }); this.onCallEnd = null; } - //call.on("close", this.onCallEnd); + + call.on("close", this.onCallEnd); call.on("error", (e) => { console.error("PeerJS error (on call):", e) this.onCallEnd?.(); onError?.(); }); - const intervalID = setInterval(() => { - if (!call.open && getState().calling !== CallingState.Requesting) { - this.onCallEnd?.(); - clearInterval(intervalID); - } - }, 5000); + // const intervalID = setInterval(() => { + // if (!call.open && getState().calling === CallingState.True) { + // this.onCallEnd?.(); + // clearInterval(intervalID); + // } + // }, 5000); - window.addEventListener("beforeunload", this.assistentCallEnd) + window.addEventListener("beforeunload", this.initiateCallEnd) - return this.assistentCallEnd; + return this.initiateCallEnd; } clear() { console.log('clearing', this.peerID) - this.assistentCallEnd(); + this.initiateCallEnd(); + this.dataCheckIntervalID && clearInterval(this.dataCheckIntervalID); + this.dataConnection?.close(); console.log("destroying peer...") this.peer?.destroy(); this.peer = null; diff --git a/tracker/tracker-assist/src/CallWindow.ts b/tracker/tracker-assist/src/CallWindow.ts index 30a69f291..273138be5 100644 --- a/tracker/tracker-assist/src/CallWindow.ts +++ b/tracker/tracker-assist/src/CallWindow.ts @@ -6,6 +6,7 @@ export default class CallWindow { private vLocal: HTMLVideoElement | null = null; private audioBtn: HTMLAnchorElement | null = null; private videoBtn: HTMLAnchorElement | null = null; + private userNameSpan: HTMLSpanElement | null = null; private tsInterval: ReturnType; constructor(endCall: () => void) { @@ -18,6 +19,7 @@ export default class CallWindow { border: "none", bottom: "10px", right: "10px", + display: "none", }); //iframe.src = "//static.openreplay.com/tracker-assist/index.html"; iframe.onload = () => { @@ -31,6 +33,7 @@ export default class CallWindow { .then(r => r.text()) .then((text) => { iframe.onload = () => { + iframe.style.display = "block"; iframe.style.height = doc.body.scrollHeight + 'px'; iframe.style.width = doc.body.scrollWidth + 'px'; } @@ -51,6 +54,9 @@ export default class CallWindow { 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; @@ -109,6 +115,19 @@ export default class CallWindow { this._trySetStreams(); } + + // TODO: determined workflow + _trySetAssistentName() { + if (this.userNameSpan && this.assistentName) { + this.userNameSpan.innerText = this.assistentName; + } + } + private assistentName: string = ""; + setAssistentName(name: string) { + this.assistentName = name; + this._trySetAssistentName(); + } + toggleAudio() { let enabled = true; this.localStream?.getAudioTracks().forEach(track => { diff --git a/tracker/tracker-assist/src/Confirm.ts b/tracker/tracker-assist/src/Confirm.ts index a7da63abd..fd1075789 100644 --- a/tracker/tracker-assist/src/Confirm.ts +++ b/tracker/tracker-assist/src/Confirm.ts @@ -1,5 +1,5 @@ -const declineIcon = ``; +const declineIcon = ``; export default class Confirm { private wrapper: HTMLDivElement; @@ -21,8 +21,8 @@ export default class Confirm { const btnStyles = { borderRadius: "50%", - width: "20px", - height: "20px", + width: "22px", + height: "22px", background: "transparent", padding: 0, margin: 0, @@ -32,6 +32,7 @@ export default class Confirm { Object.assign(answerBtn.style, btnStyles); Object.assign(declineBtn.style, btnStyles); Object.assign(buttons.style, { + marginTop: "10px", display: "flex", alignItems: "center", justifyContent: "space-evenly", diff --git a/tracker/tracker-assist/src/confirm.ts b/tracker/tracker-assist/src/confirm.ts index a7da63abd..fd1075789 100644 --- a/tracker/tracker-assist/src/confirm.ts +++ b/tracker/tracker-assist/src/confirm.ts @@ -1,5 +1,5 @@ -const declineIcon = ``; +const declineIcon = ``; export default class Confirm { private wrapper: HTMLDivElement; @@ -21,8 +21,8 @@ export default class Confirm { const btnStyles = { borderRadius: "50%", - width: "20px", - height: "20px", + width: "22px", + height: "22px", background: "transparent", padding: 0, margin: 0, @@ -32,6 +32,7 @@ export default class Confirm { Object.assign(answerBtn.style, btnStyles); Object.assign(declineBtn.style, btnStyles); Object.assign(buttons.style, { + marginTop: "10px", display: "flex", alignItems: "center", justifyContent: "space-evenly", diff --git a/tracker/tracker-assist/src/index.ts b/tracker/tracker-assist/src/index.ts index ffa3502bc..a9a632cb2 100644 --- a/tracker/tracker-assist/src/index.ts +++ b/tracker/tracker-assist/src/index.ts @@ -14,6 +14,12 @@ export interface Options { } +enum CallingState { + Requesting, + True, + False, +}; + export default function(opts: Partial = {}) { const options: Options = Object.assign( { @@ -28,11 +34,7 @@ export default function(opts: Partial = {}) { return; } - - let callingPeerDataConn app.attachStartCallback(function() { -// new CallWindow(()=>{console.log('endcall')}); - // @ts-ignore const peerID = `${app.projectKey}-${app.getSessionID()}` const peer = new Peer(peerID, { @@ -43,6 +45,8 @@ export default function(opts: Partial = {}) { }); console.log(peerID) peer.on('connection', function(conn) { + window.addEventListener("beforeunload", () => conn.open && conn.send("unload")); + console.log('connection') conn.on('open', function() { @@ -52,14 +56,14 @@ export default function(opts: Partial = {}) { const buffer: Message[][] = []; let buffering = false; function sendNext() { - setTimeout(() => { - if (buffer.length) { - conn.send(buffer.shift()); - sendNext(); - } else { - buffering = false; - } - }, 50); + if (buffer.length) { + setTimeout(() => { + conn.send(buffer.shift()); + sendNext(); + }, 50); + } else { + buffering = false; + } } app.stop(); //@ts-ignore (should update tracker dependency) @@ -76,32 +80,36 @@ export default function(opts: Partial = {}) { app.start(); }); }); - let calling = false; + + + let calling: CallingState = CallingState.False; peer.on('call', function(call) { const dataConn: DataConnection | undefined = peer .connections[call.peer].find(c => c.type === 'data'); - if (calling || !dataConn) { + if (calling !== CallingState.False || !dataConn) { call.close(); - dataConn?.send("call_error"); return; } - calling = true; - window.addEventListener("beforeunload", () => { + + calling = CallingState.Requesting; + const notifyCallEnd = () => { dataConn.open && dataConn.send("call_end"); - }); - dataConn.on('data', (data) => { // if call closed be a caller before confirm + } + + const confirm = new Confirm(options.confirmText, options.confirmStyle); + dataConn.on('data', (data) => { // if call closed by a caller before confirm if (data === "call_end") { - calling = false; - confirm.remove(); + console.log('receiving callend onconfirm') + calling = CallingState.False; + confirm.remove(); } }); - const confirm = new Confirm(options.confirmText, options.confirmStyle); confirm.mount(); - confirm.onAnswer(conf => { - if (!conf || !dataConn.open) { + confirm.onAnswer(agreed => { + if (!agreed || !dataConn.open) { call.close(); - dataConn.open && dataConn.send("call_end"); - calling = false; + notifyCallEnd(); + calling = CallingState.False; return; } @@ -109,41 +117,53 @@ export default function(opts: Partial = {}) { let callUI; navigator.mediaDevices.getUserMedia({video:true, audio:true}) - .then(oStream => { - const onClose = () => { - console.log("close call...") - if (call.open) { call.close(); } + .then(lStream => { + const onCallEnd = () => { + console.log("on callend", call.open) mouse.remove(); callUI?.remove(); - oStream.getTracks().forEach(t => t.stop()); - - calling = false; - if (dataConn.open) { - dataConn.send("call_end"); - } + lStream.getTracks().forEach(t => t.stop()); + calling = CallingState.False; + } + const initiateCallEnd = () => { + console.log("callend initiated") + call.close() + notifyCallEnd(); + onCallEnd(); } - dataConn.on("close", onClose); - call.answer(oStream); - call.on('close', onClose); // Works from time to time (peerjs bug) + call.answer(lStream); + + dataConn.on("close", onCallEnd); + + //call.on('close', onClose); // Works from time to time (peerjs bug) const intervalID = setInterval(() => { - if (!call.open) { - onClose(); + if (!dataConn.open) { + initiateCallEnd(); clearInterval(intervalID); } - }, 5000); - call.on('error', onClose); // notify about error? + if (!call.open) { + onCallEnd(); + clearInterval(intervalID); + } + }, 3000); + call.on('error', initiateCallEnd); - callUI = new CallWindow(onClose); - callUI.setLocalStream(oStream); - call.on('stream', function(iStream) { - callUI.setRemoteStream(iStream); + callUI = new CallWindow(initiateCallEnd); + callUI.setLocalStream(lStream); + call.on('stream', function(rStream) { + callUI.setRemoteStream(rStream); dataConn.on('data', (data: any) => { if (data === "call_end") { - onClose(); + console.log('receiving callend on call') + onCallEnd(); return; } - if (call.open && data && typeof data.x === 'number' && typeof data.y === 'number') { + if (data && typeof data.name === 'string') { + console.log("name",data) + callUI.setAssistentName(data.name); + } + if (data && typeof data.x === 'number' && typeof data.y === 'number') { mouse.move(data); } });