Fixes join and leave methods. Adds communication with jicofo. Implements custom commands methods.

This commit is contained in:
hristoterezov 2015-08-29 23:45:48 -05:00
parent 455a6e10fe
commit 1bba8d2032
14 changed files with 12739 additions and 1060 deletions

View File

@ -1,5 +1,3 @@
var room = null;
/**
* Creates a JitsiConference object with the given name and properties.
@ -15,13 +13,16 @@ function JitsiConference(options) {
this.options = options;
this.connection = this.options.connection;
this.xmpp = this.connection.xmpp;
this.room = this.xmpp.createRoom(this.options.name, null, null);
}
/**
* Joins the conference.
* @param password {string} the password
*/
JitsiConference.prototype.join = function () {
room = this.xmpp.joinRoom(this.options.name, null, null);
JitsiConference.prototype.join = function (password) {
this.room.joinRoom(password);
}
/**
@ -29,7 +30,7 @@ JitsiConference.prototype.join = function () {
*/
JitsiConference.prototype.leave = function () {
this.xmpp.leaveRoom(room.roomjid);
room = null;
this.room = null;
}
/**
@ -60,7 +61,7 @@ JitsiConference.prototype.getLocalTracks = function () {
* Note: consider adding eventing functionality by extending an EventEmitter impl, instead of rolling ourselves
*/
JitsiConference.prototype.on = function (eventId, handler) {
this.xmpp.addListener(eventId, handler);
this.add.addListener(eventId, handler);
}
/**
@ -71,7 +72,7 @@ JitsiConference.prototype.on = function (eventId, handler) {
* Note: consider adding eventing functionality by extending an EventEmitter impl, instead of rolling ourselves
*/
JitsiConference.prototype.off = function (eventId, handler) {
this.xmpp.removeListener(event, listener);
this.room.removeListener(eventId, listener);
}
// Common aliases for event emitter
@ -84,7 +85,7 @@ JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off
* @param handler {Function} handler for the command
*/
JitsiConference.prototype.addCommandListener = function (command, handler) {
this.room.addPresenceListener(command, handler);
}
/**
@ -92,7 +93,7 @@ JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off
* @param command {String} the name of the command
*/
JitsiConference.prototype.removeCommandListener = function (command) {
this.room.removePresenceListener(command);
}
/**
@ -100,7 +101,27 @@ JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off
* @param message the text message.
*/
JitsiConference.prototype.sendTextMessage = function (message) {
room.send
this.room.sendMessage(message);
}
/**
* Send presence command.
* @param name the name of the command.
* @param values Object with keys and values that will be send.
**/
JitsiConference.prototype.sendCommand = function (name, values) {
this.room.addToPresence(name, values);
this.room.sendPresence();
}
/**
* Send presence command one time.
* @param name the name of the command.
* @param values Object with keys and values that will be send.
**/
JitsiConference.prototype.sendCommandOnce = function (name, values) {
this.sendCommand(name, values);
this.removeCommand(name);
}
/**
@ -108,13 +129,9 @@ JitsiConference.prototype.sendTextMessage = function (message) {
* @param name the name of the command.
* @param values Object with keys and values that will be send.
* @param persistent if false the command will be sent only one time
* @param successCallback will be called when the command is successfully send.
* @param errorCallback will be called when the command is not sent successfully.
* @returns {Promise.<{void}, JitsiConferenceError>} A promise that returns an array of created streams if resolved,
* or an JitsiConferenceError if rejected.
*/
JitsiConference.prototype.sendCommand = function (name, values, persistent) {
**/
JitsiConference.prototype.removeCommand = function (name) {
this.room.removeFromPresence(name);
}
/**
@ -122,7 +139,7 @@ JitsiConference.prototype.sendCommand = function (name, values, persistent) {
* @param name the display name to set
*/
JitsiConference.prototype.setDisplayName = function(name) {
room.addToPresence("nick", {attributes: {xmlns: 'http://jabber.org/protocol/nick'}, value: name});
this.room.addToPresence("nick", {attributes: {xmlns: 'http://jabber.org/protocol/nick'}, value: name});
}
/**
@ -141,5 +158,14 @@ JitsiConference.prototype.getParticipants = function() {
}
/**
* @returns {JitsiParticipant} the participant in this conference with the specified id (or
* null if there isn't one).
* @param id the id of the participant.
*/
JitsiConference.prototype.getParticipantById = function(id) {
}
module.exports = JitsiConference;

View File

@ -62,7 +62,15 @@ var JitsiConferenceEvents = {
/**
* Indicates that the connection to the conference has been restored.
*/
CONNECTION_ESTABLISHED: "conference.connecionEstablished"
CONNECTION_RESTORED: "conference.connecionRestored",
/**
* Indicates that conference has been joined.
*/
CONFERENCE_JOINED: "conference.joined",
/**
* Indicates that conference has been left.
*/
CONFERENCE_LEFT: "conference.left"
};
module.exports = JitsiConferenceEvents;

View File

@ -1,11 +1,6 @@
var JitsiConference = require("./JitsiConference");
var XMPP = require("./modules/xmpp/xmpp");
function wrapper()
{
var jitsiconnectioninstance = new JitsiConnection();
this.a = jitsiconnectioninstance.a();
}
/**
* Creates new connection object for the Jitsi Meet server side video conferencing service. Provides access to the
* JitsiConference interface.
@ -20,6 +15,7 @@ function JitsiConnection(appID, token, options) {
this.token = token;
this.options = options;
this.xmpp = new XMPP(options);
this.conferences = {};
}
/**
@ -54,7 +50,8 @@ JitsiConnection.prototype.setToken = function (token) {
* @returns {JitsiConference} returns the new conference object.
*/
JitsiConnection.prototype.initJitsiConference = function (name, options) {
return new JitsiConference({name: name, config: options, connection: this});
this.conferences[name] = new JitsiConference({name: name, config: options, connection: this});
return this.conferences[name];
}
/**

135
JitsiParticipant.js Normal file
View File

@ -0,0 +1,135 @@
/**
* Represents a participant in (a member of) a conference.
*/
function JitsiParticipant(){
}
/**
* @returns {JitsiConference} The conference that this participant belongs to.
*/
JitsiParticipant.prototype.getConference = function() {
}
/**
* @returns {Array.<JitsiTrack>} The list of media tracks for this participant.
*/
JitsiParticipant.prototype.getTracks = function() {
}
/**
* @returns {String} The ID (i.e. JID) of this participant.
*/
JitsiParticipant.prototype.getId = function() {
}
/**
* @returns {String} The human-readable display name of this participant.
*/
JitsiParticipant.prototype.getDisplayName = function() {
}
/**
* @returns {Boolean} Whether this participant is a moderator or not.
*/
JitsiParticipant.prototype.isModerator = function() {
}
// Gets a link to an etherpad instance advertised by the participant?
//JitsiParticipant.prototype.getEtherpad = function() {
//
//}
/*
* @returns {Boolean} Whether this participant has muted their audio.
*/
JitsiParticipant.prototype.isAudioMuted = function() {
}
/*
* @returns {Boolean} Whether this participant has muted their video.
*/
JitsiParticipant.prototype.isVideoMuted = function() {
}
/*
* @returns {???} The latest statistics reported by this participant (i.e. info used to populate the GSM bars)
* TODO: do we expose this or handle it internally?
*/
JitsiParticipant.prototype.getLatestStats = function() {
}
/**
* @returns {String} The role of this participant.
*/
JitsiParticipant.prototype.getRole = function() {
}
/*
* @returns {Boolean} Whether this participant is the conference focus (i.e. jicofo).
*/
JitsiParticipant.prototype.isFocus = function() {
}
/*
* @returns {Boolean} Whether this participant is a conference recorder (i.e. jirecon).
*/
JitsiParticipant.prototype.isRecorder = function() {
}
/*
* @returns {Boolean} Whether this participant is a SIP gateway (i.e. jigasi).
*/
JitsiParticipant.prototype.isSipGateway = function() {
}
/**
* @returns {String} The ID for this participant's avatar.
*/
JitsiParticipant.prototype.getAvatarId = function() {
}
/**
* @returns {Boolean} Whether this participant is currently sharing their screen.
*/
JitsiParticipant.prototype.isScreenSharing = function() {
}
/**
* @returns {String} The user agent of this participant (i.e. browser userAgent string).
*/
JitsiParticipant.prototype.getUserAgent = function() {
}
/**
* Kicks the participant from the conference (requires certain privileges).
*/
JitsiParticipant.prototype.kick = function() {
}
/**
* Asks this participant to mute themselves.
*/
JitsiParticipant.prototype.askToMute = function() {
}
module.exports = JitsiParticipant();

View File

@ -26,7 +26,7 @@ JitsiTrack.prototype.getType = function() {
};
/**
* Returns the JitsiParticipant to which this track belongs, or null if it is a local track.
* @returns {JitsiParticipant} to which this track belongs, or null if it is a local track.
*/
JitsiTrack.prototype.getParitcipant() = function() {

File diff suppressed because it is too large Load Diff

5492
libs/strophe/strophe.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,17 +21,17 @@ function Settings(conferenceID) {
this.userId;
this.language = null;
this.confSettings = null;
this.conferenceID = conferenceID;
if (supportsLocalStorage()) {
if(!window.localStorage.jitsiConferences)
window.localStorage.jitsiConferences = {}
if (!window.localStorage.jitsiConferences[conferenceID]) {
window.localStorage.jitsiConferences[conferenceID] = {}
}
this.confSettings = window.localStorage.jitsiConferences[conferenceID];
if(!window.localStorage.getItem(conferenceID))
this.confSettings = {};
else
this.confSettings = JSON.parse(window.localStorage.getItem(conferenceID));
if(!this.confSettings.jitsiMeetId) {
this.confSettings.jitsiMeetId = generateUniqueId();
console.log("generated id",
this.confSettings.jitsiMeetId);
this.save();
}
this.userId = this.confSettings.jitsiMeetId || '';
this.email = this.confSettings.email || '';
@ -43,16 +43,23 @@ function Settings(conferenceID) {
}
}
Settings.prototype.save = function () {
if(!supportsLocalStorage())
window.localStorage.setItem(this.conferenceID, JSON.stringify(this.confSettings));
}
Settings.prototype.setDisplayName = function (newDisplayName) {
this.displayName = newDisplayName;
if(this.confSettings != null)
this.confSettings.displayname = displayName;
this.save();
return this.displayName;
},
Settings.prototype.setEmail = function (newEmail) {
this.email = newEmail;
if(this.confSettings != null)
this.confSettings.email = newEmail;
this.save();
return this.email;
},
Settings.prototype.getSettings = function () {
@ -67,6 +74,7 @@ Settings.prototype.setLanguage = function (lang) {
this.language = lang;
if(this.confSettings != null)
this.confSettings.language = lang;
this.save();
}
module.exports = Settings;

View File

@ -9,6 +9,7 @@ var transform = require("sdp-transform");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var RTCBrowserType = require("../RTC/RTCBrowserType");
var SSRCReplacement = require("./LocalSSRCReplacement");
var RTC = require("../RTC/RTC");
// Jingle stuff
function JingleSessionPC(me, sid, connection, service, eventEmitter) {
@ -86,7 +87,7 @@ JingleSessionPC.prototype.doInitialize = function () {
this.peerconnection = new TraceablePeerConnection(
this.connection.jingle.ice_config,
APP.RTC.getPCConstraints(),
RTC.getPCConstraints(),
this);
this.peerconnection.onicecandidate = function (event) {
@ -147,15 +148,20 @@ JingleSessionPC.prototype.doInitialize = function () {
this.peerconnection.onnegotiationneeded = function (event) {
self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self);
};
// add any local and relayed stream
APP.RTC.localStreams.forEach(function(stream) {
self.peerconnection.addStream(stream.getOriginalStream());
});
this.relayedStreams.forEach(function(stream) {
self.peerconnection.addStream(stream);
});
};
JingleSessionPC.prototype.addLocalStreams = function (localStreams) {
var self = this;
// add any local and relayed stream
localStreams.forEach(function(stream) {
self.peerconnection.addStream(stream.getOriginalStream());
});
}
function onIceConnectionStateChange(sid, session) {
switch (session.peerconnection.iceConnectionState) {
case 'checking':
@ -1307,8 +1313,6 @@ JingleSessionPC.onJingleError = function (session, error)
JingleSessionPC.onJingleFatalError = function (session, error)
{
this.service.sessionTerminated = true;
this.connection.emuc.doLeave();
this.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
this.eventEmitter.emit(XMPPEvents.JINGLE_FATAL_ERROR, session, error);
}
@ -1391,7 +1395,7 @@ function sendKeyframe(pc) {
JingleSessionPC.prototype.remoteStreamAdded = function (data, times) {
var self = this;
var thessrc;
var streamId = APP.RTC.getStreamID(data.stream);
var streamId = RTC.getStreamID(data.stream);
// look up an associated JID for a stream id
if (!streamId) {
@ -1426,14 +1430,14 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) {
}
}
APP.RTC.createRemoteStream(data, this.sid, thessrc);
RTC.createRemoteStream(data, this.sid, thessrc);
var isVideo = data.stream.getVideoTracks().length > 0;
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
if (isVideo &&
data.peerjid && this.peerjid === data.peerjid &&
data.stream.getVideoTracks().length === 0 &&
APP.RTC.localVideo.getTracks().length > 0) {
RTC.localVideo.getTracks().length > 0) {
window.setTimeout(function () {
sendKeyframe(self.peerconnection);
}, 3000);

View File

@ -6,13 +6,6 @@ var Settings = require("../settings/Settings");
var AuthenticationEvents
= require("../../service/authentication/AuthenticationEvents");
/**
* Contains logic responsible for enabling/disabling functionality available
* only to moderator users.
*/
var connection = null;
var focusUserJid;
function createExpBackoffTimer(step) {
var count = 1;
return function (reset) {
@ -28,402 +21,396 @@ function createExpBackoffTimer(step) {
};
}
var getNextTimeout = createExpBackoffTimer(1000);
var getNextErrorTimeout = createExpBackoffTimer(1000);
function Moderator(roomName, xmpp, emitter) {
this.roomName = roomName;
this.xmppService = xmpp;
this.getNextTimeout = createExpBackoffTimer(1000);
this.getNextErrorTimeout = createExpBackoffTimer(1000);
// External authentication stuff
var externalAuthEnabled = false;
this.externalAuthEnabled = false;
this.settings = new Settings(roomName);
// Sip gateway can be enabled by configuring Jigasi host in config.js or
// it will be enabled automatically if focus detects the component through
// service discovery.
var sipGatewayEnabled = null;
this.sipGatewayEnabled = this.xmppService.options.hosts.call_control !== undefined;
var eventEmitter = null;
this.eventEmitter = emitter;
var Moderator = {
isModerator: function () {
return connection && connection.emuc.isModerator();
},
isPeerModerator: function (peerJid) {
return connection &&
connection.emuc.getMemberRole(peerJid) === 'moderator';
},
isExternalAuthEnabled: function () {
return externalAuthEnabled;
},
isSipGatewayEnabled: function () {
return sipGatewayEnabled;
},
setConnection: function (con) {
connection = con;
},
init: function (xmpp, emitter) {
this.xmppService = xmpp;
sipGatewayEnabled = this.xmppService.options.hosts.call_control !== undefined;
eventEmitter = emitter;
// Message listener that talks to POPUP window
function listener(event) {
if (event.data && event.data.sessionId) {
if (event.origin !== window.location.origin) {
console.warn("Ignoring sessionId from different origin: " +
event.origin);
return;
}
localStorage.setItem('sessionId', event.data.sessionId);
// After popup is closed we will authenticate
this.connection = this.xmppService.connection;
this.focusUserJid;
//FIXME:
// Message listener that talks to POPUP window
function listener(event) {
if (event.data && event.data.sessionId) {
if (event.origin !== window.location.origin) {
console.warn("Ignoring sessionId from different origin: " +
event.origin);
return;
}
localStorage.setItem('sessionId', event.data.sessionId);
// After popup is closed we will authenticate
}
// Register
if (window.addEventListener) {
window.addEventListener("message", listener, false);
} else {
window.attachEvent("onmessage", listener);
}
},
}
// Register
if (window.addEventListener) {
window.addEventListener("message", listener, false);
} else {
window.attachEvent("onmessage", listener);
}
}
onMucMemberLeft: function (jid) {
console.info("Someone left is it focus ? " + jid);
var resource = Strophe.getResourceFromJid(jid);
if (resource === 'focus' && !this.xmppService.sessionTerminated) {
console.info(
"Focus has left the room - leaving conference");
//hangUp();
// We'd rather reload to have everything re-initialized
// FIXME: show some message before reload
location.reload();
}
},
setFocusUserJid: function (focusJid) {
if (!focusUserJid) {
focusUserJid = focusJid;
console.info("Focus jid set to: " + focusUserJid);
}
},
Moderator.prototype.isExternalAuthEnabled = function () {
return this.externalAuthEnabled;
};
getFocusUserJid: function () {
return focusUserJid;
},
Moderator.prototype.isSipGatewayEnabled = function () {
return this.sipGatewayEnabled;
};
getFocusComponent: function () {
// Get focus component address
var focusComponent = this.xmppService.options.hosts.focus;
// If not specified use default: 'focus.domain'
if (!focusComponent) {
focusComponent = 'focus.' + this.xmppService.options.hosts.domain;
}
return focusComponent;
},
createConferenceIq: function (roomName) {
// Generate create conference IQ
var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
// Session Id used for authentication
var sessionId = localStorage.getItem('sessionId');
var machineUID = Settings.getSettings().uid;
Moderator.prototype.onMucMemberLeft = function (jid) {
console.info("Someone left is it focus ? " + jid);
var resource = Strophe.getResourceFromJid(jid);
if (resource === 'focus' && !this.xmppService.sessionTerminated) {
console.info(
"Focus has left the room - leaving conference");
//hangUp();
// We'd rather reload to have everything re-initialized
//FIXME: show some message before reload
this.eventEmitter.emit(XMPPEvents.FOCUS_LEFT);
}
};
Moderator.prototype.setFocusUserJid = function (focusJid) {
if (!this.focusUserJid) {
this.focusUserJid = focusJid;
console.info("Focus jid set to: " + this.focusUserJid);
}
};
Moderator.prototype.getFocusUserJid = function () {
return this.focusUserJid;
};
Moderator.prototype.getFocusComponent = function () {
// Get focus component address
var focusComponent = this.xmppService.options.hosts.focus;
// If not specified use default: 'focus.domain'
if (!focusComponent) {
focusComponent = 'focus.' + this.xmppService.options.hosts.domain;
}
return focusComponent;
};
Moderator.prototype.createConferenceIq = function () {
// Generate create conference IQ
var elem = $iq({to: this.getFocusComponent(), type: 'set'});
// Session Id used for authentication
var sessionId = localStorage.getItem('sessionId');
var machineUID = this.settings.getSettings().uid;
console.info(
"Session ID: " + sessionId + " machine UID: " + machineUID);
elem.c('conference', {
xmlns: 'http://jitsi.org/protocol/focus',
room: roomName,
'machine-uid': machineUID
});
elem.c('conference', {
xmlns: 'http://jitsi.org/protocol/focus',
room: this.roomName,
'machine-uid': machineUID
});
if (sessionId) {
elem.attrs({ 'session-id': sessionId});
}
if (this.xmppService.options.hosts.bridge !== undefined) {
elem.c(
'property',
{ name: 'bridge', value: this.xmppService.options.hosts.bridge})
.up();
}
// Tell the focus we have Jigasi configured
if (this.xmppService.options.hosts.call_control !== undefined) {
elem.c(
'property',
{ name: 'call_control', value: this.xmppService.options.hosts.call_control})
.up();
}
if (this.xmppService.options.channelLastN !== undefined) {
elem.c(
'property',
{ name: 'channelLastN', value: this.xmppService.options.channelLastN})
.up();
}
if (this.xmppService.options.adaptiveLastN !== undefined) {
elem.c(
'property',
{ name: 'adaptiveLastN', value: this.xmppService.options.adaptiveLastN})
.up();
}
if (this.xmppService.options.adaptiveSimulcast !== undefined) {
elem.c(
'property',
{ name: 'adaptiveSimulcast', value: this.xmppService.options.adaptiveSimulcast})
.up();
}
if (this.xmppService.options.openSctp !== undefined) {
elem.c(
'property',
{ name: 'openSctp', value: this.xmppService.options.openSctp})
.up();
}
if(this.xmppService.options.startAudioMuted !== undefined)
{
elem.c(
'property',
{ name: 'startAudioMuted', value: this.xmppService.options.startAudioMuted})
.up();
}
if(this.xmppService.options.startVideoMuted !== undefined)
{
elem.c(
'property',
{ name: 'startVideoMuted', value: this.xmppService.options.startVideoMuted})
.up();
}
if (sessionId) {
elem.attrs({ 'session-id': sessionId});
}
if (this.xmppService.options.hosts.bridge !== undefined) {
elem.c(
'property',
{ name: 'simulcastMode', value: 'rewriting'})
{name: 'bridge',value: this.xmppService.options.hosts.bridge})
.up();
elem.up();
return elem;
},
}
// Tell the focus we have Jigasi configured
if (this.xmppService.options.hosts.call_control !== undefined) {
elem.c(
'property',
{name: 'call_control',value: this.xmppService.options.hosts.call_control})
.up();
}
if (this.xmppService.options.channelLastN !== undefined) {
elem.c(
'property',
{name: 'channelLastN',value: this.xmppService.options.channelLastN})
.up();
}
if (this.xmppService.options.adaptiveLastN !== undefined) {
elem.c(
'property',
{name: 'adaptiveLastN',value: this.xmppService.options.adaptiveLastN})
.up();
}
if (this.xmppService.options.adaptiveSimulcast !== undefined) {
elem.c(
'property',
{name: 'adaptiveSimulcast',value: this.xmppService.options.adaptiveSimulcast})
.up();
}
if (this.xmppService.options.openSctp !== undefined) {
elem.c(
'property',
{name: 'openSctp',value: this.xmppService.options.openSctp})
.up();
}
if(this.xmppService.options.startAudioMuted !== undefined)
{
elem.c(
'property',
{name: 'startAudioMuted',value: this.xmppService.options.startAudioMuted})
.up();
}
if(this.xmppService.options.startVideoMuted !== undefined)
{
elem.c(
'property',
{name: 'startVideoMuted',value: this.xmppService.options.startVideoMuted})
.up();
}
elem.c(
'property',
{name: 'simulcastMode',value: 'rewriting'})
.up();
elem.up();
return elem;
};
parseSessionId: function (resultIq) {
var sessionId = $(resultIq).find('conference').attr('session-id');
if (sessionId) {
console.info('Received sessionId: ' + sessionId);
localStorage.setItem('sessionId', sessionId);
}
},
parseConfigOptions: function (resultIq) {
Moderator.prototype.parseSessionId = function (resultIq) {
var sessionId = $(resultIq).find('conference').attr('session-id');
if (sessionId) {
console.info('Received sessionId: ' + sessionId);
localStorage.setItem('sessionId', sessionId);
}
};
Moderator.setFocusUserJid(
$(resultIq).find('conference').attr('focusjid'));
Moderator.prototype.parseConfigOptions = function (resultIq) {
var authenticationEnabled
= $(resultIq).find(
'>conference>property' +
'[name=\'authentication\'][value=\'true\']').length > 0;
this.setFocusUserJid(
$(resultIq).find('conference').attr('focusjid'));
console.info("Authentication enabled: " + authenticationEnabled);
externalAuthEnabled = $(resultIq).find(
'>conference>property' +
'[name=\'externalAuth\'][value=\'true\']').length > 0;
console.info('External authentication enabled: ' + externalAuthEnabled);
if (!externalAuthEnabled) {
// We expect to receive sessionId in 'internal' authentication mode
Moderator.parseSessionId(resultIq);
}
var authIdentity = $(resultIq).find('>conference').attr('identity');
eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
authenticationEnabled, authIdentity);
// Check if focus has auto-detected Jigasi component(this will be also
// included if we have passed our host from the config)
if ($(resultIq).find(
var authenticationEnabled
= $(resultIq).find(
'>conference>property' +
'[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
sipGatewayEnabled = true;
}
console.info("Sip gateway enabled: " + sipGatewayEnabled);
},
'[name=\'authentication\'][value=\'true\']').length > 0;
// FIXME: we need to show the fact that we're waiting for the focus
// to the user(or that focus is not available)
allocateConferenceFocus: function (roomName, callback) {
// Try to use focus user JID from the config
Moderator.setFocusUserJid(this.xmppService.options.focusUserJid);
// Send create conference IQ
var iq = Moderator.createConferenceIq(roomName);
var self = this;
connection.sendIQ(
iq,
function (result) {
console.info("Authentication enabled: " + authenticationEnabled);
// Setup config options
Moderator.parseConfigOptions(result);
this.externalAuthEnabled = $(resultIq).find(
'>conference>property' +
'[name=\'externalAuth\'][value=\'true\']').length > 0;
if ('true' === $(result).find('conference').attr('ready')) {
// Reset both timers
getNextTimeout(true);
getNextErrorTimeout(true);
// Exec callback
callback();
} else {
var waitMs = getNextTimeout();
console.info("Waiting for the focus... " + waitMs);
// Reset error timeout
getNextErrorTimeout(true);
window.setTimeout(
function () {
Moderator.allocateConferenceFocus(
roomName, callback);
}, waitMs);
}
},
function (error) {
// Invalid session ? remove and try again
// without session ID to get a new one
var invalidSession
= $(error).find('>error>session-invalid').length;
if (invalidSession) {
console.info("Session expired! - removing");
localStorage.removeItem("sessionId");
}
if ($(error).find('>error>graceful-shutdown').length) {
eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
return;
}
// Check for error returned by the reservation system
var reservationErr = $(error).find('>error>reservation-error');
if (reservationErr.length) {
// Trigger error event
var errorCode = reservationErr.attr('error-code');
var errorMsg;
if ($(error).find('>error>text')) {
errorMsg = $(error).find('>error>text').text();
}
eventEmitter.emit(
XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
return;
}
// Not authorized to create new room
if ($(error).find('>error>not-authorized').length) {
console.warn("Unauthorized to start the conference", error);
var toDomain
= Strophe.getDomainFromJid(error.getAttribute('to'));
if (toDomain !== this.xmppService.options.hosts.anonymousdomain) {
// FIXME: "is external" should come either from
// the focus or config.js
externalAuthEnabled = true;
}
eventEmitter.emit(
XMPPEvents.AUTHENTICATION_REQUIRED,
function () {
Moderator.allocateConferenceFocus(
roomName, callback);
});
return;
}
var waitMs = getNextErrorTimeout();
console.error("Focus error, retry after " + waitMs, error);
// Show message
var focusComponent = Moderator.getFocusComponent();
var retrySec = waitMs / 1000;
// FIXME: message is duplicated ?
// Do not show in case of session invalid
// which means just a retry
if (!invalidSession) {
eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED,
focusComponent, retrySec);
}
// Reset response timeout
getNextTimeout(true);
console.info('External authentication enabled: ' + this.externalAuthEnabled);
if (!this.externalAuthEnabled) {
// We expect to receive sessionId in 'internal' authentication mode
this.parseSessionId(resultIq);
}
var authIdentity = $(resultIq).find('>conference').attr('identity');
this.eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
authenticationEnabled, authIdentity);
// Check if focus has auto-detected Jigasi component(this will be also
// included if we have passed our host from the config)
if ($(resultIq).find(
'>conference>property' +
'[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
this.sipGatewayEnabled = true;
}
console.info("Sip gateway enabled: " + this.sipGatewayEnabled);
};
// FIXME = we need to show the fact that we're waiting for the focus
// to the user(or that focus is not available)
Moderator.prototype.allocateConferenceFocus = function ( callback) {
// Try to use focus user JID from the config
this.setFocusUserJid(this.xmppService.options.focusUserJid);
// Send create conference IQ
var iq = this.createConferenceIq();
var self = this;
this.connection.sendIQ(
iq,
function (result) {
// Setup config options
self.parseConfigOptions(result);
if ('true' === $(result).find('conference').attr('ready')) {
// Reset both timers
self.getNextTimeout(true);
self.getNextErrorTimeout(true);
// Exec callback
callback();
} else {
var waitMs = self.getNextTimeout();
console.info("Waiting for the focus... " + waitMs);
// Reset error timeout
self.getNextErrorTimeout(true);
window.setTimeout(
function () {
Moderator.allocateConferenceFocus(roomName, callback);
self.allocateConferenceFocus(callback);
}, waitMs);
}
);
},
getLoginUrl: function (roomName, urlCallback) {
var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
iq.c('login-url', {
xmlns: 'http://jitsi.org/protocol/focus',
room: roomName,
'machine-uid': Settings.getSettings().uid
});
connection.sendIQ(
iq,
function (result) {
var url = $(result).find('login-url').attr('url');
url = url = decodeURIComponent(url);
if (url) {
console.info("Got auth url: " + url);
urlCallback(url);
} else {
console.error(
"Failed to get auth url from the focus", result);
}
},
function (error) {
console.error("Get auth url error", error);
},
function (error) {
// Invalid session ? remove and try again
// without session ID to get a new one
var invalidSession
= $(error).find('>error>session-invalid').length;
if (invalidSession) {
console.info("Session expired! - removing");
localStorage.removeItem("sessionId");
}
);
},
getPopupLoginUrl: function (roomName, urlCallback) {
var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
iq.c('login-url', {
xmlns: 'http://jitsi.org/protocol/focus',
room: roomName,
'machine-uid': Settings.getSettings().uid,
popup: true
});
connection.sendIQ(
iq,
function (result) {
var url = $(result).find('login-url').attr('url');
url = url = decodeURIComponent(url);
if (url) {
console.info("Got POPUP auth url: " + url);
urlCallback(url);
} else {
console.error(
"Failed to get POPUP auth url from the focus", result);
}
},
function (error) {
console.error('Get POPUP auth url error', error);
if ($(error).find('>error>graceful-shutdown').length) {
self.eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
return;
}
);
},
logout: function (callback) {
var iq = $iq({to: Moderator.getFocusComponent(), type: 'set'});
var sessionId = localStorage.getItem('sessionId');
if (!sessionId) {
callback();
return;
// Check for error returned by the reservation system
var reservationErr = $(error).find('>error>reservation-error');
if (reservationErr.length) {
// Trigger error event
var errorCode = reservationErr.attr('error-code');
var errorMsg;
if ($(error).find('>error>text')) {
errorMsg = $(error).find('>error>text').text();
}
self.eventEmitter.emit(
XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
return;
}
// Not authorized to create new room
if ($(error).find('>error>not-authorized').length) {
console.warn("Unauthorized to start the conference", error);
var toDomain
= Strophe.getDomainFromJid(error.getAttribute('to'));
if (toDomain !== this.xmppService.options.hosts.anonymousdomain) {
//FIXME: "is external" should come either from
// the focus or config.js
self.externalAuthEnabled = true;
}
self.eventEmitter.emit(
XMPPEvents.AUTHENTICATION_REQUIRED,
function () {
self.allocateConferenceFocus(
callback);
});
return;
}
var waitMs = self.getNextErrorTimeout();
console.error("Focus error, retry after " + waitMs, error);
// Show message
var focusComponent = self.getFocusComponent();
var retrySec = waitMs / 1000;
//FIXME: message is duplicated ?
// Do not show in case of session invalid
// which means just a retry
if (!invalidSession) {
self.eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED,
focusComponent, retrySec);
}
// Reset response timeout
self.getNextTimeout(true);
window.setTimeout(
function () {
self.allocateConferenceFocus(callback);
}, waitMs);
}
iq.c('logout', {
xmlns: 'http://jitsi.org/protocol/focus',
'session-id': sessionId
});
connection.sendIQ(
iq,
function (result) {
var logoutUrl = $(result).find('logout').attr('logout-url');
if (logoutUrl) {
logoutUrl = decodeURIComponent(logoutUrl);
}
console.info("Log out OK, url: " + logoutUrl, result);
localStorage.removeItem('sessionId');
callback(logoutUrl);
},
function (error) {
console.error("Logout error", error);
);
};
Moderator.prototype.getLoginUrl = function (urlCallback) {
var iq = $iq({to: this.getFocusComponent(), type: 'get'});
iq.c('login-url', {
xmlns: 'http://jitsi.org/protocol/focus',
room: this.roomName,
'machine-uid': this.settings.getSettings().uid
});
this.connection.sendIQ(
iq,
function (result) {
var url = $(result).find('login-url').attr('url');
url = url = decodeURIComponent(url);
if (url) {
console.info("Got auth url: " + url);
urlCallback(url);
} else {
console.error(
"Failed to get auth url from the focus", result);
}
);
},
function (error) {
console.error("Get auth url error", error);
}
);
};
Moderator.prototype.getPopupLoginUrl = function (urlCallback) {
var iq = $iq({to: this.getFocusComponent(), type: 'get'});
iq.c('login-url', {
xmlns: 'http://jitsi.org/protocol/focus',
room: this.roomName,
'machine-uid': this.settings.getSettings().uid,
popup: true
});
this.connection.sendIQ(
iq,
function (result) {
var url = $(result).find('login-url').attr('url');
url = url = decodeURIComponent(url);
if (url) {
console.info("Got POPUP auth url: " + url);
urlCallback(url);
} else {
console.error(
"Failed to get POPUP auth url from the focus", result);
}
},
function (error) {
console.error('Get POPUP auth url error', error);
}
);
};
Moderator.prototype.logout = function (callback) {
var iq = $iq({to: this.getFocusComponent(), type: 'set'});
var sessionId = localStorage.getItem('sessionId');
if (!sessionId) {
callback();
return;
}
iq.c('logout', {
xmlns: 'http://jitsi.org/protocol/focus',
'session-id': sessionId
});
this.connection.sendIQ(
iq,
function (result) {
var logoutUrl = $(result).find('logout').attr('logout-url');
if (logoutUrl) {
logoutUrl = decodeURIComponent(logoutUrl);
}
console.info("Log out OK, url: " + logoutUrl, result);
localStorage.removeItem('sessionId');
callback(logoutUrl);
},
function (error) {
console.error("Logout error", error);
}
);
};
module.exports = Moderator;

View File

@ -5,6 +5,7 @@
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var Moderator = require("./moderator");
var RTC = require("../RTC/RTC");
var EventEmitter = require("events");
var parser = {
packet2JSON: function (packet, nodes) {
@ -46,6 +47,7 @@ var parser = {
function ChatRoom(connection, jid, password, XMPP, eventEmitter)
{
this.eventEmitter = eventEmitter;
this.roomEmitter = new EventEmitter();
this.xmpp = XMPP;
this.connection = connection;
this.roomjid = Strophe.getBareJidFromJid(jid);
@ -54,11 +56,24 @@ function ChatRoom(connection, jid, password, XMPP, eventEmitter)
console.info("Joined MUC as " + this.myroomjid);
this.members = {};
this.presMap = {};
this.presHandlers = {};
this.joined = false;
this.role = null;
this.focusMucJid = null;
this.bridgeIsDown = false;
this.moderator = new Moderator(this.roomjid, this.xmpp, eventEmitter);
this.initPresenceMap();
this.readyToJoin = false;
this.joinRequested = false;
var self = this;
this.moderator.allocateConferenceFocus(function()
{
self.readyToJoin = true;
if(self.joinRequested)
{
self.join();
}
});
}
ChatRoom.prototype.initPresenceMap = function () {
@ -84,6 +99,18 @@ ChatRoom.prototype.initPresenceMap = function () {
});
};
ChatRoom.prototype.join = function (password) {
if(password)
this.password = password;
if(!this.readyToJoin)
{
this.joinRequested = true;
return;
}
this.joinRequested = false;
this.sendPresence();
}
ChatRoom.prototype.sendPresence = function () {
if (!this.presMap['to']) {
// Too early to send presence - not initialized
@ -169,11 +196,11 @@ ChatRoom.prototype.onPresence = function (pres) {
member.jid = tmp.attr('jid');
member.isFocus = false;
if (member.jid
&& member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
&& member.jid.indexOf(this.moderator.getFocusUserJid() + "/") == 0) {
member.isFocus = true;
}
pres.find(">x").remove();
$(pres).find(">x").remove();
var nodes = [];
parser.packet2JSON(pres, nodes);
for(var i = 0; i < nodes.length; i++)
@ -206,7 +233,7 @@ ChatRoom.prototype.onPresence = function (pres) {
}
break;
default :
this.processNode(node);
this.processNode(node, from);
}
}
@ -218,7 +245,7 @@ ChatRoom.prototype.onPresence = function (pres) {
this.role = member.role;
this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
member, Moderator.isModerator());
member, this.isModerator());
}
if (!this.joined) {
this.joined = true;
@ -257,8 +284,9 @@ ChatRoom.prototype.onPresence = function (pres) {
};
ChatRoom.prototype.processNode = function (node) {
this.eventEmitter.emit(XMPPEvents.PRESENCE_SETTING, node);
ChatRoom.prototype.processNode = function (node, from) {
if(this.presHandlers[node.tagName])
this.presHandlers[node.tagName](node, from);
};
ChatRoom.prototype.sendMessage = function (body, nickname) {
@ -283,9 +311,7 @@ ChatRoom.prototype.onParticipantLeft = function (jid) {
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
this.connection.jingle.terminateByJid(jid);
Moderator.onMucMemberLeft(jid);
this.moderator.onMucMemberLeft(jid);
};
ChatRoom.prototype.onPresenceUnavailable = function (pres, from) {
@ -447,6 +473,14 @@ ChatRoom.prototype.removeFromPresence = function (key) {
}
};
ChatRoom.prototype.addPresenceListener = function (name, handler) {
this.presHandlers[name] = handler;
}
ChatRoom.prototype.removePresenceListener = function (name) {
delete this.presHandlers[name];
}
ChatRoom.prototype.isModerator = function (jid) {
return this.role === 'moderator';
};
@ -466,21 +500,18 @@ module.exports = function(XMPP) {
init: function (conn) {
this.connection = conn;
// add handlers (just once)
this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null);
this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, null, null);
this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null);
this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null);
this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null);
},
doJoin: function (jid, password, eventEmitter) {
createRoom: function (jid, password, eventEmitter) {
var roomJid = Strophe.getBareJidFromJid(jid);
if(this.rooms[roomJid])
{
if (this.rooms[roomJid]) {
console.error("You are already in the room!");
return;
}
this.rooms[roomJid] = new ChatRoom(this.connection, jid, password, XMPP, eventEmitter);
this.rooms[roomJid].sendPresence();
return this.rooms[roomJid];
},
doLeave: function (jid) {

View File

@ -11,7 +11,6 @@ module.exports = function(XMPP, eventEmitter) {
sessions: {},
jid2session: {},
ice_config: {iceServers: []},
activecall: null,
media_constraints: {
mandatory: {
'OfferToReceiveAudio': true,
@ -108,7 +107,7 @@ module.exports = function(XMPP, eventEmitter) {
sess.media_constraints = this.media_constraints;
sess.ice_config = this.ice_config;
sess.initialize(fromJid, false);
sess.initialize(Strophe.getBareJidFromJid(fromJid), false);
// FIXME: setRemoteDescription should only be done when this call is to be accepted
sess.setOffer($(iq).find('>jingle'));
@ -119,21 +118,14 @@ module.exports = function(XMPP, eventEmitter) {
// .sendAnswer and .accept
// or .sendTerminate -- not necessarily synchronous
// TODO: do we check activecall == null?
this.connection.jingle.activecall = sess;
eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
// TODO: check affiliation and/or role
console.log('emuc data for', sess.peerjid,
this.connection.emuc.members[sess.peerjid]);
sess.sendAnswer();
sess.accept();
break;
case 'session-accept':
sess.setAnswer($(iq).find('>jingle'));
sess.accept();
$(document).trigger('callaccepted.jingle', [sess.sid]);
break;
case 'session-terminate':
// If this is not the focus sending the terminate, we have
@ -188,21 +180,6 @@ module.exports = function(XMPP, eventEmitter) {
}
return true;
},
initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
var sess = new JingleSession(myjid || this.connection.jid,
Math.random().toString(36).substr(2, 12), // random string
this.connection, XMPP, eventEmitter);
// configure session
sess.media_constraints = this.media_constraints;
sess.ice_config = this.ice_config;
sess.initialize(peerjid, true);
this.sessions[sess.sid] = sess;
this.jid2session[sess.peerjid] = sess;
sess.sendOffer();
return sess;
},
terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
if (sid === null || sid === undefined) {
for (sid in this.sessions) {
@ -222,34 +199,6 @@ module.exports = function(XMPP, eventEmitter) {
delete this.sessions[sid];
}
},
// Used to terminate a session when an unavailable presence is received.
terminateByJid: function (jid) {
if (this.jid2session.hasOwnProperty(jid)) {
var sess = this.jid2session[jid];
if (sess) {
sess.terminate();
console.log('peer went away silently', jid);
delete this.sessions[sess.sid];
delete this.jid2session[jid];
$(document).trigger('callterminated.jingle',
[sess.sid, jid], 'gone');
}
}
},
terminateRemoteByJid: function (jid, reason) {
if (this.jid2session.hasOwnProperty(jid)) {
var sess = this.jid2session[jid];
if (sess) {
sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
sess.terminate();
console.log('terminate peer with jid', sess.sid, jid);
delete this.sessions[sess.sid];
delete this.jid2session[jid];
$(document).trigger('callterminated.jingle',
[sess.sid, jid, 'kicked']);
}
}
},
getStunAndTurnCredentials: function () {
// get stun and turn configuration from server via xep-0215
// uses time-limited credentials as described in

View File

@ -23,7 +23,7 @@ function createConnection(bosh) {
function initStrophePlugins(XMPP)
{
require("./strophe.emuc")(XMPP);
// require("./strophe.jingle")(XMPP, eventEmitter);
require("./strophe.jingle")(XMPP, XMPP.eventEmitter);
// require("./strophe.moderate")(XMPP, eventEmitter);
require("./strophe.util")();
require("./strophe.rayo")();
@ -52,7 +52,6 @@ function initStrophePlugins(XMPP)
//}
function XMPP(options) {
this.sessionTerminated = false;
this.eventEmitter = new EventEmitter();
this.connection = null;
this.disconnectInProgress = false;
@ -61,9 +60,8 @@ function XMPP(options) {
this.options = options;
initStrophePlugins(this);
// registerListeners();
Moderator.init(this, this.eventEmitter);
this.connection = createConnection(options.bosh);
Moderator.setConnection(this.connection);
}
@ -118,7 +116,7 @@ XMPP.prototype._connect = function (jid, password) {
if (self.connection && self.connection.connected &&
Strophe.getResourceFromJid(self.connection.jid)) {
// .connected is true while connecting?
self.connection.send($pres());
// self.connection.send($pres());
self.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_ESTABLISHED,
Strophe.getResourceFromJid(self.connection.jid));
}
@ -167,8 +165,8 @@ XMPP.prototype.connect = function (jid, password) {
return this._connect(jid, password);
};
XMPP.prototype.joinRoom = function(roomName, useNicks, nick) {
var roomjid = roomName + '@' + Strophe.getDomainFromJid(this.connection.jid);
XMPP.prototype.createRoom = function (roomName, useNicks, nick) {
var roomjid = roomName + '@' + this.options.hosts.muc;
if (useNicks) {
if (nick) {
@ -184,32 +182,9 @@ XMPP.prototype.joinRoom = function(roomName, useNicks, nick) {
roomjid += '/' + tmpJid;
}
return this.connection.emuc.doJoin(roomjid, null, this.eventEmitter);
};
XMPP.prototype.disposeConference = function (onUnload) {
var handler = this.connection.jingle.activecall;
if (handler && handler.peerconnection) {
// FIXME: probably removing streams is not required and close() should
// be enough
if (RTC.localAudio) {
handler.peerconnection.removeStream(
RTC.localAudio.getOriginalStream(), onUnload);
}
if (RTC.localVideo) {
handler.peerconnection.removeStream(
RTC.localVideo.getOriginalStream(), onUnload);
}
handler.peerconnection.close();
}
this.eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
this.connection.jingle.activecall = null;
if (!onUnload) {
this.sessionTerminated = true;
this.connection.emuc.doLeave();
}
};
return this.connection.emuc.createRoom(roomjid, null, this.eventEmitter);
}
XMPP.prototype.addListener = function(type, listener) {
this.eventEmitter.on(type, listener);
@ -220,34 +195,24 @@ XMPP.prototype.removeListener = function (type, listener) {
};
XMPP.prototype.leaveRoom = function (jid) {
var handler = this.connection.jingle.jid2session[jid];
if (handler && handler.peerconnection) {
// FIXME: probably removing streams is not required and close() should
// be enough
if (RTC.localAudio) {
handler.peerconnection.removeStream(
RTC.localAudio.getOriginalStream(), true);
}
if (RTC.localVideo) {
handler.peerconnection.removeStream(
RTC.localVideo.getOriginalStream(), true);
}
handler.peerconnection.close();
}
this.eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE);
this.connection.emuc.doLeave(jid);
};
XMPP.prototype.allocateConferenceFocus = function(roomName, callback) {
Moderator.allocateConferenceFocus(roomName, callback);
};
XMPP.prototype.getLoginUrl = function (roomName, callback) {
Moderator.getLoginUrl(roomName, callback);
}
XMPP.prototype.getPopupLoginUrl = function (roomName, callback) {
Moderator.getPopupLoginUrl(roomName, callback);
};
XMPP.prototype.isModerator = function () {
return Moderator.isModerator();
};
XMPP.prototype.isSipGatewayEnabled = function () {
return Moderator.isSipGatewayEnabled();
}
XMPP.prototype.isExternalAuthEnabled = function () {
return Moderator.isExternalAuthEnabled();
};
XMPP.prototype.isConferenceInProgress = function () {
return this.connection && this.connection.jingle.activecall &&
this.connection.jingle.activecall.peerconnection;
@ -376,10 +341,6 @@ XMPP.prototype.eject = function (jid) {
this.connection.moderate.eject(jid);
};
XMPP.prototype.logout = function (callback) {
Moderator.logout(callback);
};
XMPP.prototype.getJidFromSSRC = function (ssrc) {
if (!this.isConferenceInProgress())
return null;
@ -396,7 +357,7 @@ XMPP.prototype.removeStream = function (stream) {
this.connection.jingle.activecall.peerconnection.removeStream(stream);
};
XMPP.prototype.disconnect = function (callback) {
XMPP.prototype.disconnect = function () {
if (this.disconnectInProgress || !this.connection || !this.connection.connected)
{
this.eventEmitter.emit(JitsiConnectionEvents.WRONG_STATE);

View File

@ -49,6 +49,7 @@ var XMPPEvents = {
ROOM_JOIN_ERROR: 'xmpp.room_join_error',
ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
// xmpp is connected and obtained user media
READY_TO_JOIN: 'xmpp.ready_to_join'
READY_TO_JOIN: 'xmpp.ready_to_join',
FOCUS_LEFT: "xmpp.focus_left"
};
module.exports = XMPPEvents;