瀏覽代碼

Merge pull request #8866 from jitsi/tavram/sip-invite

feat(sipcall) implement sip invite
master
Avram Tudor 4 年之前
父節點
當前提交
58b7663a97
沒有連結到貢獻者的電子郵件帳戶。

+ 1
- 0
lang/main.json 查看文件

@@ -28,6 +28,7 @@
28 28
         "shareInvite": "Share meeting invitation",
29 29
         "shareLink": "Share the meeting link to invite others",
30 30
         "shareStream": "Share the live streaming link",
31
+        "sip": "SIP: {{address}}",
31 32
         "telephone": "Telephone: {{number}}",
32 33
         "title": "Invite people to this meeting",
33 34
         "yahooEmail": "Yahoo Email"

+ 19
- 2
react/features/invite/actions.any.js 查看文件

@@ -3,7 +3,7 @@
3 3
 import type { Dispatch } from 'redux';
4 4
 
5 5
 import { getInviteURL } from '../base/connection';
6
-import { getParticipants } from '../base/participants';
6
+import { getLocalParticipant, getParticipants } from '../base/participants';
7 7
 import { inviteVideoRooms } from '../videosipgw';
8 8
 
9 9
 import {
@@ -18,7 +18,8 @@ import {
18 18
 import {
19 19
     getDialInConferenceID,
20 20
     getDialInNumbers,
21
-    invitePeopleAndChatRooms
21
+    invitePeopleAndChatRooms,
22
+    inviteSipEndpoints
22 23
 } from './functions';
23 24
 import logger from './logger';
24 25
 
@@ -102,7 +103,9 @@ export function invite(
102 103
             inviteServiceCallFlowsUrl
103 104
         } = state['features/base/config'];
104 105
         const inviteUrl = getInviteURL(state);
106
+        const { sipInviteUrl } = state['features/base/config'];
105 107
         const { jwt } = state['features/base/jwt'];
108
+        const { name: displayName } = getLocalParticipant(state);
106 109
 
107 110
         // First create all promises for dialing out.
108 111
         const phoneNumbers
@@ -164,6 +167,20 @@ export function invite(
164 167
         invitesLeftToSend
165 168
             = invitesLeftToSend.filter(({ type }) => type !== 'videosipgw');
166 169
 
170
+        const sipEndpoints
171
+            = invitesLeftToSend.filter(({ type }) => type === 'sip');
172
+
173
+        conference && inviteSipEndpoints(
174
+            sipEndpoints,
175
+            sipInviteUrl,
176
+            jwt,
177
+            conference.options.name,
178
+            displayName
179
+        );
180
+
181
+        invitesLeftToSend
182
+            = invitesLeftToSend.filter(({ type }) => type !== 'sip');
183
+
167 184
         return (
168 185
             Promise.all(allInvitePromises)
169 186
                 .then(() => invitesLeftToSend));

+ 14
- 5
react/features/invite/components/add-people-dialog/AbstractAddPeopleDialog.js 查看文件

@@ -12,7 +12,8 @@ import {
12 12
     getInviteResultsForQuery,
13 13
     getInviteTypeCounts,
14 14
     isAddPeopleEnabled,
15
-    isDialOutEnabled
15
+    isDialOutEnabled,
16
+    isSipInviteEnabled
16 17
 } from '../../functions';
17 18
 import logger from '../../logger';
18 19
 
@@ -38,6 +39,11 @@ export type Props = {
38 39
      */
39 40
     _dialOutEnabled: boolean,
40 41
 
42
+    /**
43
+     * Whether or not to allow sip invites.
44
+     */
45
+     _sipInviteEnabled: boolean,
46
+
41 47
     /**
42 48
      * The JWT token.
43 49
      */
@@ -96,7 +102,7 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
96 102
 
97 103
     /**
98 104
      * Invite people and numbers to the conference. The logic works by inviting
99
-     * numbers, people/rooms, and videosipgw in parallel. All invitees are
105
+     * numbers, people/rooms, sip endpoints and videosipgw in parallel. All invitees are
100 106
      * stored in an array. As each invite succeeds, the invitee is removed
101 107
      * from the array. After all invites finish, close the modal if there are
102 108
      * no invites left to send. If any are left, that means an invite failed
@@ -214,7 +220,8 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
214 220
             _dialOutEnabled: dialOutEnabled,
215 221
             _jwt: jwt,
216 222
             _peopleSearchQueryTypes: peopleSearchQueryTypes,
217
-            _peopleSearchUrl: peopleSearchUrl
223
+            _peopleSearchUrl: peopleSearchUrl,
224
+            _sipInviteEnabled: sipInviteEnabled
218 225
         } = this.props;
219 226
         const options = {
220 227
             addPeopleEnabled,
@@ -222,7 +229,8 @@ export default class AbstractAddPeopleDialog<P: Props, S: State>
222 229
             dialOutEnabled,
223 230
             jwt,
224 231
             peopleSearchQueryTypes,
225
-            peopleSearchUrl
232
+            peopleSearchUrl,
233
+            sipInviteEnabled
226 234
         };
227 235
 
228 236
         return getInviteResultsForQuery(query, options);
@@ -259,6 +267,7 @@ export function _mapStateToProps(state: Object) {
259 267
         _dialOutEnabled: isDialOutEnabled(state),
260 268
         _jwt: state['features/base/jwt'].jwt,
261 269
         _peopleSearchQueryTypes: peopleSearchQueryTypes,
262
-        _peopleSearchUrl: peopleSearchUrl
270
+        _peopleSearchUrl: peopleSearchUrl,
271
+        _sipInviteEnabled: isSipInviteEnabled(state)
263 272
     };
264 273
 }

+ 18
- 2
react/features/invite/components/add-people-dialog/web/InviteContactsForm.js 查看文件

@@ -285,7 +285,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
285 285
      */
286 286
     _parseQueryResults(response = []) {
287 287
         const { t, _dialOutEnabled } = this.props;
288
-        const users = response.filter(item => item.type !== 'phone');
288
+        const users = response.filter(item => item.type !== 'phone' && item.type !== 'sip');
289 289
         const userDisplayItems = [];
290 290
 
291 291
         for (const user of users) {
@@ -348,9 +348,25 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
348 348
             };
349 349
         });
350 350
 
351
+
352
+        const sipAddresses = response.filter(item => item.type === 'sip');
353
+
354
+        const sipDisplayItems = sipAddresses.map(sip => {
355
+            return {
356
+                filterValues: [
357
+                    sip.address
358
+                ],
359
+                content: t('addPeople.sip', { address: sip.address }),
360
+                description: '',
361
+                item: sip,
362
+                value: sip.address
363
+            };
364
+        });
365
+
351 366
         return [
352 367
             ...userDisplayItems,
353
-            ...numberDisplayItems
368
+            ...numberDisplayItems,
369
+            ...sipDisplayItems
354 370
         ];
355 371
     }
356 372
 

+ 6
- 0
react/features/invite/constants.js 查看文件

@@ -42,3 +42,9 @@ export const OUTGOING_CALL_RINGING_SOUND_ID = 'OUTGOING_CALL_RINGING_SOUND_ID';
42 42
  * @type {string}
43 43
  */
44 44
 export const OUTGOING_CALL_START_SOUND_ID = 'OUTGOING_CALL_START_SOUND_ID';
45
+
46
+/**
47
+ * Regex for matching sip addresses.
48
+ */
49
+// eslint-disable-next-line max-len
50
+export const SIP_ADDRESS_REGEX = /^[a-zA-Z]+(?:([^\s>:@]+)(?::([^\s@>]+))?@)?([\w\-.]+)(?::(\d+))?((?:;[^\s=?>;]+(?:=[^\s?;]+)?)*)(?:\?(([^\s&=>]+=[^\s&=>]+)(&[^\s&=>]+=[^\s&=>]+)*))?$/;

+ 83
- 0
react/features/invite/functions.js 查看文件

@@ -10,6 +10,7 @@ import { toState } from '../base/redux';
10 10
 import { doGetJSON, parseURIString } from '../base/util';
11 11
 import { isVpaasMeeting } from '../billing-counter/functions';
12 12
 
13
+import { SIP_ADDRESS_REGEX } from './constants';
13 14
 import logger from './logger';
14 15
 
15 16
 declare var $: Function;
@@ -122,6 +123,11 @@ export type GetInviteResultsOptions = {
122 123
      */
123 124
     peopleSearchUrl: string,
124 125
 
126
+    /**
127
+     * Whether or not to check sip invites.
128
+     */
129
+    sipInviteEnabled: boolean,
130
+
125 131
     /**
126 132
      * The jwt token to pass to the search service.
127 133
      */
@@ -149,6 +155,7 @@ export function getInviteResultsForQuery(
149 155
         dialOutEnabled,
150 156
         peopleSearchQueryTypes,
151 157
         peopleSearchUrl,
158
+        sipInviteEnabled,
152 159
         jwt
153 160
     } = options;
154 161
 
@@ -233,6 +240,13 @@ export function getInviteResultsForQuery(
233 240
                 });
234 241
             }
235 242
 
243
+            if (sipInviteEnabled && isASipAddress(text)) {
244
+                results.push({
245
+                    type: 'sip',
246
+                    address: text
247
+                });
248
+            }
249
+
236 250
             return results;
237 251
         });
238 252
 }
@@ -374,6 +388,21 @@ export function isDialOutEnabled(state: Object): boolean {
374 388
         && conference && conference.isSIPCallingSupported();
375 389
 }
376 390
 
391
+/**
392
+ * Determines if inviting sip endpoints is enabled or not.
393
+ *
394
+ * @param {Object} state - Current state.
395
+ * @returns {boolean} Indication of whether dial out is currently enabled.
396
+ */
397
+export function isSipInviteEnabled(state: Object): boolean {
398
+    const { sipInviteUrl } = state['features/base/config'];
399
+    const { features = {} } = getLocalParticipant(state);
400
+
401
+    return state['features/base/jwt'].jwt
402
+        && Boolean(sipInviteUrl)
403
+        && String(features['sip-outbound-call']) === 'true';
404
+}
405
+
377 406
 /**
378 407
  * Checks whether a string looks like it could be for a phone number.
379 408
  *
@@ -392,6 +421,16 @@ function isMaybeAPhoneNumber(text: string): boolean {
392 421
     return Boolean(digits.length);
393 422
 }
394 423
 
424
+/**
425
+ * Checks whether a string matches a sip address format.
426
+ *
427
+ * @param {string} text - The text to check.
428
+ * @returns {boolean} True if provided text matches a sip address format.
429
+ */
430
+function isASipAddress(text: string): boolean {
431
+    return SIP_ADDRESS_REGEX.test(text);
432
+}
433
+
395 434
 /**
396 435
  * RegExp to use to determine if some text might be a phone number.
397 436
  *
@@ -764,3 +803,47 @@ export function isSharingEnabled(sharingFeature: string) {
764 803
         || typeof interfaceConfig.SHARING_FEATURES === 'undefined'
765 804
         || (interfaceConfig.SHARING_FEATURES.length && interfaceConfig.SHARING_FEATURES.indexOf(sharingFeature) > -1);
766 805
 }
806
+
807
+/**
808
+ * Sends a post request to an invite service.
809
+ *
810
+ * @param {Array} inviteItems - The list of the "sip" type items to invite.
811
+ * @param {string} sipInviteUrl - The invite service that generates the invitation.
812
+ * @param {string} jwt - The jwt token.
813
+ * @param {string} roomName - The name to the conference.
814
+ * @param {string} displayName - The user display name.
815
+ * @returns {Promise} - The promise created by the request.
816
+ */
817
+export function inviteSipEndpoints( // eslint-disable-line max-params
818
+        inviteItems: Array<Object>,
819
+        sipInviteUrl: string,
820
+        jwt: string,
821
+        roomName: string,
822
+        displayName: string
823
+): Promise<void> {
824
+    if (inviteItems.length === 0) {
825
+        return Promise.resolve();
826
+    }
827
+
828
+    return fetch(
829
+       `${sipInviteUrl}?token=${jwt}`,
830
+       {
831
+           body: JSON.stringify({
832
+               callParams: {
833
+                   callUrlInfo: {
834
+                       baseUrl: window.location.origin,
835
+                       callName: roomName
836
+                   }
837
+               },
838
+               sipClientParams: {
839
+                   displayName,
840
+                   sipAddress: inviteItems.map(item => item.address)
841
+               }
842
+           }),
843
+           method: 'POST',
844
+           headers: {
845
+               'Content-Type': 'application/json'
846
+           }
847
+       }
848
+    );
849
+}

Loading…
取消
儲存