Browse Source

[RN] Join password-protected rooms

j8
Lyubomir Marinov 8 years ago
parent
commit
7ecafb1e69

+ 1
- 0
package.json View File

@@ -35,6 +35,7 @@
35 35
     "react": "15.4.1",
36 36
     "react-dom": "15.4.1",
37 37
     "react-native": "0.39.0",
38
+    "react-native-prompt": "^1.0.0",
38 39
     "react-native-vector-icons": "^3.0.0",
39 40
     "react-native-webrtc": "jitsi/react-native-webrtc",
40 41
     "react-redux": "^4.4.6",

+ 25
- 0
react/features/base/conference/actionTypes.js View File

@@ -1,5 +1,17 @@
1 1
 import { Symbol } from '../react';
2 2
 
3
+/**
4
+ * The type of the Redux action which signals that a specific conference has
5
+ * failed.
6
+ *
7
+ * {
8
+ *     type: CONFERENCE_FAILED,
9
+ *     conference: JitsiConference,
10
+ *     error: string
11
+ * }
12
+ */
13
+export const CONFERENCE_FAILED = Symbol('CONFERENCE_FAILED');
14
+
3 15
 /**
4 16
  * The type of the Redux action which signals that a specific conference has
5 17
  * been joined.
@@ -33,6 +45,19 @@ export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
33 45
  */
34 46
 export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
35 47
 
48
+/**
49
+ * The type of the Redux action which sets the password to join or lock a
50
+ * specific JitsiConference.
51
+ *
52
+ * {
53
+ *     type: SET_PASSWORD,
54
+ *     conference: JitsiConference,
55
+ *     method: Function
56
+ *     password: string
57
+ * }
58
+ */
59
+export const SET_PASSWORD = Symbol('SET_PASSWORD');
60
+
36 61
 /**
37 62
  * The type of the Redux action which sets the name of the room of the
38 63
  * conference to be joined.

+ 52
- 2
react/features/base/conference/actions.js View File

@@ -9,9 +9,11 @@ import {
9 9
 import { trackAdded, trackRemoved } from '../tracks';
10 10
 
11 11
 import {
12
+    CONFERENCE_FAILED,
12 13
     CONFERENCE_JOINED,
13 14
     CONFERENCE_LEFT,
14 15
     CONFERENCE_WILL_LEAVE,
16
+    SET_PASSWORD,
15 17
     SET_ROOM
16 18
 } from './actionTypes';
17 19
 import { EMAIL_COMMAND } from './constants';
@@ -30,6 +32,9 @@ import './reducer';
30 32
 function _addConferenceListeners(conference, dispatch) {
31 33
     const JitsiConferenceEvents = JitsiMeetJS.events.conference;
32 34
 
35
+    conference.on(
36
+            JitsiConferenceEvents.CONFERENCE_FAILED,
37
+            (...args) => dispatch(_conferenceFailed(conference, ...args)));
33 38
     conference.on(
34 39
             JitsiConferenceEvents.CONFERENCE_JOINED,
35 40
             (...args) => dispatch(_conferenceJoined(conference, ...args)));
@@ -67,6 +72,26 @@ function _addConferenceListeners(conference, dispatch) {
67 72
             (data, id) => dispatch(changeParticipantEmail(id, data.value)));
68 73
 }
69 74
 
75
+/**
76
+ * Signals that a specific conference has failed.
77
+ *
78
+ * @param {JitsiConference} conference - The JitsiConference that has failed.
79
+ * @param {string} error - The error describing/detailing the cause of the
80
+ * failure.
81
+ * @returns {{
82
+ *     type: CONFERENCE_FAILED,
83
+ *     conference: JitsiConference,
84
+ *     error: string
85
+ * }}
86
+ */
87
+function _conferenceFailed(conference, error) {
88
+    return {
89
+        type: CONFERENCE_FAILED,
90
+        conference,
91
+        error
92
+    };
93
+}
94
+
70 95
 /**
71 96
  * Attach any pre-existing local media to the conference once the conference has
72 97
  * been joined.
@@ -144,7 +169,7 @@ export function createConference() {
144 169
             throw new Error('Cannot create conference without connection');
145 170
         }
146 171
 
147
-        const room = state['features/base/conference'].room;
172
+        const { password, room } = state['features/base/conference'];
148 173
 
149 174
         if (typeof room === 'undefined' || room === '') {
150 175
             throw new Error('Cannot join conference without room name');
@@ -156,7 +181,32 @@ export function createConference() {
156 181
 
157 182
         _addConferenceListeners(conference, dispatch);
158 183
 
159
-        conference.join();
184
+        conference.join(password);
185
+    };
186
+}
187
+
188
+/**
189
+ * Sets the password to join or lock a specific JitsiConference.
190
+ *
191
+ * @param {JitsiConference} conference - The JitsiConference which requires a
192
+ * password to join or is to be locked with the specified password.
193
+ * @param {Function} method - The JitsiConference method of password protection
194
+ * such as join or lock.
195
+ * @param {string} password - The password with which the specified conference
196
+ * is to be joined or locked.
197
+ * @returns {{
198
+ *     type: SET_PASSWORD,
199
+ *     conference: JitsiConference,
200
+ *     method: Function,
201
+ *     password: string
202
+ * }}
203
+ */
204
+export function setPassword(conference, method, password) {
205
+    return {
206
+        type: SET_PASSWORD,
207
+        conference,
208
+        method,
209
+        password
160 210
     };
161 211
 }
162 212
 

+ 54
- 0
react/features/base/conference/middleware.js View File

@@ -8,6 +8,7 @@ import { MiddlewareRegistry } from '../redux';
8 8
 import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
9 9
 
10 10
 import { createConference } from './actions';
11
+import { SET_PASSWORD } from './actionTypes';
11 12
 import {
12 13
     _addLocalTracksToConference,
13 14
     _handleParticipantError,
@@ -28,6 +29,9 @@ MiddlewareRegistry.register(store => next => action => {
28 29
     case PIN_PARTICIPANT:
29 30
         return _pinParticipant(store, next, action);
30 31
 
32
+    case SET_PASSWORD:
33
+        return _setPassword(store, next, action);
34
+
31 35
     case TRACK_ADDED:
32 36
     case TRACK_REMOVED:
33 37
         return _trackAddedOrRemoved(store, next, action);
@@ -107,6 +111,56 @@ function _pinParticipant(store, next, action) {
107 111
     return next(action);
108 112
 }
109 113
 
114
+/**
115
+ * Notifies the feature base/conference that the action <tt>SET_PASSWORD</tt> is
116
+ * being dispatched within a specific Redux store. Joins or locks a specific
117
+ * <tt>JitsiConference</tt> with a specific password.
118
+ *
119
+ * @param {Store} store - The Redux store in which the specified action is being
120
+ * dispatched.
121
+ * @param {Dispatch} next - The Redux dispatch function to dispatch the
122
+ * specified action to the specified store.
123
+ * @param {Action} action - The Redux action <tt>SET_PASSWORD</tt> which is
124
+ * being dispatched in the specified store.
125
+ * @private
126
+ * @returns {Object} The new state that is the result of the reduction of the
127
+ * specified action.
128
+ */
129
+function _setPassword(store, next, action) {
130
+    const { conference, method } = action;
131
+
132
+    switch (method) {
133
+    case conference.join: {
134
+        let state = store.getState()['features/base/conference'];
135
+
136
+        // Make sure that the action will set a password for a conference that
137
+        // the application wants joined.
138
+        if (state.passwordRequired === conference) {
139
+            const result = next(action);
140
+
141
+            // Join the conference with the newly-set password.
142
+            const password = action.password;
143
+
144
+            // Make sure that the action did set the password.
145
+            state = store.getState()['features/base/conference'];
146
+            if (state.password === password
147
+                    && !state.passwordRequired
148
+
149
+                    // Make sure that the application still wants the conference
150
+                    // joined.
151
+                    && !state.conference) {
152
+                method.call(conference, password);
153
+            }
154
+
155
+            return result;
156
+        }
157
+        break;
158
+    }
159
+    }
160
+
161
+    return next(action);
162
+}
163
+
110 164
 /**
111 165
  * Synchronizes local tracks from state with local tracks in JitsiConference
112 166
  * instance.

+ 85
- 3
react/features/base/conference/reducer.js View File

@@ -6,9 +6,11 @@ import {
6 6
 } from '../redux';
7 7
 
8 8
 import {
9
+    CONFERENCE_FAILED,
9 10
     CONFERENCE_JOINED,
10 11
     CONFERENCE_LEFT,
11 12
     CONFERENCE_WILL_LEAVE,
13
+    SET_PASSWORD,
12 14
     SET_ROOM
13 15
 } from './actionTypes';
14 16
 import { isRoomValid } from './functions';
@@ -19,6 +21,9 @@ import { isRoomValid } from './functions';
19 21
  */
20 22
 ReducerRegistry.register('features/base/conference', (state = {}, action) => {
21 23
     switch (action.type) {
24
+    case CONFERENCE_FAILED:
25
+        return _conferenceFailed(state, action);
26
+
22 27
     case CONFERENCE_JOINED:
23 28
         return _conferenceJoined(state, action);
24 29
 
@@ -28,6 +33,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
28 33
     case CONFERENCE_WILL_LEAVE:
29 34
         return _conferenceWillLeave(state, action);
30 35
 
36
+    case SET_PASSWORD:
37
+        return _setPassword(state, action);
38
+
31 39
     case SET_ROOM:
32 40
         return _setRoom(state, action);
33 41
     }
@@ -35,6 +43,44 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
35 43
     return state;
36 44
 });
37 45
 
46
+/**
47
+ * Reduces a specific Redux action CONFERENCE_FAILED of the feature
48
+ * base/conference.
49
+ *
50
+ * @param {Object} state - The Redux state of the feature base/conference.
51
+ * @param {Action} action - The Redux action CONFERENCE_FAILED to reduce.
52
+ * @private
53
+ * @returns {Object} The new state of the feature base/conference after the
54
+ * reduction of the specified action.
55
+ */
56
+function _conferenceFailed(state, action) {
57
+    const conference = action.conference;
58
+
59
+    if (state.conference && state.conference !== conference) {
60
+        return state;
61
+    }
62
+
63
+    const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
64
+    const passwordRequired
65
+        = JitsiConferenceErrors.PASSWORD_REQUIRED === action.error
66
+            ? conference
67
+            : undefined;
68
+
69
+    return (
70
+        setStateProperties(state, {
71
+            conference: undefined,
72
+            leaving: undefined,
73
+            password: undefined,
74
+
75
+            /**
76
+             * The JitsiConference instance which requires a password to join.
77
+             *
78
+             * @type {JitsiConference}
79
+             */
80
+            passwordRequired
81
+        }));
82
+}
83
+
38 84
 /**
39 85
  * Reduces a specific Redux action CONFERENCE_JOINED of the feature
40 86
  * base/conference.
@@ -55,7 +101,8 @@ function _conferenceJoined(state, action) {
55 101
              * @type {JitsiConference}
56 102
              */
57 103
             conference: action.conference,
58
-            leaving: undefined
104
+            leaving: undefined,
105
+            passwordRequired: undefined
59 106
         }));
60 107
 }
61 108
 
@@ -79,7 +126,9 @@ function _conferenceLeft(state, action) {
79 126
     return (
80 127
         setStateProperties(state, {
81 128
             conference: undefined,
82
-            leaving: undefined
129
+            leaving: undefined,
130
+            password: undefined,
131
+            passwordRequired: undefined
83 132
         }));
84 133
 }
85 134
 
@@ -108,10 +157,43 @@ function _conferenceWillLeave(state, action) {
108 157
              *
109 158
              * @type {JitsiConference}
110 159
              */
111
-            leaving: conference
160
+            leaving: conference,
161
+            passwordRequired: undefined
112 162
         }));
113 163
 }
114 164
 
165
+/**
166
+ * Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
167
+ *
168
+ * @param {Object} state - The Redux state of the feature base/conference.
169
+ * @param {Action} action - The Redux action SET_PASSWORD to reduce.
170
+ * @private
171
+ * @returns {Object} The new state of the feature base/conference after the
172
+ * reduction of the specified action.
173
+ */
174
+function _setPassword(state, action) {
175
+    const conference = action.conference;
176
+
177
+    switch (action.method) {
178
+    case conference.join:
179
+        if (state.passwordRequired === conference) {
180
+            return (
181
+                setStateProperties(state, {
182
+                    /**
183
+                     * The password with which the conference is to be joined.
184
+                     *
185
+                     * @type {string}
186
+                     */
187
+                    password: action.password,
188
+                    passwordRequired: undefined
189
+                }));
190
+        }
191
+        break;
192
+    }
193
+
194
+    return state;
195
+}
196
+
115 197
 /**
116 198
  * Reduces a specific Redux action SET_ROOM of the feature base/conference.
117 199
  *

+ 54
- 1
react/features/conference/components/Conference.native.js View File

@@ -7,6 +7,7 @@ import { FilmStrip } from '../../filmStrip';
7 7
 import { LargeVideo } from '../../largeVideo';
8 8
 import { Toolbar } from '../../toolbar';
9 9
 
10
+import PasswordRequiredPrompt from './PasswordRequiredPrompt';
10 11
 import { styles } from './styles';
11 12
 
12 13
 /**
@@ -24,6 +25,14 @@ class Conference extends Component {
24 25
      * @static
25 26
      */
26 27
     static propTypes = {
28
+        /**
29
+         * The indicator which determines whether a password is required to join
30
+         * the conference and has not been provided yet.
31
+         *
32
+         * @private
33
+         * @type {JitsiConference}
34
+         */
35
+        _passwordRequired: React.PropTypes.object,
27 36
         dispatch: React.PropTypes.func
28 37
     }
29 38
 
@@ -92,6 +101,10 @@ class Conference extends Component {
92 101
                 <LargeVideo />
93 102
                 <Toolbar visible = { toolbarVisible } />
94 103
                 <FilmStrip visible = { !toolbarVisible } />
104
+
105
+                {
106
+                    this._renderPrompt()
107
+                }
95 108
             </Container>
96 109
         );
97 110
     }
@@ -128,6 +141,46 @@ class Conference extends Component {
128 141
                 = setTimeout(this._onClick, TOOLBAR_TIMEOUT_MS);
129 142
         }
130 143
     }
144
+
145
+    /**
146
+     * Renders a prompt if necessary such as when a password is required to join
147
+     * the conference.
148
+     *
149
+     * @private
150
+     * @returns {ReactElement}
151
+     */
152
+    _renderPrompt() {
153
+        const passwordRequired = this.props._passwordRequired;
154
+
155
+        if (passwordRequired) {
156
+            return (
157
+                <PasswordRequiredPrompt conference = { passwordRequired } />
158
+            );
159
+        }
160
+
161
+        return null;
162
+    }
163
+}
164
+
165
+/**
166
+ * Maps (parts of) the Redux state to the associated Conference's props.
167
+ *
168
+ * @param {Object} state - The Redux state.
169
+ * @returns {{
170
+ *     _passwordRequired: boolean
171
+ * }}
172
+ */
173
+function mapStateToProps(state) {
174
+    return {
175
+        /**
176
+         * The indicator which determines whether a password is required to join
177
+         * the conference and has not been provided yet.
178
+         *
179
+         * @private
180
+         * @type {JitsiConference}
181
+         */
182
+        _passwordRequired: state['features/base/conference'].passwordRequired
183
+    };
131 184
 }
132 185
 
133
-export default reactReduxConnect()(Conference);
186
+export default reactReduxConnect(mapStateToProps)(Conference);

+ 87
- 0
react/features/conference/components/PasswordRequiredPrompt.native.js View File

@@ -0,0 +1,87 @@
1
+import React, { Component } from 'react';
2
+import Prompt from 'react-native-prompt';
3
+import { connect } from 'react-redux';
4
+
5
+import { setPassword } from '../../base/conference';
6
+
7
+/**
8
+ * Implements a React Component which prompts the user when a password is
9
+ * required to join a conference.
10
+ */
11
+class PasswordRequiredPrompt extends Component {
12
+    /**
13
+     * PasswordRequiredPrompt component's property types.
14
+     *
15
+     * @static
16
+     */
17
+    static propTypes = {
18
+        /**
19
+         * The JitsiConference which requires a password.
20
+         *
21
+         * @type {JitsiConference}
22
+         */
23
+        conference: React.PropTypes.object,
24
+        dispatch: React.PropTypes.func
25
+    }
26
+
27
+    /**
28
+     * Initializes a new PasswordRequiredPrompt instance.
29
+     *
30
+     * @param {Object} props - The read-only properties with which the new
31
+     * instance is to be initialized.
32
+     */
33
+    constructor(props) {
34
+        super(props);
35
+
36
+        // Bind event handlers so they are only bound once for every instance.
37
+        this._onCancel = this._onCancel.bind(this);
38
+        this._onSubmit = this._onSubmit.bind(this);
39
+    }
40
+
41
+    /**
42
+     * Implements React's {@link Component#render()}.
43
+     *
44
+     * @inheritdoc
45
+     * @returns {ReactElement}
46
+     */
47
+    render() {
48
+        return (
49
+            <Prompt
50
+                onCancel = { this._onCancel }
51
+                onSubmit = { this._onSubmit }
52
+                placeholder = 'Password'
53
+                title = 'Password required'
54
+                visible = { true } />
55
+        );
56
+    }
57
+
58
+    /**
59
+     * Notifies this prompt that it has been dismissed by cancel.
60
+     *
61
+     * @private
62
+     * @returns {void}
63
+     */
64
+    _onCancel() {
65
+        // XXX The user has canceled this prompt for a password so we are to
66
+        // attempt joining the conference without a password. If the conference
67
+        // still requires a password to join, the user will be prompted again
68
+        // later.
69
+        this._onSubmit(undefined);
70
+    }
71
+
72
+    /**
73
+     * Notifies this prompt that it has been dismissed by submitting a specific
74
+     * value.
75
+     *
76
+     * @param {string} value - The submitted value.
77
+     * @private
78
+     * @returns {void}
79
+     */
80
+    _onSubmit(value) {
81
+        const conference = this.props.conference;
82
+
83
+        this.props.dispatch(setPassword(conference, conference.join, value));
84
+    }
85
+}
86
+
87
+export default connect()(PasswordRequiredPrompt);

Loading…
Cancel
Save