From 149c3b56d45ce0d23bd3e408d00c4afdc747c6af Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Thu, 19 Dec 2024 23:19:43 -0300 Subject: [PATCH] chore: improve videoconf client-side error handling (#34069) --- apps/meteor/client/lib/VideoConfManager.ts | 137 +++++++++++------- .../client/providers/VideoConfProvider.tsx | 16 ++ packages/i18n/src/locales/en.i18n.json | 7 + 3 files changed, 106 insertions(+), 54 deletions(-) diff --git a/apps/meteor/client/lib/VideoConfManager.ts b/apps/meteor/client/lib/VideoConfManager.ts index 99ec760fba91..2fa07eba885f 100644 --- a/apps/meteor/client/lib/VideoConfManager.ts +++ b/apps/meteor/client/lib/VideoConfManager.ts @@ -84,9 +84,7 @@ type VideoConfEvents = { // When join call 'call/join': CurrentCallParams; - 'join/error': { error: string }; - - 'start/error': { error: string }; + 'error': { error: string }; 'capabilities/changed': void; }; @@ -112,6 +110,8 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter(); this.dismissedCalls = new Set(); this._preferences = { mic: true, cam: false }; @@ -161,18 +162,19 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { if (!this.userId || this.isBusy()) { + this.emitError('error-videoconf-cant-start-call-with-manager-busy'); throw new Error('Video manager is busy.'); } - debug && console.log(`[VideoConf] Starting new call on room ${roomId}`); + this.debugLog(`[VideoConf] Starting new call on room ${roomId}`); this.startingNewCall = true; this.emit('calling/changed'); const { data } = await sdk.rest.post('/v1/video-conference.start', { roomId, title, allowRinging: true }).catch((e: any) => { - debug && console.error(`[VideoConf] Failed to start new call on room ${roomId}`); + console.error(`[VideoConf] Failed to start new call on room ${roomId}`, e); this.startingNewCall = false; this.emit('calling/changed'); - this.emit('start/error', { error: e?.xhr?.responseJSON?.error || 'unknown-error' }); + this.emitError(e?.xhr?.responseJSON?.error || 'error-videoconf-unexpected'); return Promise.reject(e); }); @@ -197,14 +199,15 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { const { capabilities } = await sdk.rest.get('/v1/video-conference.capabilities').catch((e: any) => { - debug && console.error(`[VideoConf] Failed to load video conference capabilities`); + console.error(`[VideoConf] Failed to load video conference capabilities`, e); + this.emitError(); return Promise.reject(e); }); @@ -273,7 +278,7 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter this.dismissedCalls.delete(callId), CALL_TIMEOUT * 20); @@ -318,11 +327,11 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { - debug && console.log(`[VideoConf] Joining call ${callId}.`); + this.debugLog(`[VideoConf] Joining call ${callId}.`); if (this.incomingDirectCalls.has(callId)) { const data = this.incomingDirectCalls.get(callId); if (data?.acceptTimeout) { - debug && console.log('[VideoConf] Clearing acceptance timeout'); + this.debugLog('[VideoConf] Clearing acceptance timeout'); clearTimeout(data.acceptTimeout); } this.removeIncomingCall(callId); @@ -368,17 +381,18 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { - debug && console.error(`[VideoConf] Failed to join call ${callId}`); - this.emit('join/error', { error: e?.xhr?.responseJSON?.error || 'unknown-error' }); + console.error(`[VideoConf] Failed to join call ${callId}`, e); + this.emitError(e?.xhr?.responseJSON?.error || 'error-videoconf-join-failed'); return Promise.reject(e); }); if (!url) { + this.emitError('error-videoconf-missing-url'); throw new Error('Failed to get video conference URL.'); } - debug && console.log(`[VideoConf] Opening ${url}.`); + this.debugLog(`[VideoConf] Opening ${url}.`); this.emit('call/join', { url, callId, providerName }); } @@ -390,10 +404,22 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter= 1) && console.log(...args); + } + + private warnLog(...args: any[]): void { + (debug || this._logLevel >= 1) && console.warn(...args); + } + + private debugLog(...args: any[]): void { + (debug || this._logLevel >= 2) && console.log(...args); + } + private rejectIncomingCallsFromUser(userId: string): void { for (const [, { callId, uid }] of this.incomingDirectCalls) { if (userId === uid) { - debug && console.log(`[VideoConf] Rejecting old incoming call from user ${userId}`); + this.debugLog(`[VideoConf] Rejecting old incoming call from user ${userId}`); this.rejectIncomingCall(callId); } } @@ -401,6 +427,7 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { if (this.currentCallHandler || this.currentCallData) { + this.emitError('error-videoconf-cant-start-call-with-manager-busy'); throw new Error('Video Conference State Error.'); } @@ -408,7 +435,7 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { if (!this.currentCallHandler) { - debug && console.warn(`[VideoConf] Ringing interval was not properly cleared.`); + this.warnLog(`[VideoConf] Ringing interval was not properly cleared.`); return; } @@ -419,19 +446,19 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { const joined = this.currentCallData?.joined; - debug && console.log(`[VideoConf] Stop ringing user ${uid}.`); + this.debugLog(`[VideoConf] Stop ringing user ${uid}.`); if (this.currentCallHandler) { clearInterval(this.currentCallHandler); this.currentCallHandler = undefined; @@ -439,7 +466,7 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { if (!action || typeof action !== 'string') { - debug && console.error('[VideoConf] Invalid action received.'); + this.warnLog('[VideoConf] Invalid action received.', action, params); return; } if (!params || typeof params !== 'object' || !params.callId || !params.uid || !params.rid) { - debug && console.error('[VideoConf] Invalid params received.'); + this.warnLog('[VideoConf] Invalid params received.', action, params); return; } @@ -515,7 +542,7 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { - debug && console.log(`[VideoConf] connecting user ${userId}`); + console.log(`[VideoConf] connecting user ${userId}`); this.userId = userId; const { stop, ready } = sdk.stream('notify-user', [`${userId}/video-conference`], (data) => this.onVideoConfNotification(data)); @@ -531,25 +558,25 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter { const [outgoing, setOutgoing] = useState(); const handleOpenCall = useVideoConfOpenCall(); + const dispatchToastMessage = useToastMessageDispatch(); + const { t } = useTranslation(); + const logLevel = useSetting('Log_Level', 0); + + useEffect(() => VideoConfManager.setLogLevel(logLevel), [logLevel]); useEffect( () => @@ -21,6 +28,15 @@ const VideoConfContextProvider = ({ children }: { children: ReactNode }): ReactE [handleOpenCall], ); + useEffect( + () => + VideoConfManager.on('error', (props) => { + const message = t(props.error?.startsWith('error-') ? props.error : 'error-videoconf-unexpected'); + dispatchToastMessage({ type: 'error', message }); + }), + [dispatchToastMessage, t], + ); + useEffect(() => { VideoConfManager.on('direct/stopped', () => setOutgoing(undefined)); VideoConfManager.on('calling/ended', () => setOutgoing(undefined)); diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index e9c78cc644f9..98cf3b1ef86f 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -2260,6 +2260,13 @@ "error-user-registration-disabled": "User registration is disabled", "error-user-registration-secret": "User registration is only allowed via Secret URL", "error-validating-department-chat-closing-tags": "At least one closing tag is required when the department requires tag(s) on closing conversations.", + "error-videoconf-cant-start-call-with-manager-busy": "Unable to start a new call due to the current state of other calls.", + "error-videoconf-direct-call-accept-timeout": "No response from remote user after notifying the call was accepted.", + "error-videoconf-direct-call-accept-canceled": "The remote user hang up before we had time to accept the call.", + "error-videoconf-direct-call-accept-ended": "The server ended the call before we had time to accept it.", + "error-videoconf-join-failed": "Unexpected Server Error while joining call.", + "error-videoconf-missing-url": "Failed to get the conference's URL.", + "error-videoconf-unexpected": "Unexpected Conference Call Error", "error-no-permission-team-channel": "You don't have permission to add this channel to the team", "error-no-owner-channel": "Only owners can add this channel to the team", "error-unable-to-update-priority": "Unable to update priority",