diff --git a/doc/api.md b/doc/api.md index fed58c86d..6087e1d95 100644 --- a/doc/api.md +++ b/doc/api.md @@ -123,12 +123,13 @@ You can also execute multiple commands using the `executeCommands` method: ```javascript api.executeCommands(commands) ``` -The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands asvalues: +The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands as values: ```javascript api.executeCommands({displayName: ['nickname'], toggleAudio: []}); ``` You can add event listeners to the embedded Jitsi Meet using the `addEventListener` method. +**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods (`addListener` or `on`).** ```javascript api.addEventListener(event, listener) ``` @@ -198,6 +199,7 @@ changes. The listener will receive an object with the following structure: You can also add multiple event listeners by using `addEventListeners`. This method requires one argument of type Object. The object argument must have the names of the events as keys and the listeners of the events as values. +**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.** ```javascript function incomingMessageListener(object) @@ -216,12 +218,13 @@ api.addEventListeners({ ``` If you want to remove a listener you can use `removeEventListener` method with argument the name of the event. - +**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods( `removeListener`).** ```javascript api.removeEventListener("incomingMessage"); ``` If you want to remove more than one event you can use `removeEventListeners` method with an Array with the names of the events as an argument. +**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.** ```javascript api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]); ``` @@ -240,3 +243,4 @@ NOTE: It's a good practice to remove the conference before the page is unloaded. [config.js]: https://github.com/jitsi/jitsi-meet/blob/master/config.js [interface_config.js]: https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js +[EventEmitter]: https://nodejs.org/api/events.html diff --git a/modules/API/external/.eslintrc.js b/modules/API/external/.eslintrc.js new file mode 100644 index 000000000..fa9de82aa --- /dev/null +++ b/modules/API/external/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + 'extends': '../../../react/.eslintrc.js' +}; diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js index 623cc5709..fc107fcdf 100644 --- a/modules/API/external/external_api.js +++ b/modules/API/external/external_api.js @@ -1,80 +1,66 @@ -const logger = require("jitsi-meet-logger").getLogger(__filename); +import EventEmitter from 'events'; +import postisInit from 'postis'; -/** - * Implements API class that embeds Jitsi Meet in external applications. - */ - -var postisInit = require("postis"); - -/** - * The minimum width for the Jitsi Meet frame - * @type {number} - */ -var MIN_WIDTH = 790; - -/** - * The minimum height for the Jitsi Meet frame - * @type {number} - */ -var MIN_HEIGHT = 300; - -/** - * Last id of api object - * @type {number} - */ -var id = 0; +const logger = require('jitsi-meet-logger').getLogger(__filename); /** * Maps the names of the commands expected by the API with the name of the * commands expected by jitsi-meet */ -var commands = { - "displayName": "display-name", - "toggleAudio": "toggle-audio", - "toggleVideo": "toggle-video", - "toggleFilmStrip": "toggle-film-strip", - "toggleChat": "toggle-chat", - "toggleContactList": "toggle-contact-list", - "toggleShareScreen": "toggle-share-screen", - "hangup": "video-hangup", - "email": "email", - "avatarUrl": "avatar-url" +const commands = { + avatarUrl: 'avatar-url', + displayName: 'display-name', + email: 'email', + hangup: 'video-hangup', + toggleAudio: 'toggle-audio', + toggleChat: 'toggle-chat', + toggleContactList: 'toggle-contact-list', + toggleFilmStrip: 'toggle-film-strip', + toggleShareScreen: 'toggle-share-screen', + toggleVideo: 'toggle-video' }; /** * Maps the names of the events expected by the API with the name of the * events expected by jitsi-meet */ -var events = { - "incomingMessage": "incoming-message", - "outgoingMessage": "outgoing-message", - "displayNameChange": "display-name-change", - "participantJoined": "participant-joined", - "participantLeft": "participant-left", - "videoConferenceJoined": "video-conference-joined", - "videoConferenceLeft": "video-conference-left", - "readyToClose": "video-ready-to-close" +const events = { + displayNameChange: 'display-name-change', + incomingMessage: 'incoming-message', + outgoingMessage: 'outgoing-message', + participantJoined: 'participant-joined', + participantLeft: 'participant-left', + readyToClose: 'video-ready-to-close', + videoConferenceJoined: 'video-conference-joined', + videoConferenceLeft: 'video-conference-left' }; /** - * Sends the passed object to Jitsi Meet - * @param postis {Postis object} the postis instance that is going to be used - * to send the message - * @param object the object to be sent - * - method {sting} - * - params {object} + * Last id of api object + * @type {number} */ -function sendMessage(postis, object) { - postis.send(object); -} +let id = 0; + +/** + * The minimum height for the Jitsi Meet frame + * @type {number} + */ +const MIN_HEIGHT = 300; + +/** + * The minimum width for the Jitsi Meet frame + * @type {number} + */ +const MIN_WIDTH = 790; /** * Adds given number to the numberOfParticipants property of given APIInstance. - * @param {JitsiMeetExternalAPI} APIInstance the instance of the - * JitsiMeetExternalAPI - * @param {int} number - the number of participants to be added to + * + * @param {JitsiMeetExternalAPI} APIInstance - The instance of the API. + * @param {int} number - The number of participants to be added to * numberOfParticipants property (this parameter can be negative number if the * numberOfParticipants should be decreased). + * @returns {void} */ function changeParticipantNumber(APIInstance, number) { APIInstance.numberOfParticipants += number; @@ -84,323 +70,349 @@ function changeParticipantNumber(APIInstance, number) { * Generates array with URL params based on the passed config object that will * be used for the Jitsi Meet URL generation. * - * @param config {object} the config object. - * @returns {Array} the array with URL param strings. + * @param {Object} config - The config object. + * @returns {Array} The array with URL param strings. */ -function configToURLParamsArray(config) { +function configToURLParamsArray(config = {}) { const params = []; - for (const key in config) { + for (const key in config) { // eslint-disable-line guard-for-in try { - params.push(key + '=' - + encodeURIComponent(JSON.stringify(config[key]))); + params.push(`${key}=${ + encodeURIComponent(JSON.stringify(config[key]))}`); } catch (e) { console.warn(`Error encoding ${key}: ${e}`); } } + return params; } /** - * Constructs new API instance. Creates iframe element that loads - * Jitsi Meet. - * @param domain the domain name of the server that hosts the conference - * @param room_name the name of the room to join - * @param width width of the iframe - * @param height height of the iframe - * @param parent_node the node that will contain the iframe - * @param configOverwrite object containing configuration options defined in - * config.js to be overridden. - * @param interfaceConfigOverwrite object containing configuration options - * defined in interface_config.js to be overridden. - * @param noSsl if the value is true https won't be used - * @param {string} [jwt] the JWT token if needed by jitsi-meet for + * Generates the URL for the iframe. + * + * @param {string} domain - The domain name of the server that hosts the + * conference. + * @param {string} [options] - Another optional parameters. + * @param {Object} [options.configOverwrite] - Object containing configuration + * options defined in config.js to be overridden. + * @param {Object} [options.interfaceConfigOverwrite] - Object containing + * configuration options defined in interface_config.js to be overridden. + * @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for * authentication. - * @constructor + * @param {boolean} [options.noSsl] - If the value is true https won't be used. + * @param {string} [options.roomName] - The name of the room to join. + * @returns {string} The URL. */ -function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode, - configOverwrite, interfaceConfigOverwrite, noSsl, jwt) { - if (!width || width < MIN_WIDTH) - width = MIN_WIDTH; - if (!height || height < MIN_HEIGHT) - height = MIN_HEIGHT; +function generateURL(domain, options = {}) { + const { + configOverwrite, + interfaceConfigOverwrite, + jwt, + noSSL, + roomName + } = options; - this.parentNode = null; - if (parentNode) { - this.parentNode = parentNode; - } else { - var scriptTag = document.scripts[document.scripts.length - 1]; - this.parentNode = scriptTag.parentNode; - } - - this.iframeHolder = - this.parentNode.appendChild(document.createElement("div")); - this.iframeHolder.id = "jitsiConference" + id; - if(width) - this.iframeHolder.style.width = width + "px"; - if(height) - this.iframeHolder.style.height = height + "px"; - this.frameName = "jitsiConferenceFrame" + id; - this.url = (noSsl) ? "http" : "https" +"://" + domain + "/"; - if(room_name) - this.url += room_name; + let url = `${noSSL ? 'http' : 'https'}://${domain}/${roomName || ''}`; if (jwt) { - this.url += '?jwt=' + jwt; + url += `?jwt=${jwt}`; } - this.url += "#jitsi_meet_external_api_id=" + id; + url += `#jitsi_meet_external_api_id=${id}`; const configURLParams = configToURLParamsArray(configOverwrite); + if (configURLParams.length) { - this.url += '&config.' + configURLParams.join('&config.'); + url += `&config.${configURLParams.join('&config.')}`; } const interfaceConfigURLParams = configToURLParamsArray(interfaceConfigOverwrite); + if (interfaceConfigURLParams.length) { - this.url += '&interfaceConfig.' - + interfaceConfigURLParams.join('&interfaceConfig.'); + url += `&interfaceConfig.${ + interfaceConfigURLParams.join('&interfaceConfig.')}`; } - this.frame = document.createElement("iframe"); - this.frame.src = this.url; - this.frame.name = this.frameName; - this.frame.id = this.frameName; - this.frame.width = "100%"; - this.frame.height = "100%"; - this.frame.setAttribute("allowFullScreen","true"); - this.frame = this.iframeHolder.appendChild(this.frame); - this.postis = postisInit({ - window: this.frame.contentWindow, - scope: "jitsi_meet_external_api_" + id - }); - - this.eventHandlers = {}; - - // Map<{string} event_name, {boolean} postis_listener_added> - this.postisListeners = {}; - - this.numberOfParticipants = 1; - this._setupListeners(); - - id++; + return url; } /** - * Executes command. The available commands are: - * displayName - sets the display name of the local participant to the value - * passed in the arguments array. - * toggleAudio - mutes / unmutes audio with no arguments - * toggleVideo - mutes / unmutes video with no arguments - * filmStrip - hides / shows the film strip with no arguments - * If the command doesn't require any arguments the parameter should be set - * to empty array or it may be omitted. - * @param name the name of the command - * @param arguments array of arguments + * The IFrame API interface class. */ -JitsiMeetExternalAPI.prototype.executeCommand -= function(name, ...argumentsList) { - if(!(name in commands)) { - logger.error("Not supported command name."); - return; +class JitsiMeetExternalAPI extends EventEmitter { + /** + * Constructs new API instance. Creates iframe and loads Jitsi Meet in it. + * + * @param {string} domain - The domain name of the server that hosts the + * conference. + * @param {string} [roomName] - The name of the room to join. + * @param {number} [width] - Width of the iframe. + * @param {number} [height] - Height of the iframe. + * @param {DOMElement} [parentNode] - The node that will contain the + * iframe. + * @param {Object} [configOverwrite] - Object containing configuration + * options defined in config.js to be overridden. + * @param {Object} [interfaceConfigOverwrite] - Object containing + * configuration options defined in interface_config.js to be overridden. + * @param {boolean} [noSSL] - If the value is true https won't be used. + * @param {string} [jwt] - The JWT token if needed by jitsi-meet for + * authentication. + */ + constructor(domain, // eslint-disable-line max-params + roomName = '', + width = MIN_WIDTH, + height = MIN_HEIGHT, + parentNode = document.body, + configOverwrite = {}, + interfaceConfigOverwrite = {}, + noSSL = false, + jwt = undefined) { + super(); + this.parentNode = parentNode; + this.url = generateURL(domain, { + configOverwrite, + interfaceConfigOverwrite, + jwt, + noSSL, + roomName + }); + this._createIFrame(Math.max(height, MIN_HEIGHT), + Math.max(width, MIN_WIDTH)); + this.postis = postisInit({ + scope: `jitsi_meet_external_api_${id}`, + window: this.frame.contentWindow + }); + this.numberOfParticipants = 1; + this._setupListeners(); + id++; } - sendMessage(this.postis, {method: commands[name], params: argumentsList}); -}; -/** - * Executes commands. The available commands are: - * displayName - sets the display name of the local participant to the value - * passed in the arguments array. - * toggleAudio - mutes / unmutes audio. no arguments - * toggleVideo - mutes / unmutes video. no arguments - * filmStrip - hides / shows the film strip. no arguments - * toggleChat - hides / shows chat. no arguments. - * toggleContactList - hides / shows contact list. no arguments. - * toggleShareScreen - starts / stops screen sharing. no arguments. - * @param object the object with commands to be executed. The keys of the - * object are the commands that will be executed and the values are the - * arguments for the command. - */ -JitsiMeetExternalAPI.prototype.executeCommands = function(object) { - for(var key in object) - this.executeCommand(key, object[key]); -}; + /** + * Creates the iframe element. + * + * @param {number} height - The height of the iframe. + * @param {number} width - The with of the iframe. + * @returns {void} + * + * @private + */ + _createIFrame(height, width) { + this.iframeHolder + = this.parentNode.appendChild(document.createElement('div')); + this.iframeHolder.id = `jitsiConference${id}`; + this.iframeHolder.style.width = `${width}px`; + this.iframeHolder.style.height = `${height}px`; -/** - * Adds event listeners to Meet Jitsi. The object key should be the name of - * the event and value - the listener. - * Currently we support the following - * events: - * incomingMessage - receives event notifications about incoming - * messages. The listener will receive object with the following structure: - * {{ - * "from": from,//JID of the user that sent the message - * "nick": nick,//the nickname of the user that sent the message - * "message": txt//the text of the message - * }} - * outgoingMessage - receives event notifications about outgoing - * messages. The listener will receive object with the following structure: - * {{ - * "message": txt//the text of the message - * }} - * displayNameChanged - receives event notifications about display name - * change. The listener will receive object with the following structure: - * {{ - * jid: jid,//the JID of the participant that changed his display name - * displayname: displayName //the new display name - * }} - * participantJoined - receives event notifications about new participant. - * The listener will receive object with the following structure: - * {{ - * jid: jid //the jid of the participant - * }} - * participantLeft - receives event notifications about the participant that - * left the room. - * The listener will receive object with the following structure: - * {{ - * jid: jid //the jid of the participant - * }} - * video-conference-joined - receives event notifications about the local user - * has successfully joined the video conference. - * The listener will receive object with the following structure: - * {{ - * roomName: room //the room name of the conference - * }} - * video-conference-left - receives event notifications about the local user - * has left the video conference. - * The listener will receive object with the following structure: - * {{ - * roomName: room //the room name of the conference - * }} - * readyToClose - all hangup operations are completed and Jitsi Meet is ready - * to be disposed. - * @param object - */ -JitsiMeetExternalAPI.prototype.addEventListeners = function(object) { - for(var i in object) - this.addEventListener(i, object[i]); -}; + this.frameName = `jitsiConferenceFrame${id}`; -/** - * Adds event listeners to Meet Jitsi. Currently we support the following - * events: - * incomingMessage - receives event notifications about incoming - * messages. The listener will receive object with the following structure: - * {{ - * "from": from,//JID of the user that sent the message - * "nick": nick,//the nickname of the user that sent the message - * "message": txt//the text of the message - * }} - * outgoingMessage - receives event notifications about outgoing - * messages. The listener will receive object with the following structure: - * {{ - * "message": txt//the text of the message - * }} - * displayNameChanged - receives event notifications about display name - * change. The listener will receive object with the following structure: - * {{ - * jid: jid,//the JID of the participant that changed his display name - * displayname: displayName //the new display name - * }} - * participantJoined - receives event notifications about new participant. - * The listener will receive object with the following structure: - * {{ - * jid: jid //the jid of the participant - * }} - * participantLeft - receives event notifications about participant the that - * left the room. - * The listener will receive object with the following structure: - * {{ - * jid: jid //the jid of the participant - * }} - * video-conference-joined - receives event notifications fired when the local - * user has joined the video conference. - * The listener will receive object with the following structure: - * {{ - * roomName: room //the room name of the conference - * }} - * video-conference-left - receives event notifications fired when the local - * user has joined the video conference. - * The listener will receive object with the following structure: - * {{ - * roomName: room //the room name of the conference - * }} - * @param event the name of the event - * @param listener the listener - */ -JitsiMeetExternalAPI.prototype.addEventListener = function(event, listener) { - if(!(event in events)) { - logger.error("Not supported event name."); - return; + this.frame = document.createElement('iframe'); + this.frame.src = this.url; + this.frame.name = this.frameName; + this.frame.id = this.frameName; + this.frame.width = '100%'; + this.frame.height = '100%'; + this.frame.setAttribute('allowFullScreen', 'true'); + this.frame = this.iframeHolder.appendChild(this.frame); } - // We cannot remove listeners from postis that's why we are handling the - // callback that way. - if(!this.postisListeners[event]) { - this.postis.listen(events[event], function(data) { - if((event in this.eventHandlers) && - typeof this.eventHandlers[event] === "function") - this.eventHandlers[event].call(null, data); - }.bind(this)); - this.postisListeners[event] = true; + + /** + * Setups listeners that are used internally for JitsiMeetExternalAPI. + * + * @returns {void} + * + * @private + */ + _setupListeners() { + this.postis.listen('participant-joined', + changeParticipantNumber.bind(null, this, 1)); + this.postis.listen('participant-left', + changeParticipantNumber.bind(null, this, -1)); + + for (const eventName in events) { // eslint-disable-line guard-for-in + const postisMethod = events[eventName]; + + this.postis.listen(postisMethod, + (...args) => this.emit(eventName, ...args)); + } } - this.eventHandlers[event] = listener; -}; -/** - * Removes event listener. - * @param event the name of the event. - */ -JitsiMeetExternalAPI.prototype.removeEventListener = function(event) { - if(!(event in this.eventHandlers)) - { - logger.error("The event " + event + " is not registered."); - return; + /** + * Adds event listener to Meet Jitsi. + * + * @param {string} event - The name of the event. + * @param {Function} listener - The listener. + * @returns {void} + * + * @deprecated + * NOTE: This method is not removed for backward comatability purposes. + */ + addEventListener(event, listener) { + this.on(event, listener); } - delete this.eventHandlers[event]; -}; -/** - * Removes event listeners. - * @param events array with the names of the events. - */ -JitsiMeetExternalAPI.prototype.removeEventListeners = function(events) { - for(var i = 0; i < events.length; i++) - this.removeEventListener(events[i]); -}; + /** + * Adds event listeners to Meet Jitsi. + * + * @param {Object} listeners - The object key should be the name of + * the event and value - the listener. + * Currently we support the following + * events: + * incomingMessage - receives event notifications about incoming + * messages. The listener will receive object with the following structure: + * {{ + * 'from': from,//JID of the user that sent the message + * 'nick': nick,//the nickname of the user that sent the message + * 'message': txt//the text of the message + * }} + * outgoingMessage - receives event notifications about outgoing + * messages. The listener will receive object with the following structure: + * {{ + * 'message': txt//the text of the message + * }} + * displayNameChanged - receives event notifications about display name + * change. The listener will receive object with the following structure: + * {{ + * jid: jid,//the JID of the participant that changed his display name + * displayname: displayName //the new display name + * }} + * participantJoined - receives event notifications about new participant. + * The listener will receive object with the following structure: + * {{ + * jid: jid //the jid of the participant + * }} + * participantLeft - receives event notifications about the participant that + * left the room. + * The listener will receive object with the following structure: + * {{ + * jid: jid //the jid of the participant + * }} + * video-conference-joined - receives event notifications about the local + * user has successfully joined the video conference. + * The listener will receive object with the following structure: + * {{ + * roomName: room //the room name of the conference + * }} + * video-conference-left - receives event notifications about the local user + * has left the video conference. + * The listener will receive object with the following structure: + * {{ + * roomName: room //the room name of the conference + * }} + * readyToClose - all hangup operations are completed and Jitsi Meet is + * ready to be disposed. + * @returns {void} + * + * @deprecated + * NOTE: This method is not removed for backward comatability purposes. + */ + addEventListeners(listeners) { + for (const event in listeners) { // eslint-disable-line guard-for-in + this.addEventListener(event, listeners[event]); + } + } -/** - * Returns the number of participants in the conference. - * NOTE: the local participant is included. - * @returns {int} the number of participants in the conference. - */ -JitsiMeetExternalAPI.prototype.getNumberOfParticipants = function() { - return this.numberOfParticipants; -}; + /** + * Removes the listeners and removes the Jitsi Meet frame. + * + * @returns {void} + */ + dispose() { + const frame = document.getElementById(this.frameName); -/** - * Setups listeners that are used internally for JitsiMeetExternalAPI. - */ -JitsiMeetExternalAPI.prototype._setupListeners = function() { - this.postis.listen("participant-joined", - changeParticipantNumber.bind(null, this, 1)); - this.postis.listen("participant-left", - changeParticipantNumber.bind(null, this, -1)); -}; + this.postis.destroy(); + if (frame) { + frame.src = 'about:blank'; + } + window.setTimeout(() => { + this.iframeHolder.removeChild(this.frame); + this.iframeHolder.parentNode.removeChild(this.iframeHolder); + }, 10); + } -/** - * Removes the listeners and removes the Jitsi Meet frame. - */ -JitsiMeetExternalAPI.prototype.dispose = function() { - this.postis.destroy(); - var frame = document.getElementById(this.frameName); - if(frame) - frame.src = 'about:blank'; - var self = this; - window.setTimeout(function () { - self.iframeHolder.removeChild(self.frame); - self.iframeHolder.parentNode.removeChild(self.iframeHolder); - }, 10); -}; + /** + * Executes command. The available commands are: + * displayName - sets the display name of the local participant to the value + * passed in the arguments array. + * toggleAudio - mutes / unmutes audio with no arguments. + * toggleVideo - mutes / unmutes video with no arguments. + * filmStrip - hides / shows the film strip with no arguments. + * If the command doesn't require any arguments the parameter should be set + * to empty array or it may be omitted. + * + * @param {string} name - The name of the command. + * @returns {void} + */ + executeCommand(name, ...args) { + if (!(name in commands)) { + logger.error('Not supported command name.'); + + return; + } + this.postis.send({ + method: commands[name], + params: args + }); + } + + /** + * Executes commands. The available commands are: + * displayName - sets the display name of the local participant to the value + * passed in the arguments array. + * toggleAudio - mutes / unmutes audio. no arguments + * toggleVideo - mutes / unmutes video. no arguments + * filmStrip - hides / shows the film strip. no arguments + * toggleChat - hides / shows chat. no arguments. + * toggleContactList - hides / shows contact list. no arguments. + * toggleShareScreen - starts / stops screen sharing. no arguments. + * + * @param {Object} commandList - The object with commands to be executed. + * The keys of the object are the commands that will be executed and the + * values are the arguments for the command. + * @returns {void} + */ + executeCommands(commandList) { + for (const key in commandList) { // eslint-disable-line guard-for-in + this.executeCommand(key, commandList[key]); + } + } + + /** + * Returns the number of participants in the conference. The local + * participant is included. + * + * @returns {int} The number of participants in the conference. + */ + getNumberOfParticipants() { + return this.numberOfParticipants; + } + + /** + * Removes event listener. + * + * @param {string} event - The name of the event. + * @returns {void} + * + * @deprecated + * NOTE: This method is not removed for backward comatability purposes. + */ + removeEventListener(event) { + this.removeListeners(event); + } + + /** + * Removes event listeners. + * + * @param {Array} eventList - Array with the names of the events. + * @returns {void} + * + * @deprecated + * NOTE: This method is not removed for backward comatability purposes. + */ + removeEventListeners(eventList) { + eventList.forEach(event => this.removeEventListener(event)); + } +} module.exports = JitsiMeetExternalAPI;