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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. // @flow
  2. import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
  3. import { parseJWTFromURLParams } from '../../react/features/base/jwt';
  4. import {
  5. createApiEvent,
  6. sendAnalytics
  7. } from '../../react/features/analytics';
  8. import { getJitsiMeetTransport } from '../transport';
  9. import { API_ID } from './constants';
  10. const logger = require('jitsi-meet-logger').getLogger(__filename);
  11. declare var APP: Object;
  12. /**
  13. * List of the available commands.
  14. */
  15. let commands = {};
  16. /**
  17. * The state of screen sharing(started/stopped) before the screen sharing is
  18. * enabled and initialized.
  19. * NOTE: This flag help us to cache the state and use it if toggle-share-screen
  20. * was received before the initialization.
  21. */
  22. let initialScreenSharingState = false;
  23. /**
  24. * The transport instance used for communication with external apps.
  25. *
  26. * @type {Transport}
  27. */
  28. const transport = getJitsiMeetTransport();
  29. /**
  30. * The current audio availability.
  31. *
  32. * @type {boolean}
  33. */
  34. let audioAvailable = true;
  35. /**
  36. * The current video availability.
  37. *
  38. * @type {boolean}
  39. */
  40. let videoAvailable = true;
  41. /**
  42. * Initializes supported commands.
  43. *
  44. * @returns {void}
  45. */
  46. function initCommands() {
  47. commands = {
  48. 'display-name': displayName => {
  49. sendAnalytics(createApiEvent('display.name.changed'));
  50. APP.conference.changeLocalDisplayName(displayName);
  51. },
  52. 'submit-feedback': feedback => {
  53. sendAnalytics(createApiEvent('submit.feedback'));
  54. APP.conference.submitFeedback(feedback.score, feedback.message);
  55. },
  56. 'toggle-audio': () => {
  57. sendAnalytics(createApiEvent('toggle-audio'));
  58. logger.log('Audio toggle: API command received');
  59. APP.conference.toggleAudioMuted(false /* no UI */);
  60. },
  61. 'toggle-video': () => {
  62. sendAnalytics(createApiEvent('toggle-video'));
  63. logger.log('Video toggle: API command received');
  64. APP.conference.toggleVideoMuted(false /* no UI */);
  65. },
  66. 'toggle-film-strip': () => {
  67. sendAnalytics(createApiEvent('film.strip.toggled'));
  68. APP.UI.toggleFilmstrip();
  69. },
  70. 'toggle-chat': () => {
  71. sendAnalytics(createApiEvent('chat.toggled'));
  72. APP.UI.toggleChat();
  73. },
  74. 'toggle-contact-list': () => {
  75. sendAnalytics(createApiEvent('contact.list.toggled'));
  76. APP.UI.toggleContactList();
  77. },
  78. 'toggle-share-screen': () => {
  79. sendAnalytics(createApiEvent('screen.sharing.toggled'));
  80. toggleScreenSharing();
  81. },
  82. 'video-hangup': () => {
  83. sendAnalytics(createApiEvent('video.hangup'));
  84. APP.conference.hangup(true);
  85. },
  86. 'email': email => {
  87. sendAnalytics(createApiEvent('email.changed'));
  88. APP.conference.changeLocalEmail(email);
  89. },
  90. 'avatar-url': avatarUrl => {
  91. sendAnalytics(createApiEvent('avatar.url.changed'));
  92. APP.conference.changeLocalAvatarUrl(avatarUrl);
  93. }
  94. };
  95. transport.on('event', ({ data, name }) => {
  96. if (name && commands[name]) {
  97. commands[name](...data);
  98. return true;
  99. }
  100. return false;
  101. });
  102. transport.on('request', ({ name }, callback) => {
  103. switch (name) {
  104. case 'is-audio-muted':
  105. callback(APP.conference.isLocalAudioMuted());
  106. break;
  107. case 'is-video-muted':
  108. callback(APP.conference.isLocalVideoMuted());
  109. break;
  110. case 'is-audio-available':
  111. callback(audioAvailable);
  112. break;
  113. case 'is-video-available':
  114. callback(videoAvailable);
  115. break;
  116. default:
  117. return false;
  118. }
  119. return true;
  120. });
  121. }
  122. /**
  123. * Listens for desktop/screen sharing enabled events and toggles the screen
  124. * sharing if needed.
  125. *
  126. * @param {boolean} enabled - Current screen sharing enabled status.
  127. * @returns {void}
  128. */
  129. function onDesktopSharingEnabledChanged(enabled = false) {
  130. if (enabled && initialScreenSharingState) {
  131. toggleScreenSharing();
  132. }
  133. }
  134. /**
  135. * Check whether the API should be enabled or not.
  136. *
  137. * @returns {boolean}
  138. */
  139. function shouldBeEnabled() {
  140. return (
  141. typeof API_ID === 'number'
  142. // XXX Enable the API when a JSON Web Token (JWT) is specified in
  143. // the location/URL because then it is very likely that the Jitsi
  144. // Meet (Web) app is being used by an external/wrapping (Web) app
  145. // and, consequently, the latter will need to communicate with the
  146. // former. (The described logic is merely a heuristic though.)
  147. || parseJWTFromURLParams());
  148. }
  149. /**
  150. * Executes on toggle-share-screen command.
  151. *
  152. * @returns {void}
  153. */
  154. function toggleScreenSharing() {
  155. if (APP.conference.isDesktopSharingEnabled) {
  156. // eslint-disable-next-line no-empty-function
  157. APP.conference.toggleScreenSharing().catch(() => {});
  158. } else {
  159. initialScreenSharingState = !initialScreenSharingState;
  160. }
  161. }
  162. /**
  163. * Implements API class that communicates with external API class and provides
  164. * interface to access Jitsi Meet features by external applications that embed
  165. * Jitsi Meet.
  166. */
  167. class API {
  168. _enabled: boolean;
  169. /**
  170. * Initializes the API. Setups message event listeners that will receive
  171. * information from external applications that embed Jitsi Meet. It also
  172. * sends a message to the external application that API is initialized.
  173. *
  174. * @param {Object} options - Optional parameters.
  175. * @returns {void}
  176. */
  177. init() {
  178. if (!shouldBeEnabled()) {
  179. return;
  180. }
  181. /**
  182. * Current status (enabled/disabled) of API.
  183. *
  184. * @private
  185. * @type {boolean}
  186. */
  187. this._enabled = true;
  188. APP.conference.addListener(
  189. JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
  190. onDesktopSharingEnabledChanged);
  191. initCommands();
  192. }
  193. /**
  194. * Notify external application (if API is enabled) that the large video
  195. * visibility changed.
  196. *
  197. * @param {boolean} isHidden - True if the large video is hidden and false
  198. * otherwise.
  199. * @returns {void}
  200. */
  201. notifyLargeVideoVisibilityChanged(isHidden: boolean) {
  202. this._sendEvent({
  203. name: 'large-video-visibility-changed',
  204. isVisible: !isHidden
  205. });
  206. }
  207. /**
  208. * Sends event to the external application.
  209. *
  210. * @param {Object} event - The event to be sent.
  211. * @returns {void}
  212. */
  213. _sendEvent(event: Object = {}) {
  214. if (this._enabled) {
  215. transport.sendEvent(event);
  216. }
  217. }
  218. /**
  219. * Notify external application (if API is enabled) that message was sent.
  220. *
  221. * @param {string} message - Message body.
  222. * @returns {void}
  223. */
  224. notifySendingChatMessage(message: string) {
  225. this._sendEvent({
  226. name: 'outgoing-message',
  227. message
  228. });
  229. }
  230. /**
  231. * Notify external application (if API is enabled) that message was
  232. * received.
  233. *
  234. * @param {Object} options - Object with the message properties.
  235. * @returns {void}
  236. */
  237. notifyReceivedChatMessage(
  238. { body, id, nick, ts }: {
  239. body: *, id: string, nick: string, ts: *
  240. } = {}) {
  241. if (APP.conference.isLocalId(id)) {
  242. return;
  243. }
  244. this._sendEvent({
  245. name: 'incoming-message',
  246. from: id,
  247. message: body,
  248. nick,
  249. stamp: ts
  250. });
  251. }
  252. /**
  253. * Notify external application (if API is enabled) that user joined the
  254. * conference.
  255. *
  256. * @param {string} id - User id.
  257. * @param {Object} props - The display name of the user.
  258. * @returns {void}
  259. */
  260. notifyUserJoined(id: string, props: Object) {
  261. this._sendEvent({
  262. name: 'participant-joined',
  263. id,
  264. ...props
  265. });
  266. }
  267. /**
  268. * Notify external application (if API is enabled) that user left the
  269. * conference.
  270. *
  271. * @param {string} id - User id.
  272. * @returns {void}
  273. */
  274. notifyUserLeft(id: string) {
  275. this._sendEvent({
  276. name: 'participant-left',
  277. id
  278. });
  279. }
  280. /**
  281. * Notify external application (if API is enabled) that user changed their
  282. * avatar.
  283. *
  284. * @param {string} id - User id.
  285. * @param {string} avatarURL - The new avatar URL of the participant.
  286. * @returns {void}
  287. */
  288. notifyAvatarChanged(id: string, avatarURL: string) {
  289. this._sendEvent({
  290. name: 'avatar-changed',
  291. avatarURL,
  292. id
  293. });
  294. }
  295. /**
  296. * Notify external application (if API is enabled) that user changed their
  297. * nickname.
  298. *
  299. * @param {string} id - User id.
  300. * @param {string} displayname - User nickname.
  301. * @param {string} formattedDisplayName - The display name shown in Jitsi
  302. * meet's UI for the user.
  303. * @returns {void}
  304. */
  305. notifyDisplayNameChanged(
  306. id: string,
  307. { displayName, formattedDisplayName }: Object) {
  308. this._sendEvent({
  309. name: 'display-name-change',
  310. displayname: displayName,
  311. formattedDisplayName,
  312. id
  313. });
  314. }
  315. /**
  316. * Notify external application (if API is enabled) that the conference has
  317. * been joined.
  318. *
  319. * @param {string} roomName - The room name.
  320. * @param {string} id - The id of the local user.
  321. * @param {Object} props - The display name and avatar URL of the local
  322. * user.
  323. * @returns {void}
  324. */
  325. notifyConferenceJoined(roomName: string, id: string, props: Object) {
  326. this._sendEvent({
  327. name: 'video-conference-joined',
  328. roomName,
  329. id,
  330. ...props
  331. });
  332. }
  333. /**
  334. * Notify external application (if API is enabled) that user changed their
  335. * nickname.
  336. *
  337. * @param {string} roomName - User id.
  338. * @returns {void}
  339. */
  340. notifyConferenceLeft(roomName: string) {
  341. this._sendEvent({
  342. name: 'video-conference-left',
  343. roomName
  344. });
  345. }
  346. /**
  347. * Notify external application (if API is enabled) that we are ready to be
  348. * closed.
  349. *
  350. * @returns {void}
  351. */
  352. notifyReadyToClose() {
  353. this._sendEvent({ name: 'video-ready-to-close' });
  354. }
  355. /**
  356. * Notify external application (if API is enabled) for audio muted status
  357. * changed.
  358. *
  359. * @param {boolean} muted - The new muted status.
  360. * @returns {void}
  361. */
  362. notifyAudioMutedStatusChanged(muted: boolean) {
  363. this._sendEvent({
  364. name: 'audio-mute-status-changed',
  365. muted
  366. });
  367. }
  368. /**
  369. * Notify external application (if API is enabled) for video muted status
  370. * changed.
  371. *
  372. * @param {boolean} muted - The new muted status.
  373. * @returns {void}
  374. */
  375. notifyVideoMutedStatusChanged(muted: boolean) {
  376. this._sendEvent({
  377. name: 'video-mute-status-changed',
  378. muted
  379. });
  380. }
  381. /**
  382. * Notify external application (if API is enabled) for audio availability
  383. * changed.
  384. *
  385. * @param {boolean} available - True if available and false otherwise.
  386. * @returns {void}
  387. */
  388. notifyAudioAvailabilityChanged(available: boolean) {
  389. audioAvailable = available;
  390. this._sendEvent({
  391. name: 'audio-availability-changed',
  392. available
  393. });
  394. }
  395. /**
  396. * Notify external application (if API is enabled) for video available
  397. * status changed.
  398. *
  399. * @param {boolean} available - True if available and false otherwise.
  400. * @returns {void}
  401. */
  402. notifyVideoAvailabilityChanged(available: boolean) {
  403. videoAvailable = available;
  404. this._sendEvent({
  405. name: 'video-availability-changed',
  406. available
  407. });
  408. }
  409. /**
  410. * Notify external application (if API is enabled) that the on stage
  411. * participant has changed.
  412. *
  413. * @param {string} id - User id of the new on stage participant.
  414. * @returns {void}
  415. */
  416. notifyOnStageParticipantChanged(id: string) {
  417. this._sendEvent({
  418. name: 'on-stage-participant-changed',
  419. id
  420. });
  421. }
  422. /**
  423. * Notify external application (if API is enabled) that conference feedback
  424. * has been submitted. Intended to be used in conjunction with the
  425. * submit-feedback command to get notified if feedback was submitted.
  426. *
  427. * @returns {void}
  428. */
  429. notifyFeedbackSubmitted() {
  430. this._sendEvent({ name: 'feedback-submitted' });
  431. }
  432. /**
  433. * Notify external application (if API is enabled) that the screen sharing
  434. * has been turned on/off.
  435. *
  436. * @param {boolean} on - True if screen sharing is enabled.
  437. * @returns {void}
  438. */
  439. notifyScreenSharingStatusChanged(on: boolean) {
  440. this._sendEvent({
  441. name: 'screen-sharing-status-changed',
  442. on
  443. });
  444. }
  445. /**
  446. * Disposes the allocated resources.
  447. *
  448. * @returns {void}
  449. */
  450. dispose() {
  451. if (this._enabled) {
  452. this._enabled = false;
  453. APP.conference.removeListener(
  454. JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
  455. onDesktopSharingEnabledChanged);
  456. }
  457. }
  458. }
  459. export default new API();