From 029a13d2a08fd60287955057fd95f975051e2ef8 Mon Sep 17 00:00:00 2001
From: gcampes <gabrieldecampes@gmail.com>
Date: Fri, 20 Oct 2017 08:11:51 -0200
Subject: [PATCH] better error handling, code cleanup

---
 .../imports/api/audio/client/bridge/sip.js    | 94 ++++++++++---------
 .../audio/audio-controls/component.jsx        |  2 +
 .../audio/audio-controls/container.jsx        | 26 +----
 .../ui/services/audio-manager/index.js        | 23 ++---
 4 files changed, 63 insertions(+), 82 deletions(-)

diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
index c2cd5653a7..894457edf9 100644
--- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
+++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
@@ -9,22 +9,20 @@ const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout;
 
 const fetchStunTurnServers = sessionToken =>
   new Promise(async (resolve, reject) => {
-    const handleStunTurnResponse = ({ result, stunServers, turnServers }) =>
-      new Promise((resolve) => {
-        if (result) {
-          resolve({ error: 404, stun: [], turn: [] });
-        }
-        resolve({
-          stun: stunServers.map(server => server.url),
-          turn: turnServers.map(server => server.url),
-        });
-      });
+    const handleStunTurnResponse = ({ stunServers, turnServers }) => {
+      if (!stunServers && !turnServers) {
+        return { error: 404, stun: [], turn: [] };
+      }
+      return {
+        stun: stunServers.map(server => server.url),
+        turn: turnServers.map(server => server.url),
+      };
+    };
 
     const url = `${STUN_TURN_FETCH_URL}?sessionToken=${sessionToken}`;
-
     const response = await fetch(url)
       .then(res => res.json())
-      .then(json => handleStunTurnResponse(json));
+      .then(handleStunTurnResponse);
 
     if (response.error) return reject('Could not fetch the stuns/turns servers!');
     return resolve(response);
@@ -52,7 +50,7 @@ export default class SIPBridge extends BaseAudioBridge {
 
     this.protocol = window.document.location.protocol;
     this.hostname = window.document.location.hostname;
-    const causes = window.SIP.C.causes
+    const causes = window.SIP.C.causes;
     this.errorCodes = {
       [causes.REQUEST_TIMEOUT]: this.baseErrorCodes.REQUEST_TIMEOUT,
       [causes.INVALID_TARGET]: this.baseErrorCodes.INVALID_TARGET,
@@ -72,12 +70,42 @@ export default class SIPBridge extends BaseAudioBridge {
 
       return this.doCall({ callExtension, isListenOnly, inputStream }, callback)
                  .catch((reason) => {
-                   callback({ status: this.baseCallStates.failed, error: reason });
+                   callback({
+                     status: this.baseCallStates.failed,
+                     error: this.baseErrorCodes.GENERIC_ERROR,
+                     bridgeError: reason,
+                   });
                    reject(reason);
                  });
     });
   }
 
+  doCall(options) {
+    const {
+      isListenOnly,
+    } = options;
+
+    const {
+      userId,
+      name,
+      sessionToken,
+    } = this.user;
+
+    const callerIdName = [
+      userId,
+      'bbbID',
+      isListenOnly ? `LISTENONLY-${name}` : name,
+    ].join('-');
+
+    this.user.callerIdName = callerIdName;
+    this.callOptions = options;
+
+    return fetchStunTurnServers(sessionToken)
+                        .then(this.createUserAgent.bind(this))
+                        .then(this.inviteUserAgent.bind(this))
+                        .then(this.setupEventHandlers.bind(this));
+  }
+
   transferCall(onTransferSuccess) {
     return new Promise((resolve, reject) => {
       let trackerControl = null;
@@ -89,7 +117,7 @@ export default class SIPBridge extends BaseAudioBridge {
           status: this.baseCallStates.failed,
           error: this.baseErrorCodes.REQUEST_TIMEOUT,
           bridgeError: 'Timeout on call transfer' });
-        reject('Timeout on call transfer');
+        reject(this.baseErrorCodes.REQUEST_TIMEOUT);
       }, CALL_TRANSFER_TIMEOUT);
 
       // This is is the call transfer code ask @chadpilkey
@@ -123,32 +151,6 @@ export default class SIPBridge extends BaseAudioBridge {
     });
   }
 
-  doCall(options) {
-    const {
-      isListenOnly,
-    } = options;
-
-    const {
-      userId,
-      name,
-      sessionToken,
-    } = this.user;
-
-    const callerIdName = [
-      userId,
-      'bbbID',
-      isListenOnly ? `LISTENONLY-${name}` : name,
-    ].join('-');
-
-    this.user.callerIdName = callerIdName;
-    this.callOptions = options;
-
-    return fetchStunTurnServers(sessionToken)
-                        .then(this.createUserAgent.bind(this))
-                        .then(this.inviteUserAgent.bind(this))
-                        .then(this.setupEventHandlers.bind(this));
-  }
-
   createUserAgent({ stun, turn }) {
     return new Promise((resolve, reject) => {
       const {
@@ -187,9 +189,9 @@ export default class SIPBridge extends BaseAudioBridge {
         userAgent = null;
         this.callback({
           status: this.baseCallStates.failed,
-          error: this.baseErrorCodes.GENERIC_ERROR,
-          bridgeError: 'User Agent' });
-        reject('CONNECTION_ERROR');
+          error: this.baseErrorCodes.CONNECTION_ERROR,
+          bridgeError: 'User Agent Disconnected' });
+        reject(this.baseErrorCodes.CONNECTION_ERROR);
       };
 
       userAgent.on('connected', handleUserAgentConnection);
@@ -248,6 +250,7 @@ export default class SIPBridge extends BaseAudioBridge {
         const mappedCause = cause in this.errorCodes ?
                             this.errorCodes[cause] :
                             this.baseErrorCodes.GENERIC_ERROR;
+
         return this.callback({
           status: this.baseCallStates.failed,
           error: mappedCause,
@@ -295,7 +298,7 @@ export default class SIPBridge extends BaseAudioBridge {
       media.inputDevice.audioContext = new window.webkitAudioContext();
     }
     media.inputDevice.scriptProcessor = media.inputDevice.audioContext
-                                            .createScriptProcessor(2048, 1, 1);
+                                              .createScriptProcessor(2048, 1, 1);
     media.inputDevice.source = null;
 
     const constraints = {
@@ -318,6 +321,7 @@ export default class SIPBridge extends BaseAudioBridge {
 
     if (audioContext.setSinkId) {
       audioContext.setSinkId(value);
+      this.media.outputDeviceId = value;
     }
 
     return value;
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx
index 6d7264a3b5..c01483ec7a 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx
@@ -18,6 +18,7 @@ const AudioControls = ({
   handleLeaveAudio,
   mute,
   unmute,
+  disable,
   join,
 }) => (
   <span className={styles.container}>
@@ -34,6 +35,7 @@ const AudioControls = ({
     <Button
       className={styles.button}
       onClick={join ? handleLeaveAudio : handleJoinAudio}
+      disable={disable}
       label={join ? 'Leave Audio' : 'Join Audio'}
       color={join ? 'danger' : 'primary'}
       icon={join ? 'audio_off' : 'audio_on'}
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
index dd7738ef43..1a85e64546 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
@@ -1,39 +1,19 @@
 import React from 'react';
 import { createContainer } from 'meteor/react-meteor-data';
-// import PropTypes from 'prop-types';
 import { withModalMounter } from '/imports/ui/components/modal/service';
 import AudioControls from './component';
 import AudioModalContainer from '../audio-modal/container';
 import Service from '../service';
 
-// const propTypes = {
-//   children: PropTypes.element,
-// };
-//
-// const defaultProps = {
-//   children: null,
-// };
-
 const AudioControlsContainer = props => <AudioControls {...props} />;
 
 export default withModalMounter(createContainer(({ mountModal }) =>
-  // const APP_CONFIG = Meteor.settings.public.app;
-  //
-  // const { autoJoinAudio } = APP_CONFIG;
-  // const { isConnected, isConnecting, isListenOnly } = Service.getStats();
-  // let shouldShowMute = isConnected && !isListenOnly;
-  // let shouldShowUnmute = isConnected && !isListenOnly && isMuted;
-  // let shouldShowJoin = !isConnected;
-
    ({
-     mute: Service.isConnected() && !Service.isListenOnly(),
+     mute: Service.isConnected() && !Service.isListenOnly() && !Service.isEchoTest(),
      unmute: Service.isConnected() && !Service.isListenOnly() && Service.isMuted(),
-     join: Service.isConnected(),
-
+     join: Service.isConnected() && !Service.isEchoTest(),
+     disable: Service.isConnecting(),
      handleToggleMuteMicrophone: () => Service.toggleMuteMicrophone(),
      handleJoinAudio: () => mountModal(<AudioModalContainer />),
      handleLeaveAudio: () => Service.exitAudio(),
    }), AudioControlsContainer));
-
-// AudioControlsContainer.propTypes = propTypes;
-// AudioControlsContainer.defaultProps = defaultProps;
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index 1e3c2d4868..98fdde688f 100644
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -77,7 +77,7 @@ class AudioManager {
       inputStream: this.isListenOnly ? this.createListenOnlyStream() : this.inputStream,
     };
 
-    return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this));
+    return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this))
   }
 
   exitAudio() {
@@ -97,12 +97,11 @@ class AudioManager {
 
   onAudioJoin() {
     this.isConnecting = false;
-    if (this.isEchoTest) {
-      return;
-    }1
-
-    notify(this.messages.info.JOINED_AUDIO, 'info', 'audio_on');
     this.isConnected = true;
+
+    if (!this.isEchoTest) {
+      notify(this.messages.info.JOINED_AUDIO, 'info', 'audio_on');
+    }
   }
 
   onTransferStart() {
@@ -114,16 +113,11 @@ class AudioManager {
     this.isConnected = false;
     this.isConnecting = false;
 
-    if (this.isEchoTest) {
-      this.isEchoTest = false;
-      return;
-    }
 
-    if (this.error) {
-      return;
+    if (!this.error && !this.isEchoTest) {
+      notify(this.messages.info.LEFT_AUDIO, 'info', 'audio_on');
     }
-
-    notify(this.messages.info.LEFT_AUDIO, 'info', 'audio_on');
+    this.isEchoTest = false;
   }
 
   onToggleMicrophoneMute() {
@@ -151,6 +145,7 @@ class AudioManager {
       } else if (status === FAILED) {
         this.error = error;
         notify(this.messages.error[error], 'error', 'audio_on');
+        console.error('Audio Error:', error);
         this.onAudioExit();
       }
     });
-- 
GitLab