diff --git a/app.js b/app.js
index 4555f979a..1801f418c 100644
--- a/app.js
+++ b/app.js
@@ -2,11 +2,12 @@
/* application specific logic */
var connection = null;
var authenticatedUser = false;
+var authenticationWindow = null;
var activecall = null;
var RTC = null;
var nickname = null;
var sharedKey = '';
-var focusJid = null;
+var focusMucJid = null;
var roomUrl = null;
var roomName = null;
var ssrc2jid = {};
@@ -164,6 +165,8 @@ function connect(jid, password) {
}
document.getElementById('connect').disabled = true;
+ console.info("My Jabber ID: " + connection.jid);
+
if(password)
authenticatedUser = true;
maybeDoJoin();
@@ -755,6 +758,10 @@ $(document).bind('joined.muc', function (event, jid, info) {
// Once we've joined the muc show the toolbar
ToolbarToggler.showToolbar();
+ // Show authenticate button if needed
+ Toolbar.showAuthenticateButton(
+ Moderator.isExternalAuthEnabled() && !Moderator.isModerator());
+
var displayName = !config.displayJids
? info.displayName : Strophe.getResourceFromJid(jid);
@@ -767,10 +774,8 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
console.log('entered', jid, info);
if (info.isFocus)
{
- focusJid = jid;
+ focusMucJid = jid;
console.info("Ignore focus: " + jid +", real JID: " + info.jid);
- // We don't want this notification for the focus.
- // messageHandler.notify('Focus', 'connected', 'connected');
return;
}
@@ -944,6 +949,44 @@ $(document).bind('kicked.muc', function (event, jid) {
}
});
+$(document).bind('role.changed.muc', function (event, jid, member, pres) {
+ console.info("Role changed for " + jid + ", new role: " + member.role);
+
+ VideoLayout.showModeratorIndicator();
+
+ if (member.role === 'moderator') {
+ var displayName = member.displayName;
+ if (!displayName) {
+ displayName = 'Somebody';
+ }
+ messageHandler.notify(
+ displayName,
+ 'connected',
+ 'Moderator rights granted to ' + displayName + '!');
+ }
+ }
+);
+
+$(document).bind('local.role.changed.muc', function (event, jid, info, pres) {
+
+ console.info("My role changed, new role: " + info.role);
+ var isModerator = Moderator.isModerator();
+
+ VideoLayout.showModeratorIndicator();
+ Toolbar.showAuthenticateButton(
+ Moderator.isExternalAuthEnabled() && !isModerator);
+
+ if (isModerator) {
+ if (authenticationWindow) {
+ authenticationWindow.close();
+ authenticationWindow = null;
+ }
+ messageHandler.notify(
+ 'Me', 'connected', 'Moderator rights granted !');
+ }
+ }
+);
+
$(document).bind('passwordrequired.muc', function (event, jid) {
console.log('on password required', jid);
@@ -996,6 +1039,44 @@ $(document).bind('passwordrequired.main', function (event) {
);
});
+$(document).bind('auth_required.moderator', function () {
+ // extract room name from 'room@muc.server.net'
+ var room = roomName.substr(0, roomName.indexOf('@'));
+
+ messageHandler.openDialog(
+ 'Stop',
+ 'Authentication is required to create room:
' + room,
+ true,
+ {
+ Authenticate: 'authNow',
+ Close: 'close'
+ },
+ function (onSubmitEvent, submitValue) {
+ console.info('On submit: ' + submitValue, submitValue);
+ if (submitValue === 'authNow') {
+ authenticateClicked();
+ } else {
+ Toolbar.showAuthenticateButton(true);
+ }
+ }
+ );
+});
+
+function authenticateClicked() {
+ // Get authentication URL
+ Moderator.getAuthUrl(function (url) {
+ // Open popup with authentication URL
+ authenticationWindow = messageHandler.openCenteredPopup(
+ url, 500, 400,
+ function () {
+ // On popup closed - retry room allocation
+ Moderator.allocateConferenceFocus(
+ roomName, doJoinAfterFocus);
+ authenticationWindow = null;
+ });
+ });
+};
+
/**
* Checks if video identified by given src is desktop stream.
* @param videoSrc eg.
@@ -1474,6 +1555,9 @@ $(window).bind('beforeunload', function () {
});
function disposeConference(onUnload) {
+
+ Toolbar.showAuthenticateButton(false);
+
var handler = getConferenceHandler();
if (handler && handler.peerconnection) {
// FIXME: probably removing streams is not required and close() should
diff --git a/css/font.css b/css/font.css
index dbd44a2f7..49ebc90c6 100644
--- a/css/font.css
+++ b/css/font.css
@@ -42,6 +42,9 @@
.icon-recEnable:before {
content: "\e614";
}
+.icon-authenticate:before {
+ content: "\e1ae";
+}
.icon-kick1:before {
content: "\e60f";
}
diff --git a/index.html b/index.html
index 85e88f750..5c0618621 100644
--- a/index.html
+++ b/index.html
@@ -184,6 +184,12 @@
+
+
+
+
+
+
diff --git a/message_handler.js b/message_handler.js
index caa967fd7..40a47ccea 100644
--- a/message_handler.js
+++ b/message_handler.js
@@ -80,6 +80,32 @@ var messageHandler = (function(my) {
myPrompt.on('impromptu:statechanged', stateChangedFunction);
};
+ /**
+ * Opens new popup window for given url centered over current
+ * window.
+ *
+ * @param url the URL to be displayed in the popup window
+ * @param w the width of the popup window
+ * @param h the height of the popup window
+ * @param onPopupClosed optional callback function called when popup window
+ * has been closed.
+ */
+ my.openCenteredPopup = function (url, w, h, onPopupClosed) {
+ var l = window.screenX + (window.innerWidth / 2) - (w / 2);
+ var t = window.screenY + (window.innerHeight / 2) - (h / 2);
+ var popup = window.open(
+ url, '_blank',
+ 'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + '');
+ if (onPopupClosed) {
+ var pollTimer = window.setInterval(function () {
+ if (popup.closed !== false) {
+ window.clearInterval(pollTimer);
+ onPopupClosed();
+ }
+ }, 200);
+ }
+ };
+
/**
* Shows a dialog prompting the user to send an error report.
*
diff --git a/moderatemuc.js b/moderatemuc.js
index e9ba04ed8..314cbc35c 100644
--- a/moderatemuc.js
+++ b/moderatemuc.js
@@ -1,4 +1,4 @@
-/* global $, $iq, config, connection, focusJid, forceMuted, messageHandler,
+/* global $, $iq, config, connection, focusMucJid, forceMuted, messageHandler,
setAudioMuted, Strophe, toggleAudio */
/**
* Moderate connection plugin.
@@ -17,7 +17,7 @@ Strophe.addConnectionPlugin('moderate', {
},
setMute: function (jid, mute) {
console.info("set mute", mute);
- var iqToFocus = $iq({to: focusJid, type: 'set'})
+ var iqToFocus = $iq({to: focusMucJid, type: 'set'})
.c('mute', {
xmlns: 'http://jitsi.org/jitmeet/audio',
jid: jid
@@ -40,7 +40,7 @@ Strophe.addConnectionPlugin('moderate', {
},
onMute: function (iq) {
var from = iq.getAttribute('from');
- if (from !== focusJid) {
+ if (from !== focusMucJid) {
console.warn("Ignored mute from non focus peer");
return false;
}
diff --git a/moderator.js b/moderator.js
index 9796ce91e..a4f31c415 100644
--- a/moderator.js
+++ b/moderator.js
@@ -9,11 +9,21 @@ var Moderator = (function (my) {
var focusUserJid;
var getNextTimeout = Util.createExpBackoffTimer(1000);
var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
+ // External authentication stuff
+ var externalAuthEnabled = false;
my.isModerator = function () {
return connection.emuc.isModerator();
};
+ my.isPeerModerator = function (peerJid) {
+ return connection.emuc.getMemberRole(peerJid) === 'moderator';
+ };
+
+ my.isExternalAuthEnabled = function () {
+ return externalAuthEnabled;
+ };
+
my.onModeratorStatusChanged = function (isModerator) {
Toolbar.showSipCallButton(isModerator);
@@ -27,25 +37,12 @@ var Moderator = (function (my) {
if (isModerator && config.etherpad_base) {
Etherpad.init();
}
-
- $(document).trigger('local.role.moderator', [isModerator]);
};
my.init = function () {
- $(document).bind(
- 'role.changed.muc',
- function (event, jid, info, pres) {
- console.info(
- "Role changed for " + jid + ", new role: " + info.role);
- VideoLayout.showModeratorIndicator();
- }
- );
-
$(document).bind(
'local.role.changed.muc',
function (event, jid, info, pres) {
- console.info("My role changed, new role: " + info.role);
- VideoLayout.showModeratorIndicator();
Moderator.onModeratorStatusChanged(Moderator.isModerator());
}
);
@@ -141,6 +138,19 @@ var Moderator = (function (my) {
return elem;
};
+ my.parseConfigOptions = function (resultIq) {
+
+ Moderator.setFocusUserJid(
+ $(resultIq).find('conference').attr('focusjid'));
+
+ var extAuthParam
+ = $(resultIq).find('>conference>property[name=\'externalAuth\']');
+ if (extAuthParam.length) {
+ externalAuthEnabled = extAuthParam.attr('value') === 'true';
+ }
+ console.info("External authentication enabled: " + externalAuthEnabled);
+ };
+
// FIXME: we need to show the fact that we're waiting for the focus
// to the user(or that focus is not available)
my.allocateConferenceFocus = function (roomName, callback) {
@@ -155,8 +165,9 @@ var Moderator = (function (my) {
// Reset both timers
getNextTimeout(true);
getNextErrorTimeout(true);
- Moderator.setFocusUserJid(
- $(result).find('conference').attr('focusjid'));
+ // Setup config options
+ Moderator.parseConfigOptions(result);
+ // Exec callback
callback();
} else {
var waitMs = getNextTimeout();
@@ -171,6 +182,12 @@ var Moderator = (function (my) {
}
},
function (error) {
+ // Not authorized to create new room
+ if ($(error).find('>error>not-authorized').length) {
+ console.warn("Unauthorized to start the conference");
+ $(document).trigger('auth_required.moderator');
+ return;
+ }
var waitMs = getNextErrorTimeout();
console.error("Focus error, retry after " + waitMs, error);
// Show message
@@ -188,6 +205,30 @@ var Moderator = (function (my) {
);
};
+ my.getAuthUrl = function (urlCallback) {
+ var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
+ iq.c('auth-url', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ room: roomName
+ });
+ connection.sendIQ(
+ iq,
+ function (result) {
+ var url = $(result).find('auth-url').attr('url');
+ if (url) {
+ console.info("Got auth url: " + url);
+ urlCallback(url);
+ } else {
+ console.error(
+ "Failed to get auth url fro mthe focus", result);
+ }
+ },
+ function (error) {
+ console.error("Get auth url error", error);
+ }
+ );
+ };
+
return my;
}(Moderator || {}));
diff --git a/muc.js b/muc.js
index 5884e9aab..a246a0f01 100644
--- a/muc.js
+++ b/muc.js
@@ -503,5 +503,11 @@ Strophe.addConnectionPlugin('emuc', {
},
isModerator: function() {
return this.role === 'moderator';
+ },
+ getMemberRole: function(peerJid) {
+ if (this.members[peerJid]) {
+ return this.members[peerJid].role;
+ }
+ return null;
}
});
diff --git a/recording.js b/recording.js
index 2961475a6..26b1a1619 100644
--- a/recording.js
+++ b/recording.js
@@ -1,4 +1,4 @@
-/* global $, $iq, config, connection, focusJid, messageHandler, Moderator,
+/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
Toolbar, Util */
var Recording = (function (my) {
var recordingToken = null;
@@ -13,7 +13,7 @@ var Recording = (function (my) {
// with the new recording state, according to the IQ.
my.setRecording = function (state, token, callback) {
var self = this;
- var elem = $iq({to: focusJid, type: 'set'});
+ var elem = $iq({to: focusMucJid, type: 'set'});
elem.c('conference', {
xmlns: 'http://jitsi.org/protocol/colibri'
});
diff --git a/rtp_sts.js b/rtp_sts.js
index 654109d45..874ac8148 100644
--- a/rtp_sts.js
+++ b/rtp_sts.js
@@ -1,4 +1,4 @@
-/* global ssrc2jid */
+/* global focusMucJid, ssrc2jid */
/* jshint -W117 */
/**
* Calculates packet lost percent using the number of lost packets and the
@@ -324,7 +324,7 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
};
StatsCollector.prototype.logStats = function () {
- if (!focusJid) {
+ if (!focusMucJid) {
return;
}
@@ -337,7 +337,7 @@ StatsCollector.prototype.logStats = function () {
content = Base64.encode(content);
// XEP-0337-ish
- var message = $msg({to: focusJid, type: 'normal'});
+ var message = $msg({to: focusMucJid, type: 'normal'});
message.c('log', { xmlns: 'urn:xmpp:eventlog',
id: 'PeerConnectionStats'});
message.c('message').t(content).up();
diff --git a/toolbar.js b/toolbar.js
index d8eb7129a..32582ae8d 100644
--- a/toolbar.js
+++ b/toolbar.js
@@ -221,6 +221,19 @@ var Toolbar = (function (my) {
buttonClick("#lockIcon", "icon-security icon-security-locked");
};
+ /**
+ * Shows or hides authentication button
+ * @param show true to show or false to hide
+ */
+ my.showAuthenticateButton = function (show) {
+ if (show) {
+ $('#authentication').css({display: "inline"});
+ }
+ else {
+ $('#authentication').css({display: "none"});
+ }
+ };
+
// Shows or hides the 'recording' button.
my.showRecordingButton = function (show) {
if (!config.enableRecording) {
diff --git a/videolayout.js b/videolayout.js
index 10654ef91..1ef05cb22 100644
--- a/videolayout.js
+++ b/videolayout.js
@@ -485,7 +485,8 @@ var VideoLayout = (function (my) {
if ($('#' + videoSpanId).length > 0) {
// If there's been a focus change, make sure we add focus related
// interface!!
- if (Moderator.isModerator() && $('#remote_popupmenu_' + resourceJid).length <= 0) {
+ if (Moderator.isModerator() && !Moderator.isPeerModerator(peerJid)
+ && $('#remote_popupmenu_' + resourceJid).length <= 0) {
addRemoteVideoMenu(peerJid,
document.getElementById(videoSpanId));
}
@@ -905,38 +906,42 @@ var VideoLayout = (function (my) {
{
createModeratorIndicatorElement(indicatorSpan[0]);
}
- } else {
- Object.keys(connection.emuc.members).forEach(function (jid) {
- var member = connection.emuc.members[jid];
- if (member.role === 'moderator') {
- var moderatorId
- = 'participant_' + Strophe.getResourceFromJid(jid);
-
- var moderatorContainer
- = document.getElementById(moderatorId);
-
- if (Strophe.getResourceFromJid(jid) === 'focus') {
- // Skip server side focus
- return;
- }
- if (!moderatorContainer) {
- console.error("No moderator container for " + jid);
- return;
- }
- var indicatorSpan
- = $('#' + moderatorId + ' .focusindicator');
-
- if (!indicatorSpan || indicatorSpan.length === 0) {
- indicatorSpan = document.createElement('span');
- indicatorSpan.className = 'focusindicator';
-
- moderatorContainer.appendChild(indicatorSpan);
-
- createModeratorIndicatorElement(indicatorSpan);
- }
- }
- });
}
+ Object.keys(connection.emuc.members).forEach(function (jid) {
+ var member = connection.emuc.members[jid];
+ if (member.role === 'moderator') {
+ var moderatorId
+ = 'participant_' + Strophe.getResourceFromJid(jid);
+
+ var moderatorContainer
+ = document.getElementById(moderatorId);
+
+ if (Strophe.getResourceFromJid(jid) === 'focus') {
+ // Skip server side focus
+ return;
+ }
+ if (!moderatorContainer) {
+ console.error("No moderator container for " + jid);
+ return;
+ }
+ var menuSpan = $('#' + moderatorId + '>span.remotevideomenu');
+ if (menuSpan.length) {
+ removeRemoteVideoMenu(moderatorId);
+ }
+
+ var indicatorSpan
+ = $('#' + moderatorId + ' .focusindicator');
+
+ if (!indicatorSpan || indicatorSpan.length === 0) {
+ indicatorSpan = document.createElement('span');
+ indicatorSpan.className = 'focusindicator';
+
+ moderatorContainer.appendChild(indicatorSpan);
+
+ createModeratorIndicatorElement(indicatorSpan);
+ }
+ }
+ });
};
/**
@@ -1399,6 +1404,19 @@ var VideoLayout = (function (my) {
popupmenuElement.appendChild(paddingSpan);
}
+ /**
+ * Removes remote video menu element from video element identified by
+ * given videoElementId.
+ *
+ * @param videoElementId the id of local or remote video element.
+ */
+ function removeRemoteVideoMenu(videoElementId) {
+ var menuSpan = $('#' + videoElementId + '>span.remotevideomenu');
+ if (menuSpan.length) {
+ menuSpan.remove();
+ }
+ }
+
/**
* On contact list item clicked.
*/