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

VideoSIPGW updates (#2201)

* Adds initial documentation for sipgw jibri.

Also explains enabling the people search service and the request/response that are made around sipgw jibri service.

* Fixes add people dialog to invite users and rooms.

No invitation is sent when there is nobody to invite.

* Reuse some recording strings, by using arguments.

* Make sure web also dispatches CONFERENCE_WILL_JOIN.

* Introduces new feature videosipgw.

* Fixes lint errors.

* Renames methods to use people, chatRooms and videoRooms.

* Updates to latest lib-jitsi-meet (dc3397b18b).
master
Дамян Минков 7 лет назад
Родитель
Сommit
7b1b873b6e

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

@@ -25,6 +25,7 @@ import {
25 25
     conferenceFailed,
26 26
     conferenceJoined,
27 27
     conferenceLeft,
28
+    conferenceWillJoin,
28 29
     dataChannelOpened,
29 30
     EMAIL_COMMAND,
30 31
     lockStateChanged,
@@ -1249,6 +1250,7 @@ export default {
1249 1250
             = connection.initJitsiConference(
1250 1251
                 APP.conference.roomName,
1251 1252
                 this._getConferenceOptions());
1253
+        APP.store.dispatch(conferenceWillJoin(room));
1252 1254
         this._setLocalAudioVideoStreams(localTracks);
1253 1255
         this._room = room; // FIXME do not use this
1254 1256
 

+ 57
- 0
doc/sipgw-config.md Просмотреть файл

@@ -0,0 +1,57 @@
1
+# Configuring sipgw jibri with jitsi-meet
2
+
3
+This document describes how you can configure jitsi-meet to use sipgw jibri and enable rooms in 'Add people dialog'
4
+You will need a working deployment of jibri configured to use a regular sip video device, for more info check out the [jibri documentation](https://github.com/jitsi/jibri/blob/master/README.md).
5
+
6
+This feature is available for non-guests of the system, so this relies on setting in config.js ``enableUserRolesBasedOnToken: true`` and providing a jwt token when accessing the conference.
7
+
8
+* Jicofo configuration:
9
+edit /etc/jitsi/jicofo/sip-communicator.properties (or similar), set the appropriate MUC to look for the Jibri Controllers. This should be the same MUC as is referenced in jibri's config.json file. Restart Jicofo after setting this property.
10
+
11
+```
12
+  org.jitsi.jicofo.jibri.SIP_BREWERY=TheSipBrewery@conference.yourdomain.com
13
+ ```
14
+
15
+* Jitsi Meet configuration:
16
+ - config.js: add 
17
+```
18
+  enableUserRolesBasedOnToken: true,
19
+  peopleSearchQueryTypes: ['conferenceRooms'],
20
+  peopleSearchUrl: 'https://api.yourdomain.com/testpath/searchpeople',
21
+```
22
+ - interface_config.js:
23
+```
24
+  ADD_PEOPLE_APP_NAME: 'Jitsi'
25
+```
26
+
27
+The combination of the above settings and providing a jwt token will enable a button under invite option which will show the dialog 'Add people'.
28
+
29
+## People search service
30
+
31
+When searching in the dialog, a request for results is made to the `peopleSearchUrl` service.
32
+
33
+The request is in the following format:
34
+```
35
+https://api.yourdomain.com/testpath/searchpeople?query=testroomname&queryTypes=[%22conferenceRooms%22]&jwt=somejwt
36
+```
37
+The parameters are:
38
+ - query - The text entered by the user.
39
+ - queryTypes - What type of results we want people, rooms, conferenceRooms. This is the value from config.js `peopleSearchQueryTypes`
40
+ - jwt - The token used by the user to access the conference.
41
+
42
+The response of the service is a json in the following format:
43
+```
44
+[
45
+   {
46
+       "id": "address@sip.domain.com",
47
+       "name": "Some room name",
48
+       "type": "videosipgw"
49
+   },
50
+  {
51
+      "id": "address2@sip.domain.com",
52
+      "name": "Some room name2",
53
+      "type": "videosipgw"
54
+  }
55
+]
56
+```
57
+Type should be `videosipgw`, `name` is the name shown to the user and `id` is the sip address to be called by the sipgw jibri.

+ 16
- 2
lang/main.json Просмотреть файл

@@ -420,7 +420,8 @@
420 420
         "off": "Recording stopped",
421 421
         "on": "Recording",
422 422
         "pending": "Recording waiting for a member to join...",
423
-        "unavailable": "Oops! The recording service is currently unavailable. We're working on resolving the issue. Please try again later.",
423
+        "serviceName": "Recording service",
424
+        "unavailable": "Oops! The __serviceName__ is currently unavailable. We're working on resolving the issue. Please try again later.",
424 425
         "unavailableTitle": "Recording unavailable"
425 426
     },
426 427
     "liveStreaming":
@@ -433,11 +434,24 @@
433 434
         "off": "Live Streaming stopped",
434 435
         "on": "Live Streaming",
435 436
         "pending": "Starting Live Stream...",
437
+        "serviceName": "Live Streaming service",
436 438
         "streamIdRequired": "Please fill in the stream id in order to launch the Live Streaming.",
437 439
         "streamIdHelp": "Where do I find this?",
438
-        "unavailable": "Oops! The Live Streaming service is currently unavailable. We're working on resolving the issue. Please try again later.",
439 440
         "unavailableTitle": "Live Streaming unavailable"
440 441
     },
442
+    "videoSIPGW":
443
+    {
444
+        "busy": "We're working on freeing resources. Please try again in a few minutes.",
445
+        "busyTitle": "The Room service is currently busy",
446
+        "errorInvite": "Conference not established yet. Please try again later.",
447
+        "errorInviteTitle": "Error inviting room",
448
+        "errorAlreadyInvited": "__displayName__ already invited",
449
+        "errorInviteFailedTitle": "Inviting __displayName__ failed",
450
+        "errorInviteFailed": "We're working on resolving the issue. Please try again later.",
451
+        "pending": "__displayName__ has been invited",
452
+        "serviceName": "Room service",
453
+        "unavailableTitle": "Room service unavailable"
454
+    },
441 455
     "speakerStats":
442 456
     {
443 457
         "hours": "__count__h",

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

@@ -51,6 +51,7 @@ export const RECORDING_TRANSLATION_KEYS = {
51 51
     recordingPendingKey: 'recording.pending',
52 52
     recordingTitle: 'dialog.recording',
53 53
     recordingUnavailable: 'recording.unavailable',
54
+    recordingUnavailableParams: '$t(recording.serviceName)',
54 55
     recordingUnavailableTitle: 'recording.unavailableTitle'
55 56
 };
56 57
 
@@ -71,7 +72,8 @@ export const STREAMING_TRANSLATION_KEYS = {
71 72
     recordingOnKey: 'liveStreaming.on',
72 73
     recordingPendingKey: 'liveStreaming.pending',
73 74
     recordingTitle: 'dialog.liveStreaming',
74
-    recordingUnavailable: 'liveStreaming.unavailable',
75
+    recordingUnavailable: 'recording.unavailable',
76
+    recordingUnavailableParams: '$t(liveStreaming.serviceName)',
75 77
     recordingUnavailableTitle: 'liveStreaming.unavailableTitle'
76 78
 };
77 79
 
@@ -531,6 +533,8 @@ const Recording = {
531 533
         default: {
532 534
             APP.UI.messageHandler.showError({
533 535
                 descriptionKey: this.recordingUnavailable,
536
+                descriptionArguments: {
537
+                    serviceName: this.recordingUnavailableParams },
534 538
                 titleKey: this.recordingUnavailableTitle
535 539
             });
536 540
         }

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

@@ -10991,7 +10991,7 @@
10991 10991
       }
10992 10992
     },
10993 10993
     "lib-jitsi-meet": {
10994
-      "version": "github:jitsi/lib-jitsi-meet#21b07deabccd0b28b6d8eacaa6eee613e018b482",
10994
+      "version": "github:jitsi/lib-jitsi-meet#dc3397b18b3c3c41ee8d17224f9af9318b844c43",
10995 10995
       "requires": {
10996 10996
         "async": "0.9.0",
10997 10997
         "current-executing-script": "0.1.3",

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

@@ -46,7 +46,7 @@
46 46
     "js-md5": "0.6.1",
47 47
     "jssha": "2.2.0",
48 48
     "jwt-decode": "2.2.0",
49
-    "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#21b07deabccd0b28b6d8eacaa6eee613e018b482",
49
+    "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#dc3397b18b3c3c41ee8d17224f9af9318b844c43",
50 50
     "lodash": "4.17.4",
51 51
     "nuclear-js": "1.4.0",
52 52
     "postis": "2.2.0",

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

@@ -229,10 +229,25 @@ function _conferenceWillJoin(conference: Object) {
229 229
             _addLocalTracksToConference(conference, localTracks);
230 230
         }
231 231
 
232
-        dispatch({
233
-            type: CONFERENCE_WILL_JOIN,
234
-            conference
235
-        });
232
+        dispatch(conferenceWillJoin(conference));
233
+    };
234
+}
235
+
236
+/**
237
+ * Signals the intention of the application to have the local participant
238
+ * join the specified conference.
239
+ *
240
+ * @param {JitsiConference} conference - The {@code JitsiConference} instance
241
+ * the local participant will (try to) join.
242
+ * @returns {{
243
+ *     type: CONFERENCE_WILL_JOIN,
244
+ *     conference: JitsiConference
245
+ * }}
246
+ */
247
+export function conferenceWillJoin(conference: Object) {
248
+    return {
249
+        type: CONFERENCE_WILL_JOIN,
250
+        conference
236 251
     };
237 252
 }
238 253
 

+ 1
- 0
react/features/base/lib-jitsi-meet/index.js Просмотреть файл

@@ -16,6 +16,7 @@ export const JitsiMediaDevicesEvents = JitsiMeetJS.events.mediaDevices;
16 16
 export const JitsiParticipantConnectionStatus
17 17
     = JitsiMeetJS.constants.participantConnectionStatus;
18 18
 export const JitsiRecordingStatus = JitsiMeetJS.constants.recordingStatus;
19
+export const JitsiSIPVideoGWStatus = JitsiMeetJS.constants.sipVideoGW;
19 20
 export const JitsiTrackErrors = JitsiMeetJS.errors.track;
20 21
 export const JitsiTrackEvents = JitsiMeetJS.events.track;
21 22
 

+ 18
- 9
react/features/invite/components/AddPeopleDialog.web.js Просмотреть файл

@@ -12,7 +12,8 @@ import { Dialog, hideDialog } from '../../base/dialog';
12 12
 import { translate } from '../../base/i18n';
13 13
 import { MultiSelectAutocomplete } from '../../base/react';
14 14
 
15
-import { invitePeople, inviteRooms, searchPeople } from '../functions';
15
+import { invitePeopleAndChatRooms, searchDirectory } from '../functions';
16
+import { inviteVideoRooms } from '../../videosipgw';
16 17
 
17 18
 declare var interfaceConfig: Object;
18 19
 
@@ -62,6 +63,11 @@ class AddPeopleDialog extends Component<*, *> {
62 63
          */
63 64
         hideDialog: PropTypes.func,
64 65
 
66
+        /**
67
+         * Used to invite video rooms.
68
+         */
69
+        inviteVideoRooms: PropTypes.func,
70
+
65 71
         /**
66 72
          * Invoked to obtain translated strings.
67 73
          */
@@ -79,7 +85,7 @@ class AddPeopleDialog extends Component<*, *> {
79 85
             } = this.props; // eslint-disable-line no-invalid-this
80 86
 
81 87
             return (
82
-                searchPeople(
88
+                searchDirectory(
83 89
                     _peopleSearchUrl,
84 90
                     _jwt,
85 91
                     text,
@@ -215,16 +221,17 @@ class AddPeopleDialog extends Component<*, *> {
215 221
             });
216 222
 
217 223
             this.props._conference
218
-                && inviteRooms(
219
-                    this.props._conference,
220
-                    this.state.inviteItems.filter(
221
-                        i => i.type === 'videosipgw'));
224
+                && this.props.inviteVideoRooms(
225
+                        this.props._conference,
226
+                        this.state.inviteItems.filter(
227
+                            i => i.type === 'videosipgw'));
222 228
 
223
-            invitePeople(
229
+            invitePeopleAndChatRooms(
224 230
                 this.props._inviteServiceUrl,
225 231
                 this.props._inviteUrl,
226 232
                 this.props._jwt,
227
-                this.state.inviteItems.filter(i => i.type === 'user'))
233
+                this.state.inviteItems.filter(
234
+                    i => i.type === 'user' || i.type === 'room'))
228 235
             .then(
229 236
                 /* onFulfilled */ () => {
230 237
                     this.setState({
@@ -355,5 +362,7 @@ function _mapStateToProps(state) {
355 362
     };
356 363
 }
357 364
 
358
-export default translate(connect(_mapStateToProps, { hideDialog })(
365
+export default translate(connect(_mapStateToProps, {
366
+    hideDialog,
367
+    inviteVideoRooms })(
359 368
     AddPeopleDialog));

+ 10
- 32
react/features/invite/functions.js Просмотреть файл

@@ -22,19 +22,24 @@ export function getInviteOptionPosition(name: string): number {
22 22
  * invitation.
23 23
  * @param {string} inviteUrl - The url to the conference.
24 24
  * @param {string} jwt - The jwt token to pass to the search service.
25
- * @param {Immutable.List} people - The list of the "user" type items to invite.
25
+ * @param {Immutable.List} inviteItems - The list of the "user" or "room"
26
+ * type items to invite.
26 27
  * @returns {Promise} - The promise created by the request.
27 28
  */
28
-export function invitePeople( // eslint-disable-line max-params
29
+export function invitePeopleAndChatRooms( // eslint-disable-line max-params
29 30
         inviteServiceUrl: string,
30 31
         inviteUrl: string,
31 32
         jwt: string,
32
-        people: Object) {
33
+        inviteItems: Object) {
34
+    if (!inviteItems || inviteItems.length === 0) {
35
+        return Promise.resolve();
36
+    }
37
+
33 38
     return new Promise((resolve, reject) => {
34 39
         $.post(
35 40
                 `${inviteServiceUrl}?token=${jwt}`,
36 41
                 JSON.stringify({
37
-                    'invited': people,
42
+                    'invited': inviteItems,
38 43
                     'url': inviteUrl
39 44
                 }),
40 45
                 resolve,
@@ -43,33 +48,6 @@ export function invitePeople( // eslint-disable-line max-params
43 48
     });
44 49
 }
45 50
 
46
-/**
47
- * Invites room participants to the conference through the SIP Jibri service.
48
- *
49
- * @param {JitsiMeetConference} conference - The conference to which the rooms
50
- * will be invited to.
51
- * @param {Immutable.List} rooms - The list of the "videosipgw" type items to
52
- * invite.
53
- * @returns {void}
54
- */
55
-export function inviteRooms(
56
-        conference: { createVideoSIPGWSession: Function },
57
-        rooms: Object) {
58
-    for (const room of rooms) {
59
-        const { id: sipAddress, name: displayName } = room;
60
-
61
-        if (sipAddress && displayName) {
62
-            const newSession
63
-                = conference.createVideoSIPGWSession(sipAddress, displayName);
64
-
65
-            newSession.start();
66
-        } else {
67
-            console.error(
68
-                `No display name or sip number for ${JSON.stringify(room)}`);
69
-        }
70
-    }
71
-}
72
-
73 51
 /**
74 52
  * Indicates if an invite option is enabled in the configuration.
75 53
  *
@@ -92,7 +70,7 @@ export function isInviteOptionEnabled(name: string) {
92 70
  * executed - "conferenceRooms" | "user" | "room".
93 71
  * @returns {Promise} - The promise created by the request.
94 72
  */
95
-export function searchPeople( // eslint-disable-line max-params
73
+export function searchDirectory( // eslint-disable-line max-params
96 74
         serviceUrl: string,
97 75
         jwt: string,
98 76
         text: string,

+ 0
- 3
react/features/notifications/components/Notification.web.js Просмотреть файл

@@ -145,9 +145,6 @@ class Notification extends Component<*> {
145 145
     render() {
146 146
         const {
147 147
             appearance,
148
-            description,
149
-            descriptionArguments,
150
-            descriptionKey,
151 148
             hideErrorSupportLink,
152 149
             isDismissAllowed,
153 150
             onDismissed,

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

@@ -0,0 +1,23 @@
1
+/**
2
+ * The type of (redux) action which signals that sip GW service change its
3
+ * availability status.
4
+ *
5
+ * {
6
+ *     type: SIP_GW_AVAILABILITY_CHANGED,
7
+ *     status: string
8
+ * }
9
+ */
10
+export const SIP_GW_AVAILABILITY_CHANGED
11
+    = Symbol('SIP_GW_AVAILABILITY_CHANGED');
12
+
13
+/**
14
+ * The type of the action which signals to invite room participants to the
15
+ * conference through the SIP Jibri service.
16
+ *
17
+ * {
18
+ *     type: SIP_GW_INVITE_ROOMS,
19
+ *     conference: JitsiConference,
20
+ *     rooms: {Immutable.List}
21
+ * }
22
+ */
23
+export const SIP_GW_INVITE_ROOMS = Symbol('SIP_GW_INVITE_ROOMS');

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

@@ -0,0 +1,22 @@
1
+/* @flow */
2
+
3
+import { SIP_GW_INVITE_ROOMS } from './actionTypes';
4
+
5
+/**
6
+ * Invites room participants to the conference through the SIP Jibri service.
7
+ *
8
+ * @param {JitsiMeetConference} conference - The conference to which the rooms
9
+ * will be invited to.
10
+ * @param {Immutable.List} rooms - The list of the "videosipgw" type items to
11
+ * invite.
12
+ * @returns {void}
13
+ */
14
+export function inviteVideoRooms(
15
+        conference: Object,
16
+        rooms: Object) {
17
+    return {
18
+        type: SIP_GW_INVITE_ROOMS,
19
+        conference,
20
+        rooms
21
+    };
22
+}

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

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

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

@@ -0,0 +1,182 @@
1
+/* @flow */
2
+
3
+import Logger from 'jitsi-meet-logger';
4
+import { CONFERENCE_WILL_JOIN } from '../base/conference';
5
+import {
6
+    SIP_GW_AVAILABILITY_CHANGED,
7
+    SIP_GW_INVITE_ROOMS
8
+} from './actionTypes';
9
+import {
10
+    JitsiConferenceEvents,
11
+    JitsiSIPVideoGWStatus
12
+} from '../base/lib-jitsi-meet';
13
+import { MiddlewareRegistry } from '../base/redux';
14
+import {
15
+    Notification,
16
+    showErrorNotification,
17
+    showNotification,
18
+    showWarningNotification
19
+} from '../notifications';
20
+
21
+const logger = Logger.getLogger(__filename);
22
+
23
+/**
24
+ * Middleware that captures conference video sip gw events and stores
25
+ * the global sip gw availability in redux or show appropriate notification
26
+ * for sip gw sessions.
27
+ * Captures invitation actions that create sip gw sessions or display
28
+ * appropriate error/warning notifications.
29
+ *
30
+ * @param {Store} store - The redux store.
31
+ * @returns {Function}
32
+ */
33
+MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
34
+    const result = next(action);
35
+
36
+    switch (action.type) {
37
+    case CONFERENCE_WILL_JOIN: {
38
+        const conference = getState()['features/base/conference'].joining;
39
+
40
+        conference.on(
41
+            JitsiConferenceEvents.VIDEO_SIP_GW_AVAILABILITY_CHANGED,
42
+            (...args) => dispatch(_availabilityChanged(...args)));
43
+        conference.on(
44
+            JitsiConferenceEvents.VIDEO_SIP_GW_SESSION_STATE_CHANGED,
45
+            event => {
46
+                const toDispatch = _sessionStateChanged(event);
47
+
48
+                // sessionStateChanged can decide there is nothing to dispatch
49
+                if (toDispatch) {
50
+                    dispatch(toDispatch);
51
+                }
52
+            });
53
+
54
+        break;
55
+    }
56
+    case SIP_GW_INVITE_ROOMS: {
57
+        const { status } = getState()['features/videosipgw'];
58
+
59
+        if (status === JitsiSIPVideoGWStatus.STATUS_UNDEFINED) {
60
+            dispatch(showErrorNotification({
61
+                descriptionKey: 'recording.unavailable',
62
+                descriptionArguments: {
63
+                    serviceName: '$t(videoSIPGW.serviceName)'
64
+                },
65
+                titleKey: 'videoSIPGW.unavailableTitle'
66
+            }));
67
+
68
+            return;
69
+        } else if (status === JitsiSIPVideoGWStatus.STATUS_BUSY) {
70
+            dispatch(showWarningNotification({
71
+                descriptionKey: 'videoSIPGW.busy',
72
+                titleKey: 'videoSIPGW.busyTitle'
73
+            }));
74
+
75
+            return;
76
+        } else if (status !== JitsiSIPVideoGWStatus.STATUS_AVAILABLE) {
77
+            logger.error(`Unknown sip videogw status ${status}`);
78
+
79
+            return;
80
+        }
81
+
82
+        for (const room of action.rooms) {
83
+            const { id: sipAddress, name: displayName } = room;
84
+
85
+            if (sipAddress && displayName) {
86
+                const newSession = action.conference
87
+                    .createVideoSIPGWSession(sipAddress, displayName);
88
+
89
+                if (newSession instanceof Error) {
90
+                    const e = newSession;
91
+
92
+                    if (e) {
93
+                        switch (e.message) {
94
+                        case JitsiSIPVideoGWStatus.ERROR_NO_CONNECTION: {
95
+                            dispatch(showErrorNotification({
96
+                                descriptionKey: 'videoSIPGW.errorInvite',
97
+                                titleKey: 'videoSIPGW.errorInviteTitle'
98
+                            }));
99
+
100
+                            return;
101
+                        }
102
+                        case JitsiSIPVideoGWStatus.ERROR_SESSION_EXISTS: {
103
+                            dispatch(showWarningNotification({
104
+                                titleKey: 'videoSIPGW.errorAlreadyInvited',
105
+                                titleArguments: { displayName }
106
+                            }));
107
+
108
+                            return;
109
+                        }
110
+                        }
111
+                    }
112
+                    logger.error(
113
+                        'Unknown error trying to create sip videogw session',
114
+                        e);
115
+
116
+                    return;
117
+                }
118
+
119
+                newSession.start();
120
+            } else {
121
+                logger.error(`No display name or sip number for ${
122
+                    JSON.stringify(room)}`);
123
+            }
124
+        }
125
+    }
126
+    }
127
+
128
+    return result;
129
+});
130
+
131
+/**
132
+ * Signals that sip gw availability had changed.
133
+ *
134
+ * @param {string} status - The new status of the service.
135
+ * @returns {{
136
+ *     type: SIP_GW_AVAILABILITY_CHANGED,
137
+ *     status: string
138
+ * }}
139
+ * @private
140
+ */
141
+function _availabilityChanged(status: string) {
142
+    return {
143
+        type: SIP_GW_AVAILABILITY_CHANGED,
144
+        status
145
+    };
146
+}
147
+
148
+/**
149
+ * Signals that a session we created has a change in its status.
150
+ *
151
+ * @param {string} event - The event describing the session state change.
152
+ * @returns {{
153
+ *     type: SHOW_NOTIFICATION
154
+ * }}|null
155
+ * @private
156
+ */
157
+function _sessionStateChanged(
158
+        event: Object) {
159
+    switch (event.newState) {
160
+    case JitsiSIPVideoGWStatus.STATE_PENDING: {
161
+        return showNotification(
162
+            Notification, {
163
+                titleKey: 'videoSIPGW.pending',
164
+                titleArguments: {
165
+                    displayName: event.displayName
166
+                }
167
+            }, 2000);
168
+    }
169
+    case JitsiSIPVideoGWStatus.STATE_FAILED: {
170
+        return showErrorNotification({
171
+            titleKey: 'videoSIPGW.errorInviteFailedTitle',
172
+            titleArguments: {
173
+                displayName: event.displayName
174
+            },
175
+            descriptionKey: 'videoSIPGW.errorInviteFailed'
176
+        });
177
+    }
178
+    }
179
+
180
+    // nothing to show
181
+    return null;
182
+}

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

@@ -0,0 +1,17 @@
1
+import { ReducerRegistry } from '../base/redux';
2
+
3
+import { SIP_GW_AVAILABILITY_CHANGED } from './actionTypes';
4
+
5
+ReducerRegistry.register(
6
+    'features/videosipgw', (state = [], action) => {
7
+        switch (action.type) {
8
+        case SIP_GW_AVAILABILITY_CHANGED: {
9
+            return {
10
+                ...state,
11
+                status: action.status
12
+            };
13
+        }
14
+        }
15
+
16
+        return state;
17
+    });

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