From 8a2ee3af05d0b91e9caac1e4d376bc475c8cfa9f Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 30 Oct 2026 00:57:03 +0000 Subject: [PATCH] Update static/js/12-calls.js --- static/js/12-calls.js | 567 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 500 insertions(+), 67 deletions(-) diff --git a/static/js/12-calls.js b/static/js/12-calls.js index dcdfa6b..a69dc6c 100644 --- a/static/js/12-calls.js +++ b/static/js/12-calls.js @@ -15,10 +15,24 @@ Glow.calls.ensureState = () => { state.selfPeerId ||= null; state.peerConnections ||= new Map(); state.remoteStreams ||= new Map(); + state.participantAudioEls ||= new Map(); state.peers ||= []; state.reconnectTimer ||= null; - state.invites ||= []; state.invitePollTimer ||= null; + state.participantPollTimer ||= null; + state.heartbeatTimer ||= null; + state.invites ||= []; + state.reconnecting ||= false; + state.joining ||= false; + state.leaving ||= false; + state.permissions ||= { + can_manage: false, + can_end: false, + can_invite: false + }; + state.backendParticipants ||= []; + state.scope ||= null; + state.statusText ||= "Idle"; }; Glow.calls.scopeLabel = invite => { @@ -67,12 +81,54 @@ Glow.calls.sendSignal = payload => { Glow.calls.replaceHttpWs = url => url.replace(/^https:/, "wss:").replace(/^http:/, "ws:"); -Glow.calls.setRemoteAudioFromAnyPeer = () => { +Glow.calls.setStatusText = text => { + Glow.calls.ensureState(); + Glow.calls.state().statusText = String(text || ""); + Glow.renderCallUi(); +}; + +Glow.calls.ensureRemoteAudioElement = peerId => { Glow.calls.ensureState(); const state = Glow.calls.state(); - const firstStream = [...state.remoteStreams.values()][0] || null; - if (Glow.els.remoteAudio) { - Glow.els.remoteAudio.srcObject = firstStream; + if (!Glow.els.remoteAudioContainer) return null; + + let audio = state.participantAudioEls.get(peerId); + if (audio) return audio; + + audio = document.createElement("audio"); + audio.autoplay = true; + audio.playsInline = true; + audio.dataset.peerId = String(peerId); + Glow.els.remoteAudioContainer.append(audio); + state.participantAudioEls.set(peerId, audio); + return audio; +}; + +Glow.calls.removeRemoteAudioElement = peerId => { + Glow.calls.ensureState(); + const state = Glow.calls.state(); + const audio = state.participantAudioEls.get(peerId); + if (!audio) return; + audio.srcObject = null; + audio.remove(); + state.participantAudioEls.delete(peerId); +}; + +Glow.calls.syncRemoteAudioElements = () => { + Glow.calls.ensureState(); + const state = Glow.calls.state(); + + for (const [peerId, stream] of state.remoteStreams.entries()) { + const audio = Glow.calls.ensureRemoteAudioElement(peerId); + if (audio && audio.srcObject !== stream) { + audio.srcObject = stream; + } + } + + for (const peerId of [...state.participantAudioEls.keys()]) { + if (!state.remoteStreams.has(peerId)) { + Glow.calls.removeRemoteAudioElement(peerId); + } } }; @@ -112,7 +168,8 @@ Glow.calls.removePeerConnection = peerId => { state.peerConnections.delete(peerId); state.remoteStreams.delete(peerId); - Glow.calls.setRemoteAudioFromAnyPeer(); + Glow.calls.removeRemoteAudioElement(peerId); + Glow.calls.syncRemoteAudioElements(); }; Glow.calls.removePeer = peerId => { @@ -133,12 +190,28 @@ Glow.calls.ensureLocalAudio = async () => { const state = Glow.calls.state(); if (state.localStream) return state.localStream; - const stream = await navigator.mediaDevices.getUserMedia({ - audio: true, - video: false - }); - state.localStream = stream; - return stream; + try { + const stream = await navigator.mediaDevices.getUserMedia({ + audio: true, + video: false + }); + state.localStream = stream; + return stream; + } catch (error) { + if (error?.name === "NotAllowedError" || error?.name === "PermissionDeniedError") { + throw new Error("Microphone permission was denied."); + } + if (error?.name === "NotFoundError" || error?.name === "DevicesNotFoundError") { + throw new Error("No microphone was found on this device."); + } + if (error?.name === "NotReadableError" || error?.name === "TrackStartError") { + throw new Error("The microphone is already in use by another application."); + } + if (error?.name === "SecurityError") { + throw new Error("Microphone access is blocked by browser security settings."); + } + throw new Error("Could not access your microphone."); + } }; Glow.calls.ensurePeerConnection = async peerId => { @@ -168,7 +241,7 @@ Glow.calls.ensurePeerConnection = async peerId => { const stream = event.streams?.[0]; if (!stream) return; state.remoteStreams.set(peerId, stream); - Glow.calls.setRemoteAudioFromAnyPeer(); + Glow.calls.syncRemoteAudioElements(); }; pc.onconnectionstatechange = () => { @@ -243,9 +316,173 @@ Glow.calls.fetchGroupCallSession = async scope => { helperData.calls_base = data.calls_base; helperData.group_session = data.session; + helperData.participants = data.participants || []; + helperData.permissions = data.permissions || { + can_manage: false, + can_end: false, + can_invite: false + }; + helperData.scope = data.scope || null; return helperData; }; +Glow.calls.renderPendingInvites = () => { + const wrap = Glow.els.callPendingInvites; + if (!wrap) return; + + wrap.replaceChildren(); + + const activeCallId = Glow.state.calls.activeCall?.groupSession?.id; + const relevant = (Glow.state.calls.invites || []).filter( + invite => Number(invite.group_call_session_id) === Number(activeCallId) + ); + + if (!relevant.length) { + wrap.classList.add("hidden"); + return; + } + + const title = document.createElement("div"); + title.className = "call-pending-title"; + title.textContent = "Pending invites"; + wrap.append(title); + + for (const invite of relevant) { + const row = document.createElement("div"); + row.className = "call-pending-row"; + + const left = document.createElement("div"); + left.className = "call-pending-name"; + left.textContent = invite.inviter_username + ? `from ${invite.inviter_username}` + : "pending invite"; + + const right = document.createElement("div"); + right.className = "call-pending-status"; + right.textContent = "pending"; + + row.append(left, right); + wrap.append(row); + } + + wrap.classList.remove("hidden"); +}; + +Glow.calls.renderParticipants = () => { + if (!Glow.els.callParticipants) return; + + Glow.calls.ensureState(); + const state = Glow.calls.state(); + const box = Glow.els.callParticipants; + box.replaceChildren(); + + const backendParticipants = Array.isArray(state.backendParticipants) + ? state.backendParticipants + : []; + const peerMap = new Map( + (state.peers || []).map(peer => [String(peer.participantId), peer]) + ); + + const selfRow = document.createElement("div"); + selfRow.className = "call-participant self"; + + const selfLeft = document.createElement("div"); + selfLeft.className = "call-participant-name"; + selfLeft.textContent = + Glow.state.currentUser?.username + ? `${Glow.state.currentUser.username} (you)` + : "you"; + + const selfRight = document.createElement("div"); + selfRight.className = "call-participant-status"; + selfRight.textContent = state.activeCall + ? state.muted + ? "connected · muted" + : "connected" + : "idle"; + + selfRow.append(selfLeft, selfRight); + box.append(selfRow); + + const renderedUserIds = new Set(); + if (Glow.state.currentUser?.id) { + renderedUserIds.add(String(Glow.state.currentUser.id)); + } + + for (const participant of backendParticipants) { + const userId = String(participant.user_id); + if (renderedUserIds.has(userId)) continue; + renderedUserIds.add(userId); + + const row = document.createElement("div"); + row.className = "call-participant"; + + const left = document.createElement("div"); + left.className = "call-participant-name"; + left.textContent = + participant.display_name || participant.username || `user ${participant.user_id}`; + if (participant) { + Glow.applyUserColor(left, participant); + } + + const right = document.createElement("div"); + right.className = "call-participant-status"; + + const matchingPeer = [...peerMap.values()].find(peer => { + return ( + String(peer.username || "").toLowerCase() === + String(participant.username || "").toLowerCase() + ); + }); + + right.textContent = matchingPeer ? "connected" : "joined"; + + row.append(left, right); + box.append(row); + } + + const extraPeers = Glow.calls.listOtherPeers().filter(peer => { + return !backendParticipants.some( + participant => + String(participant.username || "").toLowerCase() === + String(peer.username || "").toLowerCase() + ); + }); + + for (const peer of extraPeers) { + const row = document.createElement("div"); + row.className = "call-participant"; + + const left = document.createElement("div"); + left.className = "call-participant-name"; + left.textContent = peer.username || peer.participantId; + + const right = document.createElement("div"); + right.className = "call-participant-status"; + right.textContent = "connecting"; + + row.append(left, right); + box.append(row); + } + + if (box.children.length === 1 && state.activeCall) { + const empty = document.createElement("div"); + empty.className = "call-participant"; + const label = document.createElement("div"); + label.className = "call-participant-name"; + label.textContent = "waiting for others to join"; + empty.append(label); + box.append(empty); + } +}; + +Glow.calls.renderHeaderCallChip = () => { + if (!Glow.els.callStatusChip) return; + const active = Boolean(Glow.state.calls.activeCall); + Glow.els.callStatusChip.classList.toggle("hidden", !active); + Glow.els.callStatusChip.textContent = active ? "Call active" : "Call"; +}; + Glow.calls.connectWebSocket = async () => { Glow.calls.ensureState(); const state = Glow.calls.state(); @@ -259,6 +496,9 @@ Glow.calls.connectWebSocket = async () => { state.ws = ws; ws.onopen = () => { + state.reconnecting = false; + state.joining = false; + Glow.calls.setStatusText("Connected"); resolve(); }; @@ -282,7 +522,9 @@ Glow.calls.connectWebSocket = async () => { if (signal.type === "joined") { state.selfPeerId = signal.participantId; + Glow.calls.setStatusText("Call connected"); Glow.renderCallUi(); + Glow.calls.renderParticipants(); return; } @@ -292,6 +534,7 @@ Glow.calls.connectWebSocket = async () => { roomId: signal.roomId }); Glow.renderCallUi(); + Glow.calls.renderParticipants(); if (signal.participantId && signal.participantId !== state.selfPeerId) { try { @@ -304,6 +547,7 @@ Glow.calls.connectWebSocket = async () => { if (signal.type === "participant-left") { Glow.calls.removePeer(signal.participantId); Glow.renderCallUi(); + Glow.calls.renderParticipants(); Glow.showToast("Peer left call."); return; } @@ -326,6 +570,13 @@ Glow.calls.connectWebSocket = async () => { const from = signal.from; if (!from) return; + Glow.calls.upsertPeer({ + participantId: from, + roomId: signal.roomId, + username: signal.username || from + }); + Glow.calls.renderParticipants(); + const pc = await Glow.calls.ensurePeerConnection(from); await pc.setRemoteDescription({ type: "offer", sdp: signal.sdp }); const answer = await pc.createAnswer(); @@ -388,18 +639,27 @@ Glow.calls.resetTransportOnly = () => { } catch (_) {} } + for (const peerId of [...state.participantAudioEls.keys()]) { + Glow.calls.removeRemoteAudioElement(peerId); + } + state.ws = null; state.peerConnections = new Map(); state.remoteStreams = new Map(); + state.participantAudioEls = new Map(); state.peers = []; state.selfPeerId = null; state.reconnectTimer = null; + state.reconnecting = false; }; Glow.calls.resetAll = () => { Glow.calls.ensureState(); const state = Glow.calls.state(); + clearTimeout(state.participantPollTimer); + clearTimeout(state.heartbeatTimer); + Glow.calls.resetTransportOnly(); try { @@ -414,14 +674,34 @@ Glow.calls.resetAll = () => { state.activeCall = null; state.session = null; state.muted = false; + state.joining = false; + state.leaving = false; + state.backendParticipants = []; + state.scope = null; + state.statusText = "Idle"; + state.permissions = { + can_manage: false, + can_end: false, + can_invite: false + }; }; Glow.endLocalCallUi = async () => { Glow.calls.resetAll(); - if (Glow.els.remoteAudio) { - Glow.els.remoteAudio.srcObject = null; + + if (Glow.els.callParticipants) { + Glow.els.callParticipants.replaceChildren(); } + if (Glow.els.callPendingInvites) { + Glow.els.callPendingInvites.replaceChildren(); + Glow.els.callPendingInvites.classList.add("hidden"); + } + if (Glow.els.remoteAudioContainer) { + Glow.els.remoteAudioContainer.replaceChildren(); + } + Glow.renderCallUi(); + Glow.calls.renderHeaderCallChip(); }; Glow.calls.scheduleReconnect = () => { @@ -429,58 +709,151 @@ Glow.calls.scheduleReconnect = () => { const state = Glow.calls.state(); clearTimeout(state.reconnectTimer); + state.reconnecting = true; + Glow.calls.setStatusText("Reconnecting…"); + Glow.renderCallUi(); + state.reconnectTimer = setTimeout(async () => { try { if (!state.activeCall) return; - const scope = state.activeCall.scope; + const scope = state.activeCall.scope || state.scope || Glow.calls.getCurrentScope(); if (!scope) return; Glow.calls.resetTransportOnly(); + Glow.calls.setStatusText("Reconnecting…"); state.session = await Glow.calls.fetchGroupCallSession(scope); + state.permissions = state.session.permissions || state.permissions; + state.scope = state.session.scope || state.scope; + if (state.session.group_session?.id) { + await Glow.apiJoinGroupCall(state.session.group_session.id); + } await Glow.calls.connectWebSocket(); + Glow.calls.startParticipantSync(); + Glow.calls.startHeartbeat(); Glow.showToast("Call reconnected."); Glow.renderCallUi(); + Glow.calls.refreshBackendParticipants(); } catch (_) { Glow.calls.scheduleReconnect(); } }, 2000); }; +Glow.calls.refreshBackendParticipants = async () => { + Glow.calls.ensureState(); + const sessionId = Glow.state.calls.activeCall?.groupSession?.id; + if (!sessionId) return; + + try { + const data = await Glow.apiGetGroupCallParticipants(sessionId); + Glow.state.calls.backendParticipants = data.participants || []; + Glow.state.calls.permissions = data.permissions || Glow.state.calls.permissions; + Glow.state.calls.scope = data.scope || Glow.state.calls.scope; + Glow.calls.renderParticipants(); + Glow.renderCallUi(); + } catch (_) {} +}; + +Glow.calls.startParticipantSync = () => { + Glow.calls.ensureState(); + clearTimeout(Glow.state.calls.participantPollTimer); + + const run = async () => { + if (!Glow.state.calls.activeCall || document.hidden) { + Glow.calls.startParticipantSync(); + return; + } + await Glow.calls.refreshBackendParticipants(); + Glow.calls.startParticipantSync(); + }; + + Glow.state.calls.participantPollTimer = setTimeout(run, 5000); +}; + +Glow.calls.startHeartbeat = () => { + Glow.calls.ensureState(); + clearTimeout(Glow.state.calls.heartbeatTimer); + + const run = async () => { + const sessionId = Glow.state.calls.activeCall?.groupSession?.id; + if (!sessionId) return; + + try { + await Glow.apiHeartbeatGroupCall(sessionId); + } catch (_) {} + + Glow.calls.startHeartbeat(); + }; + + Glow.state.calls.heartbeatTimer = setTimeout(run, 12000); +}; + Glow.renderCallUi = () => { Glow.calls.ensureState(); const state = Glow.calls.state(); const active = Boolean(state.activeCall); - const scope = state.activeCall?.scope || Glow.calls.getCurrentScope(); + const scope = state.activeCall?.scope || state.scope || Glow.calls.getCurrentScope(); const peerCount = Glow.calls.listOtherPeers().length; if (!Glow.els.callStatus) return; Glow.els.callPeerLabel.textContent = scope?.label || + state.scope?.scope_label || Glow.state.activeChannel?.name || Glow.state.activeRoom?.name || "No active call"; - Glow.els.callStatus.textContent = active - ? peerCount > 0 - ? `Group call active · ${peerCount} peer(s)` - : "Call open" - : "Idle"; + let statusText = state.statusText || "Idle"; - Glow.els.startCallButton?.classList.toggle("hidden", !Glow.calls.isGroupCallContext()); - Glow.els.answerCallButton?.classList.add("hidden"); + if (!active) { + statusText = "Idle"; + } else if (state.joining) { + statusText = "Joining call…"; + } else if (state.reconnecting) { + statusText = "Reconnecting…"; + } else if (!Glow.calls.wsReady()) { + statusText = "Connecting…"; + } else if (peerCount > 0) { + statusText = `Group call active · ${peerCount} peer(s)`; + } else { + statusText = "Call open · waiting for others"; + } + + Glow.els.callStatus.textContent = statusText; + + Glow.els.startCallButton?.classList.toggle( + "hidden", + !Glow.calls.isGroupCallContext() || active + ); + Glow.els.retryCallButton?.classList.toggle( + "hidden", + !active || !state.reconnecting + ); Glow.els.muteCallButton?.classList.toggle("hidden", !active); Glow.els.hangupCallButton?.classList.toggle("hidden", !active); - if (Glow.els.addToCallButton) { - Glow.els.addToCallButton.classList.toggle("hidden", !active); - } + Glow.els.addToCallButton?.classList.toggle( + "hidden", + !active || !state.permissions?.can_invite + ); + Glow.els.endCallButton?.classList.toggle( + "hidden", + !active || !state.permissions?.can_end + ); Glow.els.muteCallButton.textContent = state.muted ? "Unmute" : "Mute"; + + Glow.calls.renderParticipants(); + Glow.calls.renderPendingInvites(); + Glow.calls.renderHeaderCallChip(); }; -Glow.openCallPanel = () => { +Glow.openCallPanel = async () => { Glow.els.callOverlay?.classList.remove("hidden"); + if (Glow.state.calls.activeCall?.groupSession?.id) { + await Glow.calls.refreshBackendParticipants(); + } Glow.renderCallUi(); }; @@ -499,6 +872,9 @@ Glow.startCall = async () => { } try { + Glow.state.calls.joining = true; + Glow.calls.setStatusText("Preparing call…"); + const session = await Glow.calls.fetchGroupCallSession(scope); Glow.state.calls.session = session; Glow.state.calls.activeCall = { @@ -507,17 +883,28 @@ Glow.startCall = async () => { scope, groupSession: session.group_session }; - Glow.state.calls.selfPeerId = session.peer_id; + Glow.state.calls.scope = session.scope || scope; + Glow.state.calls.permissions = session.permissions || Glow.state.calls.permissions; + Glow.state.calls.selfPeerId = session.peer_id || null; Glow.state.calls.peers = []; Glow.state.calls.peerConnections = new Map(); Glow.state.calls.remoteStreams = new Map(); + Glow.state.calls.participantAudioEls = new Map(); + Glow.state.calls.backendParticipants = session.participants || []; Glow.openCallPanel(); + Glow.calls.setStatusText("Connecting microphone…"); await Glow.calls.ensureLocalAudio(); + Glow.calls.setStatusText("Joining call…"); + await Glow.apiJoinGroupCall(session.group_session.id); await Glow.calls.connectWebSocket(); + Glow.calls.startParticipantSync(); + Glow.calls.startHeartbeat(); + Glow.state.calls.joining = false; Glow.renderCallUi(); Glow.showToast("Group call ready."); } catch (error) { + Glow.state.calls.joining = false; Glow.showToast(error.message || "Could not start group call.", "error"); await Glow.endLocalCallUi(); } @@ -533,14 +920,58 @@ Glow.toggleMuteCall = () => { Glow.renderCallUi(); }; -Glow.hangupCall = async () => { +Glow.leaveCall = async () => { + Glow.calls.ensureState(); + const activeCall = Glow.state.calls.activeCall; + if (!activeCall) return; + + Glow.state.calls.leaving = true; + Glow.calls.setStatusText("Leaving call…"); + try { Glow.calls.sendSignal({ type: "hangup" }); } catch (_) {} - Glow.showToast("Call ended."); + + try { + if (activeCall.groupSession?.id) { + await Glow.apiLeaveGroupCall(activeCall.groupSession.id); + } + } catch (_) {} + + Glow.showToast("Left call."); + Glow.state.calls.leaving = false; await Glow.endLocalCallUi(); }; +Glow.endCallForEveryone = async () => { + Glow.calls.ensureState(); + const activeCall = Glow.state.calls.activeCall; + if (!activeCall?.groupSession?.id) return; + + const values = await Glow.openDialog({ + title: "End call for everyone?", + body: "This will end the active call session for all participants.", + confirmText: "End call", + cancelText: "Cancel", + danger: true + }); + + if (!values) return; + + try { + Glow.calls.setStatusText("Ending call…"); + await Glow.apiEndGroupCall(activeCall.groupSession.id); + Glow.showToast("Call ended."); + await Glow.endLocalCallUi(); + } catch (error) { + Glow.showToast(error.message || "Could not end call.", "error"); + } +}; + +Glow.hangupCall = async () => { + await Glow.leaveCall(); +}; + Glow.calls.requestInvite = async username => { Glow.calls.ensureState(); const activeCall = Glow.state.calls.activeCall; @@ -548,13 +979,9 @@ Glow.calls.requestInvite = async username => { throw new Error("No active group call session."); } - return Glow.apiRequest("/api/group-calls/invite", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - group_call_session_id: activeCall.groupSession.id, - username - }) + return Glow.apiInviteToGroupCall({ + group_call_session_id: activeCall.groupSession.id, + username }); }; @@ -591,17 +1018,14 @@ Glow.openAddToCallDialog = async () => { Glow.calls.fetchInvites = async () => { Glow.calls.ensureState(); if (!Glow.state.currentUser) return []; - const data = await Glow.apiRequest("/api/group-calls/invites"); + const data = await Glow.apiListGroupCallInvites(); Glow.state.calls.invites = data.invites || []; + Glow.calls.renderPendingInvites(); return Glow.state.calls.invites; }; Glow.calls.acceptInvite = async inviteId => { - const data = await Glow.apiRequest(`/api/group-calls/invites/${encodeURIComponent(inviteId)}/accept`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({}) - }); + const data = await Glow.apiAcceptGroupCallInvite(inviteId); const helperResp = await fetch(`${data.calls_base}/api/call/session`, { method: "POST", @@ -619,18 +1043,19 @@ Glow.calls.acceptInvite = async inviteId => { } helperData.calls_base = data.calls_base; - helperData.group_session = { - helper_call_id: data.call_id + helperData.group_session = data.session; + helperData.participants = data.participants || []; + helperData.permissions = data.permissions || { + can_manage: false, + can_end: false, + can_invite: false }; + helperData.scope = data.scope || null; return helperData; }; Glow.calls.dismissInvite = async inviteId => { - await Glow.apiRequest(`/api/group-calls/invites/${encodeURIComponent(inviteId)}/dismiss`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({}) - }); + await Glow.apiDismissGroupCallInvite(inviteId); }; Glow.calls.openIncomingInviteDialog = async invite => { @@ -649,6 +1074,9 @@ Glow.calls.openIncomingInviteDialog = async invite => { } try { + Glow.state.calls.joining = true; + Glow.calls.setStatusText("Joining call…"); + const session = await Glow.calls.acceptInvite(invite.id); Glow.state.calls.session = session; @@ -658,17 +1086,30 @@ Glow.calls.openIncomingInviteDialog = async invite => { scope: null, groupSession: session.group_session }; - Glow.state.calls.selfPeerId = session.peer_id; + Glow.state.calls.scope = session.scope || null; + Glow.state.calls.permissions = session.permissions || Glow.state.calls.permissions; + Glow.state.calls.selfPeerId = session.peer_id || null; Glow.state.calls.peers = []; Glow.state.calls.peerConnections = new Map(); Glow.state.calls.remoteStreams = new Map(); + Glow.state.calls.participantAudioEls = new Map(); + Glow.state.calls.backendParticipants = session.participants || []; Glow.openCallPanel(); + Glow.calls.setStatusText("Connecting microphone…"); await Glow.calls.ensureLocalAudio(); + if (session.group_session?.id) { + Glow.calls.setStatusText("Joining call…"); + await Glow.apiJoinGroupCall(session.group_session.id); + } await Glow.calls.connectWebSocket(); + Glow.calls.startParticipantSync(); + Glow.calls.startHeartbeat(); + Glow.state.calls.joining = false; Glow.renderCallUi(); Glow.showToast("Joined group call."); } catch (error) { + Glow.state.calls.joining = false; Glow.showToast(error.message || "Could not join group call.", "error"); } }; @@ -697,19 +1138,6 @@ Glow.calls.scheduleInvitePolling = () => { }, document.hidden ? 12000 : 6000); }; -Glow.syncActiveCallForDm = async () => { - Glow.renderCallUi(); -}; - -Glow.pollCallSignals = async () => { -}; - -Glow.scheduleCallPolling = () => { -}; - -Glow.answerIncomingCall = async () => { -}; - Glow.els.startCallButton?.addEventListener("click", async () => { if (!Glow.state.calls.activeCall) { await Glow.startCall(); @@ -718,8 +1146,9 @@ Glow.els.startCallButton?.addEventListener("click", async () => { await Glow.calls.callAllPeers(); }); -Glow.els.answerCallButton?.addEventListener("click", () => { - Glow.answerIncomingCall?.(); +Glow.els.retryCallButton?.addEventListener("click", async () => { + clearTimeout(Glow.state.calls.reconnectTimer); + Glow.calls.scheduleReconnect(); }); Glow.els.muteCallButton?.addEventListener("click", () => { @@ -730,6 +1159,10 @@ Glow.els.hangupCallButton?.addEventListener("click", () => { Glow.hangupCall?.(); }); +Glow.els.endCallButton?.addEventListener("click", () => { + Glow.endCallForEveryone?.(); +}); + Glow.els.addToCallButton?.addEventListener("click", () => { Glow.openAddToCallDialog?.(); }); \ No newline at end of file