@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
@@ -221,14 +221,6 @@ export class LocusMediaRequest extends WebexPlugin {
221
221
  localMedias.roapMessage = request.roapMessage;
222
222
  localMedias.reachability = request.reachability;
223
223
  body.clientMediaPreferences = request.clientMediaPreferences;
224
-
225
- // @ts-ignore
226
- this.webex.internal.newMetrics.submitClientEvent({
227
- name: 'client.locus.media.request',
228
- options: {
229
- meetingId: this.config.meetingId,
230
- },
231
- });
232
224
  break;
233
225
  }
234
226
 
@@ -269,16 +261,6 @@ export class LocusMediaRequest extends WebexPlugin {
269
261
  this.confluenceState = 'created';
270
262
  }
271
263
 
272
- if (request.type === 'RoapMessage') {
273
- // @ts-ignore
274
- this.webex.internal.newMetrics.submitClientEvent({
275
- name: 'client.locus.media.response',
276
- options: {
277
- meetingId: this.config.meetingId,
278
- },
279
- });
280
- }
281
-
282
264
  return result;
283
265
  })
284
266
  .catch((e) => {
@@ -119,9 +119,11 @@ export class MuteState {
119
119
  * @returns {void}
120
120
  */
121
121
  public enable(meeting: any, enable: boolean) {
122
- this.state.client.enabled = enable;
122
+ if (enable !== this.state.client.enabled) {
123
+ this.state.client.enabled = enable;
123
124
 
124
- this.applyClientStateToServer(meeting);
125
+ this.applyClientStateToServer(meeting);
126
+ }
125
127
  }
126
128
 
127
129
  /**
@@ -147,7 +149,6 @@ export class MuteState {
147
149
  * @public
148
150
  * @memberof MuteState
149
151
  * @param {Object} [meeting] the meeting object
150
- * @param {Boolean} [mute] true for muting, false for unmuting request
151
152
  * @returns {void}
152
153
  */
153
154
  public handleLocalStreamMuteStateChange(meeting?: any) {
@@ -350,7 +351,6 @@ export class MuteState {
350
351
  * @param {Meeting} meeting
351
352
  * @returns {void}
352
353
  */
353
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
354
354
  private applyUnmuteAllowedToStream(meeting: any) {
355
355
  if (this.type === AUDIO) {
356
356
  meeting.mediaProperties.audioStream?.setUnmuteAllowed(this.state.server.unmuteAllowed);
@@ -27,7 +27,12 @@ import {
27
27
  _SLIDES_,
28
28
  ANNOTATION,
29
29
  } from '../constants';
30
- import {SendReactionOptions, BrbOptions, ToggleReactionsOptions} from './request.type';
30
+ import {
31
+ SendReactionOptions,
32
+ BrbOptions,
33
+ ToggleReactionsOptions,
34
+ PostMeetingDataConsentOptions,
35
+ } from './request.type';
31
36
  import MeetingUtil from './util';
32
37
  import {AnnotationInfo} from '../annotation/annotation.types';
33
38
  import {ClientMediaPreferences} from '../reachability/reachability.types';
@@ -934,4 +939,34 @@ export default class MeetingRequest extends StatelessWebexPlugin {
934
939
  },
935
940
  });
936
941
  }
942
+
943
+ /**
944
+ * Sends a request to set post meeting data consent.
945
+ *
946
+ * @param {Object} options - The options for post meeting data consent request.
947
+ * @param {boolean} options.consent - Whether accepted or declined.
948
+ * @param {string} options.locusUrl - The URL of the locus.
949
+ * @param {string} options.deviceUrl - The URL of the device.
950
+ * @param {string} options.selfId - The ID of the participant.
951
+ * @returns {Promise}
952
+ */
953
+ setPostMeetingDataConsent({
954
+ postMeetingDataConsent,
955
+ locusUrl,
956
+ deviceUrl,
957
+ selfId,
958
+ }: PostMeetingDataConsentOptions) {
959
+ const uri = `${locusUrl}/${PARTICIPANT}/${selfId}/${CONTROLS}`;
960
+
961
+ return this.locusDeltaRequest({
962
+ method: HTTP_VERBS.PATCH,
963
+ uri,
964
+ body: {
965
+ consent: {
966
+ postMeetingDataConsent,
967
+ deviceUrl,
968
+ },
969
+ },
970
+ });
971
+ }
937
972
  }
@@ -18,3 +18,10 @@ export type BrbOptions = {
18
18
  deviceUrl: string;
19
19
  selfId: string;
20
20
  };
21
+
22
+ export type PostMeetingDataConsentOptions = {
23
+ postMeetingDataConsent: boolean;
24
+ locusUrl: string;
25
+ deviceUrl: string;
26
+ selfId: string;
27
+ };
@@ -573,9 +573,17 @@ const MeetingUtil = {
573
573
  canUserRenameSelfAndObserved: (displayHints) =>
574
574
  displayHints.includes(DISPLAY_HINTS.CAN_RENAME_SELF_AND_OBSERVED),
575
575
 
576
+ requiresPostMeetingDataConsentPrompt: (displayHints) =>
577
+ displayHints.includes(DISPLAY_HINTS.SHOW_POST_MEETING_DATA_CONSENT_PROMPT),
578
+
576
579
  canUserRenameOthers: (displayHints) => displayHints.includes(DISPLAY_HINTS.CAN_RENAME_OTHERS),
577
580
 
578
- canShareWhiteBoard: (displayHints) => displayHints.includes(DISPLAY_HINTS.SHARE_WHITEBOARD),
581
+ // Default empty value for policies if we get an undefined value (ie permissionToken is not available)
582
+ canShareWhiteBoard: (displayHints, policies = {}) =>
583
+ displayHints.includes(DISPLAY_HINTS.SHARE_WHITEBOARD) &&
584
+ !!policies[SELF_POLICY.SUPPORT_WHITEBOARD],
585
+
586
+ canMoveToLobby: (displayHints) => displayHints.includes(DISPLAY_HINTS.MOVE_TO_LOBBY),
579
587
 
580
588
  /**
581
589
  * Adds the current locus sequence information to a request body
@@ -617,6 +617,7 @@ export default class MeetingInfoV2 {
617
617
  * @param {Object} extraParams
618
618
  * @param {Object} options
619
619
  * @param {String} registrationId
620
+ * @param {String} fullSiteUrl
620
621
  * @returns {Promise} returns a meeting info object
621
622
  * @public
622
623
  * @memberof MeetingInfo
@@ -633,7 +634,8 @@ export default class MeetingInfoV2 {
633
634
  locusId = null,
634
635
  extraParams: object = {},
635
636
  options: {meetingId?: string; sendCAevents?: boolean} = {},
636
- registrationId: string = null
637
+ registrationId: string = null,
638
+ fullSiteUrl: string = null
637
639
  ) {
638
640
  const {meetingId, sendCAevents} = options;
639
641
 
@@ -659,6 +661,7 @@ export default class MeetingInfoV2 {
659
661
  locusId,
660
662
  extraParams,
661
663
  registrationId,
664
+ disableWebRedirect: true,
662
665
  });
663
666
 
664
667
  // If the body only contains the default properties, we don't have enough to
@@ -684,7 +687,9 @@ export default class MeetingInfoV2 {
684
687
 
685
688
  const directURI = await MeetingInfoUtil.getDirectMeetingInfoURI(destinationType);
686
689
 
687
- if (directURI) {
690
+ if (fullSiteUrl) {
691
+ requestOptions.uri = `https://${fullSiteUrl}/wbxappapi/v1/meetingInfo`;
692
+ } else if (directURI) {
688
693
  requestOptions.uri = directURI;
689
694
  } else {
690
695
  requestOptions.service = WBXAPPAPI_SERVICE;
@@ -237,6 +237,7 @@ export default class MeetingInfoUtil {
237
237
  locusId,
238
238
  extraParams,
239
239
  registrationId,
240
+ disableWebRedirect,
240
241
  } = options;
241
242
  const body: any = {
242
243
  ...DEFAULT_MEETING_INFO_REQUEST_BODY,
@@ -296,6 +297,10 @@ export default class MeetingInfoUtil {
296
297
  body.locusId = locusId;
297
298
  }
298
299
 
300
+ if (disableWebRedirect) {
301
+ body.disableWebRedirect = disableWebRedirect;
302
+ }
303
+
299
304
  return body;
300
305
  }
301
306
 
@@ -807,6 +807,67 @@ export default class Meetings extends WebexPlugin {
807
807
  }
808
808
  }
809
809
 
810
+ /**
811
+ * API to toggle usage of audio main DTX, needs to be called before webex.meetings.register()
812
+ *
813
+ * @param {Boolean} newValue
814
+ * @private
815
+ * @memberof Meetings
816
+ * @returns {undefined}
817
+ */
818
+ private _toggleDisableAudioMainDtx(newValue: boolean) {
819
+ if (typeof newValue !== 'boolean') {
820
+ return;
821
+ }
822
+
823
+ // @ts-ignore
824
+ if (this.config.experimental.disableAudioMainDtx !== newValue) {
825
+ // @ts-ignore
826
+ this.config.experimental.disableAudioMainDtx = newValue;
827
+ }
828
+ }
829
+
830
+ /**
831
+ * API to toggle usage of audio twcc support
832
+ *
833
+ * @param {Boolean} newValue
834
+ * @private
835
+ * @memberof Meetings
836
+ * @returns {undefined}
837
+ */
838
+ private _toggleEnableAudioTwccForMultistream(newValue: boolean) {
839
+ if (typeof newValue !== 'boolean') {
840
+ return;
841
+ }
842
+
843
+ // @ts-ignore
844
+ if (this.config.enableAudioTwccForMultistream !== newValue) {
845
+ // @ts-ignore
846
+ this.config.enableAudioTwccForMultistream = newValue;
847
+ }
848
+ }
849
+
850
+ /**
851
+ * API to toggle stopping ICE Candidates Gathering after first relay candidate,
852
+ * needs to be called before webex.meetings.joinWithMedia()
853
+ *
854
+ * @param {Boolean} newValue
855
+ * @private
856
+ * @memberof Meetings
857
+ * @returns {undefined}
858
+ */
859
+ private _toggleStopIceGatheringAfterFirstRelayCandidate(newValue: boolean) {
860
+ if (typeof newValue !== 'boolean') {
861
+ return;
862
+ }
863
+
864
+ // @ts-ignore
865
+ if (this.config.stopIceGatheringAfterFirstRelayCandidate !== newValue) {
866
+ // @ts-ignore
867
+ this.config.stopIceGatheringAfterFirstRelayCandidate = newValue;
868
+ }
869
+ }
870
+
810
871
  /**
811
872
  * Executes a registration step and updates the registration status.
812
873
  * @param {Function} step - The registration step to execute.
@@ -935,6 +996,21 @@ export default class Meetings extends WebexPlugin {
935
996
  .disconnect()
936
997
  // @ts-ignore
937
998
  .then(() => this.webex.internal.device.unregister())
999
+ .catch((error) => {
1000
+ // If error status code is 404, continue the chain
1001
+ if (error.statusCode === 404) {
1002
+ LoggerProxy.logger.info(
1003
+ 'Meetings:index#unregister --> 404 error during device unregister, proceeding normally'
1004
+ );
1005
+
1006
+ return; // returning undefined allows the chain to continue
1007
+ }
1008
+ // For any other status code, break the chain by rethrowing
1009
+ LoggerProxy.logger.error(
1010
+ `Meetings:index#unregister --> Failed to unregister device: ${error.message}`
1011
+ );
1012
+ throw error; // rethrow to break the promise chain
1013
+ })
938
1014
  .then(() => {
939
1015
  Trigger.trigger(
940
1016
  this,
@@ -99,6 +99,24 @@ MeetingsUtil.getMediaServer = (sdp) => {
99
99
  return mediaServer;
100
100
  };
101
101
 
102
+ MeetingsUtil.getMediaServerIp = (sdp) => {
103
+ let mediaServerIp;
104
+
105
+ // Attempt to collect the media server from the roap message.
106
+ try {
107
+ mediaServerIp = sdp
108
+ .split('\r\n')
109
+ .find((line) => line.startsWith('o='))
110
+ .match(/o=\S+ \d+ \d+ IN IP4 ([\d.]+)/)?.[1]
111
+ .toLowerCase()
112
+ .trim();
113
+ } catch {
114
+ mediaServerIp = undefined;
115
+ }
116
+
117
+ return mediaServerIp;
118
+ };
119
+
102
120
  MeetingsUtil.checkForCorrelationId = (deviceUrl, locus) => {
103
121
  let devices = [];
104
122
 
@@ -1,18 +1,20 @@
1
1
  /*!
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
- import {MEETINGS, _IN_LOBBY_, _NOT_IN_MEETING_, _IN_MEETING_} from '../constants';
5
- import {IExternalRoles, IMediaStatus, ParticipantWithRoles} from './types';
4
+ import {MEETINGS, _IN_LOBBY_, _NOT_IN_MEETING_, _IN_MEETING_, _OBSERVE_} from '../constants';
5
+ import {IExternalRoles, IMediaStatus, Participant, ParticipantUrl} from './types';
6
6
 
7
7
  import MemberUtil from './util';
8
8
 
9
+ export type MemberId = string;
9
10
  /**
10
11
  * @class Member
11
12
  */
12
13
  export default class Member {
13
- associatedUser: any;
14
+ associatedUser: MemberId | null; // deprecated, use associatedUsers instead
15
+ associatedUsers: Set<MemberId>; // users associated with this device, empty if this member is not a device
14
16
  canReclaimHost: boolean;
15
- id: any;
17
+ id: MemberId;
16
18
  isAudioMuted: any;
17
19
  isContentSharing: any;
18
20
  isDevice: any;
@@ -29,6 +31,7 @@ export default class Member {
29
31
  isRecording: any;
30
32
  isRemovable: any;
31
33
  isSelf: any;
34
+ isPairedWithSelf: boolean; // true for a device that we are paired with
32
35
  isBrb: boolean;
33
36
  isUser: any;
34
37
  isVideoMuted: any;
@@ -42,6 +45,10 @@ export default class Member {
42
45
  supportLiveAnnotation: boolean;
43
46
  type: any;
44
47
  namespace = MEETINGS;
48
+ pairedWith: {
49
+ participantUrl?: ParticipantUrl;
50
+ memberId?: MemberId;
51
+ };
45
52
 
46
53
  /**
47
54
  * @param {Object} participant - the locus participant
@@ -54,7 +61,7 @@ export default class Member {
54
61
  * @memberof Member
55
62
  */
56
63
  constructor(
57
- participant: object,
64
+ participant: Participant,
58
65
  options:
59
66
  | {
60
67
  selfId: string;
@@ -201,13 +208,23 @@ export default class Member {
201
208
  */
202
209
  this.isUser = null;
203
210
  /**
211
+ * Deprecated: use associatedUsers instead
204
212
  * Is this member associated to another user by way of pairing (typical of devices)
205
213
  * @instance
206
- * @type {String}
214
+ * @type {MemberId|null}
215
+ * @deprecated
207
216
  * @public
208
217
  * @memberof Member
209
218
  */
210
219
  this.associatedUser = null;
220
+ /**
221
+ * Is this member associated to another user by way of pairing (typical of devices)
222
+ * @instance
223
+ * @type {String}
224
+ * @public
225
+ * @memberof Member
226
+ */
227
+ this.associatedUsers = new Set<MemberId>();
211
228
  /**
212
229
  * @instance
213
230
  * @type {Boolean}
@@ -266,6 +283,14 @@ export default class Member {
266
283
  */
267
284
  this.isPresenterAssignmentProhibited = null;
268
285
 
286
+ /**
287
+ * @instance
288
+ * @type {Boolean}
289
+ * @public
290
+ * @memberof Member
291
+ */
292
+ this.isPairedWithSelf = false;
293
+
269
294
  /**
270
295
  * @instance
271
296
  * @type {IExternalRoles}
@@ -274,6 +299,10 @@ export default class Member {
274
299
  */
275
300
  this.roles = null;
276
301
 
302
+ this.pairedWith = {
303
+ participantUrl: undefined,
304
+ memberId: undefined,
305
+ };
277
306
  /**
278
307
  * @instance
279
308
  * @type {IMediaStatus}
@@ -299,9 +328,10 @@ export default class Member {
299
328
  * @private
300
329
  * @memberof Member
301
330
  */
302
- private processParticipant(participant: object) {
331
+ private processParticipant(participant: Participant) {
303
332
  this.participant = participant;
304
333
  if (participant) {
334
+ this.processPairedDevice(participant);
305
335
  this.canReclaimHost = MemberUtil.canReclaimHost(participant);
306
336
  this.id = MemberUtil.extractId(participant);
307
337
  this.name = MemberUtil.extractName(participant);
@@ -321,12 +351,24 @@ export default class Member {
321
351
  this.isPresenterAssignmentProhibited =
322
352
  MemberUtil.isPresenterAssignmentProhibited(participant);
323
353
  this.processStatus(participant);
324
- this.processRoles(participant as ParticipantWithRoles);
354
+ this.processRoles(participant);
325
355
  // must be done last
326
356
  this.isNotAdmitted = MemberUtil.isNotAdmitted(participant, this.isGuest, this.status);
327
357
  }
328
358
  }
329
359
 
360
+ /**
361
+ * Checks if the participant is paired with another device
362
+ *
363
+ * @param {any} participant the locus participant object
364
+ * @returns {void}
365
+ */
366
+ processPairedDevice(participant: Participant) {
367
+ // we can't populate this.pairedWith.memberId here because the member for that device might not yet exist
368
+ // so only populating the participantUrl and memberId will be set later
369
+ this.pairedWith.participantUrl = MemberUtil.extractPairedWithParticipantUrl(participant);
370
+ }
371
+
330
372
  /**
331
373
  * Use the members options and participant values to set on the member
332
374
  * @param {Object} participant the locus participant object
@@ -335,7 +377,7 @@ export default class Member {
335
377
  * @private
336
378
  * @memberof Member
337
379
  */
338
- private processParticipantOptions(participant: object, options: any) {
380
+ private processParticipantOptions(participant: Participant, options: any) {
339
381
  if (participant && options) {
340
382
  this.processIsSelf(participant, options.selfId);
341
383
  this.processIsHost(participant, options.hostId);
@@ -378,7 +420,7 @@ export default class Member {
378
420
  * @private
379
421
  * @memberof Member
380
422
  */
381
- private processStatus(participant: object) {
423
+ private processStatus(participant: Participant) {
382
424
  this.status = MemberUtil.extractStatus(participant);
383
425
  switch (this.status) {
384
426
  case _IN_LOBBY_:
@@ -440,11 +482,9 @@ export default class Member {
440
482
  * @public
441
483
  * @memberof Member
442
484
  */
443
- public processIsContentSharing(participant: object, sharingId: string) {
485
+ public processIsContentSharing(participant: Participant, sharingId: string) {
444
486
  if (MemberUtil.isUser(participant)) {
445
487
  this.isContentSharing = MemberUtil.isSame(participant, sharingId);
446
- } else if (MemberUtil.isDevice(participant)) {
447
- this.isContentSharing = MemberUtil.isAssociatedSame(participant, sharingId);
448
488
  }
449
489
  }
450
490
 
@@ -456,7 +496,7 @@ export default class Member {
456
496
  * @public
457
497
  * @memberof Member
458
498
  */
459
- public processIsRecording(participant: object, recordingId: string) {
499
+ public processIsRecording(participant: Participant, recordingId: string) {
460
500
  this.isRecording = MemberUtil.isSame(participant, recordingId);
461
501
  }
462
502
 
@@ -468,12 +508,9 @@ export default class Member {
468
508
  * @private
469
509
  * @memberof Member
470
510
  */
471
- private processIsSelf(participant: object, selfId: string) {
511
+ private processIsSelf(participant: Participant, selfId: string) {
472
512
  if (MemberUtil.isUser(participant)) {
473
513
  this.isSelf = MemberUtil.isSame(participant, selfId);
474
- } else if (MemberUtil.isDevice(participant)) {
475
- this.isSelf = MemberUtil.isAssociatedSame(participant, selfId);
476
- this.associatedUser = selfId;
477
514
  }
478
515
  }
479
516
 
@@ -485,11 +522,9 @@ export default class Member {
485
522
  * @private
486
523
  * @memberof Member
487
524
  */
488
- private processIsHost(participant: object, hostId: string) {
525
+ private processIsHost(participant: Participant, hostId: string) {
489
526
  if (MemberUtil.isUser(participant)) {
490
527
  this.isHost = MemberUtil.isSame(participant, hostId);
491
- } else if (MemberUtil.isDevice(participant)) {
492
- this.isHost = MemberUtil.isAssociatedSame(participant, hostId);
493
528
  }
494
529
  }
495
530
 
@@ -500,7 +535,7 @@ export default class Member {
500
535
  * @private
501
536
  * @memberof Member
502
537
  */
503
- private processRoles(participant: ParticipantWithRoles) {
538
+ private processRoles(participant: Participant) {
504
539
  this.roles = MemberUtil.extractControlRoles(participant);
505
540
  }
506
541
 
@@ -15,22 +15,6 @@ export type ServerRoleShape = {
15
15
  hasRole: boolean;
16
16
  };
17
17
 
18
- export type ParticipantWithRoles = {
19
- controls: {
20
- role: {
21
- roles: Array<ServerRoleShape>;
22
- };
23
- };
24
- };
25
-
26
- export type ParticipantWithBrb = {
27
- controls: {
28
- brb?: {
29
- enabled: boolean;
30
- };
31
- };
32
- };
33
-
34
18
  // values are inherited from locus so don't update these
35
19
  export enum MediaStatus {
36
20
  RECVONLY = 'RECVONLY', // participant only receiving and not sending
@@ -44,3 +28,85 @@ export interface IMediaStatus {
44
28
  audio: MediaStatus;
45
29
  video: MediaStatus;
46
30
  }
31
+
32
+ export type Csi = number;
33
+ export type Direction = 'inactive' | 'sendrecv' | 'sendonly' | 'recvonly';
34
+ export type ParticipantUrl = string;
35
+ export interface MediaSession {
36
+ csi: Csi;
37
+ direction: Direction;
38
+ mediaContent: 'main' | 'slides';
39
+ mediaType: 'audio' | 'video';
40
+ state: string;
41
+ }
42
+
43
+ export interface Intent {
44
+ associatedWith: ParticipantUrl;
45
+ id: string;
46
+ type: string; // could be "WAIT" or "OBSERVE" or other....
47
+ }
48
+ export interface ParticipantDevice {
49
+ correlationId: string;
50
+ csis: Csi[];
51
+ deviceType: string; // WDM device type, could be "WEB", "TP_ENDPOINT", "MAC" or other things, don't know the full list, so keeping it as string
52
+ intent?: Intent;
53
+ intents: Array<Intent | null>;
54
+ isVideoCallback: boolean;
55
+ mediaSessions: Array<MediaSession>;
56
+ mediaSessionsExternal: boolean;
57
+ state: string; // probably one of MEETING_STATE.STATES
58
+ }
59
+
60
+ // this is not a complete type, Locus may send more fields
61
+ export interface ParticipantPerson {
62
+ id: string;
63
+ isExternal: boolean;
64
+ name: string;
65
+ orgId: string;
66
+ }
67
+
68
+ export interface ParticipantMediaStatus {
69
+ audioStatus: MediaStatus;
70
+ videoStatus: MediaStatus;
71
+ audioSlidesStatus?: MediaStatus;
72
+ videoSlidesStatus?: MediaStatus;
73
+ csis: Csi[];
74
+ }
75
+
76
+ // this is not a complete type, Locus may send more fields
77
+ export interface ParticipantControls {
78
+ role: {
79
+ roles: Array<ServerRoleShape>;
80
+ };
81
+ brb?: {
82
+ enabled: boolean;
83
+ };
84
+ hand: {
85
+ raised: boolean;
86
+ };
87
+ localRecord: {
88
+ recording: boolean;
89
+ };
90
+ }
91
+
92
+ // this is not a complete type, Locus may send more fields
93
+ export interface Participant {
94
+ canBeController: boolean;
95
+ controls: ParticipantControls;
96
+ deviceUrl: string;
97
+ devices: Array<ParticipantDevice>;
98
+ guest: boolean;
99
+ id: string;
100
+ identity: string;
101
+ identityTrustLevel: string; // could be 'INTERNAL', 'EXTERNAL' or other....
102
+ isCreator: boolean;
103
+ moderator: boolean; // Locus docs say this is deprecated and role control should be used instead
104
+ moderatorAssignmentNotAllowed: boolean;
105
+ presenterAssignmentNotAllowed: boolean;
106
+ person: ParticipantPerson;
107
+ resourceGuest: boolean;
108
+ state: string; // probably one of MEETING_STATE.STATES
109
+ status: ParticipantMediaStatus;
110
+ type: string;
111
+ url: ParticipantUrl;
112
+ }