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.

API.js 35KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. // @flow
  2. import Logger from 'jitsi-meet-logger';
  3. import {
  4. createApiEvent,
  5. sendAnalytics
  6. } from '../../react/features/analytics';
  7. import {
  8. getCurrentConference,
  9. sendTones,
  10. setPassword,
  11. setSubject
  12. } from '../../react/features/base/conference';
  13. import { overwriteConfig, getWhitelistedJSON } from '../../react/features/base/config';
  14. import { parseJWTFromURLParams } from '../../react/features/base/jwt';
  15. import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
  16. import { MEDIA_TYPE } from '../../react/features/base/media';
  17. import {
  18. getLocalParticipant,
  19. getParticipantById,
  20. participantUpdated,
  21. pinParticipant,
  22. kickParticipant
  23. } from '../../react/features/base/participants';
  24. import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
  25. import { openChat } from '../../react/features/chat/actions.web';
  26. import {
  27. processExternalDeviceRequest
  28. } from '../../react/features/device-selection/functions';
  29. import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
  30. import { toggleE2EE } from '../../react/features/e2ee/actions';
  31. import { invite } from '../../react/features/invite';
  32. import {
  33. captureLargeVideoScreenshot,
  34. resizeLargeVideo,
  35. selectParticipantInLargeVideo
  36. } from '../../react/features/large-video/actions';
  37. import { toggleLobbyMode } from '../../react/features/lobby/actions.web';
  38. import { RECORDING_TYPES } from '../../react/features/recording/constants';
  39. import { getActiveSession } from '../../react/features/recording/functions';
  40. import { toggleTileView } from '../../react/features/video-layout';
  41. import { muteAllParticipants } from '../../react/features/video-menu/actions';
  42. import { setVideoQuality } from '../../react/features/video-quality';
  43. import { getJitsiMeetTransport } from '../transport';
  44. import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
  45. const logger = Logger.getLogger(__filename);
  46. declare var APP: Object;
  47. /**
  48. * List of the available commands.
  49. */
  50. let commands = {};
  51. /**
  52. * The transport instance used for communication with external apps.
  53. *
  54. * @type {Transport}
  55. */
  56. const transport = getJitsiMeetTransport();
  57. /**
  58. * The current audio availability.
  59. *
  60. * @type {boolean}
  61. */
  62. let audioAvailable = true;
  63. /**
  64. * The current video availability.
  65. *
  66. * @type {boolean}
  67. */
  68. let videoAvailable = true;
  69. /**
  70. * Initializes supported commands.
  71. *
  72. * @returns {void}
  73. */
  74. function initCommands() {
  75. commands = {
  76. 'display-name': displayName => {
  77. sendAnalytics(createApiEvent('display.name.changed'));
  78. APP.conference.changeLocalDisplayName(displayName);
  79. },
  80. 'mute-everyone': mediaType => {
  81. const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
  82. sendAnalytics(createApiEvent('muted-everyone'));
  83. const participants = APP.store.getState()['features/base/participants'];
  84. const localIds = participants
  85. .filter(participant => participant.local)
  86. .filter(participant => participant.role === 'moderator')
  87. .map(participant => participant.id);
  88. APP.store.dispatch(muteAllParticipants(localIds, muteMediaType));
  89. },
  90. 'toggle-lobby': isLobbyEnabled => {
  91. APP.store.dispatch(toggleLobbyMode(isLobbyEnabled));
  92. },
  93. 'password': password => {
  94. const { conference, passwordRequired }
  95. = APP.store.getState()['features/base/conference'];
  96. if (passwordRequired) {
  97. sendAnalytics(createApiEvent('submit.password'));
  98. APP.store.dispatch(setPassword(
  99. passwordRequired,
  100. passwordRequired.join,
  101. password
  102. ));
  103. } else {
  104. sendAnalytics(createApiEvent('password.changed'));
  105. APP.store.dispatch(setPassword(
  106. conference,
  107. conference.lock,
  108. password
  109. ));
  110. }
  111. },
  112. 'pin-participant': id => {
  113. logger.debug('Pin participant command received');
  114. sendAnalytics(createApiEvent('participant.pinned'));
  115. APP.store.dispatch(pinParticipant(id));
  116. },
  117. 'proxy-connection-event': event => {
  118. APP.conference.onProxyConnectionEvent(event);
  119. },
  120. 'resize-large-video': (width, height) => {
  121. logger.debug('Resize large video command received');
  122. sendAnalytics(createApiEvent('largevideo.resized'));
  123. APP.store.dispatch(resizeLargeVideo(width, height));
  124. },
  125. 'send-tones': (options = {}) => {
  126. const { duration, tones, pause } = options;
  127. APP.store.dispatch(sendTones(tones, duration, pause));
  128. },
  129. 'set-large-video-participant': participantId => {
  130. logger.debug('Set large video participant command received');
  131. sendAnalytics(createApiEvent('largevideo.participant.set'));
  132. APP.store.dispatch(selectParticipantInLargeVideo(participantId));
  133. },
  134. 'subject': subject => {
  135. sendAnalytics(createApiEvent('subject.changed'));
  136. APP.store.dispatch(setSubject(subject));
  137. },
  138. 'submit-feedback': feedback => {
  139. sendAnalytics(createApiEvent('submit.feedback'));
  140. APP.conference.submitFeedback(feedback.score, feedback.message);
  141. },
  142. 'toggle-audio': () => {
  143. sendAnalytics(createApiEvent('toggle-audio'));
  144. logger.log('Audio toggle: API command received');
  145. APP.conference.toggleAudioMuted(false /* no UI */);
  146. },
  147. 'toggle-video': () => {
  148. sendAnalytics(createApiEvent('toggle-video'));
  149. logger.log('Video toggle: API command received');
  150. APP.conference.toggleVideoMuted(false /* no UI */);
  151. },
  152. 'toggle-film-strip': () => {
  153. sendAnalytics(createApiEvent('film.strip.toggled'));
  154. APP.UI.toggleFilmstrip();
  155. },
  156. 'toggle-chat': () => {
  157. sendAnalytics(createApiEvent('chat.toggled'));
  158. APP.UI.toggleChat();
  159. },
  160. 'toggle-raise-hand': () => {
  161. const localParticipant = getLocalParticipant(APP.store.getState());
  162. if (!localParticipant) {
  163. return;
  164. }
  165. const { raisedHand } = localParticipant;
  166. sendAnalytics(createApiEvent('raise-hand.toggled'));
  167. APP.store.dispatch(
  168. participantUpdated({
  169. id: APP.conference.getMyUserId(),
  170. local: true,
  171. raisedHand: !raisedHand
  172. })
  173. );
  174. },
  175. /**
  176. * Callback to invoke when the "toggle-share-screen" command is received.
  177. *
  178. * @param {Object} options - Additional details of how to perform
  179. * the action. Note this parameter is undocumented and experimental.
  180. * @param {boolean} options.enable - Whether trying to enable screen
  181. * sharing or to turn it off.
  182. * @returns {void}
  183. */
  184. 'toggle-share-screen': (options = {}) => {
  185. sendAnalytics(createApiEvent('screen.sharing.toggled'));
  186. toggleScreenSharing(options.enable);
  187. },
  188. 'toggle-tile-view': () => {
  189. sendAnalytics(createApiEvent('tile-view.toggled'));
  190. APP.store.dispatch(toggleTileView());
  191. },
  192. 'video-hangup': (showFeedbackDialog = true) => {
  193. sendAnalytics(createApiEvent('video.hangup'));
  194. APP.conference.hangup(showFeedbackDialog);
  195. },
  196. 'email': email => {
  197. sendAnalytics(createApiEvent('email.changed'));
  198. APP.conference.changeLocalEmail(email);
  199. },
  200. 'avatar-url': avatarUrl => {
  201. sendAnalytics(createApiEvent('avatar.url.changed'));
  202. APP.conference.changeLocalAvatarUrl(avatarUrl);
  203. },
  204. 'send-endpoint-text-message': (to, text) => {
  205. logger.debug('Send endpoint message command received');
  206. try {
  207. APP.conference.sendEndpointMessage(to, {
  208. name: ENDPOINT_TEXT_MESSAGE_NAME,
  209. text
  210. });
  211. } catch (err) {
  212. logger.error('Failed sending endpoint text message', err);
  213. }
  214. },
  215. 'toggle-e2ee': enabled => {
  216. logger.debug('Toggle E2EE key command received');
  217. APP.store.dispatch(toggleE2EE(enabled));
  218. },
  219. 'set-video-quality': frameHeight => {
  220. logger.debug('Set video quality command received');
  221. sendAnalytics(createApiEvent('set.video.quality'));
  222. APP.store.dispatch(setVideoQuality(frameHeight));
  223. },
  224. /**
  225. * Starts a file recording or streaming session depending on the passed on params.
  226. * For RTMP streams, `rtmpStreamKey` must be passed on. `rtmpBroadcastID` is optional.
  227. * For youtube streams, `youtubeStreamKey` must be passed on. `youtubeBroadcastID` is optional.
  228. * For dropbox recording, recording `mode` should be `file` and a dropbox oauth2 token must be provided.
  229. * For file recording, recording `mode` should be `file` and optionally `shouldShare` could be passed on.
  230. * No other params should be passed.
  231. *
  232. * @param { string } arg.mode - Recording mode, either `file` or `stream`.
  233. * @param { string } arg.dropboxToken - Dropbox oauth2 token.
  234. * @param { string } arg.rtmpStreamKey - The RTMP stream key.
  235. * @param { string } arg.rtmpBroadcastID - The RTMP braodcast ID.
  236. * @param { boolean } arg.shouldShare - Whether the recording should be shared with the participants or not.
  237. * Only applies to certain jitsi meet deploys.
  238. * @param { string } arg.youtubeStreamKey - The youtube stream key.
  239. * @param { string } arg.youtubeBroadcastID - The youtube broacast ID.
  240. * @returns {void}
  241. */
  242. 'start-recording': ({
  243. mode,
  244. dropboxToken,
  245. shouldShare,
  246. rtmpStreamKey,
  247. rtmpBroadcastID,
  248. youtubeStreamKey,
  249. youtubeBroadcastID
  250. }) => {
  251. const state = APP.store.getState();
  252. const conference = getCurrentConference(state);
  253. if (!conference) {
  254. logger.error('Conference is not defined');
  255. return;
  256. }
  257. if (dropboxToken && !isDropboxEnabled(state)) {
  258. logger.error('Failed starting recording: dropbox is not enabled on this deployment');
  259. return;
  260. }
  261. if (mode === JitsiRecordingConstants.mode.STREAM && !(youtubeStreamKey || rtmpStreamKey)) {
  262. logger.error('Failed starting recording: missing youtube or RTMP stream key');
  263. return;
  264. }
  265. let recordingConfig;
  266. if (mode === JitsiRecordingConstants.mode.FILE) {
  267. if (dropboxToken) {
  268. recordingConfig = {
  269. mode: JitsiRecordingConstants.mode.FILE,
  270. appData: JSON.stringify({
  271. 'file_recording_metadata': {
  272. 'upload_credentials': {
  273. 'service_name': RECORDING_TYPES.DROPBOX,
  274. 'token': dropboxToken
  275. }
  276. }
  277. })
  278. };
  279. } else {
  280. recordingConfig = {
  281. mode: JitsiRecordingConstants.mode.FILE,
  282. appData: JSON.stringify({
  283. 'file_recording_metadata': {
  284. 'share': shouldShare
  285. }
  286. })
  287. };
  288. }
  289. } else if (mode === JitsiRecordingConstants.mode.STREAM) {
  290. recordingConfig = {
  291. broadcastId: youtubeBroadcastID || rtmpBroadcastID,
  292. mode: JitsiRecordingConstants.mode.STREAM,
  293. streamId: youtubeStreamKey || rtmpStreamKey
  294. };
  295. } else {
  296. logger.error('Invalid recording mode provided');
  297. return;
  298. }
  299. conference.startRecording(recordingConfig);
  300. },
  301. /**
  302. * Stops a recording or streaming in progress.
  303. *
  304. * @param {string} mode - `file` or `stream`.
  305. * @returns {void}
  306. */
  307. 'stop-recording': mode => {
  308. const state = APP.store.getState();
  309. const conference = getCurrentConference(state);
  310. if (!conference) {
  311. logger.error('Conference is not defined');
  312. return;
  313. }
  314. if (![ JitsiRecordingConstants.mode.FILE, JitsiRecordingConstants.mode.STREAM ].includes(mode)) {
  315. logger.error('Invalid recording mode provided!');
  316. return;
  317. }
  318. const activeSession = getActiveSession(state, mode);
  319. if (activeSession && activeSession.id) {
  320. conference.stopRecording(activeSession.id);
  321. } else {
  322. logger.error('No recording or streaming session found');
  323. }
  324. },
  325. 'initiate-private-chat': participantId => {
  326. const state = APP.store.getState();
  327. const participant = getParticipantById(state, participantId);
  328. if (participant) {
  329. const { isOpen: isChatOpen } = state['features/chat'];
  330. if (!isChatOpen) {
  331. APP.UI.toggleChat();
  332. }
  333. APP.store.dispatch(openChat(participant));
  334. } else {
  335. logger.error('No participant found for the given participantId');
  336. }
  337. },
  338. 'cancel-private-chat': () => {
  339. APP.store.dispatch(setPrivateMessageRecipient());
  340. },
  341. 'kick-participant': participantId => {
  342. APP.store.dispatch(kickParticipant(participantId));
  343. },
  344. 'overwrite-config': config => {
  345. const whitelistedConfig = getWhitelistedJSON('config', config);
  346. APP.store.dispatch(overwriteConfig(whitelistedConfig));
  347. }
  348. };
  349. transport.on('event', ({ data, name }) => {
  350. if (name && commands[name]) {
  351. commands[name](...data);
  352. return true;
  353. }
  354. return false;
  355. });
  356. transport.on('request', (request, callback) => {
  357. const { dispatch, getState } = APP.store;
  358. if (processExternalDeviceRequest(dispatch, getState, request, callback)) {
  359. return true;
  360. }
  361. const { name } = request;
  362. switch (name) {
  363. case 'capture-largevideo-screenshot' :
  364. APP.store.dispatch(captureLargeVideoScreenshot())
  365. .then(dataURL => {
  366. let error;
  367. if (!dataURL) {
  368. error = new Error('No large video found!');
  369. }
  370. callback({
  371. error,
  372. dataURL
  373. });
  374. });
  375. break;
  376. case 'invite': {
  377. const { invitees } = request;
  378. if (!Array.isArray(invitees) || invitees.length === 0) {
  379. callback({
  380. error: new Error('Unexpected format of invitees')
  381. });
  382. break;
  383. }
  384. // The store should be already available because API.init is called
  385. // on appWillMount action.
  386. APP.store.dispatch(
  387. invite(invitees, true))
  388. .then(failedInvitees => {
  389. let error;
  390. let result;
  391. if (failedInvitees.length) {
  392. error = new Error('One or more invites failed!');
  393. } else {
  394. result = true;
  395. }
  396. callback({
  397. error,
  398. result
  399. });
  400. });
  401. break;
  402. }
  403. case 'is-audio-muted':
  404. callback(APP.conference.isLocalAudioMuted());
  405. break;
  406. case 'is-video-muted':
  407. callback(APP.conference.isLocalVideoMuted());
  408. break;
  409. case 'is-audio-available':
  410. callback(audioAvailable);
  411. break;
  412. case 'is-video-available':
  413. callback(videoAvailable);
  414. break;
  415. case 'is-sharing-screen':
  416. callback(Boolean(APP.conference.isSharingScreen));
  417. break;
  418. case 'get-content-sharing-participants': {
  419. const tracks = getState()['features/base/tracks'];
  420. const sharingParticipantIds = tracks.filter(tr => tr.videoType === 'desktop').map(t => t.participantId);
  421. callback({
  422. sharingParticipantIds
  423. });
  424. break;
  425. }
  426. case 'get-livestream-url': {
  427. const state = APP.store.getState();
  428. const conference = getCurrentConference(state);
  429. let livestreamUrl;
  430. if (conference) {
  431. const activeSession = getActiveSession(state, JitsiRecordingConstants.mode.STREAM);
  432. livestreamUrl = activeSession?.liveStreamViewURL;
  433. } else {
  434. logger.error('Conference is not defined');
  435. }
  436. callback({
  437. livestreamUrl
  438. });
  439. break;
  440. }
  441. default:
  442. return false;
  443. }
  444. return true;
  445. });
  446. }
  447. /**
  448. * Check whether the API should be enabled or not.
  449. *
  450. * @returns {boolean}
  451. */
  452. function shouldBeEnabled() {
  453. return (
  454. typeof API_ID === 'number'
  455. // XXX Enable the API when a JSON Web Token (JWT) is specified in
  456. // the location/URL because then it is very likely that the Jitsi
  457. // Meet (Web) app is being used by an external/wrapping (Web) app
  458. // and, consequently, the latter will need to communicate with the
  459. // former. (The described logic is merely a heuristic though.)
  460. || parseJWTFromURLParams());
  461. }
  462. /**
  463. * Executes on toggle-share-screen command.
  464. *
  465. * @param {boolean} [enable] - Whether this toggle is to explicitly enable or
  466. * disable screensharing. If not defined, the application will automatically
  467. * attempt to toggle between enabled and disabled. This boolean is useful for
  468. * explicitly setting desired screensharing state.
  469. * @returns {void}
  470. */
  471. function toggleScreenSharing(enable) {
  472. if (JitsiMeetJS.isDesktopSharingEnabled()) {
  473. APP.conference.toggleScreenSharing(enable).catch(() => {
  474. logger.warn('Failed to toggle screen-sharing');
  475. });
  476. }
  477. }
  478. /**
  479. * Implements API class that communicates with external API class and provides
  480. * interface to access Jitsi Meet features by external applications that embed
  481. * Jitsi Meet.
  482. */
  483. class API {
  484. _enabled: boolean;
  485. /**
  486. * Initializes the API. Setups message event listeners that will receive
  487. * information from external applications that embed Jitsi Meet. It also
  488. * sends a message to the external application that API is initialized.
  489. *
  490. * @param {Object} options - Optional parameters.
  491. * @returns {void}
  492. */
  493. init() {
  494. if (!shouldBeEnabled()) {
  495. return;
  496. }
  497. /**
  498. * Current status (enabled/disabled) of API.
  499. *
  500. * @private
  501. * @type {boolean}
  502. */
  503. this._enabled = true;
  504. initCommands();
  505. }
  506. /**
  507. * Notify external application (if API is enabled) that the large video
  508. * visibility changed.
  509. *
  510. * @param {boolean} isHidden - True if the large video is hidden and false
  511. * otherwise.
  512. * @returns {void}
  513. */
  514. notifyLargeVideoVisibilityChanged(isHidden: boolean) {
  515. this._sendEvent({
  516. name: 'large-video-visibility-changed',
  517. isVisible: !isHidden
  518. });
  519. }
  520. /**
  521. * Notifies the external application (spot) that the local jitsi-participant
  522. * has a status update.
  523. *
  524. * @param {Object} event - The message to pass onto spot.
  525. * @returns {void}
  526. */
  527. sendProxyConnectionEvent(event: Object) {
  528. this._sendEvent({
  529. name: 'proxy-connection-event',
  530. ...event
  531. });
  532. }
  533. /**
  534. * Sends event to the external application.
  535. *
  536. * @param {Object} event - The event to be sent.
  537. * @returns {void}
  538. */
  539. _sendEvent(event: Object = {}) {
  540. if (this._enabled) {
  541. transport.sendEvent(event);
  542. }
  543. }
  544. /**
  545. * Notify external application (if API is enabled) that the chat state has been updated.
  546. *
  547. * @param {number} unreadCount - The unread messages counter.
  548. * @param {boolean} isOpen - True if the chat panel is open.
  549. * @returns {void}
  550. */
  551. notifyChatUpdated(unreadCount: number, isOpen: boolean) {
  552. this._sendEvent({
  553. name: 'chat-updated',
  554. unreadCount,
  555. isOpen
  556. });
  557. }
  558. /**
  559. * Notify external application (if API is enabled) that message was sent.
  560. *
  561. * @param {string} message - Message body.
  562. * @param {boolean} privateMessage - True if the message was a private message.
  563. * @returns {void}
  564. */
  565. notifySendingChatMessage(message: string, privateMessage: boolean) {
  566. this._sendEvent({
  567. name: 'outgoing-message',
  568. message,
  569. privateMessage
  570. });
  571. }
  572. /**
  573. * Notify external application that the video quality setting has changed.
  574. *
  575. * @param {number} videoQuality - The video quality. The number represents the maximum height of the video streams.
  576. * @returns {void}
  577. */
  578. notifyVideoQualityChanged(videoQuality: number) {
  579. this._sendEvent({
  580. name: 'video-quality-changed',
  581. videoQuality
  582. });
  583. }
  584. /**
  585. * Notify external application (if API is enabled) that message was
  586. * received.
  587. *
  588. * @param {Object} options - Object with the message properties.
  589. * @returns {void}
  590. */
  591. notifyReceivedChatMessage(
  592. { body, id, nick, privateMessage, ts }: {
  593. body: *, id: string, nick: string, privateMessage: boolean, ts: *
  594. } = {}) {
  595. if (APP.conference.isLocalId(id)) {
  596. return;
  597. }
  598. this._sendEvent({
  599. name: 'incoming-message',
  600. from: id,
  601. message: body,
  602. nick,
  603. privateMessage,
  604. stamp: ts
  605. });
  606. }
  607. /**
  608. * Notify external application (if API is enabled) that user joined the
  609. * conference.
  610. *
  611. * @param {string} id - User id.
  612. * @param {Object} props - The display name of the user.
  613. * @returns {void}
  614. */
  615. notifyUserJoined(id: string, props: Object) {
  616. this._sendEvent({
  617. name: 'participant-joined',
  618. id,
  619. ...props
  620. });
  621. }
  622. /**
  623. * Notify external application (if API is enabled) that user left the
  624. * conference.
  625. *
  626. * @param {string} id - User id.
  627. * @returns {void}
  628. */
  629. notifyUserLeft(id: string) {
  630. this._sendEvent({
  631. name: 'participant-left',
  632. id
  633. });
  634. }
  635. /**
  636. * Notify external application (if API is enabled) that the user role
  637. * has changed.
  638. *
  639. * @param {string} id - User id.
  640. * @param {string} role - The new user role.
  641. * @returns {void}
  642. */
  643. notifyUserRoleChanged(id: string, role: string) {
  644. this._sendEvent({
  645. name: 'participant-role-changed',
  646. id,
  647. role
  648. });
  649. }
  650. /**
  651. * Notify external application (if API is enabled) that user changed their
  652. * avatar.
  653. *
  654. * @param {string} id - User id.
  655. * @param {string} avatarURL - The new avatar URL of the participant.
  656. * @returns {void}
  657. */
  658. notifyAvatarChanged(id: string, avatarURL: string) {
  659. this._sendEvent({
  660. name: 'avatar-changed',
  661. avatarURL,
  662. id
  663. });
  664. }
  665. /**
  666. * Notify external application (if API is enabled) that user received
  667. * a text message through datachannels.
  668. *
  669. * @param {Object} data - The event data.
  670. * @returns {void}
  671. */
  672. notifyEndpointTextMessageReceived(data: Object) {
  673. this._sendEvent({
  674. name: 'endpoint-text-message-received',
  675. data
  676. });
  677. }
  678. /**
  679. * Notify external application (if API is enabled) that the list of sharing participants changed.
  680. *
  681. * @param {Object} data - The event data.
  682. * @returns {void}
  683. */
  684. notifySharingParticipantsChanged(data: Object) {
  685. this._sendEvent({
  686. name: 'content-sharing-participants-changed',
  687. data
  688. });
  689. }
  690. /**
  691. * Notify external application (if API is enabled) that the device list has
  692. * changed.
  693. *
  694. * @param {Object} devices - The new device list.
  695. * @returns {void}
  696. */
  697. notifyDeviceListChanged(devices: Object) {
  698. this._sendEvent({
  699. name: 'device-list-changed',
  700. devices
  701. });
  702. }
  703. /**
  704. * Notify external application (if API is enabled) that user changed their
  705. * nickname.
  706. *
  707. * @param {string} id - User id.
  708. * @param {string} displayname - User nickname.
  709. * @param {string} formattedDisplayName - The display name shown in Jitsi
  710. * meet's UI for the user.
  711. * @returns {void}
  712. */
  713. notifyDisplayNameChanged(
  714. id: string,
  715. { displayName, formattedDisplayName }: Object) {
  716. this._sendEvent({
  717. name: 'display-name-change',
  718. displayname: displayName,
  719. formattedDisplayName,
  720. id
  721. });
  722. }
  723. /**
  724. * Notify external application (if API is enabled) that user changed their
  725. * email.
  726. *
  727. * @param {string} id - User id.
  728. * @param {string} email - The new email of the participant.
  729. * @returns {void}
  730. */
  731. notifyEmailChanged(
  732. id: string,
  733. { email }: Object) {
  734. this._sendEvent({
  735. name: 'email-change',
  736. email,
  737. id
  738. });
  739. }
  740. /**
  741. * Notify external application (if API is enabled) that the an error has been logged.
  742. *
  743. * @param {string} logLevel - The message log level.
  744. * @param {Array} args - Array of strings composing the log message.
  745. * @returns {void}
  746. */
  747. notifyLog(logLevel: string, args: Array<string>) {
  748. this._sendEvent({
  749. name: 'log',
  750. logLevel,
  751. args
  752. });
  753. }
  754. /**
  755. * Notify external application (if API is enabled) that the conference has
  756. * been joined.
  757. *
  758. * @param {string} roomName - The room name.
  759. * @param {string} id - The id of the local user.
  760. * @param {Object} props - The display name and avatar URL of the local
  761. * user.
  762. * @returns {void}
  763. */
  764. notifyConferenceJoined(roomName: string, id: string, props: Object) {
  765. this._sendEvent({
  766. name: 'video-conference-joined',
  767. roomName,
  768. id,
  769. ...props
  770. });
  771. }
  772. /**
  773. * Notify external application (if API is enabled) that local user has left the conference.
  774. *
  775. * @param {string} roomName - User id.
  776. * @returns {void}
  777. */
  778. notifyConferenceLeft(roomName: string) {
  779. this._sendEvent({
  780. name: 'video-conference-left',
  781. roomName
  782. });
  783. }
  784. /**
  785. * Notify external application (if API is enabled) that we are ready to be
  786. * closed.
  787. *
  788. * @returns {void}
  789. */
  790. notifyReadyToClose() {
  791. this._sendEvent({ name: 'video-ready-to-close' });
  792. }
  793. /**
  794. * Notify external application (if API is enabled) that a suspend event in host computer.
  795. *
  796. * @returns {void}
  797. */
  798. notifySuspendDetected() {
  799. this._sendEvent({ name: 'suspend-detected' });
  800. }
  801. /**
  802. * Notify external application (if API is enabled) for audio muted status
  803. * changed.
  804. *
  805. * @param {boolean} muted - The new muted status.
  806. * @returns {void}
  807. */
  808. notifyAudioMutedStatusChanged(muted: boolean) {
  809. this._sendEvent({
  810. name: 'audio-mute-status-changed',
  811. muted
  812. });
  813. }
  814. /**
  815. * Notify external application (if API is enabled) for video muted status
  816. * changed.
  817. *
  818. * @param {boolean} muted - The new muted status.
  819. * @returns {void}
  820. */
  821. notifyVideoMutedStatusChanged(muted: boolean) {
  822. this._sendEvent({
  823. name: 'video-mute-status-changed',
  824. muted
  825. });
  826. }
  827. /**
  828. * Notify external application (if API is enabled) for audio availability
  829. * changed.
  830. *
  831. * @param {boolean} available - True if available and false otherwise.
  832. * @returns {void}
  833. */
  834. notifyAudioAvailabilityChanged(available: boolean) {
  835. audioAvailable = available;
  836. this._sendEvent({
  837. name: 'audio-availability-changed',
  838. available
  839. });
  840. }
  841. /**
  842. * Notify external application (if API is enabled) for video available
  843. * status changed.
  844. *
  845. * @param {boolean} available - True if available and false otherwise.
  846. * @returns {void}
  847. */
  848. notifyVideoAvailabilityChanged(available: boolean) {
  849. videoAvailable = available;
  850. this._sendEvent({
  851. name: 'video-availability-changed',
  852. available
  853. });
  854. }
  855. /**
  856. * Notify external application (if API is enabled) that the on stage
  857. * participant has changed.
  858. *
  859. * @param {string} id - User id of the new on stage participant.
  860. * @returns {void}
  861. */
  862. notifyOnStageParticipantChanged(id: string) {
  863. this._sendEvent({
  864. name: 'on-stage-participant-changed',
  865. id
  866. });
  867. }
  868. /**
  869. * Notify external application of an unexpected camera-related error having
  870. * occurred.
  871. *
  872. * @param {string} type - The type of the camera error.
  873. * @param {string} message - Additional information about the error.
  874. * @returns {void}
  875. */
  876. notifyOnCameraError(type: string, message: string) {
  877. this._sendEvent({
  878. name: 'camera-error',
  879. type,
  880. message
  881. });
  882. }
  883. /**
  884. * Notify external application of an unexpected mic-related error having
  885. * occurred.
  886. *
  887. * @param {string} type - The type of the mic error.
  888. * @param {string} message - Additional information about the error.
  889. * @returns {void}
  890. */
  891. notifyOnMicError(type: string, message: string) {
  892. this._sendEvent({
  893. name: 'mic-error',
  894. type,
  895. message
  896. });
  897. }
  898. /**
  899. * Notify external application (if API is enabled) that conference feedback
  900. * has been submitted. Intended to be used in conjunction with the
  901. * submit-feedback command to get notified if feedback was submitted.
  902. *
  903. * @param {string} error - A failure message, if any.
  904. * @returns {void}
  905. */
  906. notifyFeedbackSubmitted(error: string) {
  907. this._sendEvent({
  908. name: 'feedback-submitted',
  909. error
  910. });
  911. }
  912. /**
  913. * Notify external application (if API is enabled) that the feedback prompt
  914. * has been displayed.
  915. *
  916. * @returns {void}
  917. */
  918. notifyFeedbackPromptDisplayed() {
  919. this._sendEvent({ name: 'feedback-prompt-displayed' });
  920. }
  921. /**
  922. * Notify external application (if API is enabled) that the display
  923. * configuration of the filmstrip has been changed.
  924. *
  925. * @param {boolean} visible - Whether or not the filmstrip has been set to
  926. * be displayed or hidden.
  927. * @returns {void}
  928. */
  929. notifyFilmstripDisplayChanged(visible: boolean) {
  930. this._sendEvent({
  931. name: 'filmstrip-display-changed',
  932. visible
  933. });
  934. }
  935. /**
  936. * Notify external application of a participant, remote or local, being
  937. * removed from the conference by another participant.
  938. *
  939. * @param {string} kicked - The ID of the participant removed from the
  940. * conference.
  941. * @param {string} kicker - The ID of the participant that removed the
  942. * other participant.
  943. * @returns {void}
  944. */
  945. notifyKickedOut(kicked: Object, kicker: Object) {
  946. this._sendEvent({
  947. name: 'participant-kicked-out',
  948. kicked,
  949. kicker
  950. });
  951. }
  952. /**
  953. * Notify external application of the current meeting requiring a password
  954. * to join.
  955. *
  956. * @returns {void}
  957. */
  958. notifyOnPasswordRequired() {
  959. this._sendEvent({ name: 'password-required' });
  960. }
  961. /**
  962. * Notify external application (if API is enabled) that the screen sharing
  963. * has been turned on/off.
  964. *
  965. * @param {boolean} on - True if screen sharing is enabled.
  966. * @param {Object} details - Additional information about the screen
  967. * sharing.
  968. * @param {string} details.sourceType - Type of device or window the screen
  969. * share is capturing.
  970. * @returns {void}
  971. */
  972. notifyScreenSharingStatusChanged(on: boolean, details: Object) {
  973. this._sendEvent({
  974. name: 'screen-sharing-status-changed',
  975. on,
  976. details
  977. });
  978. }
  979. /**
  980. * Notify external application (if API is enabled) that the dominant speaker
  981. * has been turned on/off.
  982. *
  983. * @param {string} id - Id of the dominant participant.
  984. * @returns {void}
  985. */
  986. notifyDominantSpeakerChanged(id: string) {
  987. this._sendEvent({
  988. name: 'dominant-speaker-changed',
  989. id
  990. });
  991. }
  992. /**
  993. * Notify external application (if API is enabled) that the conference
  994. * changed their subject.
  995. *
  996. * @param {string} subject - Conference subject.
  997. * @returns {void}
  998. */
  999. notifySubjectChanged(subject: string) {
  1000. this._sendEvent({
  1001. name: 'subject-change',
  1002. subject
  1003. });
  1004. }
  1005. /**
  1006. * Notify external application (if API is enabled) that tile view has been
  1007. * entered or exited.
  1008. *
  1009. * @param {string} enabled - True if tile view is currently displayed, false
  1010. * otherwise.
  1011. * @returns {void}
  1012. */
  1013. notifyTileViewChanged(enabled: boolean) {
  1014. this._sendEvent({
  1015. name: 'tile-view-changed',
  1016. enabled
  1017. });
  1018. }
  1019. /**
  1020. * Notify external application (if API is enabled) that the localStorage has changed.
  1021. *
  1022. * @param {string} localStorageContent - The new localStorageContent.
  1023. * @returns {void}
  1024. */
  1025. notifyLocalStorageChanged(localStorageContent: string) {
  1026. this._sendEvent({
  1027. name: 'local-storage-changed',
  1028. localStorageContent
  1029. });
  1030. }
  1031. /**
  1032. * Notify external application (if API is enabled) that user updated their hand raised.
  1033. *
  1034. * @param {string} id - User id.
  1035. * @param {boolean} handRaised - Whether user has raised hand.
  1036. * @returns {void}
  1037. */
  1038. notifyRaiseHandUpdated(id: string, handRaised: boolean) {
  1039. this._sendEvent({
  1040. name: 'raise-hand-updated',
  1041. handRaised,
  1042. id
  1043. });
  1044. }
  1045. /**
  1046. * Disposes the allocated resources.
  1047. *
  1048. * @returns {void}
  1049. */
  1050. dispose() {
  1051. if (this._enabled) {
  1052. this._enabled = false;
  1053. }
  1054. }
  1055. }
  1056. export default new API();