@webex/plugin-meetings 3.8.0-web-workers-keepalive.1 → 3.8.1-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +70 -6
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/common/errors/webex-errors.js +12 -2
  5. package/dist/common/errors/webex-errors.js.map +1 -1
  6. package/dist/config.js +4 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +22 -123
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controls-options-manager/enums.js +2 -0
  11. package/dist/controls-options-manager/enums.js.map +1 -1
  12. package/dist/controls-options-manager/types.js.map +1 -1
  13. package/dist/controls-options-manager/util.js +52 -0
  14. package/dist/controls-options-manager/util.js.map +1 -1
  15. package/dist/interpretation/index.js +1 -1
  16. package/dist/interpretation/siLanguage.js +1 -1
  17. package/dist/locus-info/controlsUtils.js +30 -10
  18. package/dist/locus-info/controlsUtils.js.map +1 -1
  19. package/dist/locus-info/index.js +83 -12
  20. package/dist/locus-info/index.js.map +1 -1
  21. package/dist/locus-info/selfUtils.js +432 -418
  22. package/dist/locus-info/selfUtils.js.map +1 -1
  23. package/dist/media/index.js +17 -17
  24. package/dist/media/index.js.map +1 -1
  25. package/dist/media/properties.js +94 -6
  26. package/dist/media/properties.js.map +1 -1
  27. package/dist/meeting/brbState.js +9 -2
  28. package/dist/meeting/brbState.js.map +1 -1
  29. package/dist/meeting/in-meeting-actions.js +17 -1
  30. package/dist/meeting/in-meeting-actions.js.map +1 -1
  31. package/dist/meeting/index.js +568 -328
  32. package/dist/meeting/index.js.map +1 -1
  33. package/dist/meeting/locusMediaRequest.js +0 -17
  34. package/dist/meeting/locusMediaRequest.js.map +1 -1
  35. package/dist/meeting/muteState.js +4 -4
  36. package/dist/meeting/muteState.js.map +1 -1
  37. package/dist/meeting/request.js +30 -0
  38. package/dist/meeting/request.js.map +1 -1
  39. package/dist/meeting/request.type.js.map +1 -1
  40. package/dist/meeting/util.js +9 -1
  41. package/dist/meeting/util.js.map +1 -1
  42. package/dist/meeting-info/meeting-info-v2.js +19 -13
  43. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  44. package/dist/meeting-info/utilv2.js +5 -1
  45. package/dist/meeting-info/utilv2.js.map +1 -1
  46. package/dist/meetings/index.js +76 -0
  47. package/dist/meetings/index.js.map +1 -1
  48. package/dist/meetings/util.js +14 -0
  49. package/dist/meetings/util.js.map +1 -1
  50. package/dist/member/index.js +45 -9
  51. package/dist/member/index.js.map +1 -1
  52. package/dist/member/types.js +3 -0
  53. package/dist/member/types.js.map +1 -1
  54. package/dist/member/util.js +335 -356
  55. package/dist/member/util.js.map +1 -1
  56. package/dist/members/collection.js.map +1 -1
  57. package/dist/members/index.js +137 -29
  58. package/dist/members/index.js.map +1 -1
  59. package/dist/members/request.js +38 -0
  60. package/dist/members/request.js.map +1 -1
  61. package/dist/members/util.js +36 -1
  62. package/dist/members/util.js.map +1 -1
  63. package/dist/metrics/constants.js +1 -0
  64. package/dist/metrics/constants.js.map +1 -1
  65. package/dist/reachability/clusterReachability.js +23 -31
  66. package/dist/reachability/clusterReachability.js.map +1 -1
  67. package/dist/reachability/index.js +42 -2
  68. package/dist/reachability/index.js.map +1 -1
  69. package/dist/reconnection-manager/index.js +2 -2
  70. package/dist/reconnection-manager/index.js.map +1 -1
  71. package/dist/roap/index.js.map +1 -1
  72. package/dist/roap/turnDiscovery.js +45 -27
  73. package/dist/roap/turnDiscovery.js.map +1 -1
  74. package/dist/roap/types.js +17 -0
  75. package/dist/roap/types.js.map +1 -0
  76. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  77. package/dist/types/config.d.ts +2 -0
  78. package/dist/types/constants.d.ts +15 -85
  79. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  80. package/dist/types/controls-options-manager/types.d.ts +7 -1
  81. package/dist/types/locus-info/index.d.ts +3 -3
  82. package/dist/types/locus-info/selfUtils.d.ts +216 -1
  83. package/dist/types/media/properties.d.ts +15 -0
  84. package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
  85. package/dist/types/meeting/index.d.ts +35 -1
  86. package/dist/types/meeting/muteState.d.ts +0 -1
  87. package/dist/types/meeting/request.d.ts +12 -1
  88. package/dist/types/meeting/request.type.d.ts +6 -0
  89. package/dist/types/meeting/util.d.ts +3 -1
  90. package/dist/types/meeting-info/meeting-info-v2.d.ts +2 -1
  91. package/dist/types/meetings/index.d.ts +28 -0
  92. package/dist/types/member/index.d.ts +20 -6
  93. package/dist/types/member/types.d.ts +73 -14
  94. package/dist/types/member/util.d.ts +156 -1
  95. package/dist/types/members/collection.d.ts +6 -5
  96. package/dist/types/members/index.d.ts +32 -43
  97. package/dist/types/members/request.d.ts +26 -0
  98. package/dist/types/members/util.d.ts +27 -0
  99. package/dist/types/metrics/constants.d.ts +1 -0
  100. package/dist/types/reachability/clusterReachability.d.ts +2 -6
  101. package/dist/types/reachability/index.d.ts +8 -0
  102. package/dist/types/roap/index.d.ts +3 -2
  103. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  104. package/dist/types/roap/types.d.ts +16 -0
  105. package/dist/webinar/index.js +1 -1
  106. package/package.json +24 -23
  107. package/src/breakouts/index.ts +69 -0
  108. package/src/common/errors/webex-errors.ts +8 -1
  109. package/src/config.ts +2 -0
  110. package/src/constants.ts +23 -90
  111. package/src/controls-options-manager/enums.ts +2 -0
  112. package/src/controls-options-manager/types.ts +11 -1
  113. package/src/controls-options-manager/util.ts +62 -0
  114. package/src/locus-info/controlsUtils.ts +48 -12
  115. package/src/locus-info/index.ts +88 -13
  116. package/src/locus-info/selfUtils.ts +496 -442
  117. package/src/media/index.ts +23 -21
  118. package/src/media/properties.ts +96 -0
  119. package/src/meeting/brbState.ts +11 -2
  120. package/src/meeting/in-meeting-actions.ts +32 -0
  121. package/src/meeting/index.ts +356 -87
  122. package/src/meeting/locusMediaRequest.ts +0 -18
  123. package/src/meeting/muteState.ts +4 -4
  124. package/src/meeting/request.ts +36 -1
  125. package/src/meeting/request.type.ts +7 -0
  126. package/src/meeting/util.ts +9 -1
  127. package/src/meeting-info/meeting-info-v2.ts +7 -2
  128. package/src/meeting-info/utilv2.ts +5 -0
  129. package/src/meetings/index.ts +76 -0
  130. package/src/meetings/util.ts +18 -0
  131. package/src/member/index.ts +57 -22
  132. package/src/member/types.ts +82 -16
  133. package/src/member/util.ts +357 -353
  134. package/src/members/collection.ts +4 -3
  135. package/src/members/index.ts +137 -18
  136. package/src/members/request.ts +44 -0
  137. package/src/members/util.ts +43 -1
  138. package/src/metrics/constants.ts +1 -0
  139. package/src/reachability/clusterReachability.ts +26 -25
  140. package/src/reachability/index.ts +55 -1
  141. package/src/reconnection-manager/index.ts +2 -2
  142. package/src/roap/index.ts +3 -7
  143. package/src/roap/turnDiscovery.ts +34 -39
  144. package/src/roap/types.ts +23 -0
  145. package/test/unit/spec/breakouts/index.ts +167 -95
  146. package/test/unit/spec/controls-options-manager/util.js +120 -0
  147. package/test/unit/spec/locus-info/controlsUtils.js +131 -9
  148. package/test/unit/spec/locus-info/index.js +195 -73
  149. package/test/unit/spec/locus-info/selfUtils.js +98 -24
  150. package/test/unit/spec/media/index.ts +150 -18
  151. package/test/unit/spec/media/properties.ts +130 -0
  152. package/test/unit/spec/meeting/brbState.ts +40 -2
  153. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  154. package/test/unit/spec/meeting/index.js +553 -36
  155. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  156. package/test/unit/spec/meeting/muteState.js +73 -2
  157. package/test/unit/spec/meeting/request.js +32 -1
  158. package/test/unit/spec/meeting/utils.js +79 -33
  159. package/test/unit/spec/meeting-info/meetinginfov2.js +41 -0
  160. package/test/unit/spec/meeting-info/utilv2.js +19 -0
  161. package/test/unit/spec/meetings/index.js +68 -1
  162. package/test/unit/spec/members/index.js +304 -78
  163. package/test/unit/spec/members/request.js +68 -22
  164. package/test/unit/spec/members/utils.js +75 -0
  165. package/test/unit/spec/reachability/clusterReachability.ts +41 -55
  166. package/test/unit/spec/reachability/index.ts +89 -0
  167. package/test/unit/spec/reconnection-manager/index.js +4 -4
  168. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
@@ -1,10 +1,11 @@
1
1
  import {MEETINGS} from '../constants';
2
+ import Member from '../member';
2
3
 
3
4
  /**
4
5
  * @class MembersCollection
5
6
  */
6
7
  export default class MembersCollection {
7
- members: any;
8
+ members: Record<string, Member>;
8
9
  namespace = MEETINGS;
9
10
  /**
10
11
  * @param {Object} locus
@@ -14,11 +15,11 @@ export default class MembersCollection {
14
15
  this.members = {};
15
16
  }
16
17
 
17
- set(id, member) {
18
+ set(id: string, member: Member) {
18
19
  this.members[id] = member;
19
20
  }
20
21
 
21
- setAll(members) {
22
+ setAll(members: Record<string, Member>) {
22
23
  this.members = members;
23
24
  }
24
25
 
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
- import {isEmpty} from 'lodash';
4
+ import {get, isEmpty, set} from 'lodash';
5
5
  // @ts-ignore
6
6
  import {StatelessWebexPlugin} from '@webex/webex-core';
7
7
 
@@ -73,6 +73,7 @@ import {ServerRoleShape} from './types';
73
73
  * @memberof Members
74
74
  */
75
75
 
76
+ type UpdatedMembers = {added: Array<Member>; updated: Array<Member>};
76
77
  /**
77
78
  * @class Members
78
79
  */
@@ -81,7 +82,7 @@ export default class Members extends StatelessWebexPlugin {
81
82
  locusUrl: any;
82
83
  mediaShareContentId: any;
83
84
  mediaShareWhiteboardId: any;
84
- membersCollection: any;
85
+ membersCollection: MembersCollection;
85
86
  membersRequest: any;
86
87
  receiveSlotManager: ReceiveSlotManager;
87
88
  mediaRequestManagers: {
@@ -321,6 +322,63 @@ export default class Members extends StatelessWebexPlugin {
321
322
  );
322
323
  }
323
324
 
325
+ /**
326
+ * Updates properties on members that rely on information from other members.
327
+ * This function MUST be called only after the membersCollection has been fully updated
328
+ * @param {UpdatedMembers} membersUpdate
329
+ * @returns {Object} membersCollection
330
+ * @private
331
+ * @memberof Members
332
+ */
333
+ private updateRelationsBetweenMembers(membersUpdate: UpdatedMembers) {
334
+ const updatePairedMembers = (membersList: Member[]) => {
335
+ membersList.forEach((member) => {
336
+ if (!member.pairedWith.participantUrl) {
337
+ // if we don't have a participantUrl set, it may be that we had it in the past and not anymore, so cleanup the rest of the data
338
+ if (member.pairedWith.memberId) {
339
+ const pairedMember = this.membersCollection.get(member.pairedWith.memberId);
340
+
341
+ if (pairedMember) {
342
+ // remove member from pairedMember's associatedUsers array
343
+ pairedMember.associatedUsers.delete(member.id);
344
+
345
+ if (pairedMember.associatedUser === member.id) {
346
+ pairedMember.associatedUser = null;
347
+ }
348
+
349
+ // reset all the props that we set on pairedMember
350
+ pairedMember.isPairedWithSelf = false;
351
+ pairedMember.isHost = false;
352
+ }
353
+ }
354
+ member.pairedWith.memberId = undefined;
355
+ } else if (member.pairedWith.memberId === undefined) {
356
+ // we have participantUrl set but not memberId, so find the member and set it
357
+ const pairedMember = Object.values(this.membersCollection.getAll()).find(
358
+ (m) => m.participant?.url === member.pairedWith.participantUrl
359
+ );
360
+
361
+ if (pairedMember) {
362
+ member.pairedWith.memberId = pairedMember.id;
363
+ pairedMember.associatedUsers.add(member.id);
364
+
365
+ if (pairedMember.associatedUsers.size === 1) {
366
+ // associatedUser is deprecated, because it's broken - device can have multiple associated users,
367
+ // so for backwards compatibility we set it to the first associated user
368
+ pairedMember.associatedUser = member.id;
369
+ }
370
+
371
+ pairedMember.isPairedWithSelf = member.isSelf;
372
+ pairedMember.isHost = member.isHost;
373
+ }
374
+ }
375
+ });
376
+ };
377
+
378
+ updatePairedMembers(membersUpdate.updated);
379
+ updatePairedMembers(membersUpdate.added);
380
+ }
381
+
324
382
  /**
325
383
  * when new participant updates come in, both delta and full participants, update them in members collection
326
384
  * delta object in the event will have {updated, added} and full will be the full membersCollection
@@ -338,6 +396,8 @@ export default class Members extends StatelessWebexPlugin {
338
396
  const delta = this.handleLocusInfoUpdatedParticipants(payload);
339
397
  const full = this.handleMembersUpdate(delta); // SDK should propagate the full list for both delta and non delta updates
340
398
 
399
+ this.updateRelationsBetweenMembers(delta);
400
+
341
401
  this.receiveSlotManager?.updateMemberIds();
342
402
 
343
403
  Trigger.trigger(
@@ -478,20 +538,14 @@ export default class Members extends StatelessWebexPlugin {
478
538
 
479
539
  /**
480
540
  * sets values in the members collection for updated and added properties from delta
481
- * @param {Object} membersUpdate {updated: [], added: []}
541
+ * @param {UpdatedMembers} membersUpdate
482
542
  * @returns {Object} membersCollection
483
543
  * @private
484
544
  * @memberof Members
485
545
  */
486
- private handleMembersUpdate(membersUpdate: any) {
487
- if (membersUpdate) {
488
- if (membersUpdate.updated) {
489
- this.constructMembers(membersUpdate.updated);
490
- }
491
- if (membersUpdate.added) {
492
- this.constructMembers(membersUpdate.added);
493
- }
494
- }
546
+ private handleMembersUpdate(membersUpdate: UpdatedMembers) {
547
+ this.constructMembers(membersUpdate.updated, true);
548
+ this.constructMembers(membersUpdate.added, false);
495
549
 
496
550
  return this.membersCollection.getAll();
497
551
  }
@@ -499,12 +553,30 @@ export default class Members extends StatelessWebexPlugin {
499
553
  /**
500
554
  * set members to the member collection from each updated/added lists as passed in
501
555
  * @param {Array} list
556
+ * @param {boolean} isUpdate
502
557
  * @returns {undefined}
503
558
  * @private
504
559
  * @memberof Members
505
560
  */
506
- private constructMembers(list: Array<any>) {
561
+ private constructMembers(list: Array<any>, isUpdate: boolean) {
507
562
  list.forEach((member) => {
563
+ if (isUpdate) {
564
+ // some member props are generated by SDK and need to be preserved on update,
565
+ // because they depend on relationships with other members so they need to be handled
566
+ // at the end, once all members are updated - this is done in updateRelationsBetweenMembers()
567
+ const propsToKeepOnUpdate = ['pairedWith.memberId'];
568
+
569
+ const existingMember = this.membersCollection.get(member.id);
570
+ if (existingMember) {
571
+ propsToKeepOnUpdate.forEach((prop) => {
572
+ const existingValue = get(existingMember, prop);
573
+
574
+ if (existingValue !== undefined) {
575
+ set(member, prop, existingValue);
576
+ }
577
+ });
578
+ }
579
+ }
508
580
  this.membersCollection.set(member.id, member);
509
581
  });
510
582
  }
@@ -512,11 +584,11 @@ export default class Members extends StatelessWebexPlugin {
512
584
  /**
513
585
  * Internal update the participants value
514
586
  * @param {Object} payload
515
- * @returns {Object}
587
+ * @returns {UpdatedMembers}
516
588
  * @private
517
589
  * @memberof Members
518
590
  */
519
- private handleLocusInfoUpdatedParticipants(payload: any) {
591
+ private handleLocusInfoUpdatedParticipants(payload: any): UpdatedMembers {
520
592
  this.hostId = payload.hostId || this.hostId;
521
593
  this.selfId = payload.selfId || this.selfId;
522
594
  this.recordingId = payload.recordingId;
@@ -681,12 +753,12 @@ export default class Members extends StatelessWebexPlugin {
681
753
  * Removed/left members will end up in updates
682
754
  * Each array contains only members
683
755
  * @param {Array} participants the locus participants
684
- * @returns {Object} {added: {Array}, updated: {Array}}
756
+ * @returns {UpdatedMembers} {added: {Array}, updated: {Array}}
685
757
  * @private
686
758
  * @memberof Members
687
759
  */
688
- private update(participants: Array<any>) {
689
- const membersUpdate = {added: [], updated: []};
760
+ private update(participants: Array<any>): UpdatedMembers {
761
+ const membersUpdate: UpdatedMembers = {added: [], updated: []};
690
762
 
691
763
  if (participants) {
692
764
  participants.forEach((participant) => {
@@ -773,6 +845,28 @@ export default class Members extends StatelessWebexPlugin {
773
845
  return this.membersRequest.cancelPhoneInvite(options);
774
846
  }
775
847
 
848
+ /**
849
+ * Cancels an SIP call to the associated meeting
850
+ * @param {String} invitee
851
+ * @returns {Promise}
852
+ * @memberof Members
853
+ */
854
+ cancelSIPInvite(invitee: any) {
855
+ if (!this.locusUrl) {
856
+ return Promise.reject(
857
+ new ParameterError('The associated locus url for this meeting object must be defined.')
858
+ );
859
+ }
860
+ if (!invitee?.memberId) {
861
+ return Promise.reject(
862
+ new ParameterError('The invitee must be defined with a memberId property.')
863
+ );
864
+ }
865
+ const options = MembersUtil.cancelSIPInviteOptions(invitee, this.locusUrl);
866
+
867
+ return this.membersRequest.cancelSIPInvite(options);
868
+ }
869
+
776
870
  /**
777
871
  * Admits waiting members (invited guests to meeting)
778
872
  * @param {Array} memberIds
@@ -886,6 +980,31 @@ export default class Members extends StatelessWebexPlugin {
886
980
  });
887
981
  }
888
982
 
983
+ /**
984
+ * Moves a meeting member into the lobby.
985
+ * @param {String} memberId -- The ID of the member to move.
986
+ * @returns {Promise} -- Resolves with the lobby‐move response.
987
+ * @public
988
+ * @memberof Members
989
+ */
990
+ public moveToLobby(memberId: string) {
991
+ if (!this.locusUrl) {
992
+ return Promise.reject(
993
+ new ParameterError(
994
+ 'The associated locus url for this meetings members object must be defined.'
995
+ )
996
+ );
997
+ }
998
+ if (!memberId) {
999
+ return Promise.reject(
1000
+ new ParameterError('The member id must be defined to move the member to lobby.')
1001
+ );
1002
+ }
1003
+ const body = MembersUtil.getMoveMemberToLobbyRequestBody(memberId);
1004
+
1005
+ return this.membersRequest.moveToLobbyMember({locusUrl: this.locusUrl, memberId}, body);
1006
+ }
1007
+
889
1008
  /**
890
1009
  * Raise or lower the hand of a member in a meeting
891
1010
  * @param {String} memberId
@@ -129,6 +129,32 @@ export default class MembersRequest extends StatelessWebexPlugin {
129
129
  return this.locusDeltaRequest(requestParams);
130
130
  }
131
131
 
132
+ /**
133
+ * Sends a request to move a meeting member into the lobby.
134
+ * *
135
+ * @param {Object} options - Request options.
136
+ * @param {string} options.locusUrl - The locus URL for the meeting.
137
+ * @param {string} options.memberId - The ID of the member to move.
138
+ * @param {Object} body - The request payload.
139
+ * @param {Object} body.moveToLobby - Container for move‐to‐lobby data.
140
+ * @param {string[]} body.moveToLobby.participantIds - Array of participant IDs to move.
141
+ * @returns {Promise} - Resolves with the locus‐delta response.
142
+ */
143
+ moveToLobbyMember(
144
+ options: {locusUrl: string; memberId: string},
145
+ body: {moveToLobby: {participantIds: string[]}}
146
+ ) {
147
+ if (!options || !options.locusUrl || !options.memberId) {
148
+ throw new ParameterError(
149
+ 'memberId must be defined, and the associated locus url for this meeting object must be defined.'
150
+ );
151
+ }
152
+
153
+ const requestParams = MembersUtil.getMoveMemberToLobbyRequestParams(options, body);
154
+
155
+ return this.locusDeltaRequest(requestParams);
156
+ }
157
+
132
158
  /**
133
159
  * Sends a request to raise or lower a member's hand
134
160
  * @param {Object} options
@@ -252,4 +278,22 @@ export default class MembersRequest extends StatelessWebexPlugin {
252
278
 
253
279
  return this.locusDeltaRequest(requestParams);
254
280
  }
281
+
282
+ /**
283
+ * @param {Object} options with format of {invitee: object, locusUrl: string}
284
+ * @returns {Promise}
285
+ * @throws {Error} if the options are not valid and complete, must have invitee with memberId AND locusUrl
286
+ * @memberof MembersRequest
287
+ */
288
+ cancelSIPInvite(options: any) {
289
+ if (!options?.invitee?.memberId || !options?.locusUrl) {
290
+ throw new ParameterError(
291
+ 'invitee must be passed and the associated locus url for this meeting object must be defined.'
292
+ );
293
+ }
294
+
295
+ const requestParams = MembersUtil.generateCancelSIPInviteRequestParams(options);
296
+
297
+ return this.locusDeltaRequest(requestParams);
298
+ }
255
299
  }
@@ -110,7 +110,10 @@ const MembersUtil = {
110
110
  return !DIALER_REGEX.E164_FORMAT.test(invitee.phoneNumber);
111
111
  }
112
112
 
113
- return !VALID_EMAIL_ADDRESS.test(invitee.email || invitee.emailAddress);
113
+ return !(
114
+ VALID_EMAIL_ADDRESS.test(invitee.email || invitee.emailAddress) ||
115
+ DIALER_REGEX.SIP_ADDRESS.test(invitee.email || invitee.emailAddress)
116
+ );
114
117
  },
115
118
 
116
119
  getRemoveMemberRequestParams: (options) => {
@@ -203,6 +206,22 @@ const MembersUtil = {
203
206
  };
204
207
  },
205
208
 
209
+ getMoveMemberToLobbyRequestBody: (memberId: string) => ({
210
+ moveToLobby: {
211
+ participantIds: [memberId],
212
+ },
213
+ }),
214
+
215
+ getMoveMemberToLobbyRequestParams: (options: {memberId: string; locusUrl: string}, body) => {
216
+ const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;
217
+
218
+ return {
219
+ method: HTTP_VERBS.PATCH,
220
+ uri,
221
+ body,
222
+ };
223
+ },
224
+
206
225
  /**
207
226
  * @param {ServerRoleShape} role
208
227
  * @returns {ServerRoleShape} the role shape to be added to the body
@@ -351,6 +370,29 @@ const MembersUtil = {
351
370
 
352
371
  return requestParams;
353
372
  },
373
+
374
+ cancelSIPInviteOptions: (invitee, locusUrl) => ({
375
+ invitee,
376
+ locusUrl,
377
+ }),
378
+
379
+ generateCancelSIPInviteRequestParams: (options) => {
380
+ const body = {
381
+ actionType: _REMOVE_,
382
+ invitees: [
383
+ {
384
+ address: options.invitee.memberId,
385
+ },
386
+ ],
387
+ };
388
+ const requestParams = {
389
+ method: HTTP_VERBS.PUT,
390
+ uri: options.locusUrl,
391
+ body,
392
+ };
393
+
394
+ return requestParams;
395
+ },
354
396
  };
355
397
 
356
398
  export default MembersUtil;
@@ -72,6 +72,7 @@ const BEHAVIORAL_METRICS = {
72
72
  MEETING_INFO_POLICY_ERROR: 'js_sdk_meeting_info_policy_error',
73
73
  LOCUS_DELTA_SYNC_FAILED: 'js_sdk_locus_delta_sync_failed',
74
74
  LOCUS_DELTA_OUT_OF_ORDER: 'js_sdk_locus_delta_ooo',
75
+ LOCUS_SYNC_HANDLING_FAILED: 'js_sdk_locus_sync_handling_failed',
75
76
  PERMISSION_TOKEN_REFRESH: 'js_sdk_permission_token_refresh',
76
77
  PERMISSION_TOKEN_REFRESH_ERROR: 'js_sdk_permission_token_refresh_error',
77
78
  TURN_DISCOVERY_LATENCY: 'js_sdk_turn_discovery_latency',
@@ -49,6 +49,7 @@ export class ClusterReachability extends EventsScope {
49
49
  private srflxIceCandidates: RTCIceCandidate[] = [];
50
50
  public readonly isVideoMesh: boolean;
51
51
  public readonly name;
52
+ public readonly reachedSubnets: Set<string> = new Set();
52
53
 
53
54
  /**
54
55
  * Constructor for ClusterReachability
@@ -234,27 +235,13 @@ export class ClusterReachability extends EventsScope {
234
235
  */
235
236
  private registerIceGatheringStateChangeListener() {
236
237
  this.pc.onicegatheringstatechange = () => {
237
- const {COMPLETE} = ICE_GATHERING_STATE;
238
-
239
- if (this.pc.iceConnectionState === COMPLETE) {
238
+ if (this.pc.iceGatheringState === ICE_GATHERING_STATE.COMPLETE) {
240
239
  this.closePeerConnection();
241
240
  this.finishReachabilityCheck();
242
241
  }
243
242
  };
244
243
  }
245
244
 
246
- /**
247
- * Checks if we have the results for all the protocols (UDP and TCP)
248
- *
249
- * @returns {boolean} true if we have all results, false otherwise
250
- */
251
- private haveWeGotAllResults(): boolean {
252
- return ['udp', 'tcp', 'xtls'].every(
253
- (protocol) =>
254
- this.result[protocol].result === 'reachable' || this.result[protocol].result === 'untested'
255
- );
256
- }
257
-
258
245
  /**
259
246
  * Saves the latency in the result for the given protocol and marks it as reachable,
260
247
  * emits the "resultReady" event if this is the first result for that protocol,
@@ -264,9 +251,15 @@ export class ClusterReachability extends EventsScope {
264
251
  * @param {string} protocol
265
252
  * @param {number} latency
266
253
  * @param {string|null} [publicIp]
254
+ * @param {string|null} [serverIp]
267
255
  * @returns {void}
268
256
  */
269
- private saveResult(protocol: 'udp' | 'tcp' | 'xtls', latency: number, publicIp?: string | null) {
257
+ private saveResult(
258
+ protocol: 'udp' | 'tcp' | 'xtls',
259
+ latency: number,
260
+ publicIp?: string | null,
261
+ serverIp?: string | null
262
+ ) {
270
263
  const result = this.result[protocol];
271
264
 
272
265
  if (result.latencyInMilliseconds === undefined) {
@@ -294,6 +287,10 @@ export class ClusterReachability extends EventsScope {
294
287
  } else {
295
288
  this.addPublicIP(protocol, publicIp);
296
289
  }
290
+
291
+ if (serverIp) {
292
+ this.reachedSubnets.add(serverIp);
293
+ }
297
294
  }
298
295
 
299
296
  /**
@@ -351,21 +348,25 @@ export class ClusterReachability extends EventsScope {
351
348
 
352
349
  if (e.candidate) {
353
350
  if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
354
- this.saveResult('udp', latencyInMilliseconds, e.candidate.address);
351
+ let serverIp = null;
352
+ if ('url' in e.candidate) {
353
+ const stunServerUrlRegex = /stun:([\d.]+):\d+/;
354
+
355
+ const match = (e.candidate as any).url.match(stunServerUrlRegex);
356
+ if (match) {
357
+ // eslint-disable-next-line prefer-destructuring
358
+ serverIp = match[1];
359
+ }
360
+ }
361
+
362
+ this.saveResult('udp', latencyInMilliseconds, e.candidate.address, serverIp);
355
363
 
356
364
  this.determineNatType(e.candidate);
357
365
  }
358
366
 
359
367
  if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
360
368
  const protocol = e.candidate.port === TURN_TLS_PORT ? 'xtls' : 'tcp';
361
- this.saveResult(protocol, latencyInMilliseconds);
362
- // we don't add public IP for TCP, because in the case of relay candidates
363
- // e.candidate.address is the TURN server address, not the client's public IP
364
- }
365
-
366
- if (this.haveWeGotAllResults()) {
367
- this.closePeerConnection();
368
- this.finishReachabilityCheck();
369
+ this.saveResult(protocol, latencyInMilliseconds, null, e.candidate.address);
369
370
  }
370
371
  }
371
372
  };
@@ -138,6 +138,60 @@ export default class Reachability extends EventsScope {
138
138
  }
139
139
  }
140
140
 
141
+ /**
142
+ * Checks if the given subnet is reachable
143
+ * @param {string} mediaServerIp - media server ip
144
+ * @returns {boolean | null} true if reachable, false if not reachable, null if mediaServerIp is not provided
145
+ * @public
146
+ * @memberof Reachability
147
+ */
148
+ public isSubnetReachable(mediaServerIp?: string): boolean | null {
149
+ if (!mediaServerIp) {
150
+ LoggerProxy.logger.error(`Reachability:index#isSubnetReachable --> mediaServerIp is null`);
151
+
152
+ return null;
153
+ }
154
+
155
+ const subnetFirstOctet = mediaServerIp.split('.')[0];
156
+
157
+ LoggerProxy.logger.info(
158
+ `Reachability:index#isSubnetReachable --> Looking for subnet: ${subnetFirstOctet}.X.X.X`
159
+ );
160
+
161
+ const matchingReachedClusters = Object.values(this.clusterReachability).reduce(
162
+ (acc, cluster) => {
163
+ const reachedSubnetsArray = Array.from(cluster.reachedSubnets);
164
+
165
+ let logMessage = `Reachability:index#isSubnetReachable --> Cluster ${cluster.name} reached [`;
166
+ for (let i = 0; i < reachedSubnetsArray.length; i += 1) {
167
+ const subnet = reachedSubnetsArray[i];
168
+ const reachedSubnetFirstOctet = subnet.split('.')[0];
169
+
170
+ if (subnetFirstOctet === reachedSubnetFirstOctet) {
171
+ acc.add(cluster.name);
172
+ }
173
+
174
+ logMessage += `${subnet}`;
175
+ if (i < reachedSubnetsArray.length - 1) {
176
+ logMessage += ',';
177
+ }
178
+ }
179
+ logMessage += `]`;
180
+
181
+ LoggerProxy.logger.info(logMessage);
182
+
183
+ return acc;
184
+ },
185
+ new Set<string>()
186
+ );
187
+
188
+ LoggerProxy.logger.info(
189
+ `Reachability:index#isSubnetReachable --> Found ${matchingReachedClusters.size} clusters that use the subnet ${subnetFirstOctet}.X.X.X`
190
+ );
191
+
192
+ return matchingReachedClusters.size > 0;
193
+ }
194
+
141
195
  /**
142
196
  * Gets a list of media clusters from the backend and performs reachability checks on all the clusters
143
197
  * @param {string} trigger - explains the reason for starting reachability
@@ -288,7 +342,7 @@ export default class Reachability extends EventsScope {
288
342
  {}
289
343
  );
290
344
  this.sendMetric(true);
291
- this.resolveReachabilityPromise();
345
+ this.resolveReachabilityPromise(false);
292
346
  }
293
347
  }
294
348
 
@@ -591,9 +591,9 @@ export default class ReconnectionManager {
591
591
 
592
592
  const iceServers = [];
593
593
 
594
- if (turnServerResult.turnServerInfo?.url) {
594
+ if (turnServerResult.turnServerInfo?.urls.length > 0) {
595
595
  iceServers.push({
596
- urls: turnServerResult.turnServerInfo.url,
596
+ urls: turnServerResult.turnServerInfo.urls,
597
597
  username: turnServerResult.turnServerInfo.username || '',
598
598
  credential: turnServerResult.turnServerInfo.password || '',
599
599
  });
package/src/roap/index.ts CHANGED
@@ -5,17 +5,13 @@ import {ROAP} from '../constants';
5
5
  import LoggerProxy from '../common/logs/logger-proxy';
6
6
 
7
7
  import RoapRequest from './request';
8
- import TurnDiscovery, {TurnDiscoveryResult} from './turnDiscovery';
8
+ import TurnDiscovery from './turnDiscovery';
9
+ import {TurnDiscoveryResult} from './types';
9
10
  import Meeting from '../meeting';
10
- import MeetingUtil from '../meeting/util';
11
11
  import Metrics from '../metrics';
12
12
  import BEHAVIORAL_METRICS from '../metrics/constants';
13
13
 
14
- export {
15
- type TurnDiscoveryResult,
16
- type TurnServerInfo,
17
- type TurnDiscoverySkipReason,
18
- } from './turnDiscovery';
14
+ export {type TurnDiscoveryResult, type TurnServerInfo, type TurnDiscoverySkipReason} from './types';
19
15
 
20
16
  /**
21
17
  * Roap options