您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

middleware.js 6.0KB

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