Add jwt module to react
This commit is contained in:
parent
4f72225372
commit
ab5c2e9ded
|
@ -273,9 +273,11 @@ function muteLocalVideo(muted) {
|
|||
function maybeRedirectToWelcomePage(options) {
|
||||
// if close page is enabled redirect to it, without further action
|
||||
if (config.enableClosePage) {
|
||||
const { isGuest } = APP.store.getState()['features/jwt'];
|
||||
|
||||
// save whether current user is guest or not, before navigating
|
||||
// to close page
|
||||
window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
|
||||
window.sessionStorage.setItem('guest', isGuest);
|
||||
assignWindowLocationPathname('static/'
|
||||
+ (options.feedbackSubmitted ? "close.html" : "close2.html"));
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* global APP, JitsiMeetJS, config */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
|
||||
|
@ -14,6 +13,7 @@ import {
|
|||
|
||||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Checks if we have data to use attach instead of connect. If we have the data
|
||||
|
@ -61,22 +61,27 @@ function checkForAttachParametersAndConnect(id, password, connection) {
|
|||
* everything is ok, else error.
|
||||
*/
|
||||
function connect(id, password, roomName) {
|
||||
|
||||
let connectionConfig = Object.assign({}, config);
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { issuer, jwt } = APP.store.getState()['features/jwt'];
|
||||
|
||||
connectionConfig.bosh += '?room=' + roomName;
|
||||
|
||||
let connection
|
||||
= new JitsiMeetJS.JitsiConnection(null, config.token, connectionConfig);
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
null,
|
||||
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
|
||||
connectionConfig);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished
|
||||
);
|
||||
ConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished);
|
||||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
|
||||
);
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed);
|
||||
connection.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED, connectionFailedHandler);
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
|
||||
function connectionFailedHandler(error, errMsg) {
|
||||
APP.store.dispatch(connectionFailed(connection, error, errMsg));
|
||||
|
@ -91,12 +96,10 @@ function connect(id, password, roomName) {
|
|||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
ConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished
|
||||
);
|
||||
handleConnectionEstablished);
|
||||
connection.removeEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed
|
||||
);
|
||||
handleConnectionFailed);
|
||||
}
|
||||
|
||||
function handleConnectionEstablished() {
|
||||
|
@ -129,7 +132,6 @@ function connect(id, password, roomName) {
|
|||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
export function openConnection({id, password, retry, roomName}) {
|
||||
|
||||
let usernameOverride
|
||||
= jitsiLocalStorage.getItem("xmpp_username_override");
|
||||
let passwordOverride
|
||||
|
@ -138,25 +140,20 @@ export function openConnection({id, password, retry, roomName}) {
|
|||
if (usernameOverride && usernameOverride.length > 0) {
|
||||
id = usernameOverride;
|
||||
}
|
||||
|
||||
if (passwordOverride && passwordOverride.length > 0) {
|
||||
password = passwordOverride;
|
||||
}
|
||||
|
||||
return connect(id, password, roomName).catch(function (err) {
|
||||
if (!retry) {
|
||||
throw err;
|
||||
}
|
||||
return connect(id, password, roomName).catch(err => {
|
||||
if (retry) {
|
||||
const { issuer, jwt } = APP.store.getState()['features/jwt'];
|
||||
|
||||
if (err === ConnectionErrors.PASSWORD_REQUIRED) {
|
||||
// do not retry if token is not valid
|
||||
if (config.token) {
|
||||
throw err;
|
||||
} else {
|
||||
if (err === ConnectionErrors.PASSWORD_REQUIRED
|
||||
&& (!jwt || issuer === 'anonymous')) {
|
||||
return AuthHandler.requestAuth(roomName, connect);
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,35 +7,29 @@ import {
|
|||
|
||||
/**
|
||||
* Implements external connect using createConnectionExternally function defined
|
||||
* in external_connect.js for Jitsi Meet. Parses the room name and token from
|
||||
* the URL and executes createConnectionExternally.
|
||||
* in external_connect.js for Jitsi Meet. Parses the room name and JSON Web
|
||||
* Token (JWT) from the URL and executes createConnectionExternally.
|
||||
*
|
||||
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet you should use this
|
||||
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet, you should use this
|
||||
* file as reference only because the implementation is Jitsi Meet-specific.
|
||||
*
|
||||
* NOTE: For optimal results this file should be included right after
|
||||
* external_connect.js.
|
||||
*/
|
||||
|
||||
const hashParams = parseURLParams(window.location, true);
|
||||
if (typeof createConnectionExternally === 'function') {
|
||||
// URL params have higher proirity than config params.
|
||||
let url
|
||||
= parseURLParams(window.location, true, 'hash')[
|
||||
'config.externalConnectUrl']
|
||||
|| config.externalConnectUrl;
|
||||
let roomName;
|
||||
|
||||
// URL params have higher proirity than config params.
|
||||
let url = hashParams['config.externalConnectUrl'] || config.externalConnectUrl;
|
||||
|
||||
if (url && window.createConnectionExternally) {
|
||||
const roomName = getRoomName();
|
||||
|
||||
if (roomName) {
|
||||
if (url && (roomName = getRoomName())) {
|
||||
url += `?room=${roomName}`;
|
||||
|
||||
let token = hashParams['config.token'] || config.token;
|
||||
const token = parseURLParams(window.location, true, 'search').jwt;
|
||||
|
||||
if (!token) {
|
||||
const searchParams
|
||||
= parseURLParams(window.location, true, 'search');
|
||||
|
||||
token = searchParams.jwt;
|
||||
}
|
||||
if (token) {
|
||||
url += `&token=${token}`;
|
||||
}
|
||||
|
|
|
@ -362,9 +362,9 @@ UI.start = function () {
|
|||
|
||||
}
|
||||
|
||||
if(APP.tokenData.callee) {
|
||||
UI.showRingOverlay();
|
||||
}
|
||||
const { callee } = APP.store.getState()['features/jwt'];
|
||||
|
||||
callee && UI.showRingOverlay();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1332,7 +1332,10 @@ UI.setMicrophoneButtonEnabled
|
|||
= enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
|
||||
|
||||
UI.showRingOverlay = function () {
|
||||
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
|
||||
const { callee } = APP.store.getState()['features/jwt'];
|
||||
|
||||
callee && RingOverlay.show(callee, interfaceConfig.DISABLE_RINGING);
|
||||
|
||||
Filmstrip.toggleFilmstrip(false, false);
|
||||
};
|
||||
|
||||
|
@ -1397,7 +1400,13 @@ const UIListeners = new Map([
|
|||
UI.toggleContactList
|
||||
], [
|
||||
UIEvents.TOGGLE_PROFILE,
|
||||
() => APP.tokenData.isGuest && UI.toggleSidePanel("profile_container")
|
||||
() => {
|
||||
const {
|
||||
isGuest
|
||||
} = APP.store.getState()['features/jwt'];
|
||||
|
||||
isGuest && UI.toggleSidePanel('profile_container');
|
||||
}
|
||||
], [
|
||||
UIEvents.TOGGLE_FILMSTRIP,
|
||||
UI.handleToggleFilmstrip
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
/* global APP, config, JitsiMeetJS, Promise */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import { openConnection } from '../../../connection';
|
||||
import { setJWT } from '../../../react/features/jwt';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import LoginDialog from './LoginDialog';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
import {openConnection} from '../../../connection';
|
||||
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
let externalAuthWindow;
|
||||
let authRequiredDialog;
|
||||
|
@ -73,15 +75,20 @@ function redirectToTokenAuthService(roomName) {
|
|||
* @param room the name fo the conference room.
|
||||
*/
|
||||
function initJWTTokenListener(room) {
|
||||
var listener = function (event) {
|
||||
if (externalAuthWindow !== event.source) {
|
||||
var listener = function ({ data, source }) {
|
||||
if (externalAuthWindow !== source) {
|
||||
logger.warn("Ignored message not coming " +
|
||||
"from external authnetication window");
|
||||
return;
|
||||
}
|
||||
if (event.data && event.data.jwtToken) {
|
||||
config.token = event.data.jwtToken;
|
||||
logger.info("Received JWT token:", config.token);
|
||||
|
||||
let jwt;
|
||||
|
||||
if (data && (jwt = data.jwtToken)) {
|
||||
logger.info("Received JSON Web Token (JWT):", jwt);
|
||||
|
||||
APP.store.dispatch(setJWT(jwt));
|
||||
|
||||
var roomName = room.getName();
|
||||
openConnection({retry: false, roomName: roomName })
|
||||
.then(function (connection) {
|
||||
|
|
|
@ -22,7 +22,8 @@ function onAvatarVisible(shown) {
|
|||
*/
|
||||
class RingOverlay {
|
||||
/**
|
||||
* @param callee instance of User class from TokenData.js
|
||||
*
|
||||
* @param callee The callee (Object) as defined by the JWT support.
|
||||
* @param {boolean} disableRingingSound if true the ringing sound wont be played.
|
||||
*/
|
||||
constructor(callee, disableRingingSound) {
|
||||
|
@ -77,9 +78,9 @@ class RingOverlay {
|
|||
<div id="${this._containerId}" class='ringing' >
|
||||
<div class='ringing__content'>
|
||||
${callingLabel}
|
||||
<img class='ringing__avatar' src="${callee.getAvatarUrl()}" />
|
||||
<img class='ringing__avatar' src="${callee.avatarUrl}" />
|
||||
<div class="ringing__caller-info">
|
||||
<p>${callee.getName()}${callerStateLabel}</p>
|
||||
<p>${callee.name}${callerStateLabel}</p>
|
||||
</div>
|
||||
</div>
|
||||
${audioHTML}
|
||||
|
@ -137,9 +138,12 @@ class RingOverlay {
|
|||
export default {
|
||||
/**
|
||||
* Shows the ring overlay for the passed callee.
|
||||
* @param callee {class User} the callee. Instance of User class from
|
||||
* TokenData.js
|
||||
* @param {boolean} disableRingingSound if true the ringing sound wont be played.
|
||||
*
|
||||
* @param {Object} callee - The callee. Object containing data about
|
||||
* callee.
|
||||
* @param {boolean} disableRingingSound - If true the ringing sound won't be
|
||||
* played.
|
||||
* @returns {void}
|
||||
*/
|
||||
show(callee, disableRingingSound = false) {
|
||||
if(overlay) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* global JitsiMeetJS, config, APP */
|
||||
|
||||
/**
|
||||
* Load the integration of a third-party analytics API such as Google
|
||||
* Analytics. Since we cannot guarantee the quality of the third-party service
|
||||
|
@ -101,26 +102,31 @@ class Analytics {
|
|||
* null.
|
||||
*/
|
||||
init() {
|
||||
let analytics = JitsiMeetJS.analytics;
|
||||
if(!this.isEnabled() || !analytics)
|
||||
const { analytics } = JitsiMeetJS;
|
||||
|
||||
if (!this.isEnabled() || !analytics)
|
||||
return;
|
||||
|
||||
this._loadHandlers()
|
||||
.then(handlers => {
|
||||
let permanentProperties = {
|
||||
userAgent: navigator.userAgent,
|
||||
roomName: APP.conference.roomName
|
||||
this._loadHandlers().then(
|
||||
handlers => {
|
||||
const permanentProperties = {
|
||||
roomName: APP.conference.roomName,
|
||||
userAgent: navigator.userAgent
|
||||
};
|
||||
let {server, group} = APP.tokenData;
|
||||
if(server) {
|
||||
|
||||
const { group, server } = APP.store.getState()['features/jwt'];
|
||||
|
||||
if (server) {
|
||||
permanentProperties.server = server;
|
||||
}
|
||||
if(group) {
|
||||
if (group) {
|
||||
permanentProperties.group = group;
|
||||
}
|
||||
|
||||
analytics.addPermanentProperties(permanentProperties);
|
||||
analytics.setAnalyticsHandlers(handlers);
|
||||
}, error => analytics.dispose() && console.error(error));
|
||||
},
|
||||
error => analytics.dispose() && console.error(error));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
/* global config */
|
||||
|
||||
/**
|
||||
* Parses and handles JWT tokens. Sets config.token.
|
||||
*/
|
||||
|
||||
import * as jws from "jws";
|
||||
|
||||
import { getConfigParamsFromUrl } from '../../react/features/base/config';
|
||||
|
||||
/**
|
||||
* Get the JWT token from the URL.
|
||||
*/
|
||||
let params = getConfigParamsFromUrl(window.location, true, 'search');
|
||||
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) {
|
||||
this.isGuest = true;
|
||||
if(!jwt)
|
||||
return;
|
||||
|
||||
this.isGuest = config.enableUserRolesBasedOnToken !== true;
|
||||
|
||||
this.jwt = jwt;
|
||||
|
||||
this._decode();
|
||||
// Use JWT param as token if there is not other token set and if the
|
||||
// iss field is not anonymous. If you want to pass data with JWT token
|
||||
// but you don't want to pass the JWT token for verification the iss
|
||||
// field should be set to "anonymous"
|
||||
if(!config.token && this.payload && this.payload.iss !== "anonymous")
|
||||
config.token = jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
this.server = this.payload.context.server;
|
||||
this.group = this.payload.context.group;
|
||||
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;
|
||||
}
|
|
@ -39,7 +39,7 @@
|
|||
"jQuery-Impromptu": "trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jquery-ui": "1.10.5",
|
||||
"jssha": "1.5.0",
|
||||
"jws": "3.1.4",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "jitsi/lib-jitsi-meet",
|
||||
"lodash": "4.17.4",
|
||||
"postis": "2.2.0",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { setRoom, setRoomUrl } from '../base/conference';
|
||||
import { setRoom, setRoomURL } from '../base/conference';
|
||||
import { setConfig } from '../base/config';
|
||||
import { getDomain, setDomain } from '../base/connection';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet';
|
||||
|
@ -18,7 +18,7 @@ import {
|
|||
* @returns {Function}
|
||||
*/
|
||||
export function appInit() {
|
||||
return () => init();
|
||||
return (dispatch, getState) => init(getState());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,7 +55,7 @@ export function appNavigate(uri) {
|
|||
urlObject = new URL(urlWithoutDomain, `https://${domain}`);
|
||||
}
|
||||
|
||||
dispatch(setRoomUrl(urlObject));
|
||||
dispatch(setRoomURL(urlObject));
|
||||
|
||||
// TODO Kostiantyn Tsaregradskyi: We should probably detect if user is
|
||||
// currently in a conference and ask her if she wants to close the
|
||||
|
|
|
@ -16,7 +16,6 @@ import { WelcomePage } from '../welcome';
|
|||
|
||||
import KeyboardShortcut
|
||||
from '../../../modules/keyboardshortcut/keyboardshortcut';
|
||||
import getTokenData from '../../../modules/tokendata/TokenData';
|
||||
import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage';
|
||||
|
||||
declare var APP: Object;
|
||||
|
@ -111,18 +110,20 @@ export function _getRouteToRender(stateOrGetState: Object | Function) {
|
|||
* Temporary solution. Later we'll get rid of global APP and set its properties
|
||||
* in redux store.
|
||||
*
|
||||
* @param {Object} state - Snapshot of current state of redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function init() {
|
||||
export function init(state: Object) {
|
||||
_initLogging();
|
||||
|
||||
APP.keyboardshortcut = KeyboardShortcut;
|
||||
APP.tokenData = getTokenData();
|
||||
|
||||
const { jwt } = state['features/jwt'];
|
||||
|
||||
// Force enable the API if jwt token is passed because most probably
|
||||
// jitsi meet is displayed inside of wrapper that will need to communicate
|
||||
// with jitsi meet.
|
||||
APP.API.init(APP.tokenData.jwt ? { forceEnable: true } : undefined);
|
||||
APP.API.init(jwt ? { forceEnable: true } : undefined);
|
||||
|
||||
APP.translation.init();
|
||||
}
|
||||
|
|
|
@ -3,4 +3,9 @@ export * from './actionTypes';
|
|||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
// We need to import the jwt module in order to register the reducer and
|
||||
// middleware, because the module is not used outside of this feature.
|
||||
import '../jwt';
|
||||
|
||||
import './reducer';
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../i18n';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
|
@ -131,7 +131,7 @@ class Watermarks extends Component {
|
|||
let reactElement = null;
|
||||
|
||||
if (this.state.showJitsiWatermark
|
||||
|| (APP.tokenData.isGuest
|
||||
|| (this.props._isGuest
|
||||
&& this.state.showJitsiWatermarkForGuests)) {
|
||||
reactElement = <div className = 'watermark leftwatermark' />;
|
||||
|
||||
|
@ -175,4 +175,27 @@ class Watermarks extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default translate(Watermarks);
|
||||
/**
|
||||
* Maps parts of Redux store to component prop types.
|
||||
*
|
||||
* @param {Object} state - Snapshot of Redux store.
|
||||
* @returns {{
|
||||
* _isGuest: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { isGuest } = state['features/jwt'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* The indicator which determines whether the local participant is a
|
||||
* guest in the conference.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_isGuest: isGuest
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(translate(Watermarks));
|
||||
|
|
|
@ -113,12 +113,13 @@ function _obtainConfigHandler() {
|
|||
* @returns {void}
|
||||
*/
|
||||
function _setTokenData() {
|
||||
const localUser = APP.tokenData.caller;
|
||||
const state = APP.store.getState();
|
||||
const { caller } = state['features/jwt'];
|
||||
|
||||
if (localUser) {
|
||||
const email = localUser.getEmail();
|
||||
const avatarUrl = localUser.getAvatarUrl();
|
||||
const name = localUser.getName();
|
||||
if (caller) {
|
||||
const email = caller.email;
|
||||
const avatarUrl = caller.avatarUrl;
|
||||
const name = caller.name;
|
||||
|
||||
APP.settings.setEmail((email || '').trim(), true);
|
||||
APP.settings.setAvatarUrl((avatarUrl || '').trim());
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { Symbol } from '../base/react';
|
||||
|
||||
/**
|
||||
* The type of redux action which stores a specific JSON Web Token (JWT) into
|
||||
* the redux store.
|
||||
*
|
||||
* {
|
||||
* type: SET_JWT,
|
||||
* jwt: string
|
||||
* }
|
||||
*/
|
||||
export const SET_JWT = Symbol('SET_JWT');
|
|
@ -0,0 +1,19 @@
|
|||
/* @flow */
|
||||
|
||||
import { SET_JWT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Stores a specific JSON Web Token (JWT) into the redux store.
|
||||
*
|
||||
* @param {string} jwt - The JSON Web Token (JWT) to store.
|
||||
* @returns {{
|
||||
* type: SET_TOKEN_DATA,
|
||||
* jwt: string
|
||||
* }}
|
||||
*/
|
||||
export function setJWT(jwt: string) {
|
||||
return {
|
||||
type: SET_JWT,
|
||||
jwt
|
||||
};
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from './actions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
|
@ -0,0 +1,106 @@
|
|||
import jwtDecode from 'jwt-decode';
|
||||
|
||||
import { SET_ROOM_URL } from '../base/conference';
|
||||
import { parseURLParams, SET_CONFIG } from '../base/config';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { setJWT } from './actions';
|
||||
import { SET_JWT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Middleware to parse token data upon setting a new room URL.
|
||||
*
|
||||
* @param {Store} store - The Redux store.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_CONFIG:
|
||||
case SET_ROOM_URL:
|
||||
// XXX The JSON Web Token (JWT) is not the only piece of state that we
|
||||
// have decided to store in the feature jwt, there is isGuest as well
|
||||
// which depends on the states of the features base/config and jwt. So
|
||||
// the JSON Web Token comes from the room's URL and isGuest needs a
|
||||
// recalculation upon SET_CONFIG as well.
|
||||
return _setConfigOrRoomURL(store, next, action);
|
||||
|
||||
case SET_JWT:
|
||||
return _setJWT(store, next, action);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies the feature jwt that the action {@link SET_CONFIG} or
|
||||
* {@link SET_ROOM_URL} is being dispatched within a specific Redux
|
||||
* {@code store}.
|
||||
*
|
||||
* @param {Store} store - The Redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The Redux dispatch function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The Redux action {@code SET_CONFIG} or
|
||||
* {@code SET_ROOM_NAME} which is being dispatched in the specified
|
||||
* {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified {@code action}.
|
||||
*/
|
||||
function _setConfigOrRoomURL({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const { roomURL } = getState()['features/base/conference'];
|
||||
let jwt;
|
||||
|
||||
if (roomURL) {
|
||||
jwt = parseURLParams(roomURL, true, 'search').jwt;
|
||||
}
|
||||
dispatch(setJWT(jwt));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
|
||||
* within a specific Redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The Redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The Redux dispatch function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The Redux action {@code SET_JWT} which is being
|
||||
* dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified {@code action}.
|
||||
*/
|
||||
function _setJWT({ getState }, next, action) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { jwt, type, ...actionPayload } = action;
|
||||
|
||||
if (jwt && !Object.keys(actionPayload).length) {
|
||||
const {
|
||||
enableUserRolesBasedOnToken
|
||||
} = getState()['features/base/config'];
|
||||
|
||||
action.isGuest = !enableUserRolesBasedOnToken;
|
||||
|
||||
const jwtPayload = jwtDecode(jwt);
|
||||
|
||||
if (jwtPayload) {
|
||||
const { context, iss } = jwtPayload;
|
||||
|
||||
action.issuer = iss;
|
||||
if (context) {
|
||||
action.callee = context.callee;
|
||||
action.caller = context.user;
|
||||
action.group = context.group;
|
||||
action.server = context.server;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import { equals, ReducerRegistry } from '../base/redux';
|
||||
|
||||
import { SET_JWT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* The initial redux state of the feature jwt.
|
||||
*
|
||||
* @private
|
||||
* @type {{
|
||||
* isGuest: boolean
|
||||
* }}
|
||||
*/
|
||||
const _INITIAL_STATE = {
|
||||
/**
|
||||
* The indicator which determines whether the local participant is a guest
|
||||
* in the conference.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
isGuest: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces redux actions which affect the JSON Web Token (JWT) stored in the
|
||||
* redux store.
|
||||
*
|
||||
* @param {Object} state - The current redux state.
|
||||
* @param {Object} action - The redux action to reduce.
|
||||
* @returns {Object} The next redux state which is the result of reducing the
|
||||
* specified {@code action}.
|
||||
*/
|
||||
ReducerRegistry.register('features/jwt', (state = _INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_JWT: {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { type, ...payload } = action;
|
||||
const nextState = {
|
||||
..._INITIAL_STATE,
|
||||
...payload
|
||||
};
|
||||
|
||||
return equals(state, nextState) ? state : nextState;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
|
@ -33,6 +33,12 @@ class SecondaryToolbar extends Component {
|
|||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The indicator which determines whether the local participant is a
|
||||
* guest in the conference.
|
||||
*/
|
||||
_isGuest: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Handler dispatching local "Raise hand".
|
||||
*/
|
||||
|
@ -79,9 +85,14 @@ class SecondaryToolbar extends Component {
|
|||
* @type {Object}
|
||||
*/
|
||||
profile: {
|
||||
onMount: () =>
|
||||
APP.tokenData.isGuest
|
||||
|| this.props._onSetProfileButtonUnclickable(true)
|
||||
onMount: () => {
|
||||
const {
|
||||
_isGuest,
|
||||
_onSetProfileButtonUnclickable
|
||||
} = this.props;
|
||||
|
||||
_isGuest || _onSetProfileButtonUnclickable(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -237,18 +248,26 @@ function _mapDispatchToProps(dispatch: Function): Object {
|
|||
*
|
||||
* @param {Object} state - Snapshot of Redux store.
|
||||
* @returns {{
|
||||
* _isGuest: boolean,
|
||||
* _secondaryToolbarButtons: Map,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapStateToProps(state: Object): Object {
|
||||
const {
|
||||
secondaryToolbarButtons,
|
||||
visible
|
||||
} = state['features/toolbox'];
|
||||
const { isGuest } = state['features/jwt'];
|
||||
const { secondaryToolbarButtons, visible } = state['features/toolbox'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* The indicator which determines whether the local participant is a
|
||||
* guest in the conference.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_isGuest: isGuest,
|
||||
|
||||
/**
|
||||
* Default toolbar buttons for secondary toolbar.
|
||||
*
|
||||
|
@ -258,7 +277,8 @@ function _mapStateToProps(state: Object): Object {
|
|||
_secondaryToolbarButtons: secondaryToolbarButtons,
|
||||
|
||||
/**
|
||||
* Shows whether toolbar is visible.
|
||||
* The indicator which determines whether the {@code SecondaryToolbar}
|
||||
* is visible.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
|
|
Loading…
Reference in New Issue