diff --git a/app/actions/websocket/channel.ts b/app/actions/websocket/channel.ts index 8f234e46b0..569a97cf42 100644 --- a/app/actions/websocket/channel.ts +++ b/app/actions/websocket/channel.ts @@ -15,6 +15,7 @@ import {fetchMissingDirectChannelsInfo, fetchMyChannel, fetchChannelStats, fetch import {fetchPostsForChannel} from '@actions/remote/post'; import {fetchRolesIfNeeded} from '@actions/remote/role'; import {fetchUsersByIds, updateUsersNoLongerVisible} from '@actions/remote/user'; +import {loadCallForChannel} from '@calls/actions/calls'; import {Events, Screens} from '@constants'; import DatabaseManager from '@database/manager'; import {queryActiveServer} from '@queries/app/servers'; @@ -293,6 +294,8 @@ export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any) models.push(...prepared); } } + + loadCallForChannel(serverUrl, channelId); } else { const addedUser = getUserById(database, userId); if (!addedUser) { diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index fa9c6d1a2f..6ed8aae4c3 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -11,7 +11,8 @@ import {fetchStatusByIds} from '@actions/remote/user'; import {loadConfigAndCalls} from '@calls/actions/calls'; import { handleCallChannelDisabled, - handleCallChannelEnabled, handleCallEnded, + handleCallChannelEnabled, + handleCallEnded, handleCallScreenOff, handleCallScreenOn, handleCallStarted, @@ -175,10 +176,6 @@ async function doReconnectRest(serverUrl: string, operator: ServerDataOperator, const {locale: currentUserLocale} = (await getCurrentUser(database))!; await deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId, switchedToChannel ? initialChannelId : undefined); - if (isSupportedServerCalls(config?.Version)) { - loadConfigAndCalls(serverUrl, currentUserId); - } - // https://mattermost.atlassian.net/browse/MM-41520 } @@ -207,6 +204,11 @@ async function doReconnect(serverUrl: string) { } else { await doReconnectRest(serverUrl, operator, system.currentTeamId, system.currentUserId, config, license, lastDisconnectedAt); } + + // Calls is not set up for GraphQL yet + if (isSupportedServerCalls(config?.Version)) { + loadConfigAndCalls(serverUrl, system.currentUserId); + } } export async function handleEvent(serverUrl: string, msg: WebSocketMessage) { diff --git a/app/products/calls/actions/calls.ts b/app/products/calls/actions/calls.ts index 9f9d27b12e..82712d0b95 100644 --- a/app/products/calls/actions/calls.ts +++ b/app/products/calls/actions/calls.ts @@ -6,7 +6,8 @@ import InCallManager from 'react-native-incall-manager'; import {forceLogoutIfNecessary} from '@actions/remote/session'; import {fetchUsersByIds} from '@actions/remote/user'; import { - getCallsConfig, getCallsState, + getCallsConfig, + getCallsState, myselfLeftCall, setCalls, setChannelEnabled, @@ -14,6 +15,7 @@ import { setPluginEnabled, setScreenShareURL, setSpeakerPhone, + setCallForChannel, } from '@calls/state'; import {General, Preferences} from '@constants'; import Calls from '@constants/calls'; @@ -33,6 +35,7 @@ import type { Call, CallParticipant, CallsConnection, + ServerCallState, ServerChannelState, } from '@calls/types/calls'; import type {Client} from '@client/rest'; @@ -94,24 +97,7 @@ export const loadCalls = async (serverUrl: string, userId: string) => { for (const channel of resp) { if (channel.call) { - const call = channel.call; - callsResults[channel.channel_id] = { - participants: channel.call.users.reduce((accum, cur, curIdx) => { - // Add the id to the set of UserModels we want to ensure are loaded. - ids.add(cur); - - // Create the CallParticipant - const muted = call.states && call.states[curIdx] ? !call.states[curIdx].unmuted : true; - const raisedHand = call.states && call.states[curIdx] ? call.states[curIdx].raised_hand : 0; - accum[cur] = {id: cur, muted, raisedHand}; - return accum; - }, {} as Dictionary), - channelId: channel.channel_id, - startTime: call.start_at, - screenOn: call.screen_sharing_id, - threadId: call.thread_id, - ownerId: call.owner_id, - }; + callsResults[channel.channel_id] = createCallAndAddToIds(channel.channel_id, channel.call, ids); } enabledChannels[channel.channel_id] = channel.enabled; } @@ -126,6 +112,58 @@ export const loadCalls = async (serverUrl: string, userId: string) => { return {data: {calls: callsResults, enabled: enabledChannels}}; }; +export const loadCallForChannel = async (serverUrl: string, channelId: string) => { + let client: Client; + try { + client = NetworkManager.getClient(serverUrl); + } catch (error) { + return {error}; + } + + let resp: ServerChannelState; + try { + resp = await client.getCallForChannel(channelId); + } catch (error) { + await forceLogoutIfNecessary(serverUrl, error as ClientError); + return {error}; + } + + let call: Call | undefined; + const ids = new Set(); + if (resp.call) { + call = createCallAndAddToIds(channelId, resp.call, ids); + } + + // Batch load user models async because we'll need them later + if (ids.size > 0) { + fetchUsersByIds(serverUrl, Array.from(ids)); + } + + setCallForChannel(serverUrl, channelId, resp.enabled, call); + + return {data: {call, enabled: resp.enabled}}; +}; + +const createCallAndAddToIds = (channelId: string, call: ServerCallState, ids: Set) => { + return { + participants: call.users.reduce((accum, cur, curIdx) => { + // Add the id to the set of UserModels we want to ensure are loaded. + ids.add(cur); + + // Create the CallParticipant + const muted = call.states && call.states[curIdx] ? !call.states[curIdx].unmuted : true; + const raisedHand = call.states && call.states[curIdx] ? call.states[curIdx].raised_hand : 0; + accum[cur] = {id: cur, muted, raisedHand}; + return accum; + }, {} as Dictionary), + channelId, + startTime: call.start_at, + screenOn: call.screen_sharing_id, + threadId: call.thread_id, + ownerId: call.owner_id, + } as Call; +}; + export const loadConfigAndCalls = async (serverUrl: string, userId: string) => { const res = await checkIsCallsPluginEnabled(serverUrl); if (res.data) { diff --git a/app/products/calls/client/rest.ts b/app/products/calls/client/rest.ts index f86a008234..471f7b9669 100644 --- a/app/products/calls/client/rest.ts +++ b/app/products/calls/client/rest.ts @@ -6,6 +6,7 @@ import type {ServerChannelState, ServerCallsConfig, ApiResp, ICEServersConfigs} export interface ClientCallsMix { getEnabled: () => Promise; getCalls: () => Promise; + getCallForChannel: (channelId: string) => Promise; getCallsConfig: () => Promise; enableChannelCalls: (channelId: string, enable: boolean) => Promise; endCall: (channelId: string) => Promise; @@ -32,6 +33,13 @@ const ClientCalls = (superclass: any) => class extends superclass { ); }; + getCallForChannel = async (channelId: string) => { + return this.doFetch( + `${this.getCallsRoute()}/${channelId}`, + {method: 'get'}, + ); + }; + getCallsConfig = async () => { return this.doFetch( `${this.getCallsRoute()}/config`, diff --git a/app/products/calls/state/actions.ts b/app/products/calls/state/actions.ts index 91e49f4e78..f35bd23bef 100644 --- a/app/products/calls/state/actions.ts +++ b/app/products/calls/state/actions.ts @@ -24,6 +24,40 @@ export const setCalls = (serverUrl: string, myUserId: string, calls: Dictionary< setCallsState(serverUrl, {serverUrl, myUserId, calls, enabled}); }; +export const setCallForChannel = (serverUrl: string, channelId: string, enabled: boolean, call?: Call) => { + const callsState = getCallsState(serverUrl); + const nextEnabled = {...callsState.enabled, [channelId]: enabled}; + + const nextCalls = {...callsState.calls}; + if (call) { + nextCalls[channelId] = call; + + // In case we got a complete update on the currentCall + const currentCall = getCurrentCall(); + if (currentCall?.channelId === channelId) { + setCurrentCall({ + ...currentCall, + ...call, + }); + } + } else { + delete nextCalls[channelId]; + } + + setCallsState(serverUrl, {...callsState, calls: nextCalls, enabled: nextEnabled}); + + const channelsWithCalls = getChannelsWithCalls(serverUrl); + if (call && !channelsWithCalls[channelId]) { + const nextChannelsWithCalls = {...channelsWithCalls}; + nextChannelsWithCalls[channelId] = true; + setChannelsWithCalls(serverUrl, nextChannelsWithCalls); + } else if (!call && channelsWithCalls[channelId]) { + const nextChannelsWithCalls = {...channelsWithCalls}; + delete nextChannelsWithCalls[channelId]; + setChannelsWithCalls(serverUrl, nextChannelsWithCalls); + } +}; + export const userJoinedCall = (serverUrl: string, channelId: string, userId: string) => { const callsState = getCallsState(serverUrl); if (!callsState.calls[channelId]) { @@ -46,11 +80,11 @@ export const userJoinedCall = (serverUrl: string, channelId: string, userId: str // Did the user join the current call? If so, update that too. const currentCall = getCurrentCall(); if (currentCall && currentCall.channelId === channelId) { - const nextCall2 = { + const nextCurrentCall = { ...currentCall, participants: {...currentCall.participants, [userId]: nextCall.participants[userId]}, }; - setCurrentCall(nextCall2); + setCurrentCall(nextCurrentCall); } // Was it me that joined the call? @@ -97,12 +131,12 @@ export const userLeftCall = (serverUrl: string, channelId: string, userId: strin return; } - const nextCall2 = { + const nextCurrentCall = { ...currentCall, participants: {...currentCall.participants}, }; - delete nextCall2.participants[userId]; - setCurrentCall(nextCall2); + delete nextCurrentCall.participants[userId]; + setCurrentCall(nextCurrentCall); }; export const myselfLeftCall = () => {