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

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