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.

AbstractApp.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import React, { Component } from 'react';
  2. import { Provider } from 'react-redux';
  3. import { compose, createStore } from 'redux';
  4. import Thunk from 'redux-thunk';
  5. import {
  6. localParticipantJoined,
  7. localParticipantLeft
  8. } from '../../base/participants';
  9. import { RouteRegistry } from '../../base/react';
  10. import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
  11. import {
  12. appNavigate,
  13. appWillMount,
  14. appWillUnmount
  15. } from '../actions';
  16. /**
  17. * Base (abstract) class for main App component.
  18. *
  19. * @abstract
  20. */
  21. export class AbstractApp extends Component {
  22. /**
  23. * AbstractApp component's property types.
  24. *
  25. * @static
  26. */
  27. static propTypes = {
  28. config: React.PropTypes.object,
  29. store: React.PropTypes.object,
  30. /**
  31. * The URL, if any, with which the app was launched.
  32. */
  33. url: React.PropTypes.string
  34. }
  35. /**
  36. * Initializes a new AbstractApp instance.
  37. *
  38. * @param {Object} props - The read-only React Component props with which
  39. * the new instance is to be initialized.
  40. */
  41. constructor(props) {
  42. super(props);
  43. this.state = {
  44. /**
  45. * The Route rendered by this AbstractApp.
  46. *
  47. * @type {Route}
  48. */
  49. route: undefined,
  50. /**
  51. * The Redux store used by this AbstractApp.
  52. *
  53. * @type {Store}
  54. */
  55. store: this._maybeCreateStore(props)
  56. };
  57. }
  58. /**
  59. * Init lib-jitsi-meet and create local participant when component is going
  60. * to be mounted.
  61. *
  62. * @inheritdoc
  63. */
  64. componentWillMount() {
  65. const dispatch = this._getStore().dispatch;
  66. dispatch(appWillMount(this));
  67. dispatch(localParticipantJoined());
  68. // If a URL was explicitly specified to this React Component, then open
  69. // it; otherwise, use a default.
  70. this._openURL(this.props.url || this._getDefaultURL());
  71. }
  72. /**
  73. * Notifies this mounted React Component that it will receive new props.
  74. * Makes sure that this AbstractApp has a Redux store to use.
  75. *
  76. * @inheritdoc
  77. * @param {Object} nextProps - The read-only React Component props that this
  78. * instance will receive.
  79. * @returns {void}
  80. */
  81. componentWillReceiveProps(nextProps) {
  82. // The consumer of this AbstractApp did not provide a Redux store.
  83. if (typeof nextProps.store === 'undefined'
  84. // The consumer of this AbstractApp did provide a Redux store
  85. // before. Which means that the consumer changed their mind. In
  86. // such a case this instance should create its own internal
  87. // Redux store. If the consumer did not provide a Redux store
  88. // before, then this instance is using its own internal Redux
  89. // store already.
  90. && typeof this.props.store !== 'undefined') {
  91. this.setState({
  92. store: this._maybeCreateStore(nextProps)
  93. });
  94. }
  95. }
  96. /**
  97. * Dispose lib-jitsi-meet and remove local participant when component is
  98. * going to be unmounted.
  99. *
  100. * @inheritdoc
  101. */
  102. componentWillUnmount() {
  103. const dispatch = this._getStore().dispatch;
  104. dispatch(localParticipantLeft());
  105. dispatch(appWillUnmount(this));
  106. }
  107. /**
  108. * Implements React's {@link Component#render()}.
  109. *
  110. * @inheritdoc
  111. * @returns {ReactElement}
  112. */
  113. render() {
  114. const route = this.state.route;
  115. if (route) {
  116. return (
  117. <Provider store = { this._getStore() }>
  118. {
  119. this._createElement(route.component)
  120. }
  121. </Provider>
  122. );
  123. }
  124. return null;
  125. }
  126. /**
  127. * Create a ReactElement from the specified component, the specified props
  128. * and the props of this AbstractApp which are suitable for propagation to
  129. * the children of this Component.
  130. *
  131. * @param {Component} component - The component from which the ReactElement
  132. * is to be created.
  133. * @param {Object} props - The read-only React Component props with which
  134. * the ReactElement is to be initialized.
  135. * @returns {ReactElement}
  136. * @protected
  137. */
  138. _createElement(component, props) {
  139. /* eslint-disable no-unused-vars, lines-around-comment */
  140. const {
  141. // Don't propagate the config prop(erty) because the config is
  142. // stored inside the Redux state and, thus, is visible to the
  143. // children anyway.
  144. config,
  145. // Don't propagate the dispatch and store props because they usually
  146. // come from react-redux and programmers don't really expect them to
  147. // be inherited but rather explicitly connected.
  148. dispatch, // eslint-disable-line react/prop-types
  149. store,
  150. // The url property was introduced to be consumed entirely by
  151. // AbstractApp.
  152. url,
  153. // The remaining props, if any, are considered suitable for
  154. // propagation to the children of this Component.
  155. ...thisProps
  156. } = this.props;
  157. /* eslint-enable no-unused-vars, lines-around-comment */
  158. // eslint-disable-next-line object-property-newline
  159. return React.createElement(component, { ...thisProps, ...props });
  160. }
  161. /**
  162. * Initializes a new Redux store instance suitable for use by
  163. * this AbstractApp.
  164. *
  165. * @private
  166. * @returns {Store} - A new Redux store instance suitable for use by
  167. * this AbstractApp.
  168. */
  169. _createStore() {
  170. // Create combined reducer from all reducers in ReducerRegistry.
  171. const reducer = ReducerRegistry.combineReducers();
  172. // Apply all registered middleware from the MiddlewareRegistry and
  173. // additional 3rd party middleware:
  174. // - Thunk - allows us to dispatch async actions easily. For more info
  175. // @see https://github.com/gaearon/redux-thunk.
  176. let middleware = MiddlewareRegistry.applyMiddleware(Thunk);
  177. // Try to enable Redux DevTools Chrome extension in order to make it
  178. // available for the purposes of facilitating development.
  179. let devToolsExtension;
  180. if (typeof window === 'object'
  181. && (devToolsExtension = window.devToolsExtension)) {
  182. middleware = compose(middleware, devToolsExtension());
  183. }
  184. return createStore(reducer, middleware);
  185. }
  186. /**
  187. * Gets the default URL to be opened when this App mounts.
  188. *
  189. * @protected
  190. * @returns {string} The default URL to be opened when this App mounts.
  191. */
  192. _getDefaultURL() {
  193. // If the execution environment provides a Location abstraction, then
  194. // this App at already at that location but it must be made aware of the
  195. // fact.
  196. const windowLocation = this._getWindowLocation();
  197. if (windowLocation) {
  198. const href = windowLocation.toString();
  199. if (href) {
  200. return href;
  201. }
  202. }
  203. // By default, open the domain configured in the configuration file
  204. // which may be the domain at which the whole server infrastructure is
  205. // deployed.
  206. const config = this.props.config;
  207. if (typeof config === 'object') {
  208. const hosts = config.hosts;
  209. if (typeof hosts === 'object') {
  210. const domain = hosts.domain;
  211. if (domain) {
  212. return `https://${domain}`;
  213. }
  214. }
  215. }
  216. return 'https://meet.jit.si';
  217. }
  218. /**
  219. * Gets the Redux store used by this AbstractApp.
  220. *
  221. * @protected
  222. * @returns {Store} - The Redux store used by this AbstractApp.
  223. */
  224. _getStore() {
  225. let store = this.state.store;
  226. if (typeof store === 'undefined') {
  227. store = this.props.store;
  228. }
  229. return store;
  230. }
  231. /**
  232. * Gets a Location object from the window with information about the current
  233. * location of the document. Explicitly defined to allow extenders to
  234. * override because React Native does not usually have a location property
  235. * on its window unless debugging remotely in which case the browser that is
  236. * the remote debugger will provide a location property on the window.
  237. *
  238. * @protected
  239. * @returns {Location} A Location object with information about the current
  240. * location of the document.
  241. */
  242. _getWindowLocation() {
  243. return undefined;
  244. }
  245. /**
  246. * Creates a Redux store to be used by this AbstractApp if such as store is
  247. * not defined by the consumer of this AbstractApp through its
  248. * read-only React Component props.
  249. *
  250. * @param {Object} props - The read-only React Component props that will
  251. * eventually be received by this AbstractApp.
  252. * @private
  253. * @returns {Store} - The Redux store to be used by this AbstractApp.
  254. */
  255. _maybeCreateStore(props) {
  256. // The application Jitsi Meet is architected with Redux. However, I do
  257. // not want consumers of the App React Component to be forced into
  258. // dealing with Redux. If the consumer did not provide an external Redux
  259. // store, utilize an internal Redux store.
  260. let store = props.store;
  261. if (typeof store === 'undefined') {
  262. store = this._createStore();
  263. }
  264. return store;
  265. }
  266. /**
  267. * Navigates to a specific Route.
  268. *
  269. * @param {Route} route - The Route to which to navigate.
  270. * @returns {void}
  271. */
  272. _navigate(route) {
  273. if (RouteRegistry.areRoutesEqual(this.state.route, route)) {
  274. return;
  275. }
  276. let nextState = {
  277. ...this.state,
  278. route
  279. };
  280. // The Web App was using react-router so it utilized react-router's
  281. // onEnter. During the removal of react-router, modifications were
  282. // minimized by preserving the onEnter interface:
  283. // (1) Router would provide its nextState to the Route's onEnter. As the
  284. // role of Router is now this AbstractApp, provide its nextState.
  285. // (2) A replace function would be provided to the Route in case it
  286. // chose to redirect to another path.
  287. this._onRouteEnter(route, nextState, pathname => {
  288. this._openURL(pathname);
  289. // Do not proceed with the route because it chose to redirect to
  290. // another path.
  291. nextState = undefined;
  292. });
  293. nextState && this.setState(nextState);
  294. }
  295. /**
  296. * Notifies this App that a specific Route is about to be rendered.
  297. *
  298. * @param {Route} route - The Route that is about to be rendered.
  299. * @private
  300. * @returns {void}
  301. */
  302. _onRouteEnter(route, ...args) {
  303. // Notify the route that it is about to be entered.
  304. const onEnter = route.onEnter;
  305. if (typeof onEnter === 'function') {
  306. onEnter(...args);
  307. }
  308. }
  309. /**
  310. * Navigates this AbstractApp to (i.e. opens) a specific URL.
  311. *
  312. * @param {string} url - The URL to which to navigate this AbstractApp (i.e.
  313. * the URL to open).
  314. * @protected
  315. * @returns {void}
  316. */
  317. _openURL(url) {
  318. this._getStore().dispatch(appNavigate(url));
  319. }
  320. }