Derive connection state for current call from RTC peer (#8878)

Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
Claudio Costa 2025-06-16 15:08:06 -06:00 committed by GitHub
parent 47241539d4
commit 5030525c3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 67 additions and 7 deletions

View file

@ -28,6 +28,7 @@ import {
userJoinedCall,
getCurrentCall,
} from '@calls/state';
import * as StateActions from '@calls/state/actions';
import {
type Call,
type CallsState,
@ -86,7 +87,7 @@ jest.mock('@calls/connection/connection', () => ({
disconnect: jest.fn((err?: Error) => onClose(err)),
mute: jest.fn(),
unmute: jest.fn(),
waitForPeerConnection: jest.fn(() => Promise.resolve()),
waitForPeerConnection: jest.fn(() => Promise.resolve('session-id')),
initializeVoiceTrack: jest.fn(),
sendReaction: jest.fn(),
})),
@ -258,6 +259,7 @@ describe('Actions.Calls', () => {
return [useCallsState('server1'), useCurrentCall()];
});
addFakeCall('server1', 'channel-id');
const setCurrentCallConnectedMock = jest.spyOn(StateActions, 'setCurrentCallConnected');
let response: { data?: string };
await act(async () => {
@ -266,6 +268,8 @@ describe('Actions.Calls', () => {
messages: {},
}));
expect(setCurrentCallConnectedMock).toHaveBeenCalledWith('channel-id', 'session-id');
// manually call newCurrentConnection because newConnection is mocked
newCurrentCall('server1', 'channel-id', 'myUserId');
});

View file

@ -21,6 +21,7 @@ import {
getCurrentCall,
myselfLeftCall,
newCurrentCall,
setCurrentCallConnected,
setCallForChannel,
setCalls,
setChannelEnabled,
@ -249,7 +250,9 @@ export const joinCall = async (
}
try {
await connection.waitForPeerConnection();
const sessionId = await connection.waitForPeerConnection();
setCurrentCallConnected(channelId, sessionId);
// Follow the thread.
const database = DatabaseManager.serverDatabases[serverUrl]?.database;

View file

@ -585,6 +585,7 @@ describe('newConnection', () => {
}
},
send: jest.fn(),
sessionID: 'sessionID',
}));
connection = await newConnection(
@ -600,7 +601,7 @@ describe('newConnection', () => {
res = connection.waitForPeerConnection();
jest.runAllTimers();
await expect(res).resolves.not.toThrow();
await expect(res).resolves.toBe('sessionID');
jest.useRealTimers();
});
});

View file

@ -458,7 +458,7 @@ export async function newConnection(
});
const waitForPeerConnection = () => {
const waitForReadyImpl = (callback: () => void, fail: (reason: string) => void, timeout: number) => {
const waitForReadyImpl = (callback: (sessionId: string) => void, fail: (reason: string) => void, timeout: number) => {
if (timeout <= 0) {
fail('timed out waiting for peer connection');
return;
@ -466,14 +466,14 @@ export async function newConnection(
setTimeout(() => {
if (peer?.connected) {
rtcMonitor?.start();
callback();
callback(ws.sessionID);
} else {
waitForReadyImpl(callback, fail, timeout - 200);
}
}, 200);
};
return new Promise<void>((resolve, reject) => {
return new Promise<string>((resolve, reject) => {
waitForReadyImpl(resolve, reject, peerConnectTimeout);
});
};

View file

@ -180,4 +180,8 @@ export class WebSocketClient extends EventEmitter {
}
return this.ws.readyState;
}
get sessionID() {
return this.originalConnID;
}
}

View file

@ -30,6 +30,7 @@ import {
useCallsConfig,
useCallsState,
useChannelsWithCalls,
setCurrentCallConnected,
useCurrentCall,
useGlobalCallsState,
useIncomingCalls,
@ -1628,6 +1629,39 @@ describe('useCallsState', () => {
assert.deepEqual(result.current[1], {'channel-2': true});
});
it('setCurrentCallConnected', () => {
const initialCurrentCallState: CurrentCall = {
...DefaultCurrentCall,
serverUrl: 'server1',
myUserId: 'myUserId',
connected: false,
mySessionId: '',
...call1,
};
// setup
const {result} = renderHook(() => useCurrentCall());
act(() => {
setCurrentCall(initialCurrentCallState);
});
assert.deepEqual(result.current, initialCurrentCallState);
// test
act(() => setCurrentCallConnected('channel-1', 'session-test-id'));
assert.deepEqual(result.current, {
...initialCurrentCallState,
connected: true,
mySessionId: 'session-test-id',
});
// test with wrong channel ID (should not change state)
act(() => {
setCurrentCall(initialCurrentCallState);
setCurrentCallConnected('wrong-channel', 'session-test-id');
});
assert.deepEqual(result.current, initialCurrentCallState);
});
it('captions', () => {
const initialCallsState = {
...DefaultCallsState,

View file

@ -451,6 +451,20 @@ export const newCurrentCall = (serverUrl: string, channelId: string, myUserId: s
});
};
export const setCurrentCallConnected = (channelId: string, sessionId: string) => {
const currentCall = getCurrentCall();
if (!currentCall || currentCall.channelId !== channelId) {
return;
}
const nextCurrentCall: CurrentCall = {
...currentCall,
connected: true,
mySessionId: sessionId,
};
setCurrentCall(nextCurrentCall);
};
export const myselfLeftCall = () => {
setCurrentCall(null);

View file

@ -143,7 +143,7 @@ export type CallsConnection = {
disconnect: (err?: Error) => void;
mute: () => void;
unmute: () => void;
waitForPeerConnection: () => Promise<void>;
waitForPeerConnection: () => Promise<string>;
raiseHand: () => void;
unraiseHand: () => void;
initializeVoiceTrack: () => void;