JWT client support

This commit is contained in:
hristoterezov 2016-06-13 16:11:44 -05:00
parent 10b2746a3e
commit 8deb003ef6
28 changed files with 671 additions and 138 deletions

View File

@ -3,7 +3,7 @@ BROWSERIFY = ./node_modules/.bin/browserify
UGLIFYJS = ./node_modules/.bin/uglifyjs
EXORCIST = ./node_modules/.bin/exorcist
CLEANCSS = ./node_modules/.bin/cleancss
CSS_FILES = font.css toastr.css main.css videolayout_default.css font-awesome.css jquery-impromptu.css modaldialog.css notice.css popup_menu.css login_menu.css popover.css jitsi_popover.css contact_list.css chat.css welcome_page.css settingsmenu.css feedback.css jquery.contextMenu.css
CSS_FILES = font.css toastr.css main.css overlay.css videolayout_default.css font-awesome.css jquery-impromptu.css modaldialog.css notice.css popup_menu.css login_menu.css popover.css jitsi_popover.css contact_list.css chat.css welcome_page.css settingsmenu.css feedback.css jquery.contextMenu.css
DEPLOY_DIR = libs
BROWSERIFY_FLAGS = -d
OUTPUT_DIR = .

22
app.js
View File

@ -23,6 +23,7 @@ import conference from './conference';
import API from './modules/API/API';
import UIEvents from './service/UI/UIEvents';
import getTokenData from "./modules/TokenData/TokenData";
/**
* Tries to push history state with the following parameters:
@ -84,10 +85,24 @@ const APP = {
require("./modules/keyboardshortcut/keyboardshortcut");
this.translation = require("./modules/translation/translation");
this.configFetch = require("./modules/config/HttpConfigFetch");
this.tokenData = getTokenData();
}
};
/**
* If JWT token data it will be used for local user settings
*/
function setTokenData() {
let localUser = APP.tokenData.caller;
if(localUser) {
APP.settings.setEmail((localUser.getEmail() || "").trim());
APP.settings.setAvatarUrl((localUser.getAvatarUrl() || "").trim());
APP.settings.setDisplayName((localUser.getName() || "").trim());
}
}
function init() {
setTokenData();
var isUIReady = APP.UI.start();
if (isUIReady) {
APP.conference.init({roomName: buildRoomName()}).then(function () {
@ -100,6 +115,11 @@ function init() {
APP.keyboardshortcut.init();
}).catch(function (err) {
APP.UI.hideRingOverLay();
APP.API.sendPostisMessage({
method: 'video-conference-left',
params: {roomName: APP.conference.roomName}
});
console.error(err);
});
}
@ -151,7 +171,7 @@ $(document).ready(function () {
APP.translation.init(settings.getLanguage());
APP.API.init();
APP.API.init(APP.tokenData.externalAPISettings);
obtainConfigAndInit();
});

View File

@ -26,6 +26,18 @@ let currentAudioInputDevices, currentVideoInputDevices;
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
/**
* Known custom conference commands.
*/
const commands = {
CONNECTION_QUALITY: "stats",
EMAIL: "email",
AVATAR_URL: "avatar-url",
ETHERPAD: "etherpad",
SHARED_VIDEO: "shared-video",
CUSTOM_ROLE: "custom-role"
};
/**
* Open Connection. When authentication failed it shows auth dialog.
* @param roomName the room name to use
@ -44,17 +56,13 @@ function connect(roomName) {
}
/**
* Share email with other users.
* @param emailCommand the email command
* @param {string} email new email
* Share data to other users.
* @param command the command
* @param {string} value new value
*/
function sendEmail (emailCommand, email) {
room.sendCommand(emailCommand, {
value: email,
attributes: {
id: room.myUserId()
}
});
function sendData (command, value) {
room.removeCommand(command);
room.sendCommand(command, {value: value});
}
/**
@ -144,7 +152,12 @@ function maybeRedirectToWelcomePage() {
* @returns Promise.
*/
function disconnectAndShowFeedback(requestFeedback) {
APP.UI.hideRingOverLay();
connection.disconnect();
APP.API.sendPostisMessage({
method: 'video-conference-left',
params: {roomName: APP.conference.roomName}
});
if (requestFeedback) {
return APP.UI.requestFeedback();
} else {
@ -209,6 +222,55 @@ function setCurrentMediaDevices(devices) {
d => d.kind === 'videoinput');
}
/**
* Changes the email for the local user
* @param email {string} the new email
*/
function changeLocalEmail(email = '') {
email = email.trim();
if (email === APP.settings.getEmail()) {
return;
}
APP.settings.setEmail(email);
APP.UI.setUserEmail(room.myUserId(), email);
sendData(commands.EMAIL, email);
}
/**
* Changes the local avatar url for the local user
* @param avatarUrl {string} the new avatar url
*/
function changeLocalAvatarUrl(avatarUrl = '') {
avatarUrl = avatarUrl.trim();
if (avatarUrl === APP.settings.getAvatarUrl()) {
return;
}
APP.settings.setAvatarUrl(avatarUrl);
APP.UI.setUserAvatarUrl(room.myUserId(), avatarUrl);
sendData(commands.AVATAR_URL, avatarUrl);
}
/**
* Changes the display name for the local user
* @param nickname {string} the new display name
*/
function changeLocalDisplayName(nickname = '') {
nickname = nickname.trim();
if (nickname === APP.settings.getDisplayName()) {
return;
}
APP.settings.setDisplayName(nickname);
room.setDisplayName(nickname);
APP.UI.changeDisplayName(APP.conference.localId, nickname);
}
class ConferenceConnector {
constructor(resolve, reject) {
this._resolve = resolve;
@ -227,6 +289,7 @@ class ConferenceConnector {
}
_onConferenceFailed(err, ...params) {
console.error('CONFERENCE FAILED:', err, ...params);
APP.UI.hideRingOverLay();
switch (err) {
// room is locked by the password
case ConferenceErrors.PASSWORD_REQUIRED:
@ -412,6 +475,9 @@ export default {
this._createRoom(tracks);
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
if(this.isDesktopSharingEnabled)
APP.API.addPostisMessageListener('toggle-share-screen',
() => this.toggleScreenSharing());
// if user didn't give access to mic or camera or doesn't have
// them at all, we disable corresponding toolbar buttons
@ -851,13 +917,7 @@ export default {
/**
* Known custom conference commands.
*/
defaults: {
CONNECTION_QUALITY: "stats",
EMAIL: "email",
ETHERPAD: "etherpad",
SHARED_VIDEO: "shared-video",
CUSTOM_ROLE: "custom-role"
},
defaults: commands,
/**
* Receives notifications from other participants about commands aka
* custom events (sent by sendCommand or sendCommandOnce methods).
@ -910,7 +970,11 @@ export default {
this._room = room; // FIXME do not use this
let email = APP.settings.getEmail();
email && sendEmail(this.commands.defaults.EMAIL, email);
email && sendData(this.commands.defaults.EMAIL, email);
let avatarUrl = APP.settings.getAvatarUrl();
avatarUrl && sendData(this.commands.defaults.AVATAR_URL,
avatarUrl);
let nick = APP.settings.getDisplayName();
if (config.useNicks && !nick) {
@ -1093,6 +1157,10 @@ export default {
// add local streams when joined to the conference
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
APP.UI.mucJoined();
APP.API.sendPostisMessage({
method: 'video-conference-joined',
params: {roomName: APP.conference.roomName}
});
});
room.on(
@ -1297,33 +1365,20 @@ export default {
APP.UI.initEtherpad(value);
});
APP.UI.addListener(UIEvents.EMAIL_CHANGED, (email = '') => {
email = email.trim();
if (email === APP.settings.getEmail()) {
return;
}
APP.settings.setEmail(email);
APP.UI.setUserAvatar(room.myUserId(), email);
sendEmail(this.commands.defaults.EMAIL, email);
});
room.addCommandListener(this.commands.defaults.EMAIL, (data) => {
APP.UI.setUserAvatar(data.attributes.id, data.value);
APP.UI.addListener(UIEvents.EMAIL_CHANGED, changeLocalEmail);
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
APP.UI.setUserEmail(from, data.value);
});
APP.UI.addListener(UIEvents.NICKNAME_CHANGED, (nickname = '') => {
nickname = nickname.trim();
APP.UI.addListener(UIEvents.AVATAR_URL_CHANGED, changeLocalAvatarUrl);
if (nickname === APP.settings.getDisplayName()) {
return;
}
APP.settings.setDisplayName(nickname);
room.setDisplayName(nickname);
APP.UI.changeDisplayName(APP.conference.localId, nickname);
room.addCommandListener(this.commands.defaults.AVATAR_URL,
(data, from) => {
APP.UI.setUserAvatarUrl(from, data.value);
});
APP.UI.addListener(UIEvents.NICKNAME_CHANGED, changeLocalDisplayName);
APP.UI.addListener(UIEvents.START_MUTED_CHANGED,
(startAudioMuted, startVideoMuted) => {
room.setStartMutedPolicy({

View File

@ -16,12 +16,13 @@
* Executes createConnectionExternally function.
*/
(function () {
var params = getConfigParamsFromUrl();
var hashParams = getConfigParamsFromUrl("hash", true);
var searchParams = getConfigParamsFromUrl("search", true);
//Url params have higher proirity than config params
var url = config.externalConnectUrl;
if(params.hasOwnProperty('config.externalConnectUrl'))
url = params["config.externalConnectUrl"];
if(hashParams.hasOwnProperty('config.externalConnectUrl'))
url = hashParams["config.externalConnectUrl"];
/**
* Check if connect from connection.js was executed and executes the handler
@ -57,7 +58,8 @@
url += "?room=" + room_name;
var token = params["config.token"] || config.token;
var token = hashParams["config.token"] || config.token ||
searchParams.jwt;
if(token)
url += "&token=" + token;

View File

@ -33,6 +33,10 @@ html, body{
display:none;
}
#header_container {
z-index: 1014;
}
.toolbar_span {
display: inline-block;
position: relative;

51
css/overlay.css Normal file
View File

@ -0,0 +1,51 @@
.overlay {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1013;
background: #000000; /* Old browsers */
opacity: 0.75;
display: block;
}
.overlay_container {
width: 100%;
height: 100%;
position: fixed;
z-index: 1013;
}
.overlay_content {
color: #fff;
font-weight: normal;
font-size: 20px;
text-align: center;
width: 400px;
height: 250px;
top: 50%;
left: 50%;
position:absolute;
margin-top: -125px;
margin-left: -200px;
}
.overlay_avatar {
width: 200px;
height: 200px;
position: relative;
border-radius: 100px;
z-index: 1013;
float: left;
margin-left: 100px;
}
.overlay_text {
position: relative;
width: 400px;
z-index: 1013;
margin-top: 20px;
float: left;
}

View File

@ -2,7 +2,7 @@
position: absolute;
top: 0;
left: 0;
z-index: 1010;
z-index: 1015;
display: none;
max-width: 300px;
min-width: 100px;
@ -121,4 +121,4 @@
border-right-width: 0;
border-left-color: #ffffff;
bottom: -10px;
}
}

View File

@ -84,7 +84,7 @@ button.toast-close-button {
}
#toast-container {
position: fixed;
z-index: 999999;
z-index: 1012;
/*overrides*/
}

View File

@ -468,7 +468,7 @@
position: absolute;
width: 100%;
top:50%;
z-index: 10000;
z-index: 1011;
font-weight: 600;
font-size: 14px;
text-align: center;
@ -488,7 +488,7 @@
background: rgba(0,0,0,.5);
padding: 10px;
color: rgba(255,255,255,.5);
z-index: 10000;
z-index: 1011;
}
.centeredVideoLabel {
@ -506,7 +506,7 @@
margin-left: auto;
background: rgba(0,0,0,.5);
color: #FFF;
z-index: 10000;
z-index: 1011;
border-radius: 2px;
-webkit-transition: all 2s 2s linear;
transition: all 2s 2s linear;
@ -522,4 +522,4 @@
}
.hidden {
}
}

View File

@ -67,8 +67,8 @@ case "$1" in
sed -i 's/ --modules_enabled = { "token_verification" }/ modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
# Install luajwt
if ! luarocks install luajwt; then
echo "Failed to install luajwt - try installing it manually"
if ! luarocks install jwt; then
echo "Failed to install jwt - try installing it manually"
fi
if [ -x "/etc/init.d/prosody" ]; then
@ -85,7 +85,7 @@ case "$1" in
else
echo "Prosody config not found at $PROSODY_HOST_CONFIG - unable to auto-configure token authentication"
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)

View File

@ -241,6 +241,7 @@
<div class="arrow-up"></div>
<input type="text" id="setDisplayName" data-i18n="[placeholder]settings.name" placeholder="Name">
<input type="text" id="setEmail" placeholder="E-Mail">
<input type="text" id="setAvatarUrl" placeholder="Avatar URL" data-i18n="[placeholder]settings.avatar">
<select id="languages_selectbox"></select>
<div id = "startMutedOptions">
<label class = "startMutedLabel">

View File

@ -10,6 +10,7 @@
"speaker": "Speaker",
"defaultNickname": "ex. Jane Pink",
"defaultLink": "e.g. __url__",
"calling": "Calling __name__ ...",
"welcomepage":{
"go": "GO",
"roomname": "Enter room name",
@ -94,7 +95,8 @@
"selectAudioOutput": "Select audio output",
"followMe": "Enable follow me",
"noDevice": "None",
"noPermission": "Permission to use device is not granted"
"noPermission": "Permission to use device is not granted",
"avatarUrl": "Avatar URL"
},
"videothumbnail":
{

View File

@ -5,6 +5,8 @@
* applications that embed Jitsi Meet
*/
import postis from 'postis';
/**
* List of the available commands.
* @type {{
@ -16,7 +18,28 @@
* toggleContactList: toggleContactList
* }}
*/
var commands = {};
let commands = {};
/**
* Object that will execute sendMessage
*/
let target = window.opener ? window.opener : window.parent;
/**
* Array of functions that are going to receive the objects passed to this
* window
*/
let messageListeners = [];
/**
* Current status (enabled/disabled) of Postis.
*/
let enablePostis = false;
/**
* Current status (enabled/disabled) of Post Message API.
*/
let enablePostMessage = false;
function initCommands() {
commands = {
@ -91,14 +114,6 @@ function processEvent(event) {
}
}
/**
* Sends message to the external application.
* @param object
*/
function sendMessage(object) {
window.parent.postMessage(JSON.stringify(object), "*");
}
/**
* Processes a message event from the external application
* @param event the message event
@ -107,10 +122,11 @@ function processMessage(event) {
var message;
try {
message = JSON.parse(event.data);
} catch (e) {}
if(!message.type)
} catch (e) {
console.error("Cannot parse data", event.data);
return;
}
switch (message.type) {
case "command":
processCommand(message);
@ -119,18 +135,26 @@ function processMessage(event) {
processEvent(message);
break;
default:
console.error("Unknown type of the message");
return;
console.warn("Unknown message type");
}
}
/**
* Sends message to the external application.
* @param object {object} the object that will be sent as JSON string
*/
function sendMessage(object) {
if(enablePostMessage)
target.postMessage(JSON.stringify(object), "*");
}
/**
* Check whether the API should be enabled or not.
* @returns {boolean}
*/
function isEnabled () {
let hash = location.hash;
return hash && hash.indexOf("external=true") > -1 && window.postMessage;
return !!(hash && hash.indexOf("external=true") > -1 && window.postMessage);
}
/**
@ -149,7 +173,7 @@ function isEventEnabled (name) {
* @param object data associated with the event
*/
function triggerEvent (name, object) {
if (isEnabled() && isEventEnabled(name)) {
if (isEventEnabled(name) && enablePostMessage) {
sendMessage({
type: "event",
action: "result",
@ -165,18 +189,34 @@ export default {
* receive information from external applications that embed Jitsi Meet.
* It also sends a message to the external application that APIConnector
* is initialized.
* @param options {object}
* @param enablePostis {boolean} if true the postis npm
* package for comminication with the parent window will be enabled.
* @param enablePostMessage {boolean} if true the postMessageAPI for
* comminication with the parent window will be enabled.
*/
init: function () {
if (!isEnabled()) {
init: function (options = {}) {
options.enablePostMessage = options.enablePostMessage || isEnabled();
if (!options.enablePostis &&
!options.enablePostMessage) {
return;
}
initCommands();
if (window.addEventListener) {
window.addEventListener('message', processMessage, false);
} else {
window.attachEvent('onmessage', processMessage);
enablePostis = options.enablePostis;
enablePostMessage = options.enablePostMessage;
if(enablePostMessage) {
initCommands();
if (window.addEventListener) {
window.addEventListener('message', processMessage, false);
} else {
window.attachEvent('onmessage', processMessage);
}
sendMessage({type: "system", loaded: true});
}
if(enablePostis) {
this.postis = postis({window: target});
}
sendMessage({type: "system", loaded: true});
},
/**
@ -187,6 +227,27 @@ export default {
triggerEvent("outgoingMessage", {"message": body});
},
/**
* Sends message to the external application.
* @param options {object}
* @param method {string}
* @param params {object} the object that will be sent as JSON string
*/
sendPostisMessage(options) {
if(enablePostis)
this.postis.send(options);
},
/**
* Adds listener for Postis messages.
* @param method {string} postis mehtod
* @param listener {function}
*/
addPostisMessageListener (method, listener) {
if(enablePostis)
this.postis.listen(method, listener);
},
/**
* Notify external application (if API is enabled) that
* message was received.
@ -238,14 +299,14 @@ export default {
* Removes the listeners.
*/
dispose: function () {
if (!isEnabled()) {
return;
}
if (window.removeEventListener) {
window.removeEventListener("message", processMessage, false);
} else {
window.detachEvent('onmessage', processMessage);
if (enablePostMessage) {
if (window.removeEventListener) {
window.removeEventListener("message", processMessage, false);
} else {
window.detachEvent('onmessage', processMessage);
}
}
if(enablePostis)
this.postis.destroy();
}
};

View File

@ -0,0 +1,116 @@
/* global getConfigParamsFromUrl, config */
/**
* Parses and handles JWT tokens. Sets config.token.
*/
import * as jws from "jws";
/**
* Get the JWT token from the URL.
*/
let params = getConfigParamsFromUrl("search", true);
let jwt = params.jwt;
/**
* Implements a user of conference.
*/
class User {
/**
* @param name {string} the name of the user.
* @param email {string} the email of the user.
* @param avatarUrl {string} the URL for the avatar of the user.
*/
constructor(name, email, avatarUrl) {
this._name = name;
this._email = email;
this._avatarUrl = avatarUrl;
}
/**
* GETERS START.
*/
/**
* Returns the name property
*/
getName() {
return this._name;
}
/**
* Returns the email property
*/
getEmail() {
return this._email;
}
/**
* Returns the URL of the avatar
*/
getAvatarUrl() {
return this._avatarUrl;
}
/**
* GETERS END.
*/
}
/**
* Represent the data parsed from the JWT token
*/
class TokenData{
/**
* @param {string} the JWT token
*/
constructor(jwt) {
if(!jwt)
return;
//Use jwt param as token if there is not other token set
if(!config.token)
config.token = jwt;
this.jwt = jwt;
//External API settings
this.externalAPISettings = {
enablePostis: true
};
this._decode();
}
/**
* Decodes the JWT token and sets the decoded data to properties.
*/
_decode() {
this.decodedJWT = jws.decode(jwt);
if(!this.decodedJWT || !this.decodedJWT.payload)
return;
this.payload = this.decodedJWT.payload;
if(!this.payload.context)
return;
let callerData = this.payload.context.user;
let calleeData = this.payload.context.callee;
if(callerData)
this.caller = new User(callerData.name, callerData.email,
callerData.avatarUrl);
if(calleeData)
this.callee = new User(calleeData.name, calleeData.email,
calleeData.avatarUrl);
}
}
/**
* Stores the TokenData instance.
*/
let data = null;
/**
* Returns the data variable. Creates new TokenData instance if <tt>data</tt>
* variable is null.
*/
export default function getTokenData() {
if(!data)
data = new TokenData(jwt);
return data;
}

View File

@ -21,6 +21,7 @@ import FilmStrip from "./videolayout/FilmStrip";
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
import Settings from "./../settings/Settings";
import { reload } from '../util/helpers';
import RingOverlay from "./ring_overlay/RingOverlay";
var EventEmitter = require("events");
UI.messageHandler = require("./util/MessageHandler");
@ -277,7 +278,7 @@ UI.initConference = function () {
}
// Make sure we configure our avatar id, before creating avatar for us
UI.setUserAvatar(id, Settings.getEmail());
UI.setUserEmail(id, Settings.getEmail());
Toolbar.checkAutoEnableDesktopSharing();
@ -470,6 +471,10 @@ UI.start = function () {
SettingsMenu.init(eventEmitter);
}
if(APP.tokenData.callee) {
UI.showRingOverLay();
}
// Return true to indicate that the UI has been fully started and
// conference ready.
return true;
@ -551,6 +556,7 @@ UI.getSharedDocumentManager = function () {
* @param {string} displayName user nickname
*/
UI.addUser = function (id, displayName) {
UI.hideRingOverLay();
ContactList.addContact(id);
messageHandler.notify(
@ -565,7 +571,7 @@ UI.addUser = function (id, displayName) {
VideoLayout.addParticipantContainer(id);
// Configure avatar
UI.setUserAvatar(id);
UI.setUserEmail(id);
// set initial display name
if(displayName)
@ -800,21 +806,41 @@ UI.dockToolbar = function (isDock) {
};
/**
* Update user avatar.
* Updates the avatar for participant.
* @param {string} id user id
* @param {stirng} email user email
* @param {stirng} avatarUrl the URL for the avatar
*/
UI.setUserAvatar = function (id, email) {
// update avatar
Avatar.setUserAvatar(id, email);
var avatarUrl = Avatar.getAvatarUrl(id);
function changeAvatar(id, avatarUrl) {
VideoLayout.changeUserAvatar(id, avatarUrl);
ContactList.changeUserAvatar(id, avatarUrl);
if (APP.conference.isLocalId(id)) {
SettingsMenu.changeAvatar(avatarUrl);
}
}
/**
* Update user email.
* @param {string} id user id
* @param {stirng} email user email
*/
UI.setUserEmail = function (id, email) {
// update avatar
Avatar.setUserEmail(id, email);
changeAvatar(id, Avatar.getAvatarUrl(id));
};
/**
* Update user avatar URL.
* @param {string} id user id
* @param {stirng} url user avatar url
*/
UI.setUserAvatarUrl = function (id, url) {
// update avatar
Avatar.setUserAvatarUrl(id, url);
changeAvatar(id, Avatar.getAvatarUrl(id));
};
/**
@ -1216,7 +1242,7 @@ UI.showDeviceErrorDialog = function (micError, cameraError) {
: ``;
let doNotShowWarningAgainSection = showDoNotShowWarning
? `<label>
<input type='checkbox' id='doNotShowWarningAgain'>
<input type='checkbox' id='doNotShowWarningAgain'>
<span data-i18n='dialog.doNotShowWarningAgain'></span>
</label>`
: ``;
@ -1343,4 +1369,19 @@ UI.enableMicrophoneButton = function () {
Toolbar.markAudioIconAsDisabled(false);
};
let bottomToolbarEnabled = null;
UI.showRingOverLay = function () {
RingOverlay.show(APP.tokenData.callee);
ToolbarToggler.setAlwaysVisibleToolbar(true);
FilmStrip.toggleFilmStrip(false);
};
UI.hideRingOverLay = function () {
if(!RingOverlay.hide())
return;
ToolbarToggler.resetAlwaysVisibleToolbar();
FilmStrip.toggleFilmStrip(true);
};
module.exports = UI;

View File

@ -3,6 +3,19 @@
let users = {};
export default {
/**
* Sets prop in users object.
* @param id {string} user id
* @param prop {string} name of the prop
* @param val {string} value to be set
*/
_setUserProp: function (id, prop, val) {
if(!val || (users[id] && users[id][prop] === val))
return;
if(!users[id])
users[id] = {};
users[id][prop] = val;
},
/**
* Sets the user's avatar in the settings menu(if local user), contact list
@ -10,13 +23,18 @@ export default {
* @param id id of the user
* @param email email or nickname to be used as a hash
*/
setUserAvatar: function (id, email) {
if (email) {
if (users[id] === email) {
return;
}
users[id] = email;
}
setUserEmail: function (id, email) {
this._setUserProp(id, "email", email);
},
/**
* Sets the user's avatar in the settings menu(if local user), contact list
* and thumbnail
* @param id id of the user
* @param url the url for the avatar
*/
setUserAvatarUrl: function (id, url) {
this._setUserProp(id, "url", url);
},
/**
@ -34,7 +52,15 @@ export default {
return null;
}
let avatarId = users[userId];
let avatarId = null;
const user = users[userId];
if(user) {
if(user.url)
return users[userId].url;
avatarId = users[userId].email;
}
// If the ID looks like an email, we'll use gravatar.
// Otherwise, it's a random avatar, and we'll use the configured

View File

@ -0,0 +1,82 @@
/* global $ */
/**
* Shows ring overlay
*/
class RingOverlay {
/**
* @param callee instance of User class from TokenData.js
*/
constructor(callee) {
this.callee = callee;
this._buildHtml();
this.audio = $("#ring_overlay_ringing");
this.audio[0].play();
this._setAudioTimeout();
}
/**
* Builds and appends the ring overlay to the html document
*/
_buildHtml() {
$("body").append("<div class='overlay_container' >" +
"<div class='overlay' /><div class='overlay_content'>" +
"<img class='overlay_avatar' src='" +
this.callee.getAvatarUrl() + "' />" +
"<span data-i18n='calling' data-i18n-options='" +
JSON.stringify({name: this.callee.getName()}) +
"' class='overlay_text'>Calling " +
this.callee.getName() + "...</span></div>" +
"<audio id='ring_overlay_ringing' src='/sounds/ring.ogg' /></div>");
}
/**
* Sets the interval that is going to play the ringing sound.
*/
_setAudioTimeout() {
this.interval = setInterval( () => {
this.audio[0].play();
}, 5000);
}
/**
* Destroys and clears all the objects (html elements and audio interval)
* related to the ring overlay.
*/
destroy() {
if(this.interval)
clearInterval(this.interval);
$(".overlay_container").remove();
}
}
/**
* Store the current ring overlay instance.
* Note: We want to have only 1 instance at a time.
*/
let overlay = null;
export default {
/**
* Shows the ring overlay for the passed callee.
* @param callee {class User} the callee. Instance of User class from
* TokenData.js
*/
show(callee) {
if(overlay) {
this.hide();
}
overlay = new RingOverlay(callee);
},
/**
* Hides the ring overlay. Destroys all the elements related to the ring
* overlay.
*/
hide() {
if(!overlay)
return false;
overlay.destroy();
overlay = null;
return true;
}
};

View File

@ -81,6 +81,12 @@ export default {
function updateEmail () {
emitter.emit(UIEvents.EMAIL_CHANGED, $('#setEmail').val());
}
// AVATAR URL CHANGED
function updateAvatarUrl () {
emitter.emit(UIEvents.AVATAR_URL_CHANGED, $('#setAvatarUrl').val());
}
$('#setEmail')
.val(Settings.getEmail())
.keyup(function (event) {
@ -89,6 +95,14 @@ export default {
}
}).focusout(updateEmail);
$('#setAvatarUrl')
.val(Settings.getAvatarUrl())
.keyup(function (event) {
if (event.keyCode === 13) { // enter
updateAvatarUrl();
}
}).focusout(updateAvatarUrl);
// START MUTED
$("#startMutedOptions").change(function () {

View File

@ -7,6 +7,10 @@ import FilmStrip from '../videolayout/FilmStrip.js';
let toolbarTimeoutObject;
let toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
/**
* If true the toolbar will be always displayed
*/
let alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
function showDesktopSharingButton() {
if (APP.conference.isDesktopSharingEnabled &&
@ -21,7 +25,7 @@ function showDesktopSharingButton() {
* Hides the toolbar.
*/
function hideToolbar() {
if (config.alwaysVisibleToolbar) {
if (alwaysVisibleToolbar) {
return;
}
@ -40,6 +44,19 @@ function hideToolbar() {
}
const ToolbarToggler = {
/**
* Sets the value of alwaysVisibleToolbar variable.
* @param value {boolean} the new value of alwaysVisibleToolbar variable
*/
setAlwaysVisibleToolbar(value) {
alwaysVisibleToolbar = value;
},
/**
* Resets the value of alwaysVisibleToolbar variable to the default one.
*/
resetAlwaysVisibleToolbar() {
alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
},
/**
* Shows the main toolbar.
*/

View File

@ -9,6 +9,7 @@ let cameraDeviceId = '';
let micDeviceId = '';
let welcomePageDisabled = false;
let localFlipX = null;
let avatarUrl = '';
function supportsLocalStorage() {
try {
@ -34,6 +35,7 @@ if (supportsLocalStorage()) {
}
email = UIUtil.unescapeHtml(window.localStorage.email || '');
avatarUrl = UIUtil.unescapeHtml(window.localStorage.avatarUrl || '');
localFlipX = JSON.parse(window.localStorage.localFlipX || true);
displayName = UIUtil.unescapeHtml(window.localStorage.displayname || '');
language = window.localStorage.language;
@ -98,6 +100,24 @@ export default {
return email;
},
/**
* Sets new avatarUrl for local user and saves it to the local storage.
* @param {string} newAvatarUrl new avatarUrl for the local user
*/
setAvatarUrl: function (newAvatarUrl) {
avatarUrl = newAvatarUrl;
window.localStorage.avatarUrl = UIUtil.escapeHtml(newAvatarUrl);
},
/**
* Returns avatarUrl address of the local user.
* @returns {string} avatarUrl
*/
getAvatarUrl: function () {
return avatarUrl;
},
getLanguage () {
return language;
},

View File

@ -29,8 +29,10 @@
"jssha": "1.5.0",
"retry": "0.6.1",
"strophe": "^1.2.2",
"strophejs-plugins": "^0.0.6",
"toastr": "^2.0.3"
"strophejs-plugins": "strophe/strophejs-plugins",
"toastr": "^2.0.3",
"postis": "^2.2.0",
"jws": "*"
},
"devDependencies": {
"browserify": "11.1.x",

View File

@ -15,6 +15,7 @@ local host = module.host;
local appId = module:get_option_string("app_id");
local appSecret = module:get_option_string("app_secret");
local allowEmptyToken = module:get_option_boolean("allow_empty_token");
local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints");
if allowEmptyToken == true then
module:log("warn", "WARNING - empty tokens allowed");
@ -79,7 +80,7 @@ function provider.get_sasl_handler(session)
-- here we check if 'room' claim exists
local room, roomErr = token_util.get_room_name(token, appSecret);
if room == nil then
if room == nil and disableRoomNameConstraints ~= true then
if roomErr == nil then
roomErr = "'room' claim is missing";
end
@ -88,7 +89,7 @@ function provider.get_sasl_handler(session)
-- now verify the whole token
local result, msg
= token_util.verify_token(token, appId, appSecret, room);
= token_util.verify_token(token, appId, appSecret, room, disableRoomNameConstraints);
if result == true then
-- Binds room name to the session which is later checked on MUC join
session.jitsi_meet_room = room;
@ -121,4 +122,3 @@ local function anonymous(self, message)
end
sasl.registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);

View File

@ -24,6 +24,7 @@ end
local appId = parentCtx:get_option_string("app_id");
local appSecret = parentCtx:get_option_string("app_secret");
local allowEmptyToken = parentCtx:get_option_boolean("allow_empty_token");
local disableRoomNameConstraints = parentCtx:get_option_boolean("disable_room_name_constraints")
log("debug",
"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
@ -35,13 +36,6 @@ local function verify_user(session, stanza)
tostring(session.auth_token),
tostring(session.jitsi_meet_room));
if allowEmptyToken and session.auth_token == nil then
module:log(
"debug",
"Skipped room token verification - empty tokens are allowed");
return nil;
end
-- token not required for admin users
local user_jid = stanza.attr.from;
if is_admin(user_jid) then
@ -49,6 +43,13 @@ local function verify_user(session, stanza)
return nil;
end
if allowEmptyToken and session.auth_token == nil then
module:log(
"debug",
"Skipped room token verification - empty tokens are allowed");
return nil;
end
local room = string.match(stanza.attr.to, "^(%w+)@");
log("debug", "Will verify token for user: %s, room: %s ", user_jid, room);
if room == nil then
@ -59,7 +60,7 @@ local function verify_user(session, stanza)
local token = session.auth_token;
local auth_room = session.jitsi_meet_room;
if room ~= auth_room then
if room ~= auth_room and disableRoomNameConstraints ~= true then
log("error", "Token %s not allowed to join: %s",
tostring(token), tostring(auth_room));
session.send(
@ -81,4 +82,3 @@ module:hook("muc-occupant-pre-join", function(event)
log("debug", "pre join: %s %s", tostring(room), tostring(stanza));
return verify_user(origin, stanza);
end);

View File

@ -1,7 +1,7 @@
-- Token authentication
-- Copyright (C) 2015 Atlassian
local jwt = require "luajwt";
local jwt = require "jwt";
local _M = {};
@ -14,7 +14,7 @@ local function _get_room_name(token, appSecret)
end
end
local function _verify_token(token, appId, appSecret, roomName)
local function _verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints)
local claims, err = jwt.decode(token, appSecret, true);
if claims == nil then
@ -30,22 +30,22 @@ local function _verify_token(token, appId, appSecret, roomName)
end
local roomClaim = claims["room"];
if roomClaim == nil then
if roomClaim == nil and disableRoomNameConstraints ~= true then
return nil, "'room' claim is missing";
end
if roomName ~= nil and roomName ~= roomClaim then
if roomName ~= nil and roomName ~= roomClaim and disableRoomNameConstraints ~= true then
return nil, "Invalid room name('room' claim)";
end
return true;
end
function _M.verify_token(token, appId, appSecret, roomName)
return _verify_token(token, appId, appSecret, roomName);
function _M.verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints)
return _verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints);
end
function _M.get_room_name(token, appSecret)
return _get_room_name(token, appSecret);
end
return _M;
return _M;

View File

@ -14,6 +14,10 @@ export default {
* Notifies that local user changed email.
*/
EMAIL_CHANGED: "UI.email_changed",
/**
* Notifies that local user changed avatar url.
*/
AVATAR_URL_CHANGED: "UI.avatar_url_changed",
/**
* Notifies that "start muted" settings changed.
*/

BIN
sounds/ring.ogg Normal file

Binary file not shown.

BIN
sounds/ring.wav Normal file

Binary file not shown.

View File

@ -36,17 +36,32 @@ function getRoomName () {
}
/**
* Parses the hash parameters from the URL and returns them as a JS object.
* Parses the parameters from the URL and returns them as a JS object.
* @param source {string} values - "hash"/"search" if "search" the parameters
* will parsed from location.search otherwise from location.hash
* @param dontParse if false or undefined some transformations
* (for parsing the value as JSON) are going to be executed
*/
function getConfigParamsFromUrl() {
if (!location.hash)
function getConfigParamsFromUrl(source, dontParse) {
var paramStr = (source === "search")? location.search : location.hash;
if (!paramStr)
return {};
var hash = location.hash.substr(1);
paramStr = paramStr.substr(1);
var result = {};
hash.split("&").forEach(function (part) {
paramStr.split("&").forEach(function (part) {
var item = part.split("=");
result[item[0]] = JSON.parse(
decodeURIComponent(item[1]).replace(/\\&/, "&"));
var value;
try {
value = (dontParse)? item[1] : JSON.parse(
decodeURIComponent(item[1]).replace(/\\&/, "&"));
} catch (e) {
console.warn("Failed to parse URL argument", e);
if(window.onerror)
window.onerror("Failed to parse URL argument", null, null,
null, e);
return;
}
result[item[0]] = value;
});
return result;
}