You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

middleware.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. // @flow
  2. import { Platform } from 'react-native';
  3. import * as watch from 'react-native-watch-connectivity';
  4. import { appNavigate } from '../../app/actions';
  5. import { APP_WILL_MOUNT } from '../../base/app';
  6. import { CONFERENCE_JOINED } from '../../base/conference';
  7. import { getCurrentConferenceUrl } from '../../base/connection';
  8. import { setAudioMuted } from '../../base/media';
  9. import {
  10. MiddlewareRegistry,
  11. StateListenerRegistry,
  12. toState
  13. } from '../../base/redux';
  14. import { setConferenceTimestamp, setSessionId, setWatchReachable } from './actions';
  15. import { CMD_HANG_UP, CMD_JOIN_CONFERENCE, CMD_SET_MUTED, MAX_RECENT_URLS } from './constants';
  16. import logger from './logger';
  17. const watchOSEnabled = Platform.OS === 'ios';
  18. // Handles the recent URLs state sent to the watch
  19. watchOSEnabled && StateListenerRegistry.register(
  20. /* selector */ state => state['features/recent-list'],
  21. /* listener */ (recentListState, { getState }) => {
  22. _updateApplicationContext(getState);
  23. });
  24. // Handles the mic muted state sent to the watch
  25. watchOSEnabled && StateListenerRegistry.register(
  26. /* selector */ state => _isAudioMuted(state),
  27. /* listener */ (isAudioMuted, { getState }) => {
  28. _updateApplicationContext(getState);
  29. });
  30. // Handles the conference URL state sent to the watch
  31. watchOSEnabled && StateListenerRegistry.register(
  32. /* selector */ state => getCurrentConferenceUrl(state),
  33. /* listener */ (currentUrl, { dispatch, getState }) => {
  34. dispatch(setSessionId());
  35. _updateApplicationContext(getState);
  36. });
  37. /**
  38. * Middleware that captures conference actions.
  39. *
  40. * @param {Store} store - The redux store.
  41. * @returns {Function}
  42. */
  43. watchOSEnabled && MiddlewareRegistry.register(store => next => action => {
  44. switch (action.type) {
  45. case APP_WILL_MOUNT:
  46. _appWillMount(store);
  47. break;
  48. case CONFERENCE_JOINED:
  49. store.dispatch(setConferenceTimestamp(new Date().getTime()));
  50. _updateApplicationContext(store.getState());
  51. break;
  52. }
  53. return next(action);
  54. });
  55. /**
  56. * Registers listeners to the react-native-watch-connectivity lib.
  57. *
  58. * @param {Store} store - The redux store.
  59. * @private
  60. * @returns {void}
  61. */
  62. function _appWillMount({ dispatch, getState }) {
  63. watch.subscribeToWatchReachability((error, reachable) => {
  64. dispatch(setWatchReachable(reachable));
  65. _updateApplicationContext(getState);
  66. });
  67. watch.subscribeToMessages((error, message) => {
  68. if (error) {
  69. logger.error('watch.subscribeToMessages error:', error);
  70. return;
  71. }
  72. const {
  73. command,
  74. sessionID
  75. } = message;
  76. const currentSessionID = _getSessionId(getState());
  77. if (!sessionID || sessionID !== currentSessionID) {
  78. logger.warn(
  79. `Ignoring outdated watch command: ${message.command}`
  80. + ` sessionID: ${sessionID} current session ID: ${currentSessionID}`);
  81. return;
  82. }
  83. switch (command) {
  84. case CMD_HANG_UP:
  85. if (typeof getCurrentConferenceUrl(getState()) !== 'undefined') {
  86. dispatch(appNavigate(undefined));
  87. }
  88. break;
  89. case CMD_JOIN_CONFERENCE: {
  90. const newConferenceURL = message.data;
  91. const oldConferenceURL = getCurrentConferenceUrl(getState());
  92. if (oldConferenceURL !== newConferenceURL) {
  93. dispatch(appNavigate(newConferenceURL));
  94. }
  95. break;
  96. }
  97. case CMD_SET_MUTED:
  98. dispatch(
  99. setAudioMuted(
  100. message.muted === 'true',
  101. /* ensureTrack */ true));
  102. break;
  103. }
  104. });
  105. }
  106. /**
  107. * Gets the current Apple Watch session's ID. A new session is started whenever the conference URL has changed. It is
  108. * used to filter out outdated commands which may arrive very later if the Apple Watch loses the connectivity.
  109. *
  110. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  111. * @returns {number}
  112. * @private
  113. */
  114. function _getSessionId(stateful) {
  115. const state = toState(stateful);
  116. return state['features/mobile/watchos'].sessionID;
  117. }
  118. /**
  119. * Gets the list of recent URLs to be passed over to the Watch app.
  120. *
  121. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  122. * @returns {Array<Object>}
  123. * @private
  124. */
  125. function _getRecentUrls(stateful) {
  126. const state = toState(stateful);
  127. const recentURLs = state['features/recent-list'];
  128. // Trim to MAX_RECENT_URLS and reverse the list
  129. const reversedList = recentURLs.slice(-MAX_RECENT_URLS);
  130. reversedList.reverse();
  131. return reversedList;
  132. }
  133. /**
  134. * Determines the audio muted state to be sent to the apple watch.
  135. *
  136. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  137. * @returns {boolean}
  138. * @private
  139. */
  140. function _isAudioMuted(stateful) {
  141. const state = toState(stateful);
  142. const { audio } = state['features/base/media'];
  143. return audio.muted;
  144. }
  145. /**
  146. * Sends the context to the watch os app. At the time of this writing it's the entire state of
  147. * the 'features/mobile/watchos' reducer.
  148. *
  149. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  150. * @private
  151. * @returns {void}
  152. */
  153. function _updateApplicationContext(stateful) {
  154. const state = toState(stateful);
  155. const { conferenceTimestamp, sessionID, watchReachable } = state['features/mobile/watchos'];
  156. if (!watchReachable) {
  157. return;
  158. }
  159. try {
  160. watch.updateApplicationContext({
  161. conferenceTimestamp,
  162. conferenceURL: getCurrentConferenceUrl(state),
  163. micMuted: _isAudioMuted(state),
  164. recentURLs: _getRecentUrls(state),
  165. sessionID
  166. });
  167. } catch (error) {
  168. logger.error('Failed to stringify or send the context', error);
  169. }
  170. }