diff --git a/react/features/base/tracks/actions.js b/react/features/base/tracks/actions.js index 4b123ed84..cc2c04556 100644 --- a/react/features/base/tracks/actions.js +++ b/react/features/base/tracks/actions.js @@ -83,19 +83,10 @@ export function createLocalTracksA(options = {}) { }, /* firePermissionPromptIsShownEvent */ false, store) - .then(localTracks => dispatch(_updateLocalTracks(localTracks))) - .catch(({ gum }) => { - // If permissions are not allowed, alert the user. - if (gum - && gum.error - && gum.error.name === 'DOMException' - && gum.error.message === 'NotAllowedError') { - dispatch({ - type: TRACK_PERMISSION_ERROR, - trackType: device - }); - } - }); + .then( + localTracks => dispatch(_updateLocalTracks(localTracks)), + reason => + dispatch(_onCreateLocalTracksRejected(reason, device))); } }; } @@ -390,6 +381,52 @@ function _getLocalTracksToChange(currentTracks, newTracks) { }; } +/** + * Implements the Promise rejection handler of + * createLocalTracksA and createLocalTracksF. + * + * @param {Object} reason - The Promise rejection reason. + * @param {string} device - The device/MEDIA_TYPE associated with the + * rejection. + * @private + * @returns {Function} + */ +function _onCreateLocalTracksRejected({ gum }, device) { + return dispatch => { + // If permissions are not allowed, alert the user. + if (gum) { + const { error } = gum; + + if (error) { + // FIXME For whatever reason (which is probably an + // implementation fault), react-native-webrtc will give the + // error in one of the following formats depending on whether it + // is attached to a remote debugger or not. (The remote debugger + // scenario suggests that react-native-webrtc is at fault + // because the remote debugger is Google Chrome and then its + // JavaScript engine will define DOMException. I suspect I wrote + // react-native-webrtc to return the error in the alternative + // format if DOMException is not defined.) + let trackPermissionError; + + switch (error.name) { + case 'DOMException': + trackPermissionError = error.message === 'NotAllowedError'; + break; + + case 'NotAllowedError': + trackPermissionError = error instanceof DOMException; + break; + } + trackPermissionError && dispatch({ + type: TRACK_PERMISSION_ERROR, + trackType: device + }); + } + } + }; +} + /** * Returns true if the provided JitsiTrack should be rendered as a mirror. * diff --git a/react/features/mobile/permissions/functions.js b/react/features/mobile/permissions/functions.js deleted file mode 100644 index e9fa54693..000000000 --- a/react/features/mobile/permissions/functions.js +++ /dev/null @@ -1,47 +0,0 @@ -import { Alert, Linking, NativeModules } from 'react-native'; - -import { Platform } from '../../base/react'; - -/** - * Shows an alert panel which tells the user they have to manually grant some - * permissions by opening Settings. A button which opens Settings is provided. - * - * FIXME: translate. - * - * @param {string} trackType - Type of track that failed with a permission - * error. - * @returns {void} - */ -export function alertPermissionErrorWithSettings(trackType) { - const type = trackType === 'video' ? 'Camera' : 'Microphone'; - - Alert.alert( - 'Permissions Error', - `${type} permission is required, please enable it in Settings.`, - [ - { text: 'Cancel' }, - { - onPress: _openSettings, - text: 'Settings' - } - ], - { cancelable: false }); -} - -/** - * Opens the settings panel for the current platform. - * - * @private - * @returns {void} - */ -function _openSettings() { - switch (Platform.OS) { - case 'android': - NativeModules.AndroidSettings.open(); - break; - - case 'ios': - Linking.openURL('app-settings:'); - break; - } -} diff --git a/react/features/mobile/permissions/middleware.js b/react/features/mobile/permissions/middleware.js index c9dfcd528..02674d9f4 100644 --- a/react/features/mobile/permissions/middleware.js +++ b/react/features/mobile/permissions/middleware.js @@ -1,10 +1,12 @@ /* @flow */ +import { Alert, Linking, NativeModules } from 'react-native'; + +import { isRoomValid } from '../../base/conference'; +import { Platform } from '../../base/react'; import { MiddlewareRegistry } from '../../base/redux'; import { TRACK_PERMISSION_ERROR } from '../../base/tracks'; -import { alertPermissionErrorWithSettings } from './functions'; - /** * Middleware that captures track permission errors and alerts the user so they * can enable the permission themselves. @@ -12,14 +14,68 @@ import { alertPermissionErrorWithSettings } from './functions'; * @param {Store} store - The redux store. * @returns {Function} */ -MiddlewareRegistry.register(() => next => action => { +MiddlewareRegistry.register(store => next => action => { const result = next(action); switch (action.type) { case TRACK_PERMISSION_ERROR: - alertPermissionErrorWithSettings(action.trackType); + // XXX We do not currently have user interface outside of a conference + // which the user may tap and cause a permission-related error. If we + // alert whenever we (intend to) ask for a permission, the scenario of + // entering the WelcomePage, being asked for the camera permission, me + // denying it, and being alerted that there is an error is overwhelming + // me. + if (isRoomValid(store.getState()['features/base/conference'].room)) { + _alertPermissionErrorWithSettings(action.trackType); + } break; } return result; }); + +/** + * Shows an alert panel which tells the user they have to manually grant some + * permissions by opening Settings. A button which opens Settings is provided. + * + * @param {string} trackType - Type of track that failed with a permission + * error. + * @private + * @returns {void} + */ +function _alertPermissionErrorWithSettings(trackType) { + // TODO i18n + const deviceType = trackType === 'video' ? 'Camera' : 'Microphone'; + + Alert.alert( + 'Permission required', + `${deviceType + } permission is required to participate in conferences with ${ + trackType}. Please grant it in Settings.`, + [ + { text: 'Cancel' }, + { + onPress: _openSettings, + text: 'Settings' + } + ], + { cancelable: false }); +} + +/** + * Opens the settings panel for the current platform. + * + * @private + * @returns {void} + */ +function _openSettings() { + switch (Platform.OS) { + case 'android': + NativeModules.AndroidSettings.open(); + break; + + case 'ios': + Linking.openURL('app-settings:'); + break; + } +}