Просмотр исходного кода

Add jwt module to react

master
Ilya Daynatovich 8 лет назад
Родитель
Сommit
ab5c2e9ded

+ 3
- 1
conference.js Просмотреть файл

@@ -273,9 +273,11 @@ function muteLocalVideo(muted) {
273 273
 function maybeRedirectToWelcomePage(options) {
274 274
     // if close page is enabled redirect to it, without further action
275 275
     if (config.enableClosePage) {
276
+        const { isGuest } = APP.store.getState()['features/jwt'];
277
+
276 278
         // save whether current user is guest or not, before navigating
277 279
         // to close page
278
-        window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
280
+        window.sessionStorage.setItem('guest', isGuest);
279 281
         assignWindowLocationPathname('static/'
280 282
                 + (options.feedbackSubmitted ? "close.html" : "close2.html"));
281 283
         return;

+ 23
- 26
connection.js Просмотреть файл

@@ -1,5 +1,4 @@
1 1
 /* global APP, JitsiMeetJS, config */
2
-const logger = require("jitsi-meet-logger").getLogger(__filename);
3 2
 
4 3
 import AuthHandler from './modules/UI/authentication/AuthHandler';
5 4
 import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
@@ -14,6 +13,7 @@ import {
14 13
 
15 14
 const ConnectionEvents = JitsiMeetJS.events.connection;
16 15
 const ConnectionErrors = JitsiMeetJS.errors.connection;
16
+const logger = require("jitsi-meet-logger").getLogger(__filename);
17 17
 
18 18
 /**
19 19
  * 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) {
61 61
  * everything is ok, else error.
62 62
  */
63 63
 function connect(id, password, roomName) {
64
-
65
-    let connectionConfig = Object.assign({}, config);
64
+    const connectionConfig = Object.assign({}, config);
65
+    const { issuer, jwt } = APP.store.getState()['features/jwt'];
66 66
 
67 67
     connectionConfig.bosh += '?room=' + roomName;
68
+
68 69
     let connection
69
-        = new JitsiMeetJS.JitsiConnection(null, config.token, connectionConfig);
70
+        = new JitsiMeetJS.JitsiConnection(
71
+            null,
72
+            jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
73
+            connectionConfig);
70 74
 
71 75
     return new Promise(function (resolve, reject) {
72 76
         connection.addEventListener(
73
-            ConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished
74
-        );
77
+            ConnectionEvents.CONNECTION_ESTABLISHED,
78
+            handleConnectionEstablished);
75 79
         connection.addEventListener(
76
-            ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
77
-        );
80
+            ConnectionEvents.CONNECTION_FAILED,
81
+            handleConnectionFailed);
78 82
         connection.addEventListener(
79
-            ConnectionEvents.CONNECTION_FAILED, connectionFailedHandler);
83
+            ConnectionEvents.CONNECTION_FAILED,
84
+            connectionFailedHandler);
80 85
 
81 86
         function connectionFailedHandler(error, errMsg) {
82 87
             APP.store.dispatch(connectionFailed(connection, error, errMsg));
@@ -91,12 +96,10 @@ function connect(id, password, roomName) {
91 96
         function unsubscribe() {
92 97
             connection.removeEventListener(
93 98
                 ConnectionEvents.CONNECTION_ESTABLISHED,
94
-                handleConnectionEstablished
95
-            );
99
+                handleConnectionEstablished);
96 100
             connection.removeEventListener(
97 101
                 ConnectionEvents.CONNECTION_FAILED,
98
-                handleConnectionFailed
99
-            );
102
+                handleConnectionFailed);
100 103
         }
101 104
 
102 105
         function handleConnectionEstablished() {
@@ -129,7 +132,6 @@ function connect(id, password, roomName) {
129 132
  * @returns {Promise<JitsiConnection>}
130 133
  */
131 134
 export function openConnection({id, password, retry, roomName}) {
132
-
133 135
     let usernameOverride
134 136
         = jitsiLocalStorage.getItem("xmpp_username_override");
135 137
     let passwordOverride
@@ -138,25 +140,20 @@ export function openConnection({id, password, retry, roomName}) {
138 140
     if (usernameOverride && usernameOverride.length > 0) {
139 141
         id = usernameOverride;
140 142
     }
141
-
142 143
     if (passwordOverride && passwordOverride.length > 0) {
143 144
         password = passwordOverride;
144 145
     }
145 146
 
146
-    return connect(id, password, roomName).catch(function (err) {
147
-        if (!retry) {
148
-            throw err;
149
-        }
147
+    return connect(id, password, roomName).catch(err => {
148
+        if (retry) {
149
+            const { issuer, jwt } = APP.store.getState()['features/jwt'];
150 150
 
151
-        if (err === ConnectionErrors.PASSWORD_REQUIRED) {
152
-            // do not retry if token is not valid
153
-            if (config.token) {
154
-                throw err;
155
-            } else {
151
+            if (err === ConnectionErrors.PASSWORD_REQUIRED
152
+                    && (!jwt || issuer === 'anonymous')) {
156 153
                 return AuthHandler.requestAuth(roomName, connect);
157 154
             }
158
-        } else {
159
-            throw err;
160 155
         }
156
+
157
+        throw err;
161 158
     });
162 159
 }

+ 12
- 18
connection_optimization/do_external_connect.js Просмотреть файл

@@ -7,35 +7,29 @@ import {
7 7
 
8 8
 /**
9 9
  * Implements external connect using createConnectionExternally function defined
10
- * in external_connect.js for Jitsi Meet. Parses the room name and token from
11
- * the URL and executes createConnectionExternally.
10
+ * in external_connect.js for Jitsi Meet. Parses the room name and JSON Web
11
+ * Token (JWT) from the URL and executes createConnectionExternally.
12 12
  *
13
- * NOTE: If you are using lib-jitsi-meet without Jitsi Meet you should use this
13
+ * NOTE: If you are using lib-jitsi-meet without Jitsi Meet, you should use this
14 14
  * file as reference only because the implementation is Jitsi Meet-specific.
15 15
  *
16 16
  * NOTE: For optimal results this file should be included right after
17 17
  * external_connect.js.
18 18
  */
19 19
 
20
-const hashParams = parseURLParams(window.location, true);
20
+if (typeof createConnectionExternally === 'function') {
21
+    // URL params have higher proirity than config params.
22
+    let url
23
+        = parseURLParams(window.location, true, 'hash')[
24
+                'config.externalConnectUrl']
25
+            || config.externalConnectUrl;
26
+    let roomName;
21 27
 
22
-// URL params have higher proirity than config params.
23
-let url = hashParams['config.externalConnectUrl'] || config.externalConnectUrl;
24
-
25
-if (url && window.createConnectionExternally) {
26
-    const roomName = getRoomName();
27
-
28
-    if (roomName) {
28
+    if (url && (roomName = getRoomName())) {
29 29
         url += `?room=${roomName}`;
30 30
 
31
-        let token = hashParams['config.token'] || config.token;
31
+        const token = parseURLParams(window.location, true, 'search').jwt;
32 32
 
33
-        if (!token) {
34
-            const searchParams
35
-                = parseURLParams(window.location, true, 'search');
36
-
37
-            token = searchParams.jwt;
38
-        }
39 33
         if (token) {
40 34
             url += `&token=${token}`;
41 35
         }

+ 14
- 5
modules/UI/UI.js Просмотреть файл

@@ -362,9 +362,9 @@ UI.start = function () {
362 362
 
363 363
     }
364 364
 
365
-    if(APP.tokenData.callee) {
366
-        UI.showRingOverlay();
367
-    }
365
+    const { callee } = APP.store.getState()['features/jwt'];
366
+
367
+    callee && UI.showRingOverlay();
368 368
 };
369 369
 
370 370
 /**
@@ -1332,7 +1332,10 @@ UI.setMicrophoneButtonEnabled
1332 1332
     = enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
1333 1333
 
1334 1334
 UI.showRingOverlay = function () {
1335
-    RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
1335
+    const { callee } = APP.store.getState()['features/jwt'];
1336
+
1337
+    callee && RingOverlay.show(callee, interfaceConfig.DISABLE_RINGING);
1338
+
1336 1339
     Filmstrip.toggleFilmstrip(false, false);
1337 1340
 };
1338 1341
 
@@ -1397,7 +1400,13 @@ const UIListeners = new Map([
1397 1400
         UI.toggleContactList
1398 1401
     ], [
1399 1402
         UIEvents.TOGGLE_PROFILE,
1400
-        () => APP.tokenData.isGuest && UI.toggleSidePanel("profile_container")
1403
+        () => {
1404
+            const {
1405
+                isGuest
1406
+            } = APP.store.getState()['features/jwt'];
1407
+
1408
+            isGuest && UI.toggleSidePanel('profile_container');
1409
+        }
1401 1410
     ], [
1402 1411
         UIEvents.TOGGLE_FILMSTRIP,
1403 1412
         UI.handleToggleFilmstrip

+ 15
- 8
modules/UI/authentication/AuthHandler.js Просмотреть файл

@@ -1,11 +1,13 @@
1 1
 /* global APP, config, JitsiMeetJS, Promise */
2
-const logger = require("jitsi-meet-logger").getLogger(__filename);
3 2
 
4
-import LoginDialog from './LoginDialog';
3
+import { openConnection } from '../../../connection';
4
+import { setJWT } from '../../../react/features/jwt';
5 5
 import UIUtil from '../util/UIUtil';
6
-import {openConnection} from '../../../connection';
6
+
7
+import LoginDialog from './LoginDialog';
7 8
 
8 9
 const ConnectionErrors = JitsiMeetJS.errors.connection;
10
+const logger = require("jitsi-meet-logger").getLogger(__filename);
9 11
 
10 12
 let externalAuthWindow;
11 13
 let authRequiredDialog;
@@ -73,15 +75,20 @@ function redirectToTokenAuthService(roomName) {
73 75
  * @param room the name fo the conference room.
74 76
  */
75 77
 function initJWTTokenListener(room) {
76
-    var listener = function (event) {
77
-        if (externalAuthWindow !== event.source) {
78
+    var listener = function ({ data, source }) {
79
+        if (externalAuthWindow !== source) {
78 80
             logger.warn("Ignored message not coming " +
79 81
                 "from external authnetication window");
80 82
             return;
81 83
         }
82
-        if (event.data && event.data.jwtToken) {
83
-            config.token = event.data.jwtToken;
84
-            logger.info("Received JWT token:", config.token);
84
+
85
+        let jwt;
86
+
87
+        if (data && (jwt = data.jwtToken)) {
88
+            logger.info("Received JSON Web Token (JWT):", jwt);
89
+
90
+            APP.store.dispatch(setJWT(jwt));
91
+
85 92
             var roomName = room.getName();
86 93
             openConnection({retry: false, roomName: roomName })
87 94
                 .then(function (connection) {

+ 10
- 6
modules/UI/ring_overlay/RingOverlay.js Просмотреть файл

@@ -22,7 +22,8 @@ function onAvatarVisible(shown) {
22 22
  */
23 23
 class RingOverlay {
24 24
     /**
25
-     * @param callee instance of User class from TokenData.js
25
+     *
26
+     * @param callee The callee (Object) as defined by the JWT support.
26 27
      * @param {boolean} disableRingingSound if true the ringing sound wont be played.
27 28
      */
28 29
     constructor(callee, disableRingingSound) {
@@ -77,9 +78,9 @@ class RingOverlay {
77 78
             <div id="${this._containerId}" class='ringing' >
78 79
                 <div class='ringing__content'>
79 80
                     ${callingLabel}
80
-                    <img class='ringing__avatar' src="${callee.getAvatarUrl()}" />
81
+                    <img class='ringing__avatar' src="${callee.avatarUrl}" />
81 82
                     <div class="ringing__caller-info">
82
-                        <p>${callee.getName()}${callerStateLabel}</p>
83
+                        <p>${callee.name}${callerStateLabel}</p>
83 84
                     </div>
84 85
                 </div>
85 86
                 ${audioHTML}
@@ -137,9 +138,12 @@ class RingOverlay {
137 138
 export default {
138 139
     /**
139 140
      * Shows the ring overlay for the passed callee.
140
-     * @param callee {class User} the callee. Instance of User class from
141
-     * TokenData.js
142
-     * @param {boolean} disableRingingSound if true the ringing sound wont be played.
141
+     *
142
+     * @param {Object} callee - The callee. Object containing data about
143
+     * callee.
144
+     * @param {boolean} disableRingingSound - If true the ringing sound won't be
145
+     * played.
146
+     * @returns {void}
143 147
      */
144 148
     show(callee, disableRingingSound = false) {
145 149
         if(overlay) {

+ 17
- 11
modules/analytics/analytics.js Просмотреть файл

@@ -1,4 +1,5 @@
1 1
 /* global JitsiMeetJS, config, APP */
2
+
2 3
 /**
3 4
  * Load the integration of a third-party analytics API such as Google
4 5
  * Analytics. Since we cannot guarantee the quality of the third-party service
@@ -101,26 +102,31 @@ class Analytics {
101 102
      * null.
102 103
      */
103 104
     init() {
104
-        let analytics = JitsiMeetJS.analytics;
105
-        if(!this.isEnabled() || !analytics)
105
+        const { analytics } = JitsiMeetJS;
106
+
107
+        if (!this.isEnabled() || !analytics)
106 108
             return;
107 109
 
108
-        this._loadHandlers()
109
-            .then(handlers => {
110
-                let permanentProperties = {
111
-                    userAgent: navigator.userAgent,
112
-                    roomName: APP.conference.roomName
110
+        this._loadHandlers().then(
111
+            handlers => {
112
+                const permanentProperties = {
113
+                    roomName: APP.conference.roomName,
114
+                    userAgent: navigator.userAgent
113 115
                 };
114
-                let {server, group} = APP.tokenData;
115
-                if(server) {
116
+
117
+                const { group, server } = APP.store.getState()['features/jwt'];
118
+
119
+                if (server) {
116 120
                     permanentProperties.server = server;
117 121
                 }
118
-                if(group) {
122
+                if (group) {
119 123
                     permanentProperties.group = group;
120 124
                 }
125
+
121 126
                 analytics.addPermanentProperties(permanentProperties);
122 127
                 analytics.setAnalyticsHandlers(handlers);
123
-            }, error => analytics.dispose() && console.error(error));
128
+            },
129
+            error => analytics.dispose() && console.error(error));
124 130
 
125 131
     }
126 132
 }

+ 0
- 123
modules/tokendata/TokenData.js Просмотреть файл

@@ -1,123 +0,0 @@
1
-/* global config */
2
-
3
-/**
4
- * Parses and handles JWT tokens. Sets config.token.
5
- */
6
-
7
-import * as jws from "jws";
8
-
9
-import { getConfigParamsFromUrl } from '../../react/features/base/config';
10
-
11
-/**
12
- * Get the JWT token from the URL.
13
- */
14
-let params = getConfigParamsFromUrl(window.location, true, 'search');
15
-let jwt = params.jwt;
16
-
17
-/**
18
- * Implements a user of conference.
19
- */
20
-class User {
21
-    /**
22
-     * @param name {string} the name of the user.
23
-     * @param email {string} the email of the user.
24
-     * @param avatarUrl {string} the URL for the avatar of the user.
25
-     */
26
-    constructor(name, email, avatarUrl) {
27
-        this._name = name;
28
-        this._email = email;
29
-        this._avatarUrl = avatarUrl;
30
-    }
31
-
32
-    /**
33
-     * GETERS START.
34
-     */
35
-
36
-    /**
37
-     * Returns the name property
38
-     */
39
-    getName() {
40
-        return this._name;
41
-    }
42
-
43
-    /**
44
-     * Returns the email property
45
-     */
46
-    getEmail() {
47
-        return this._email;
48
-    }
49
-
50
-    /**
51
-     * Returns the URL of the avatar
52
-     */
53
-    getAvatarUrl() {
54
-        return this._avatarUrl;
55
-    }
56
-
57
-    /**
58
-     * GETERS END.
59
-     */
60
-}
61
-
62
-/**
63
- * Represent the data parsed from the JWT token
64
- */
65
-class TokenData{
66
-    /**
67
-     * @param {string} the JWT token
68
-     */
69
-    constructor(jwt) {
70
-        this.isGuest = true;
71
-        if(!jwt)
72
-            return;
73
-
74
-        this.isGuest = config.enableUserRolesBasedOnToken !== true;
75
-
76
-        this.jwt = jwt;
77
-
78
-        this._decode();
79
-        // Use JWT param as token if there is not other token set and if the
80
-        // iss field is not anonymous. If you want to pass data with JWT token
81
-        // but you don't want to pass the JWT token for verification the iss
82
-        // field should be set to "anonymous"
83
-        if(!config.token && this.payload && this.payload.iss !== "anonymous")
84
-            config.token = jwt;
85
-    }
86
-
87
-    /**
88
-     * Decodes the JWT token and sets the decoded data to properties.
89
-     */
90
-    _decode() {
91
-        this.decodedJWT = jws.decode(jwt);
92
-        if(!this.decodedJWT || !this.decodedJWT.payload)
93
-            return;
94
-        this.payload = this.decodedJWT.payload;
95
-        if(!this.payload.context)
96
-            return;
97
-        this.server = this.payload.context.server;
98
-        this.group = this.payload.context.group;
99
-        let callerData = this.payload.context.user;
100
-        let calleeData = this.payload.context.callee;
101
-        if(callerData)
102
-            this.caller = new User(callerData.name, callerData.email,
103
-                callerData.avatarUrl);
104
-        if(calleeData)
105
-            this.callee = new User(calleeData.name, calleeData.email,
106
-                calleeData.avatarUrl);
107
-    }
108
-}
109
-
110
-/**
111
- * Stores the TokenData instance.
112
- */
113
-let data = null;
114
-
115
-/**
116
- * Returns the data variable. Creates new TokenData instance if <tt>data</tt>
117
- * variable is null.
118
- */
119
-export default function getTokenData() {
120
-    if(!data)
121
-        data = new TokenData(jwt);
122
-    return data;
123
-}

+ 1
- 1
package.json Просмотреть файл

@@ -39,7 +39,7 @@
39 39
     "jQuery-Impromptu": "trentrichardson/jQuery-Impromptu#v6.0.0",
40 40
     "jquery-ui": "1.10.5",
41 41
     "jssha": "1.5.0",
42
-    "jws": "3.1.4",
42
+    "jwt-decode": "2.2.0",
43 43
     "lib-jitsi-meet": "jitsi/lib-jitsi-meet",
44 44
     "lodash": "4.17.4",
45 45
     "postis": "2.2.0",

+ 3
- 3
react/features/app/actions.js Просмотреть файл

@@ -1,4 +1,4 @@
1
-import { setRoom, setRoomUrl } from '../base/conference';
1
+import { setRoom, setRoomURL } from '../base/conference';
2 2
 import { setConfig } from '../base/config';
3 3
 import { getDomain, setDomain } from '../base/connection';
4 4
 import { loadConfig } from '../base/lib-jitsi-meet';
@@ -18,7 +18,7 @@ import {
18 18
  * @returns {Function}
19 19
  */
20 20
 export function appInit() {
21
-    return () => init();
21
+    return (dispatch, getState) => init(getState());
22 22
 }
23 23
 
24 24
 /**
@@ -55,7 +55,7 @@ export function appNavigate(uri) {
55 55
             urlObject = new URL(urlWithoutDomain, `https://${domain}`);
56 56
         }
57 57
 
58
-        dispatch(setRoomUrl(urlObject));
58
+        dispatch(setRoomURL(urlObject));
59 59
 
60 60
         // TODO Kostiantyn Tsaregradskyi: We should probably detect if user is
61 61
         // currently in a conference and ask her if she wants to close the

+ 5
- 4
react/features/app/functions.web.js Просмотреть файл

@@ -16,7 +16,6 @@ import { WelcomePage } from '../welcome';
16 16
 
17 17
 import KeyboardShortcut
18 18
     from '../../../modules/keyboardshortcut/keyboardshortcut';
19
-import getTokenData from '../../../modules/tokendata/TokenData';
20 19
 import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage';
21 20
 
22 21
 declare var APP: Object;
@@ -111,18 +110,20 @@ export function _getRouteToRender(stateOrGetState: Object | Function) {
111 110
  * Temporary solution. Later we'll get rid of global APP and set its properties
112 111
  * in redux store.
113 112
  *
113
+ * @param {Object} state - Snapshot of current state of redux store.
114 114
  * @returns {void}
115 115
  */
116
-export function init() {
116
+export function init(state: Object) {
117 117
     _initLogging();
118 118
 
119 119
     APP.keyboardshortcut = KeyboardShortcut;
120
-    APP.tokenData = getTokenData();
120
+
121
+    const { jwt } = state['features/jwt'];
121 122
 
122 123
     // Force enable the API if jwt token is passed because most probably
123 124
     // jitsi meet is displayed inside of wrapper that will need to communicate
124 125
     // with jitsi meet.
125
-    APP.API.init(APP.tokenData.jwt ? { forceEnable: true } : undefined);
126
+    APP.API.init(jwt ? { forceEnable: true } : undefined);
126 127
 
127 128
     APP.translation.init();
128 129
 }

+ 5
- 0
react/features/app/index.js Просмотреть файл

@@ -3,4 +3,9 @@ export * from './actionTypes';
3 3
 export * from './components';
4 4
 export * from './functions';
5 5
 
6
+// We need to import the jwt module in order to register the reducer and
7
+// middleware, because the module is not used outside of this feature.
8
+import '../jwt';
9
+
6 10
 import './reducer';
11
+

+ 26
- 3
react/features/base/react/components/Watermarks.web.js Просмотреть файл

@@ -1,10 +1,10 @@
1 1
 /* @flow */
2 2
 
3 3
 import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
4 5
 
5 6
 import { translate } from '../../i18n';
6 7
 
7
-declare var APP: Object;
8 8
 declare var interfaceConfig: Object;
9 9
 
10 10
 /**
@@ -131,7 +131,7 @@ class Watermarks extends Component {
131 131
         let reactElement = null;
132 132
 
133 133
         if (this.state.showJitsiWatermark
134
-                || (APP.tokenData.isGuest
134
+                || (this.props._isGuest
135 135
                     && this.state.showJitsiWatermarkForGuests)) {
136 136
             reactElement = <div className = 'watermark leftwatermark' />;
137 137
 
@@ -175,4 +175,27 @@ class Watermarks extends Component {
175 175
     }
176 176
 }
177 177
 
178
-export default translate(Watermarks);
178
+/**
179
+ * Maps parts of Redux store to component prop types.
180
+ *
181
+ * @param {Object} state - Snapshot of Redux store.
182
+ * @returns {{
183
+ *      _isGuest: boolean
184
+ * }}
185
+ */
186
+function _mapStateToProps(state) {
187
+    const { isGuest } = state['features/jwt'];
188
+
189
+    return {
190
+        /**
191
+         * The indicator which determines whether the local participant is a
192
+         * guest in the conference.
193
+         *
194
+         * @private
195
+         * @type {boolean}
196
+         */
197
+        _isGuest: isGuest
198
+    };
199
+}
200
+
201
+export default connect(_mapStateToProps)(translate(Watermarks));

+ 6
- 5
react/features/conference/route.js Просмотреть файл

@@ -113,12 +113,13 @@ function _obtainConfigHandler() {
113 113
  * @returns {void}
114 114
  */
115 115
 function _setTokenData() {
116
-    const localUser = APP.tokenData.caller;
116
+    const state = APP.store.getState();
117
+    const { caller } = state['features/jwt'];
117 118
 
118
-    if (localUser) {
119
-        const email = localUser.getEmail();
120
-        const avatarUrl = localUser.getAvatarUrl();
121
-        const name = localUser.getName();
119
+    if (caller) {
120
+        const email = caller.email;
121
+        const avatarUrl = caller.avatarUrl;
122
+        const name = caller.name;
122 123
 
123 124
         APP.settings.setEmail((email || '').trim(), true);
124 125
         APP.settings.setAvatarUrl((avatarUrl || '').trim());

+ 12
- 0
react/features/jwt/actionTypes.js Просмотреть файл

@@ -0,0 +1,12 @@
1
+import { Symbol } from '../base/react';
2
+
3
+/**
4
+ * The type of redux action which stores a specific JSON Web Token (JWT) into
5
+ * the redux store.
6
+ *
7
+ * {
8
+ *     type: SET_JWT,
9
+ *     jwt: string
10
+ * }
11
+ */
12
+export const SET_JWT = Symbol('SET_JWT');

+ 19
- 0
react/features/jwt/actions.js Просмотреть файл

@@ -0,0 +1,19 @@
1
+/* @flow */
2
+
3
+import { SET_JWT } from './actionTypes';
4
+
5
+/**
6
+ * Stores a specific JSON Web Token (JWT) into the redux store.
7
+ *
8
+ * @param {string} jwt - The JSON Web Token (JWT) to store.
9
+ * @returns {{
10
+ *     type: SET_TOKEN_DATA,
11
+ *     jwt: string
12
+ * }}
13
+ */
14
+export function setJWT(jwt: string) {
15
+    return {
16
+        type: SET_JWT,
17
+        jwt
18
+    };
19
+}

+ 4
- 0
react/features/jwt/index.js Просмотреть файл

@@ -0,0 +1,4 @@
1
+export * from './actions';
2
+
3
+import './middleware';
4
+import './reducer';

+ 106
- 0
react/features/jwt/middleware.js Просмотреть файл

@@ -0,0 +1,106 @@
1
+import jwtDecode from 'jwt-decode';
2
+
3
+import { SET_ROOM_URL } from '../base/conference';
4
+import { parseURLParams, SET_CONFIG } from '../base/config';
5
+import { MiddlewareRegistry } from '../base/redux';
6
+
7
+import { setJWT } from './actions';
8
+import { SET_JWT } from './actionTypes';
9
+
10
+/**
11
+ * Middleware to parse token data upon setting a new room URL.
12
+ *
13
+ * @param {Store} store - The Redux store.
14
+ * @private
15
+ * @returns {Function}
16
+ */
17
+MiddlewareRegistry.register(store => next => action => {
18
+    switch (action.type) {
19
+    case SET_CONFIG:
20
+    case SET_ROOM_URL:
21
+        // XXX The JSON Web Token (JWT) is not the only piece of state that we
22
+        // have decided to store in the feature jwt, there is isGuest as well
23
+        // which depends on the states of the features base/config and jwt. So
24
+        // the JSON Web Token comes from the room's URL and isGuest needs a
25
+        // recalculation upon SET_CONFIG as well.
26
+        return _setConfigOrRoomURL(store, next, action);
27
+
28
+    case SET_JWT:
29
+        return _setJWT(store, next, action);
30
+    }
31
+
32
+    return next(action);
33
+});
34
+
35
+/**
36
+ * Notifies the feature jwt that the action {@link SET_CONFIG} or
37
+ * {@link SET_ROOM_URL} is being dispatched within a specific Redux
38
+ * {@code store}.
39
+ *
40
+ * @param {Store} store - The Redux store in which the specified {@code action}
41
+ * is being dispatched.
42
+ * @param {Dispatch} next - The Redux dispatch function to dispatch the
43
+ * specified {@code action} to the specified {@code store}.
44
+ * @param {Action} action - The Redux action {@code SET_CONFIG} or
45
+ * {@code SET_ROOM_NAME} which is being dispatched in the specified
46
+ * {@code store}.
47
+ * @private
48
+ * @returns {Object} The new state that is the result of the reduction of the
49
+ * specified {@code action}.
50
+ */
51
+function _setConfigOrRoomURL({ dispatch, getState }, next, action) {
52
+    const result = next(action);
53
+
54
+    const { roomURL } = getState()['features/base/conference'];
55
+    let jwt;
56
+
57
+    if (roomURL) {
58
+        jwt = parseURLParams(roomURL, true, 'search').jwt;
59
+    }
60
+    dispatch(setJWT(jwt));
61
+
62
+    return result;
63
+}
64
+
65
+/**
66
+ * Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
67
+ * within a specific Redux {@code store}.
68
+ *
69
+ * @param {Store} store - The Redux store in which the specified {@code action}
70
+ * is being dispatched.
71
+ * @param {Dispatch} next - The Redux dispatch function to dispatch the
72
+ * specified {@code action} to the specified {@code store}.
73
+ * @param {Action} action - The Redux action {@code SET_JWT} which is being
74
+ * dispatched in the specified {@code store}.
75
+ * @private
76
+ * @returns {Object} The new state that is the result of the reduction of the
77
+ * specified {@code action}.
78
+ */
79
+function _setJWT({ getState }, next, action) {
80
+    // eslint-disable-next-line no-unused-vars
81
+    const { jwt, type, ...actionPayload } = action;
82
+
83
+    if (jwt && !Object.keys(actionPayload).length) {
84
+        const {
85
+            enableUserRolesBasedOnToken
86
+        } = getState()['features/base/config'];
87
+
88
+        action.isGuest = !enableUserRolesBasedOnToken;
89
+
90
+        const jwtPayload = jwtDecode(jwt);
91
+
92
+        if (jwtPayload) {
93
+            const { context, iss } = jwtPayload;
94
+
95
+            action.issuer = iss;
96
+            if (context) {
97
+                action.callee = context.callee;
98
+                action.caller = context.user;
99
+                action.group = context.group;
100
+                action.server = context.server;
101
+            }
102
+        }
103
+    }
104
+
105
+    return next(action);
106
+}

+ 47
- 0
react/features/jwt/reducer.js Просмотреть файл

@@ -0,0 +1,47 @@
1
+import { equals, ReducerRegistry } from '../base/redux';
2
+
3
+import { SET_JWT } from './actionTypes';
4
+
5
+/**
6
+ * The initial redux state of the feature jwt.
7
+ *
8
+ * @private
9
+ * @type {{
10
+ *     isGuest: boolean
11
+ * }}
12
+ */
13
+const _INITIAL_STATE = {
14
+    /**
15
+     * The indicator which determines whether the local participant is a guest
16
+     * in the conference.
17
+     *
18
+     * @type {boolean}
19
+     */
20
+    isGuest: true
21
+};
22
+
23
+/**
24
+ * Reduces redux actions which affect the JSON Web Token (JWT) stored in the
25
+ * redux store.
26
+ *
27
+ * @param {Object} state - The current redux state.
28
+ * @param {Object} action - The redux action to reduce.
29
+ * @returns {Object} The next redux state which is the result of reducing the
30
+ * specified {@code action}.
31
+ */
32
+ReducerRegistry.register('features/jwt', (state = _INITIAL_STATE, action) => {
33
+    switch (action.type) {
34
+    case SET_JWT: {
35
+        // eslint-disable-next-line no-unused-vars
36
+        const { type, ...payload } = action;
37
+        const nextState = {
38
+            ..._INITIAL_STATE,
39
+            ...payload
40
+        };
41
+
42
+        return equals(state, nextState) ? state : nextState;
43
+    }
44
+    }
45
+
46
+    return state;
47
+});

+ 28
- 8
react/features/toolbox/components/SecondaryToolbar.web.js Просмотреть файл

@@ -33,6 +33,12 @@ class SecondaryToolbar extends Component {
33 33
      * @static
34 34
      */
35 35
     static propTypes = {
36
+        /**
37
+         * The indicator which determines whether the local participant is a
38
+         * guest in the conference.
39
+         */
40
+        _isGuest: React.PropTypes.bool,
41
+
36 42
         /**
37 43
          * Handler dispatching local "Raise hand".
38 44
          */
@@ -79,9 +85,14 @@ class SecondaryToolbar extends Component {
79 85
              * @type {Object}
80 86
              */
81 87
             profile: {
82
-                onMount: () =>
83
-                    APP.tokenData.isGuest
84
-                        || this.props._onSetProfileButtonUnclickable(true)
88
+                onMount: () => {
89
+                    const {
90
+                        _isGuest,
91
+                        _onSetProfileButtonUnclickable
92
+                    } = this.props;
93
+
94
+                    _isGuest || _onSetProfileButtonUnclickable(true);
95
+                }
85 96
             },
86 97
 
87 98
             /**
@@ -237,18 +248,26 @@ function _mapDispatchToProps(dispatch: Function): Object {
237 248
  *
238 249
  * @param {Object} state - Snapshot of Redux store.
239 250
  * @returns {{
251
+ *     _isGuest: boolean,
240 252
  *     _secondaryToolbarButtons: Map,
241 253
  *     _visible: boolean
242 254
  * }}
243 255
  * @private
244 256
  */
245 257
 function _mapStateToProps(state: Object): Object {
246
-    const {
247
-        secondaryToolbarButtons,
248
-        visible
249
-    } = state['features/toolbox'];
258
+    const { isGuest } = state['features/jwt'];
259
+    const { secondaryToolbarButtons, visible } = state['features/toolbox'];
250 260
 
251 261
     return {
262
+        /**
263
+         * The indicator which determines whether the local participant is a
264
+         * guest in the conference.
265
+         *
266
+         * @private
267
+         * @type {boolean}
268
+         */
269
+        _isGuest: isGuest,
270
+
252 271
         /**
253 272
          * Default toolbar buttons for secondary toolbar.
254 273
          *
@@ -258,7 +277,8 @@ function _mapStateToProps(state: Object): Object {
258 277
         _secondaryToolbarButtons: secondaryToolbarButtons,
259 278
 
260 279
         /**
261
-         * Shows whether toolbar is visible.
280
+         * The indicator which determines whether the {@code SecondaryToolbar}
281
+         * is visible.
262 282
          *
263 283
          * @private
264 284
          * @type {boolean}

Загрузка…
Отмена
Сохранить