Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ScreenObtainer.js 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. /* global chrome, $, alert */
  2. import JitsiTrackError from '../../JitsiTrackError';
  3. import * as JitsiTrackErrors from '../../JitsiTrackErrors';
  4. import RTCBrowserType from './RTCBrowserType';
  5. const logger = require('jitsi-meet-logger').getLogger(__filename);
  6. const GlobalOnErrorHandler = require('../util/GlobalOnErrorHandler');
  7. /**
  8. * Indicates whether the Chrome desktop sharing extension is installed.
  9. * @type {boolean}
  10. */
  11. let chromeExtInstalled = false;
  12. /**
  13. * Indicates whether an update of the Chrome desktop sharing extension is
  14. * required.
  15. * @type {boolean}
  16. */
  17. let chromeExtUpdateRequired = false;
  18. /**
  19. * Whether the jidesha extension for firefox is installed for the domain on
  20. * which we are running. Null designates an unknown value.
  21. * @type {null}
  22. */
  23. let firefoxExtInstalled = null;
  24. /**
  25. * If set to true, detection of an installed firefox extension will be started
  26. * again the next time obtainScreenOnFirefox is called (e.g. next time the
  27. * user tries to enable screen sharing).
  28. */
  29. let reDetectFirefoxExtension = false;
  30. let gumFunction = null;
  31. /**
  32. * The error returned by chrome when trying to start inline installation from
  33. * popup.
  34. */
  35. const CHROME_EXTENSION_POPUP_ERROR
  36. = 'Inline installs can not be initiated from pop-up windows.';
  37. /**
  38. * The error returned by chrome when trying to start inline installation from
  39. * iframe.
  40. */
  41. const CHROME_EXTENSION_IFRAME_ERROR
  42. = 'Chrome Web Store installations can only be started by the top frame.';
  43. /**
  44. * The error returned by chrome when trying to start inline installation
  45. * not from the "main" whitelisted site.
  46. * @type {string}
  47. */
  48. const CHROME_EXTENSION_INLINE_ERROR
  49. = 'Installs can only be initiated by one of'
  50. + ' the Chrome Web Store item\'s verified sites.';
  51. /**
  52. * The error returned by chrome when trying to start inline installation
  53. * with extension that doesn't support inline installation.
  54. *
  55. * @type {string}
  56. */
  57. const CHROME_EXTENSION_INLINE_NOT_SUPPORTED_ERROR
  58. = 'Inline installation is not supported for this item. '
  59. + 'The user will be redirected to the Chrome Web Store.';
  60. /**
  61. * The error message returned by chrome when the extension is installed.
  62. */
  63. const CHROME_NO_EXTENSION_ERROR_MSG // eslint-disable-line no-unused-vars
  64. = 'Could not establish connection. Receiving end does not exist.';
  65. /**
  66. * The error message returned by chrome when the extension install action needs
  67. * to be initiated by a user gesture.
  68. * @type {string}
  69. */
  70. const CHROME_USER_GESTURE_REQ_ERROR
  71. = 'Chrome Web Store installations can only be initated by a user gesture.';
  72. /**
  73. * Handles obtaining a stream from a screen capture on different browsers.
  74. */
  75. const ScreenObtainer = {
  76. /**
  77. * If not <tt>null</tt> it means that the initialization process is still in
  78. * progress. It is used to make desktop stream request wait and continue
  79. * after it's done.
  80. * {@type Promise|null}
  81. */
  82. intChromeExtPromise: null,
  83. obtainStream: null,
  84. /**
  85. * Initializes the function used to obtain a screen capture
  86. * (this.obtainStream).
  87. *
  88. * @param {object} options
  89. * @param {boolean} [options.disableDesktopSharing]
  90. * @param {boolean} [options.desktopSharingChromeDisabled]
  91. * @param {boolean} [options.desktopSharingChromeExtId]
  92. * @param {boolean} [options.desktopSharingFirefoxDisabled]
  93. * @param {boolean} [options.desktopSharingFirefoxExtId] (deprecated)
  94. * @param {Function} gum GUM method
  95. */
  96. init(options = {
  97. disableDesktopSharing: false,
  98. desktopSharingChromeDisabled: false,
  99. desktopSharingChromeExtId: null,
  100. desktopSharingFirefoxDisabled: false,
  101. desktopSharingFirefoxExtId: null
  102. }, gum) {
  103. // eslint-disable-next-line no-param-reassign
  104. this.options = options = options || {};
  105. gumFunction = gum;
  106. this.obtainStream
  107. = this.options.disableDesktopSharing
  108. ? null : this._createObtainStreamMethod(options);
  109. if (!this.obtainStream) {
  110. logger.info('Desktop sharing disabled');
  111. }
  112. },
  113. /**
  114. * Returns a method which will be used to obtain the screen sharing stream
  115. * (based on the browser type).
  116. *
  117. * @param {object} options passed from {@link init} - check description
  118. * there
  119. * @returns {Function}
  120. * @private
  121. */
  122. _createObtainStreamMethod(options) {
  123. if (RTCBrowserType.isNWJS()) {
  124. return (_, onSuccess, onFailure) => {
  125. window.JitsiMeetNW.obtainDesktopStream(
  126. onSuccess,
  127. (error, constraints) => {
  128. let jitsiError;
  129. // FIXME:
  130. // This is very very dirty fix for recognising that the
  131. // user have clicked the cancel button from the Desktop
  132. // sharing pick window. The proper solution would be to
  133. // detect this in the NWJS application by checking the
  134. // streamId === "". Even better solution would be to
  135. // stop calling GUM from the NWJS app and just pass the
  136. // streamId to lib-jitsi-meet. This way the desktop
  137. // sharing implementation for NWJS and chrome extension
  138. // will be the same and lib-jitsi-meet will be able to
  139. // control the constraints, check the streamId, etc.
  140. //
  141. // I cannot find documentation about "InvalidStateError"
  142. // but this is what we are receiving from GUM when the
  143. // streamId for the desktop sharing is "".
  144. if (error && error.name === 'InvalidStateError') {
  145. jitsiError = new JitsiTrackError(
  146. JitsiTrackErrors.CHROME_EXTENSION_USER_CANCELED
  147. );
  148. } else {
  149. jitsiError = new JitsiTrackError(
  150. error, constraints, [ 'desktop' ]);
  151. }
  152. (typeof onFailure === 'function')
  153. && onFailure(jitsiError);
  154. });
  155. };
  156. } else if (RTCBrowserType.isElectron()) {
  157. return this.obtainScreenOnElectron;
  158. } else if (RTCBrowserType.isTemasysPluginUsed()) {
  159. // XXX Don't require Temasys unless it's to be used because it
  160. // doesn't run on React Native, for example.
  161. const plugin
  162. = require('./adapter.screenshare').WebRTCPlugin.plugin;
  163. if (!plugin.HasScreensharingFeature) {
  164. logger.warn(
  165. 'Screensharing not supported by this plugin version');
  166. return null;
  167. } else if (!plugin.isScreensharingAvailable) {
  168. logger.warn(
  169. 'Screensharing not available with Temasys plugin on'
  170. + ' this site');
  171. return null;
  172. }
  173. logger.info('Using Temasys plugin for desktop sharing');
  174. return obtainWebRTCScreen;
  175. } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
  176. if ((RTCBrowserType.getChromeVersion()
  177. || RTCBrowserType.getOperaVersion()) < 34) {
  178. logger.info('Chrome extension not supported until ver 34');
  179. return null;
  180. } else if (options.desktopSharingChromeDisabled
  181. || options.desktopSharingChromeMethod === false
  182. || !options.desktopSharingChromeExtId) {
  183. // TODO: desktopSharingChromeMethod is deprecated, remove.
  184. return null;
  185. }
  186. logger.info('Using Chrome extension for desktop sharing');
  187. this.intChromeExtPromise
  188. = initChromeExtension(options).then(() => {
  189. this.intChromeExtPromise = null;
  190. });
  191. return this.obtainScreenFromExtension;
  192. } else if (RTCBrowserType.isFirefox()) {
  193. if (options.desktopSharingFirefoxDisabled) {
  194. return null;
  195. } else if (window.location.protocol === 'http:') {
  196. logger.log('Screen sharing is not supported over HTTP. '
  197. + 'Use of HTTPS is required.');
  198. return null;
  199. }
  200. initFirefoxExtensionDetection(options);
  201. return this.obtainScreenOnFirefox;
  202. }
  203. logger.log(
  204. 'Screen sharing not supported by the current browser: ',
  205. RTCBrowserType.getBrowserType(),
  206. RTCBrowserType.getBrowserName());
  207. return null;
  208. },
  209. /**
  210. * Checks whether obtaining a screen capture is supported in the current
  211. * environment.
  212. * @returns {boolean}
  213. */
  214. isSupported() {
  215. return this.obtainStream !== null;
  216. },
  217. /**
  218. * Obtains a screen capture stream on Firefox.
  219. * @param callback
  220. * @param errorCallback
  221. */
  222. obtainScreenOnFirefox(options, callback, errorCallback) {
  223. let extensionRequired = false;
  224. const { desktopSharingFirefoxMaxVersionExtRequired } = this.options;
  225. if (desktopSharingFirefoxMaxVersionExtRequired === -1
  226. || (desktopSharingFirefoxMaxVersionExtRequired >= 0
  227. && RTCBrowserType.getFirefoxVersion()
  228. <= desktopSharingFirefoxMaxVersionExtRequired)) {
  229. extensionRequired = true;
  230. logger.log(
  231. `Jidesha extension required on firefox version ${
  232. RTCBrowserType.getFirefoxVersion()}`);
  233. }
  234. if (!extensionRequired || firefoxExtInstalled === true) {
  235. obtainWebRTCScreen(options, callback, errorCallback);
  236. return;
  237. }
  238. if (reDetectFirefoxExtension) {
  239. reDetectFirefoxExtension = false;
  240. initFirefoxExtensionDetection(this.options);
  241. }
  242. // Give it some (more) time to initialize, and assume lack of
  243. // extension if it hasn't.
  244. if (firefoxExtInstalled === null) {
  245. window.setTimeout(
  246. () => {
  247. if (firefoxExtInstalled === null) {
  248. firefoxExtInstalled = false;
  249. }
  250. this.obtainScreenOnFirefox(callback, errorCallback);
  251. },
  252. 300);
  253. logger.log(
  254. 'Waiting for detection of jidesha on firefox to finish.');
  255. return;
  256. }
  257. // We need an extension and it isn't installed.
  258. // Make sure we check for the extension when the user clicks again.
  259. firefoxExtInstalled = null;
  260. reDetectFirefoxExtension = true;
  261. // Make sure desktopsharing knows that we failed, so that it doesn't get
  262. // stuck in 'switching' mode.
  263. errorCallback(
  264. new JitsiTrackError(JitsiTrackErrors.FIREFOX_EXTENSION_NEEDED));
  265. },
  266. /**
  267. * Obtains a screen capture stream on Electron.
  268. *
  269. * @param {Object} [options] - Screen sharing options.
  270. * @param {Array<string>} [options.desktopSharingSources] - Array with the
  271. * sources that have to be displayed in the desktop picker window ('screen',
  272. * 'window', etc.).
  273. * @param onSuccess - Success callback.
  274. * @param onFailure - Failure callback.
  275. */
  276. obtainScreenOnElectron(options = {}, onSuccess, onFailure) {
  277. if (window.JitsiMeetScreenObtainer
  278. && window.JitsiMeetScreenObtainer.openDesktopPicker) {
  279. window.JitsiMeetScreenObtainer.openDesktopPicker(
  280. {
  281. desktopSharingSources:
  282. options.desktopSharingSources
  283. || this.options.desktopSharingChromeSources
  284. },
  285. (streamId, streamType) =>
  286. onGetStreamResponse(
  287. {
  288. streamId,
  289. streamType
  290. },
  291. onSuccess,
  292. onFailure
  293. ),
  294. err => onFailure(new JitsiTrackError(
  295. JitsiTrackErrors.ELECTRON_DESKTOP_PICKER_ERROR,
  296. err
  297. ))
  298. );
  299. } else {
  300. onFailure(new JitsiTrackError(
  301. JitsiTrackErrors.ELECTRON_DESKTOP_PICKER_NOT_FOUND));
  302. }
  303. },
  304. /**
  305. * Asks Chrome extension to call chooseDesktopMedia and gets chrome
  306. * 'desktop' stream for returned stream token.
  307. */
  308. obtainScreenFromExtension(options, streamCallback, failCallback) {
  309. if (this.intChromeExtPromise !== null) {
  310. this.intChromeExtPromise.then(() => {
  311. this.obtainScreenFromExtension(
  312. options, streamCallback, failCallback);
  313. });
  314. return;
  315. }
  316. const {
  317. desktopSharingChromeExtId,
  318. desktopSharingChromeSources
  319. } = this.options;
  320. const gumOptions = {
  321. desktopSharingChromeExtId,
  322. desktopSharingChromeSources:
  323. options.desktopSharingSources
  324. || desktopSharingChromeSources
  325. };
  326. if (chromeExtInstalled) {
  327. doGetStreamFromExtension(
  328. gumOptions,
  329. streamCallback,
  330. failCallback);
  331. } else {
  332. if (chromeExtUpdateRequired) {
  333. /* eslint-disable no-alert */
  334. alert(
  335. 'Jitsi Desktop Streamer requires update. '
  336. + 'Changes will take effect after next Chrome restart.');
  337. /* eslint-enable no-alert */
  338. }
  339. // for opera there is no inline install
  340. // extension "Download Chrome Extension" allows us to open
  341. // the chrome webstore and install from there and then activate our
  342. // extension
  343. if (RTCBrowserType.isOpera()) {
  344. this.handleExternalInstall(options, streamCallback,
  345. failCallback);
  346. return;
  347. }
  348. try {
  349. chrome.webstore.install(
  350. getWebStoreInstallUrl(this.options),
  351. arg => {
  352. logger.log('Extension installed successfully', arg);
  353. chromeExtInstalled = true;
  354. // We need to give a moment to the endpoint to become
  355. // available.
  356. waitForExtensionAfterInstall(this.options, 200, 10)
  357. .then(() => {
  358. doGetStreamFromExtension(
  359. gumOptions,
  360. streamCallback,
  361. failCallback);
  362. })
  363. .catch(() => {
  364. this.handleExtensionInstallationError(options,
  365. streamCallback, failCallback);
  366. });
  367. },
  368. this.handleExtensionInstallationError.bind(this,
  369. options, streamCallback, failCallback)
  370. );
  371. } catch (e) {
  372. this.handleExtensionInstallationError(options, streamCallback,
  373. failCallback, e);
  374. }
  375. }
  376. },
  377. /* eslint-disable max-params */
  378. handleExternalInstall(options, streamCallback, failCallback, e) {
  379. const webStoreInstallUrl = getWebStoreInstallUrl(this.options);
  380. options.listener('waitingForExtension', webStoreInstallUrl);
  381. this.checkForChromeExtensionOnInterval(options, streamCallback,
  382. failCallback, e);
  383. },
  384. handleExtensionInstallationError(options, streamCallback, failCallback, e) {
  385. const webStoreInstallUrl = getWebStoreInstallUrl(this.options);
  386. if ((CHROME_EXTENSION_POPUP_ERROR === e
  387. || CHROME_EXTENSION_IFRAME_ERROR === e
  388. || CHROME_EXTENSION_INLINE_ERROR === e
  389. || CHROME_EXTENSION_INLINE_NOT_SUPPORTED_ERROR === e)
  390. && options.interval > 0
  391. && typeof options.checkAgain === 'function'
  392. && typeof options.listener === 'function') {
  393. this.handleExternalInstall(options, streamCallback,
  394. failCallback, e);
  395. return;
  396. }
  397. const msg
  398. = `Failed to install the extension from ${webStoreInstallUrl}`;
  399. logger.log(msg, e);
  400. const error
  401. = e === CHROME_USER_GESTURE_REQ_ERROR
  402. ? JitsiTrackErrors.CHROME_EXTENSION_USER_GESTURE_REQUIRED
  403. : JitsiTrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR;
  404. failCallback(new JitsiTrackError(error, msg));
  405. },
  406. /* eslint-enable max-params */
  407. checkForChromeExtensionOnInterval(options, streamCallback, failCallback) {
  408. if (options.checkAgain() === false) {
  409. failCallback(new JitsiTrackError(
  410. JitsiTrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR));
  411. return;
  412. }
  413. waitForExtensionAfterInstall(this.options, options.interval, 1)
  414. .then(() => {
  415. chromeExtInstalled = true;
  416. options.listener('extensionFound');
  417. this.obtainScreenFromExtension(options,
  418. streamCallback, failCallback);
  419. })
  420. .catch(() => {
  421. this.checkForChromeExtensionOnInterval(options,
  422. streamCallback, failCallback);
  423. });
  424. }
  425. };
  426. /**
  427. * Obtains a desktop stream using getUserMedia.
  428. * For this to work on Chrome, the
  429. * 'chrome://flags/#enable-usermedia-screen-capture' flag must be enabled.
  430. *
  431. * On firefox, the document's domain must be white-listed in the
  432. * 'media.getusermedia.screensharing.allowed_domains' preference in
  433. * 'about:config'.
  434. */
  435. function obtainWebRTCScreen(options, streamCallback, failCallback) {
  436. gumFunction(
  437. [ 'screen' ],
  438. stream => streamCallback({ stream }),
  439. failCallback
  440. );
  441. }
  442. /**
  443. * Constructs inline install URL for Chrome desktop streaming extension.
  444. * The 'chromeExtensionId' must be defined in options parameter.
  445. * @param options supports "desktopSharingChromeExtId"
  446. * @returns {string}
  447. */
  448. function getWebStoreInstallUrl(options) {
  449. return (
  450. `https://chrome.google.com/webstore/detail/${
  451. options.desktopSharingChromeExtId}`);
  452. }
  453. /**
  454. * Checks whether an update of the Chrome extension is required.
  455. * @param minVersion minimal required version
  456. * @param extVersion current extension version
  457. * @returns {boolean}
  458. */
  459. function isUpdateRequired(minVersion, extVersion) {
  460. try {
  461. const s1 = minVersion.split('.');
  462. const s2 = extVersion.split('.');
  463. const len = Math.max(s1.length, s2.length);
  464. for (let i = 0; i < len; i++) {
  465. let n1 = 0,
  466. n2 = 0;
  467. if (i < s1.length) {
  468. n1 = parseInt(s1[i], 10);
  469. }
  470. if (i < s2.length) {
  471. n2 = parseInt(s2[i], 10);
  472. }
  473. if (isNaN(n1) || isNaN(n2)) {
  474. return true;
  475. } else if (n1 !== n2) {
  476. return n1 > n2;
  477. }
  478. }
  479. // will happen if both versions have identical numbers in
  480. // their components (even if one of them is longer, has more components)
  481. return false;
  482. } catch (e) {
  483. GlobalOnErrorHandler.callErrorHandler(e);
  484. logger.error('Failed to parse extension version', e);
  485. return true;
  486. }
  487. }
  488. /**
  489. *
  490. * @param callback
  491. * @param options
  492. */
  493. function checkChromeExtInstalled(callback, options) {
  494. if (typeof chrome === 'undefined' || !chrome || !chrome.runtime) {
  495. // No API, so no extension for sure
  496. callback(false, false);
  497. return;
  498. }
  499. chrome.runtime.sendMessage(
  500. options.desktopSharingChromeExtId,
  501. { getVersion: true },
  502. response => {
  503. if (!response || !response.version) {
  504. // Communication failure - assume that no endpoint exists
  505. logger.warn(
  506. 'Extension not installed?: ', chrome.runtime.lastError);
  507. callback(false, false);
  508. return;
  509. }
  510. // Check installed extension version
  511. const extVersion = response.version;
  512. logger.log(`Extension version is: ${extVersion}`);
  513. const updateRequired
  514. = isUpdateRequired(
  515. options.desktopSharingChromeMinExtVersion,
  516. extVersion);
  517. callback(!updateRequired, updateRequired);
  518. }
  519. );
  520. }
  521. /**
  522. *
  523. * @param options
  524. * @param streamCallback
  525. * @param failCallback
  526. */
  527. function doGetStreamFromExtension(options, streamCallback, failCallback) {
  528. // Sends 'getStream' msg to the extension.
  529. // Extension id must be defined in the config.
  530. chrome.runtime.sendMessage(
  531. options.desktopSharingChromeExtId,
  532. {
  533. getStream: true,
  534. sources: options.desktopSharingChromeSources
  535. },
  536. response => {
  537. if (!response) {
  538. // possibly re-wraping error message to make code consistent
  539. const lastError = chrome.runtime.lastError;
  540. failCallback(lastError instanceof Error
  541. ? lastError
  542. : new JitsiTrackError(
  543. JitsiTrackErrors.CHROME_EXTENSION_GENERIC_ERROR,
  544. lastError));
  545. return;
  546. }
  547. logger.log('Response from extension: ', response);
  548. onGetStreamResponse(response, streamCallback, failCallback);
  549. }
  550. );
  551. }
  552. /**
  553. * Initializes <link rel=chrome-webstore-item /> with extension id set in
  554. * config.js to support inline installs. Host site must be selected as main
  555. * website of published extension.
  556. * @param options supports "desktopSharingChromeExtId"
  557. */
  558. function initInlineInstalls(options) {
  559. if ($('link[rel=chrome-webstore-item]').length === 0) {
  560. $('head').append('<link rel="chrome-webstore-item">');
  561. }
  562. $('link[rel=chrome-webstore-item]').attr('href',
  563. getWebStoreInstallUrl(options));
  564. }
  565. /**
  566. *
  567. * @param options
  568. *
  569. * @return {Promise} - a Promise resolved once the initialization process is
  570. * finished.
  571. */
  572. function initChromeExtension(options) {
  573. // Initialize Chrome extension inline installs
  574. initInlineInstalls(options);
  575. return new Promise(resolve => {
  576. // Check if extension is installed
  577. checkChromeExtInstalled((installed, updateRequired) => {
  578. chromeExtInstalled = installed;
  579. chromeExtUpdateRequired = updateRequired;
  580. logger.info(
  581. `Chrome extension installed: ${
  582. chromeExtInstalled} updateRequired: ${
  583. chromeExtUpdateRequired}`);
  584. resolve();
  585. }, options);
  586. });
  587. }
  588. /**
  589. * Checks "retries" times on every "waitInterval"ms whether the ext is alive.
  590. * @param {Object} options the options passed to ScreanObtainer.obtainStream
  591. * @param {int} waitInterval the number of ms between retries
  592. * @param {int} retries the number of retries
  593. * @returns {Promise} returns promise that will be resolved when the extension
  594. * is alive and rejected if the extension is not alive even after "retries"
  595. * checks
  596. */
  597. function waitForExtensionAfterInstall(options, waitInterval, retries) {
  598. if (retries === 0) {
  599. return Promise.reject();
  600. }
  601. return new Promise((resolve, reject) => {
  602. let currentRetries = retries;
  603. const interval = window.setInterval(() => {
  604. checkChromeExtInstalled(installed => {
  605. if (installed) {
  606. window.clearInterval(interval);
  607. resolve();
  608. } else {
  609. currentRetries--;
  610. if (currentRetries === 0) {
  611. reject();
  612. window.clearInterval(interval);
  613. }
  614. }
  615. }, options);
  616. }, waitInterval);
  617. });
  618. }
  619. /**
  620. * Handles response from external application / extension and calls GUM to
  621. * receive the desktop streams or reports error.
  622. * @param {object} response
  623. * @param {string} response.streamId - the streamId for the desktop stream
  624. * @param {string} response.error - error to be reported.
  625. * @param {Function} onSuccess - callback for success.
  626. * @param {Function} onFailure - callback for failure.
  627. */
  628. function onGetStreamResponse(
  629. { streamId, streamType, error },
  630. onSuccess,
  631. onFailure) {
  632. if (streamId) {
  633. gumFunction(
  634. [ 'desktop' ],
  635. stream => onSuccess({
  636. stream,
  637. sourceId: streamId,
  638. sourceType: streamType
  639. }),
  640. onFailure,
  641. { desktopStream: streamId });
  642. } else {
  643. // As noted in Chrome Desktop Capture API:
  644. // If user didn't select any source (i.e. canceled the prompt)
  645. // then the callback is called with an empty streamId.
  646. if (streamId === '') {
  647. onFailure(new JitsiTrackError(
  648. JitsiTrackErrors.CHROME_EXTENSION_USER_CANCELED));
  649. return;
  650. }
  651. onFailure(new JitsiTrackError(
  652. JitsiTrackErrors.CHROME_EXTENSION_GENERIC_ERROR,
  653. error));
  654. }
  655. }
  656. /**
  657. * Starts the detection of an installed jidesha extension for firefox.
  658. * @param options supports "desktopSharingFirefoxDisabled",
  659. * "desktopSharingFirefoxExtId"
  660. */
  661. function initFirefoxExtensionDetection(options) {
  662. if (options.desktopSharingFirefoxDisabled) {
  663. return;
  664. }
  665. if (firefoxExtInstalled === false || firefoxExtInstalled === true) {
  666. return;
  667. }
  668. if (!options.desktopSharingFirefoxExtId) {
  669. firefoxExtInstalled = false;
  670. return;
  671. }
  672. const img = document.createElement('img');
  673. img.onload = () => {
  674. logger.log('Detected firefox screen sharing extension.');
  675. firefoxExtInstalled = true;
  676. };
  677. img.onerror = () => {
  678. logger.log('Detected lack of firefox screen sharing extension.');
  679. firefoxExtInstalled = false;
  680. };
  681. // The jidesha extension exposes an empty image file under the url:
  682. // "chrome://EXT_ID/content/DOMAIN.png"
  683. // Where EXT_ID is the ID of the extension with "@" replaced by ".", and
  684. // DOMAIN is a domain whitelisted by the extension.
  685. const extId = options.desktopSharingFirefoxExtId.replace('@', '.');
  686. const domain = document.location.hostname;
  687. const src = `chrome://${extId}/content/${domain}.png`;
  688. img.setAttribute('src', src);
  689. }
  690. export default ScreenObtainer;