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.

ChromeExtensionBanner.web.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // @flow
  2. import React, { PureComponent } from 'react';
  3. import { connect } from '../../base/redux';
  4. import { Icon, IconClose } from '../../base/icons';
  5. import { translate } from '../../base/i18n';
  6. import { getCurrentConference } from '../../base/conference/functions';
  7. import { browser } from '../../base/lib-jitsi-meet';
  8. import { isMobileBrowser } from '../../base/environment/utils';
  9. import logger from '../logger';
  10. declare var interfaceConfig: Object;
  11. /**
  12. * Local storage key name for flag telling if user checked 'Don't show again' checkbox on the banner
  13. * If the user checks this before closing the banner, next time he will access a jitsi domain
  14. * the banner will not be shown regardless of extensions being installed or not.
  15. */
  16. const DONT_SHOW_AGAIN_CHECKED = 'hide_chrome_extension_banner';
  17. /**
  18. * The type of the React {@code PureComponent} props of {@link ChromeExtensionBanner}.
  19. */
  20. type Props = {
  21. /**
  22. * Conference data, if any
  23. */
  24. conference: Object,
  25. /**
  26. * The url of the chrome extension
  27. */
  28. chromeExtensionUrl: string,
  29. /**
  30. * An array containing info for identifying a chrome extension
  31. */
  32. chromeExtensionsInfo: Array<Object>,
  33. /**
  34. * Whether I am the current recorder.
  35. */
  36. iAmRecorder: boolean,
  37. /**
  38. * Invoked to obtain translated strings.
  39. */
  40. t: Function,
  41. };
  42. /**
  43. * The type of the React {@link PureComponent} state of {@link ChromeExtensionBanner}.
  44. */
  45. type State = {
  46. /**
  47. * Keeps the current value of dont show again checkbox
  48. */
  49. dontShowAgainChecked: boolean,
  50. /**
  51. * Tells whether user pressed install extension or close button.
  52. */
  53. closePressed: boolean,
  54. /**
  55. * Tells whether should show the banner or not based on extension being installed or not.
  56. */
  57. shouldShow: boolean,
  58. };
  59. /**
  60. * Implements a React {@link PureComponent} which displays a banner having a link to the chrome extension.
  61. * @class ChromeExtensionBanner
  62. * @extends PureComponent
  63. */
  64. class ChromeExtensionBanner extends PureComponent<Props, State> {
  65. /**
  66. * Initializes a new {@code ChromeExtensionBanner} instance.
  67. *
  68. * @param {Object} props - The read-only React {@code PureComponent} props with
  69. * which the new instance is to be initialized.
  70. */
  71. constructor(props: Props) {
  72. super(props);
  73. this.state = {
  74. dontShowAgainChecked: false,
  75. closePressed: false,
  76. shouldShow: false
  77. };
  78. this._onClosePressed = this._onClosePressed.bind(this);
  79. this._onInstallExtensionClick = this._onInstallExtensionClick.bind(this);
  80. this._checkExtensionsInstalled = this._checkExtensionsInstalled.bind(this);
  81. this._shouldNotRender = this._shouldNotRender.bind(this);
  82. this._onDontShowAgainChange = this._onDontShowAgainChange.bind(this);
  83. }
  84. /**
  85. * Executed on component update.
  86. * Checks whether any chrome extension from the config is installed.
  87. *
  88. * @inheritdoc
  89. */
  90. async componentDidUpdate() {
  91. if (!this._isSupportedEnvironment()) {
  92. return;
  93. }
  94. const hasExtensions = await this._checkExtensionsInstalled();
  95. if (
  96. hasExtensions
  97. && hasExtensions.length
  98. && hasExtensions.every(ext => !ext)
  99. && !this.state.shouldShow
  100. ) {
  101. this.setState({ shouldShow: true }); // eslint-disable-line
  102. }
  103. }
  104. /**
  105. * Checks whether the feature is enabled and whether the environment(browser/os)
  106. * supports it.
  107. *
  108. * @returns {boolean}
  109. */
  110. _isSupportedEnvironment() {
  111. return interfaceConfig.SHOW_CHROME_EXTENSION_BANNER
  112. && browser.isChrome()
  113. && !isMobileBrowser();
  114. }
  115. _onClosePressed: () => void;
  116. /**
  117. * Closes the banner for the current session.
  118. *
  119. * @returns {void}
  120. */
  121. _onClosePressed() {
  122. this.setState({ closePressed: true });
  123. }
  124. _onInstallExtensionClick: () => void;
  125. /**
  126. * Opens the chrome extension page.
  127. *
  128. * @returns {void}
  129. */
  130. _onInstallExtensionClick() {
  131. window.open(this.props.chromeExtensionUrl);
  132. this.setState({ closePressed: true });
  133. }
  134. _checkExtensionsInstalled: () => Promise<*>;
  135. /**
  136. * Checks whether the chrome extensions defined in the config file are installed or not.
  137. *
  138. * @returns {Promise[]}
  139. */
  140. _checkExtensionsInstalled() {
  141. const isExtensionInstalled = info => new Promise(resolve => {
  142. const img = new Image();
  143. img.src = `chrome-extension://${info.id}/${info.path}`;
  144. img.onload = function() {
  145. resolve(true);
  146. };
  147. img.onerror = function() {
  148. resolve(false);
  149. };
  150. });
  151. const extensionInstalledFunction = info => isExtensionInstalled(info);
  152. if (!this.props.chromeExtensionsInfo.length) {
  153. logger.warn('Further configuration needed, missing chrome extension(s) info');
  154. }
  155. return Promise.all(
  156. this.props.chromeExtensionsInfo.map(info => extensionInstalledFunction(info))
  157. );
  158. }
  159. _shouldNotRender: () => boolean;
  160. /**
  161. * Checks whether the banner should not be rendered.
  162. *
  163. * @returns {boolean} Whether to show the banner or not.
  164. */
  165. _shouldNotRender() {
  166. if (!this._isSupportedEnvironment()) {
  167. return true;
  168. }
  169. if (!this.props.chromeExtensionUrl) {
  170. logger.warn('Further configuration needed, missing chrome extension URL');
  171. return true;
  172. }
  173. const dontShowAgain = localStorage.getItem(DONT_SHOW_AGAIN_CHECKED) === 'true';
  174. return dontShowAgain
  175. || this.state.closePressed
  176. || !this.state.shouldShow
  177. || this.props.iAmRecorder;
  178. }
  179. _onDontShowAgainChange: (object: Object) => void;
  180. /**
  181. * Handles the current `don't show again` checkbox state.
  182. *
  183. * @param {Object} event - Input change event.
  184. * @returns {void}
  185. */
  186. _onDontShowAgainChange(event) {
  187. this.setState({ dontShowAgainChecked: event.target.checked });
  188. }
  189. /**
  190. * Implements React's {@link PureComponent#render()}.
  191. *
  192. * @inheritdoc
  193. * @returns {ReactElement}
  194. */
  195. render() {
  196. if (this._shouldNotRender()) {
  197. if (this.state.dontShowAgainChecked) {
  198. localStorage.setItem(DONT_SHOW_AGAIN_CHECKED, 'true');
  199. }
  200. return null;
  201. }
  202. const { t } = this.props;
  203. const mainClassNames = this.props.conference
  204. ? 'chrome-extension-banner chrome-extension-banner__pos_in_meeting'
  205. : 'chrome-extension-banner';
  206. return (
  207. <div className = { mainClassNames }>
  208. <div className = 'chrome-extension-banner__container'>
  209. <div
  210. className = 'chrome-extension-banner__icon-container' />
  211. <div
  212. className = 'chrome-extension-banner__text-container'>
  213. { t('chromeExtensionBanner.installExtensionText') }
  214. </div>
  215. <div
  216. className = 'chrome-extension-banner__close-container'
  217. onClick = { this._onClosePressed }>
  218. <Icon
  219. className = 'gray'
  220. size = { 12 }
  221. src = { IconClose } />
  222. </div>
  223. </div>
  224. <div
  225. className = 'chrome-extension-banner__button-container'>
  226. <div
  227. className = 'chrome-extension-banner__button-open-url'
  228. onClick = { this._onInstallExtensionClick }>
  229. <div
  230. className = 'chrome-extension-banner__button-text'>
  231. { t('chromeExtensionBanner.buttonText') }
  232. </div>
  233. </div>
  234. </div>
  235. <div className = 'chrome-extension-banner__checkbox-container'>
  236. <label className = 'chrome-extension-banner__checkbox-label'>
  237. <input
  238. checked = { this.state.dontShowAgainChecked }
  239. onChange = { this._onDontShowAgainChange }
  240. type = 'checkbox' />
  241. &nbsp;{ t('chromeExtensionBanner.dontShowAgain') }
  242. </label>
  243. </div>
  244. </div>
  245. );
  246. }
  247. }
  248. /**
  249. * Function that maps parts of Redux state tree into component props.
  250. *
  251. * @param {Object} state - Redux state.
  252. * @returns {Object}
  253. */
  254. const _mapStateToProps = state => {
  255. const bannerCfg = state['features/base/config'].chromeExtensionBanner || {};
  256. return {
  257. chromeExtensionUrl: bannerCfg.url,
  258. chromeExtensionsInfo: bannerCfg.chromeExtensionsInfo || [],
  259. conference: getCurrentConference(state),
  260. iAmRecorder: state['features/base/config'].iAmRecorder
  261. };
  262. };
  263. export default translate(connect(_mapStateToProps)(ChromeExtensionBanner));