@webex/plugin-meetings 3.0.0-stream-classes.4 → 3.0.0-test.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 (239) hide show
  1. package/README.md +12 -0
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/common/errors/no-meeting-info.js +51 -0
  5. package/dist/common/errors/no-meeting-info.js.map +1 -0
  6. package/dist/common/errors/reclaim-host-role-errors.js +158 -0
  7. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
  8. package/dist/common/errors/webex-errors.js +23 -3
  9. package/dist/common/errors/webex-errors.js.map +1 -1
  10. package/dist/common/logs/request.js +5 -1
  11. package/dist/common/logs/request.js.map +1 -1
  12. package/dist/config.js +1 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/constants.js +69 -9
  15. package/dist/constants.js.map +1 -1
  16. package/dist/index.js +11 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/interceptors/index.js +15 -0
  19. package/dist/interceptors/index.js.map +1 -0
  20. package/dist/interceptors/locusRetry.js +93 -0
  21. package/dist/interceptors/locusRetry.js.map +1 -0
  22. package/dist/interpretation/index.js +16 -2
  23. package/dist/interpretation/index.js.map +1 -1
  24. package/dist/interpretation/siLanguage.js +1 -1
  25. package/dist/locus-info/index.js +40 -11
  26. package/dist/locus-info/index.js.map +1 -1
  27. package/dist/locus-info/mediaSharesUtils.js +15 -1
  28. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  29. package/dist/locus-info/parser.js +42 -21
  30. package/dist/locus-info/parser.js.map +1 -1
  31. package/dist/media/index.js +10 -6
  32. package/dist/media/index.js.map +1 -1
  33. package/dist/media/properties.js +13 -3
  34. package/dist/media/properties.js.map +1 -1
  35. package/dist/mediaQualityMetrics/config.js +135 -330
  36. package/dist/mediaQualityMetrics/config.js.map +1 -1
  37. package/dist/meeting/in-meeting-actions.js +4 -0
  38. package/dist/meeting/in-meeting-actions.js.map +1 -1
  39. package/dist/meeting/index.js +2187 -1074
  40. package/dist/meeting/index.js.map +1 -1
  41. package/dist/meeting/muteState.js +37 -25
  42. package/dist/meeting/muteState.js.map +1 -1
  43. package/dist/meeting/request.js +34 -19
  44. package/dist/meeting/request.js.map +1 -1
  45. package/dist/meeting/util.js +71 -0
  46. package/dist/meeting/util.js.map +1 -1
  47. package/dist/meeting-info/index.js +48 -23
  48. package/dist/meeting-info/index.js.map +1 -1
  49. package/dist/meeting-info/meeting-info-v2.js +25 -4
  50. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  51. package/dist/meeting-info/utilv2.js +1 -1
  52. package/dist/meeting-info/utilv2.js.map +1 -1
  53. package/dist/meetings/collection.js +17 -0
  54. package/dist/meetings/collection.js.map +1 -1
  55. package/dist/meetings/index.js +142 -57
  56. package/dist/meetings/index.js.map +1 -1
  57. package/dist/meetings/util.js +2 -6
  58. package/dist/meetings/util.js.map +1 -1
  59. package/dist/member/index.js +9 -0
  60. package/dist/member/index.js.map +1 -1
  61. package/dist/member/util.js +11 -0
  62. package/dist/member/util.js.map +1 -1
  63. package/dist/members/index.js +17 -1
  64. package/dist/members/index.js.map +1 -1
  65. package/dist/members/types.js.map +1 -1
  66. package/dist/members/util.js +15 -4
  67. package/dist/members/util.js.map +1 -1
  68. package/dist/metrics/constants.js +15 -1
  69. package/dist/metrics/constants.js.map +1 -1
  70. package/dist/multistream/mediaRequestManager.js +1 -1
  71. package/dist/multistream/mediaRequestManager.js.map +1 -1
  72. package/dist/multistream/remoteMediaGroup.js +16 -2
  73. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  74. package/dist/multistream/remoteMediaManager.js +222 -73
  75. package/dist/multistream/remoteMediaManager.js.map +1 -1
  76. package/dist/multistream/sendSlotManager.js +22 -0
  77. package/dist/multistream/sendSlotManager.js.map +1 -1
  78. package/dist/reachability/clusterReachability.js +356 -0
  79. package/dist/reachability/clusterReachability.js.map +1 -0
  80. package/dist/reachability/index.js +262 -432
  81. package/dist/reachability/index.js.map +1 -1
  82. package/dist/reachability/request.js +1 -1
  83. package/dist/reachability/request.js.map +1 -1
  84. package/dist/reachability/util.js +29 -0
  85. package/dist/reachability/util.js.map +1 -0
  86. package/dist/reconnection-manager/index.js +113 -96
  87. package/dist/reconnection-manager/index.js.map +1 -1
  88. package/dist/roap/index.js +57 -25
  89. package/dist/roap/index.js.map +1 -1
  90. package/dist/roap/request.js +5 -13
  91. package/dist/roap/request.js.map +1 -1
  92. package/dist/roap/turnDiscovery.js +173 -81
  93. package/dist/roap/turnDiscovery.js.map +1 -1
  94. package/dist/rtcMetrics/index.js +68 -6
  95. package/dist/rtcMetrics/index.js.map +1 -1
  96. package/dist/statsAnalyzer/index.js +338 -289
  97. package/dist/statsAnalyzer/index.js.map +1 -1
  98. package/dist/statsAnalyzer/mqaUtil.js +296 -156
  99. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  100. package/dist/types/common/errors/no-meeting-info.d.ts +14 -0
  101. package/dist/types/common/errors/reclaim-host-role-errors.d.ts +60 -0
  102. package/dist/types/common/errors/webex-errors.d.ts +13 -1
  103. package/dist/types/common/logs/request.d.ts +2 -0
  104. package/dist/types/config.d.ts +1 -1
  105. package/dist/types/constants.d.ts +66 -13
  106. package/dist/types/index.d.ts +1 -1
  107. package/dist/types/interceptors/index.d.ts +2 -0
  108. package/dist/types/interceptors/locusRetry.d.ts +27 -0
  109. package/dist/types/locus-info/index.d.ts +1 -1
  110. package/dist/types/locus-info/parser.d.ts +3 -2
  111. package/dist/types/mediaQualityMetrics/config.d.ts +99 -223
  112. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  113. package/dist/types/meeting/index.d.ts +285 -34
  114. package/dist/types/meeting/locusMediaRequest.d.ts +1 -2
  115. package/dist/types/meeting/muteState.d.ts +2 -8
  116. package/dist/types/meeting/request.d.ts +4 -1
  117. package/dist/types/meeting/util.d.ts +25 -1
  118. package/dist/types/meeting-info/index.d.ts +7 -0
  119. package/dist/types/meeting-info/meeting-info-v2.d.ts +1 -0
  120. package/dist/types/meetings/collection.d.ts +9 -0
  121. package/dist/types/meetings/index.d.ts +42 -14
  122. package/dist/types/member/index.d.ts +1 -0
  123. package/dist/types/members/types.d.ts +1 -0
  124. package/dist/types/members/util.d.ts +5 -0
  125. package/dist/types/metrics/constants.d.ts +15 -0
  126. package/dist/types/multistream/mediaRequestManager.d.ts +2 -0
  127. package/dist/types/multistream/remoteMediaGroup.d.ts +2 -0
  128. package/dist/types/multistream/remoteMediaManager.d.ts +25 -1
  129. package/dist/types/multistream/sendSlotManager.d.ts +9 -0
  130. package/dist/types/reachability/clusterReachability.d.ts +109 -0
  131. package/dist/types/reachability/index.d.ts +59 -112
  132. package/dist/types/reachability/request.d.ts +1 -1
  133. package/dist/types/reachability/util.d.ts +8 -0
  134. package/dist/types/reconnection-manager/index.d.ts +10 -0
  135. package/dist/types/roap/index.d.ts +2 -1
  136. package/dist/types/roap/request.d.ts +2 -1
  137. package/dist/types/roap/turnDiscovery.d.ts +21 -4
  138. package/dist/types/rtcMetrics/index.d.ts +15 -1
  139. package/dist/types/statsAnalyzer/index.d.ts +28 -11
  140. package/dist/types/statsAnalyzer/mqaUtil.d.ts +28 -4
  141. package/dist/types/webinar/collection.d.ts +16 -0
  142. package/dist/types/webinar/index.d.ts +5 -0
  143. package/dist/webinar/collection.js +44 -0
  144. package/dist/webinar/collection.js.map +1 -0
  145. package/dist/webinar/index.js +69 -0
  146. package/dist/webinar/index.js.map +1 -0
  147. package/package.json +3 -2
  148. package/src/common/errors/no-meeting-info.ts +24 -0
  149. package/src/common/errors/reclaim-host-role-errors.ts +134 -0
  150. package/src/common/errors/webex-errors.ts +19 -2
  151. package/src/common/logs/request.ts +5 -1
  152. package/src/config.ts +1 -1
  153. package/src/constants.ts +71 -6
  154. package/src/index.ts +5 -0
  155. package/src/interceptors/index.ts +3 -0
  156. package/src/interceptors/locusRetry.ts +67 -0
  157. package/src/interpretation/index.ts +18 -1
  158. package/src/locus-info/index.ts +52 -16
  159. package/src/locus-info/mediaSharesUtils.ts +16 -0
  160. package/src/locus-info/parser.ts +47 -21
  161. package/src/media/index.ts +8 -6
  162. package/src/media/properties.ts +17 -2
  163. package/src/mediaQualityMetrics/config.ts +103 -238
  164. package/src/meeting/in-meeting-actions.ts +8 -0
  165. package/src/meeting/index.ts +1510 -529
  166. package/src/meeting/muteState.ts +34 -20
  167. package/src/meeting/request.ts +19 -1
  168. package/src/meeting/util.ts +97 -0
  169. package/src/meeting-info/index.ts +47 -20
  170. package/src/meeting-info/meeting-info-v2.ts +27 -5
  171. package/src/meeting-info/utilv2.ts +1 -1
  172. package/src/meetings/collection.ts +13 -0
  173. package/src/meetings/index.ts +112 -31
  174. package/src/meetings/util.ts +2 -8
  175. package/src/member/index.ts +9 -0
  176. package/src/member/util.ts +14 -0
  177. package/src/members/index.ts +29 -2
  178. package/src/members/types.ts +1 -0
  179. package/src/members/util.ts +15 -1
  180. package/src/metrics/constants.ts +14 -0
  181. package/src/multistream/mediaRequestManager.ts +4 -1
  182. package/src/multistream/remoteMediaGroup.ts +19 -0
  183. package/src/multistream/remoteMediaManager.ts +141 -18
  184. package/src/multistream/sendSlotManager.ts +29 -0
  185. package/src/reachability/clusterReachability.ts +320 -0
  186. package/src/reachability/index.ts +221 -382
  187. package/src/reachability/request.ts +1 -1
  188. package/src/reachability/util.ts +24 -0
  189. package/src/reconnection-manager/index.ts +87 -83
  190. package/src/roap/index.ts +60 -24
  191. package/src/roap/request.ts +3 -16
  192. package/src/roap/turnDiscovery.ts +112 -39
  193. package/src/rtcMetrics/index.ts +71 -5
  194. package/src/statsAnalyzer/index.ts +430 -427
  195. package/src/statsAnalyzer/mqaUtil.ts +317 -168
  196. package/src/webinar/collection.ts +31 -0
  197. package/src/webinar/index.ts +62 -0
  198. package/test/integration/spec/converged-space-meetings.js +7 -7
  199. package/test/integration/spec/journey.js +86 -104
  200. package/test/integration/spec/space-meeting.js +9 -9
  201. package/test/unit/spec/interceptors/locusRetry.ts +131 -0
  202. package/test/unit/spec/interpretation/index.ts +36 -3
  203. package/test/unit/spec/locus-info/index.js +205 -12
  204. package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
  205. package/test/unit/spec/locus-info/mediaSharesUtils.ts +10 -0
  206. package/test/unit/spec/locus-info/parser.js +54 -13
  207. package/test/unit/spec/media/index.ts +20 -4
  208. package/test/unit/spec/media/properties.ts +2 -2
  209. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  210. package/test/unit/spec/meeting/index.js +4027 -1075
  211. package/test/unit/spec/meeting/muteState.js +219 -67
  212. package/test/unit/spec/meeting/request.js +63 -12
  213. package/test/unit/spec/meeting/utils.js +93 -0
  214. package/test/unit/spec/meeting-info/index.js +180 -61
  215. package/test/unit/spec/meeting-info/meetinginfov2.js +196 -53
  216. package/test/unit/spec/meetings/collection.js +12 -0
  217. package/test/unit/spec/meetings/index.js +619 -206
  218. package/test/unit/spec/meetings/utils.js +35 -12
  219. package/test/unit/spec/member/index.js +8 -7
  220. package/test/unit/spec/member/util.js +32 -0
  221. package/test/unit/spec/members/index.js +130 -17
  222. package/test/unit/spec/members/utils.js +26 -0
  223. package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
  224. package/test/unit/spec/multistream/remoteMediaGroup.ts +80 -1
  225. package/test/unit/spec/multistream/remoteMediaManager.ts +210 -3
  226. package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
  227. package/test/unit/spec/reachability/clusterReachability.ts +279 -0
  228. package/test/unit/spec/reachability/index.ts +505 -135
  229. package/test/unit/spec/reachability/util.ts +40 -0
  230. package/test/unit/spec/reconnection-manager/index.js +74 -17
  231. package/test/unit/spec/roap/index.ts +181 -61
  232. package/test/unit/spec/roap/request.ts +27 -3
  233. package/test/unit/spec/roap/turnDiscovery.ts +362 -101
  234. package/test/unit/spec/rtcMetrics/index.ts +57 -3
  235. package/test/unit/spec/stats-analyzer/index.js +1225 -12
  236. package/test/unit/spec/webinar/collection.ts +13 -0
  237. package/test/unit/spec/webinar/index.ts +60 -0
  238. package/test/utils/integrationTestUtils.js +4 -4
  239. package/test/utils/webex-test-users.js +12 -4
package/src/config.ts CHANGED
@@ -88,7 +88,7 @@ export default {
88
88
  enableMediaNegotiatedEvent: false,
89
89
  enableUnifiedMeetings: true,
90
90
  enableAdhocMeetings: true,
91
- enableTurnDiscovery: true,
91
+ enableTcpReachability: false,
92
92
  },
93
93
  degradationPreferences: {
94
94
  maxMacroblocksLimit: 8192,
package/src/constants.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // @ts-ignore
2
2
  import {hydraTypes} from '@webex/common';
3
3
 
4
+ type Enum<T extends Record<string, unknown>> = T[keyof T];
5
+
4
6
  // *********** LOWERCASE / CAMELCASE STRINGS ************
5
7
 
6
8
  export const AUDIO = 'audio';
@@ -190,7 +192,8 @@ export const ICE_FAIL_TIMEOUT = 3000;
190
192
 
191
193
  export const RETRY_TIMEOUT = 3000;
192
194
 
193
- export const PC_BAIL_TIMEOUT = 15000;
195
+ export const ICE_AND_DTLS_CONNECTION_TIMEOUT = 10000;
196
+ export const ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT = 35000;
194
197
 
195
198
  // ******************** REGEX **********************
196
199
  // Please alphabetize
@@ -236,6 +239,13 @@ export const CALENDAR_EVENTS = {
236
239
  DELETE: 'event:calendar.meeting.delete',
237
240
  };
238
241
 
242
+ export const ASSIGN_ROLES_ERROR_CODES = {
243
+ ReclaimHostNotSupportedErrorCode: 2400127,
244
+ ReclaimHostNotAllowedErrorCode: 2403135,
245
+ ReclaimHostEmptyWrongKeyErrorCode: 2403136,
246
+ ReclaimHostIsHostAlreadyErrorCode: 2409150,
247
+ };
248
+
239
249
  export const DEFAULT_GET_STATS_FILTER = {
240
250
  types: [
241
251
  'track',
@@ -331,6 +341,7 @@ export const EVENT_TRIGGERS = {
331
341
  MEETING_UNLOCKED: 'meeting:unlocked',
332
342
  MEETING_LOCKED: 'meeting:locked',
333
343
  MEETING_INFO_AVAILABLE: 'meeting:meetingInfoAvailable',
344
+ MEETING_INFO_UPDATED: 'meeting:meetingInfoUpdated',
334
345
  MEETING_LOG_UPLOAD_SUCCESS: 'meeting:logUpload:success',
335
346
  MEETING_LOG_UPLOAD_FAILURE: 'meeting:logUpload:failure',
336
347
  MEETING_ACTIONS_UPDATE: 'meeting:actionsUpdate',
@@ -348,6 +359,7 @@ export const EVENT_TRIGGERS = {
348
359
  REMOTE_VIDEO_SOURCE_COUNT_CHANGED: 'media:remoteVideoSourceCountChanged',
349
360
  REMOTE_AUDIO_SOURCE_COUNT_CHANGED: 'media:remoteAudioSourceCountChanged',
350
361
  REMOTE_MEDIA_AUDIO_CREATED: 'media:remoteAudio:created',
362
+ REMOTE_MEDIA_INTERPRETATION_AUDIO_CREATED: 'media:remoteInterpretationAudio:created',
351
363
  REMOTE_MEDIA_SCREEN_SHARE_AUDIO_CREATED: 'media:remoteScreenShareAudio:created',
352
364
  REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED: 'media:remoteVideo:layoutChanged',
353
365
  // Controls
@@ -388,6 +400,8 @@ export const MEETING_REMOVED_REASON = {
388
400
  USER_ENDED_SHARE_STREAMS: 'USER_ENDED_SHARE_STREAMS', // user triggered stop share
389
401
  NO_MEETINGS_TO_SYNC: 'NO_MEETINGS_TO_SYNC', // After the syncMeeting no meeting exists
390
402
  MEETING_CONNECTION_FAILED: 'MEETING_CONNECTION_FAILED', // meeting failed to connect due to ice failures or firewall issue
403
+ LOCUS_DTO_SYNC_FAILED: 'LOCUS_DTO_SYNC_FAILED', // failed to get any Locus DTO for that meeting
404
+ MISSING_MEETING_INFO: 'MISSING_MEETING_INFO', // meeting info failed to be fetched
391
405
  };
392
406
 
393
407
  // One one one calls ends for the following reasons
@@ -484,6 +498,35 @@ export const ERROR_DICTIONARY = {
484
498
  MESSAGE: 'Edit lock token mismatch',
485
499
  CODE: 9,
486
500
  },
501
+ NO_MEETING_INFO: {
502
+ NAME: 'NoMeetingInfo',
503
+ MESSAGE: 'No meeting info found for the meeting',
504
+ CODE: 10,
505
+ },
506
+ RECLAIM_HOST_ROLE_NOT_SUPPORTED: {
507
+ NAME: 'ReclaimHostRoleNotSupported',
508
+ MESSAGE:
509
+ 'Non converged meetings, PSTN or SIP users in converged meetings are not supported currently.',
510
+ CODE: 11,
511
+ },
512
+ RECLAIM_HOST_ROLE_NOT_ALLOWED: {
513
+ NAME: 'ReclaimHostRoleNotAllowed',
514
+ MESSAGE:
515
+ 'Reclaim Host Role Not Allowed For Other Participants. Participants cannot claim host role in PMR meeting, space instant meeting or escalated instant meeting. However, the original host still can reclaim host role when it manually makes another participant to be the host.',
516
+ CODE: 12,
517
+ },
518
+ RECLAIM_HOST_ROLE_EMPTY_OR_WRONG_KEY: {
519
+ NAME: 'ReclaimHostRoleEmptyOrWrongKey',
520
+ MESSAGE:
521
+ 'Host Key Not Specified Or Matched. The original host can reclaim the host role without entering the host key. However, any other person who claims the host role must enter the host key to get it.',
522
+ CODE: 13,
523
+ },
524
+ RECLAIM_HOST_ROLE_IS_ALREADY_HOST: {
525
+ NAME: 'ReclaimHostRoleIsAlreadyHost',
526
+ MESSAGE:
527
+ 'Participant Having Host Role Already. Participant who sends request to reclaim host role has already a host role.',
528
+ CODE: 14,
529
+ },
487
530
  };
488
531
 
489
532
  export const FLOOR_ACTION = {
@@ -539,6 +582,8 @@ export const LOCUS = {
539
582
  INACTIVE: 'INACTIVE',
540
583
  ENDED: 'ENDED',
541
584
  INITIALIZING: 'INITIALIZING',
585
+ ACTIVE: 'ACTIVE',
586
+ TERMINATING: 'TERMINATING',
542
587
  },
543
588
  SEQUENCE: {
544
589
  UN_DEF: 'undef',
@@ -622,7 +667,6 @@ export const LOCUSINFO = {
622
667
  CONTROLS_MEETING_CONTAINER_UPDATED: 'CONTROLS_MEETING_CONTAINER_UPDATED',
623
668
  CONTROLS_MEETING_INTERPRETATION_UPDATED: 'CONTROLS_MEETING_INTERPRETATION_UPDATED',
624
669
  CONTROLS_ENTRY_EXIT_TONE_UPDATED: 'CONTROLS_ENTRY_EXIT_TONE_UPDATED',
625
- CONTROLS_JOIN_BREAKOUT_FROM_MAIN: 'CONTROLS_JOIN_BREAKOUT_FROM_MAIN',
626
670
  CONTROLS_MUTE_ON_ENTRY_CHANGED: 'CONTROLS_MUTE_ON_ENTRY_CHANGED',
627
671
  CONTROLS_SHARE_CONTROL_CHANGED: 'CONTROLS_SHARE_CONTROL_CHANGED',
628
672
  CONTROLS_DISALLOW_UNMUTE_CHANGED: 'CONTROLS_DISALLOW_UNMUTE_CHANGED',
@@ -995,7 +1039,9 @@ export const RECONNECTION = {
995
1039
  DEFAULT_TRY_COUNT: 0,
996
1040
  DEFAULT_STATUS: '',
997
1041
  },
998
- };
1042
+ } as const;
1043
+
1044
+ export type RECONNECTION_STATE = Enum<typeof RECONNECTION.STATE>;
999
1045
 
1000
1046
  export const RESOURCE = {
1001
1047
  CLUSTERS: 'clusters',
@@ -1031,7 +1077,9 @@ export const NETWORK_STATUS = {
1031
1077
  DISCONNECTED: 'DISCONNECTED',
1032
1078
  RECONNECTING: 'RECONNECTING',
1033
1079
  CONNECTED: 'CONNECTED',
1034
- };
1080
+ } as const;
1081
+
1082
+ export type NETWORK_STATUS = Enum<typeof NETWORK_STATUS>;
1035
1083
 
1036
1084
  export const NETWORK_TYPE = {
1037
1085
  VPN: 'vpn',
@@ -1190,7 +1238,7 @@ export const AVAILABLE_RESOLUTIONS = {
1190
1238
  * mqa Interval for sending stats metrics
1191
1239
  */
1192
1240
 
1193
- export const MQA_INTEVAL = 60000; // mqa analyzer interval its fixed to 60000
1241
+ export const MQA_INTERVAL = 60000; // mqa analyzer interval its fixed to 60000
1194
1242
 
1195
1243
  export const MEDIA_DEVICES = {
1196
1244
  MICROPHONE: 'microphone',
@@ -1247,4 +1295,21 @@ export const IP_VERSION = {
1247
1295
  ipv4_and_ipv6: 1,
1248
1296
  } as const;
1249
1297
 
1250
- export type IP_VERSION = (typeof IP_VERSION)[keyof typeof IP_VERSION];
1298
+ export const LOCAL_SHARE_ERRORS = {
1299
+ DEVICE_NOT_JOINED: 'Floor requested but device not yet joined',
1300
+ UNDEFINED: 'undefined PUT',
1301
+ NO_MEDIA_FOR_DEVICE: 'No media session found for device',
1302
+ NO_CONFLUENCE_ID: 'Failed to request floor: 404 (null) could not find confluence id',
1303
+ CONTENT_SHARING_DISABLED: 'Content share policies are disabled',
1304
+ LOCUS_PARTICIPANT_DNE: 'does not exist in Locus',
1305
+ CONTENT_REQUEST_WHILE_PENDING_WHITEBOARD:
1306
+ 'Content share request is not allowed while whiteboard floor request is pending',
1307
+ };
1308
+ export type IP_VERSION = Enum<typeof IP_VERSION>;
1309
+
1310
+ // constant for if the permissionToken is about to expire in the next 30 seconds, refresh it
1311
+ export const MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC = 30;
1312
+ export const MEETING_PERMISSION_TOKEN_REFRESH_REASON = 'ttl-join';
1313
+
1314
+ // constant for named media group type
1315
+ export const NAMED_MEDIA_GROUP_TYPE_AUDIO = 1;
package/src/index.ts CHANGED
@@ -3,9 +3,13 @@ import {registerPlugin} from '@webex/webex-core';
3
3
 
4
4
  import Meetings from './meetings';
5
5
  import config from './config';
6
+ import {LocusRetryStatusInterceptor} from './interceptors';
6
7
 
7
8
  registerPlugin('meetings', Meetings, {
8
9
  config,
10
+ interceptors: {
11
+ LocusRetryStatusInterceptor: LocusRetryStatusInterceptor.create,
12
+ },
9
13
  });
10
14
 
11
15
  export {
@@ -15,6 +19,7 @@ export {
15
19
  LocalSystemAudioStream,
16
20
  LocalStreamEventNames,
17
21
  StreamEventNames,
22
+ RemoteStreamEventNames,
18
23
  type ServerMuteReason,
19
24
  LocalMicrophoneStreamEventNames,
20
25
  LocalCameraStreamEventNames,
@@ -0,0 +1,3 @@
1
+ import LocusRetryStatusInterceptor from './locusRetry';
2
+
3
+ export {LocusRetryStatusInterceptor};
@@ -0,0 +1,67 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {Interceptor} from '@webex/http-core';
6
+
7
+ const rateLimitExpiryTime = new WeakMap();
8
+ /**
9
+ * @class
10
+ */
11
+ export default class LocusRetryStatusInterceptor extends Interceptor {
12
+ /**
13
+ * @returns {LocusRetryStatusInterceptor}
14
+ */
15
+ static create() {
16
+ // @ts-ignore
17
+ return new LocusRetryStatusInterceptor({webex: this});
18
+ }
19
+
20
+ /**
21
+ * Handle response errors
22
+ * @param {Object} options
23
+ * @param {WebexHttpError} reason
24
+ * @returns {Promise<WebexHttpError>}
25
+ */
26
+ onResponseError(options, reason) {
27
+ if ((reason.statusCode === 503 || reason.statusCode === 429) && options.uri.includes('locus')) {
28
+ const hasRetriedLocusRequest = rateLimitExpiryTime.get(this);
29
+ const retryAfterTime = options.headers['retry-after'] || 2000;
30
+
31
+ if (hasRetriedLocusRequest) {
32
+ rateLimitExpiryTime.set(this, false);
33
+
34
+ return Promise.reject(options);
35
+ }
36
+ rateLimitExpiryTime.set(this, true);
37
+
38
+ return this.handleRetryRequestLocusServiceError(options, retryAfterTime);
39
+ }
40
+
41
+ return Promise.reject(reason);
42
+ }
43
+
44
+ /**
45
+ * Handle retries for locus service unavailable errors
46
+ * @param {Object} options associated with the request
47
+ * @param {number} retryAfterTime retry after time in milliseconds
48
+ * @returns {Promise}
49
+ */
50
+ handleRetryRequestLocusServiceError(options, retryAfterTime) {
51
+ return new Promise((resolve, reject) => {
52
+ const timeout = setTimeout(() => {
53
+ clearTimeout(timeout);
54
+
55
+ // @ts-ignore
56
+ this.webex
57
+ .request({
58
+ method: options.method,
59
+ uri: options.uri,
60
+ body: options.body,
61
+ })
62
+ .then(resolve)
63
+ .catch(reject);
64
+ }, retryAfterTime);
65
+ });
66
+ }
67
+ }
@@ -121,14 +121,31 @@ const SimultaneousInterpretation = WebexPlugin.extend({
121
121
  * Update self's interpretation information (self is interpreter)
122
122
  * @param {Object} interpretation
123
123
  * @param {String} selfParticipantId
124
- * @returns {void}
124
+ * @returns {bool} is target language changed
125
125
  */
126
126
  updateSelfInterpretation({interpretation, selfParticipantId}) {
127
+ this.set('selfIsInterpreter', true);
128
+ const preTargetLanguage = this.targetLanguage;
127
129
  const {originalLanguage, sourceLanguage, order, isActive, targetLanguage, receiveLanguage} =
128
130
  interpretation || {};
129
131
  this.set({originalLanguage, sourceLanguage, order, isActive, targetLanguage, receiveLanguage});
130
132
  this.set('selfParticipantId', selfParticipantId);
133
+
134
+ return !!(targetLanguage && preTargetLanguage !== targetLanguage);
131
135
  },
136
+
137
+ /**
138
+ * Get the language code of the interpreter target language
139
+ * @returns {number}
140
+ */
141
+ getTargetLanguageCode() {
142
+ if (this.selfIsInterpreter) {
143
+ return this.siLanguages.get(this.targetLanguage)?.languageCode;
144
+ }
145
+
146
+ return 0;
147
+ },
148
+
132
149
  /**
133
150
  * query interpretation languages
134
151
  * @returns {Promise}
@@ -25,6 +25,8 @@ import ControlsUtils from './controlsUtils';
25
25
  import EmbeddedAppsUtils from './embeddedAppsUtils';
26
26
  import MediaSharesUtils from './mediaSharesUtils';
27
27
  import LocusDeltaParser from './parser';
28
+ import Metrics from '../metrics';
29
+ import BEHAVIORAL_METRICS from '../metrics/constants';
28
30
 
29
31
  /**
30
32
  * @description LocusInfo extends ChildEmitter to convert locusInfo info a private emitter to parent object
@@ -110,6 +112,37 @@ export default class LocusInfo extends EventsScope {
110
112
  // return value ignored on purpose
111
113
  meeting.meetingRequest
112
114
  .getLocusDTO({url})
115
+ .catch((e) => {
116
+ if (isDelta) {
117
+ LoggerProxy.logger.info(
118
+ 'Locus-info:index#doLocusSync --> delta sync failed, falling back to full sync'
119
+ );
120
+
121
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LOCUS_DELTA_SYNC_FAILED, {
122
+ correlationId: meeting.correlationId,
123
+ url,
124
+ reason: e.message,
125
+ errorName: e.name,
126
+ stack: e.stack,
127
+ code: e.code,
128
+ });
129
+
130
+ isDelta = false;
131
+
132
+ return meeting.meetingRequest.getLocusDTO({url: meeting.locusUrl}).catch((err) => {
133
+ LoggerProxy.logger.info(
134
+ 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
135
+ );
136
+ this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
137
+ throw err;
138
+ });
139
+ }
140
+ LoggerProxy.logger.info(
141
+ 'Locus-info:index#doLocusSync --> fallback full sync failed, destroying the meeting'
142
+ );
143
+ this.webex.meetings.destroy(meeting, MEETING_REMOVED_REASON.LOCUS_DTO_SYNC_FAILED);
144
+ throw e;
145
+ })
113
146
  .then((res) => {
114
147
  if (isDelta) {
115
148
  if (!isEmpty(res.body)) {
@@ -122,8 +155,6 @@ export default class LocusInfo extends EventsScope {
122
155
  } else {
123
156
  meeting.locusInfo.onFullLocus(res.body);
124
157
  }
125
- })
126
- .finally(() => {
127
158
  // Notify parser to resume processing delta events.
128
159
  // Any deltas in the queue that have now been superseded by this sync will simply be ignored
129
160
  this.locusParser.resume();
@@ -138,7 +169,7 @@ export default class LocusInfo extends EventsScope {
138
169
  * @returns {undefined}
139
170
  */
140
171
  applyLocusDeltaData(action: string, locus: any, meeting: any) {
141
- const {DESYNC, USE_CURRENT, USE_INCOMING, WAIT} = LocusDeltaParser.loci;
172
+ const {DESYNC, USE_CURRENT, USE_INCOMING, WAIT, LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
142
173
 
143
174
  switch (action) {
144
175
  case USE_INCOMING:
@@ -149,6 +180,7 @@ export default class LocusInfo extends EventsScope {
149
180
  // do nothing
150
181
  break;
151
182
  case DESYNC:
183
+ case LOCUS_URL_CHANGED:
152
184
  this.doLocusSync(meeting);
153
185
  break;
154
186
  default:
@@ -1137,7 +1169,10 @@ export default class LocusInfo extends EventsScope {
1137
1169
  file: 'locus-info',
1138
1170
  function: 'updateMeetingInfo',
1139
1171
  },
1140
- LOCUSINFO.EVENTS.MEETING_INFO_UPDATED
1172
+ LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
1173
+ {
1174
+ isInitializing: !self, // if self is undefined, then the update is caused by locus init
1175
+ }
1141
1176
  );
1142
1177
  }
1143
1178
  this.roles = roles;
@@ -1572,7 +1607,7 @@ export default class LocusInfo extends EventsScope {
1572
1607
 
1573
1608
  /**
1574
1609
  * if return from breakout to main session, need to use cached main session DTO since locus won't send the full locus (participants)
1575
- * if join breakout from main session, need to query main locus url (if response with 403 means no privilege, need to clear the cache)
1610
+ * if join breakout from main session, main session is not active for the attendee and remove main session locus cache
1576
1611
  * @param {Object} newLocus
1577
1612
  * @returns {Object}
1578
1613
  * @memberof LocusInfo
@@ -1582,17 +1617,18 @@ export default class LocusInfo extends EventsScope {
1582
1617
  if (switchStatus.isReturnToMain && this.mainSessionLocusCache) {
1583
1618
  return cloneDeep(this.mainSessionLocusCache);
1584
1619
  }
1585
- if (switchStatus.isJoinToBreakout) {
1586
- this.emitScoped(
1587
- {
1588
- file: 'locus-info',
1589
- function: 'updateControls',
1590
- },
1591
- LOCUSINFO.EVENTS.CONTROLS_JOIN_BREAKOUT_FROM_MAIN,
1592
- {
1593
- mainLocusUrl: this.url,
1594
- }
1595
- );
1620
+ const isMainSessionDTO =
1621
+ this.mainSessionLocusCache && ControlsUtils.isMainSessionDTO(this.mainSessionLocusCache);
1622
+
1623
+ if (isMainSessionDTO) {
1624
+ const isActive =
1625
+ [LOCUS.STATE.ACTIVE, LOCUS.STATE.INITIALIZING, LOCUS.STATE.TERMINATING].includes(
1626
+ this.fullState?.state
1627
+ ) && !this.mainSessionLocusCache?.self?.removed;
1628
+
1629
+ if (!isActive) {
1630
+ this.clearMainSessionLocusCache();
1631
+ }
1596
1632
  }
1597
1633
 
1598
1634
  return newLocus;
@@ -16,6 +16,7 @@ MediaSharesUtils.parse = (mediaShares: object) => {
16
16
  annotation: MediaSharesUtils.getContentAnnotation(mediaShares),
17
17
  url: MediaSharesUtils.getContentUrl(mediaShares),
18
18
  shareInstanceId: MediaSharesUtils.getShareInstanceId(mediaShares),
19
+ deviceUrlSharing: MediaSharesUtils.getContentBeneficiaryDeviceUrl(mediaShares),
19
20
  },
20
21
  whiteboard: {
21
22
  beneficiaryId: MediaSharesUtils.getWhiteboardBeneficiaryId(mediaShares),
@@ -188,6 +189,21 @@ MediaSharesUtils.getShareInstanceId = (mediaShares: object) => {
188
189
  return extractContent.floor.shareInstanceId;
189
190
  };
190
191
 
192
+ /**
193
+ * get deviceUrl that is requesting the floor for media shares (content)
194
+ * @param {Object} mediaShares
195
+ * @returns {Object}
196
+ */
197
+ MediaSharesUtils.getContentBeneficiaryDeviceUrl = (mediaShares: object) => {
198
+ const contentFloor = MediaSharesUtils.extractContentFloor(mediaShares);
199
+
200
+ if (!contentFloor || !contentFloor.beneficiary || !contentFloor.beneficiary.deviceUrl) {
201
+ return null;
202
+ }
203
+
204
+ return contentFloor.beneficiary.deviceUrl;
205
+ };
206
+
191
207
  /**
192
208
  * get who is sharing from media shares (whiteboard)
193
209
  * @param {Object} mediaShares
@@ -3,11 +3,15 @@ import {difference} from 'lodash';
3
3
  import SortedQueue from '../common/queue';
4
4
  import LoggerProxy from '../common/logs/logger-proxy';
5
5
 
6
+ import Metrics from '../metrics';
7
+ import BEHAVIORAL_METRICS from '../metrics/constants';
8
+
6
9
  const MAX_OOO_DELTA_COUNT = 5; // when we receive an out-of-order delta and the queue builds up to MAX_OOO_DELTA_COUNT, we do a sync with Locus
7
10
  const OOO_DELTA_WAIT_TIME = 10000; // [ms] minimum wait time before we do a sync if we get out-of-order deltas
8
11
  const OOO_DELTA_WAIT_TIME_RANDOM_DELAY = 5000; // [ms] max random delay added to OOO_DELTA_WAIT_TIME
9
12
 
10
13
  type LocusDeltaDto = {
14
+ url: string;
11
15
  baseSequence: {
12
16
  rangeStart: number;
13
17
  rangeEnd: number;
@@ -44,11 +48,12 @@ export default class Parser {
44
48
  USE_CURRENT: 'USE_CURRENT',
45
49
  WAIT: 'WAIT',
46
50
  ERROR: 'ERROR',
51
+ LOCUS_URL_CHANGED: 'LOCUS_URL_CHANGED',
47
52
  };
48
53
 
49
54
  queue: SortedQueue<LocusDeltaDto>;
50
55
  workingCopy: any;
51
- syncTimer: null | number | NodeJS.Timeout;
56
+ syncTimer?: ReturnType<typeof setTimeout>;
52
57
 
53
58
  /**
54
59
  * @constructs Parser
@@ -80,7 +85,7 @@ export default class Parser {
80
85
  this.status = 'IDLE';
81
86
  this.onDeltaAction = null;
82
87
  this.workingCopy = null;
83
- this.syncTimer = null;
88
+ this.syncTimer = undefined;
84
89
  }
85
90
 
86
91
  /**
@@ -264,7 +269,7 @@ export default class Parser {
264
269
  * @returns {string} loci comparison state
265
270
  */
266
271
  private static compareDelta(current, incoming) {
267
- const {LT, GT, EQ, DESYNC, USE_INCOMING, WAIT} = Parser.loci;
272
+ const {LT, GT, EQ, DESYNC, USE_INCOMING, WAIT, LOCUS_URL_CHANGED} = Parser.loci;
268
273
 
269
274
  const {extractComparisonState: extract} = Parser;
270
275
  const {packComparisonResult: pack} = Parser;
@@ -276,6 +281,13 @@ export default class Parser {
276
281
  return pack(Parser.compareToAction(comparison), result);
277
282
  }
278
283
 
284
+ if (incoming.url !== current.url) {
285
+ // when moving to/from a breakout session, the locus URL will change and also
286
+ // the baseSequence, making incoming and current incomparable, so use a
287
+ // unique comparison state
288
+ return pack(LOCUS_URL_CHANGED, result);
289
+ }
290
+
279
291
  comparison = Parser.compareSequence(current.sequence, incoming.baseSequence);
280
292
 
281
293
  switch (extract(comparison)) {
@@ -293,6 +305,10 @@ export default class Parser {
293
305
  // the incoming locus has baseSequence from the future, so it is out-of-order,
294
306
  // we are missing 1 or more locus that should be in front of it, we need to wait for it
295
307
  comparison = WAIT;
308
+
309
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LOCUS_DELTA_OUT_OF_ORDER, {
310
+ stack: new Error().stack,
311
+ });
296
312
  }
297
313
  break;
298
314
  default:
@@ -657,11 +673,11 @@ export default class Parser {
657
673
  * @returns {undefined}
658
674
  */
659
675
  private startSyncTimer() {
660
- if (this.syncTimer === null) {
676
+ if (this.syncTimer === undefined) {
661
677
  const timeout = OOO_DELTA_WAIT_TIME + Math.random() * OOO_DELTA_WAIT_TIME_RANDOM_DELAY;
662
678
 
663
679
  this.syncTimer = setTimeout(() => {
664
- this.syncTimer = null;
680
+ this.syncTimer = undefined;
665
681
  this.triggerSync('timer expired, blocked on out-of-order delta');
666
682
  }, timeout);
667
683
  }
@@ -673,9 +689,9 @@ export default class Parser {
673
689
  * @returns {undefined}
674
690
  */
675
691
  private stopSyncTimer() {
676
- if (this.syncTimer !== null) {
692
+ if (this.syncTimer !== undefined) {
677
693
  clearTimeout(this.syncTimer);
678
- this.syncTimer = null;
694
+ this.syncTimer = undefined;
679
695
  }
680
696
  }
681
697
 
@@ -686,7 +702,7 @@ export default class Parser {
686
702
  * @returns {undefined}
687
703
  */
688
704
  processDeltaEvent() {
689
- const {DESYNC, USE_INCOMING, WAIT} = Parser.loci;
705
+ const {DESYNC, USE_INCOMING, WAIT, LOCUS_URL_CHANGED} = Parser.loci;
690
706
  const {extractComparisonState: extract} = Parser;
691
707
  const newLoci = this.queue.dequeue();
692
708
 
@@ -705,19 +721,29 @@ export default class Parser {
705
721
 
706
722
  let needToWait = false;
707
723
 
708
- if (lociComparison === DESYNC) {
709
- // wait for desync response
710
- this.pause();
711
- } else if (lociComparison === USE_INCOMING) {
712
- // update working copy for future comparisons.
713
- // Note: The working copy of parser gets updated in .onFullLocus()
714
- // and here when USE_INCOMING locus.
715
- this.workingCopy = newLoci;
716
- } else if (lociComparison === WAIT) {
717
- // we've taken newLoci from the front of the queue, so put it back there as we have to wait
718
- // for the one that should be in front of it, before we can process it
719
- this.queue.enqueue(newLoci);
720
- needToWait = true;
724
+ switch (lociComparison) {
725
+ case DESYNC:
726
+ // wait for desync response
727
+ this.pause();
728
+ break;
729
+
730
+ case USE_INCOMING:
731
+ case LOCUS_URL_CHANGED:
732
+ // update working copy for future comparisons.
733
+ // Note: The working copy of parser gets updated in .onFullLocus()
734
+ // and here when USE_INCOMING or LOCUS_URL_CHANGED locus.
735
+ this.workingCopy = newLoci;
736
+ break;
737
+
738
+ case WAIT:
739
+ // we've taken newLoci from the front of the queue, so put it back there as we have to wait
740
+ // for the one that should be in front of it, before we can process it
741
+ this.queue.enqueue(newLoci);
742
+ needToWait = true;
743
+ break;
744
+
745
+ default:
746
+ break;
721
747
  }
722
748
 
723
749
  if (needToWait) {
@@ -182,8 +182,10 @@ Media.createMediaConnection = (
182
182
  return new MultistreamRoapMediaConnection(
183
183
  config,
184
184
  meetingId,
185
+ /* the rtc metrics objects callbacks */
185
186
  (data) => rtcMetrics.addMetrics(data),
186
- () => rtcMetrics.closeMetrics()
187
+ () => rtcMetrics.closeMetrics(),
188
+ () => rtcMetrics.sendMetricsInQueue()
187
189
  );
188
190
  }
189
191
 
@@ -215,10 +217,10 @@ Media.createMediaConnection = (
215
217
  {
216
218
  // TODO: RoapMediaConnection is not ready to use stream classes yet, so we pass the raw MediaStreamTrack for now SPARK-460530
217
219
  localTracks: {
218
- audio: audioStream?.outputTrack,
219
- video: videoStream?.outputTrack,
220
- screenShareVideo: shareVideoStream?.outputTrack,
221
- screenShareAudio: shareAudioStream?.outputTrack, // TODO: add type for screenShareAudio in internal-media-core SPARK-446923
220
+ audio: audioStream?.outputStream?.getTracks()[0],
221
+ video: videoStream?.outputStream?.getTracks()[0],
222
+ screenShareVideo: shareVideoStream?.outputStream?.getTracks()[0],
223
+ screenShareAudio: shareAudioStream?.outputStream?.getTracks()[0], // TODO: add type for screenShareAudio in internal-media-core SPARK-446923
222
224
  } as unknown,
223
225
  direction: {
224
226
  audio: Media.getDirection(true, mediaDirection.receiveAudio, mediaDirection.sendAudio),
@@ -397,7 +399,7 @@ Media.stopStream = (stream: LocalStream) => {
397
399
  stream.stop();
398
400
  } catch (e) {
399
401
  LoggerProxy.logger.error(
400
- `Media:index#stopStream --> Unable to stop the stream with ready state of the output track => ${stream.outputTrack.readyState} & input track => ${stream.inputTrack.readyState}, error: ${e}`
402
+ `Media:index#stopStream --> Unable to stop the stream with ready state => ${stream.readyState}, error: ${e}`
401
403
  );
402
404
  }
403
405
  });
@@ -8,7 +8,7 @@ import {
8
8
  RemoteStream,
9
9
  } from '@webex/media-helpers';
10
10
 
11
- import {MEETINGS, PC_BAIL_TIMEOUT, QUALITY_LEVELS} from '../constants';
11
+ import {MEETINGS, ICE_AND_DTLS_CONNECTION_TIMEOUT, QUALITY_LEVELS} from '../constants';
12
12
  import LoggerProxy from '../common/logs/logger-proxy';
13
13
 
14
14
  export type MediaDirection = {
@@ -200,7 +200,7 @@ export default class MediaProperties {
200
200
  timer = setTimeout(() => {
201
201
  this.webrtcMediaConnection.off(Event.CONNECTION_STATE_CHANGED, connectionStateListener);
202
202
  reject();
203
- }, PC_BAIL_TIMEOUT);
203
+ }, ICE_AND_DTLS_CONNECTION_TIMEOUT);
204
204
 
205
205
  this.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, connectionStateListener);
206
206
  });
@@ -259,10 +259,25 @@ export default class MediaProperties {
259
259
 
260
260
  return true;
261
261
  }
262
+ LoggerProxy.logger.warn(
263
+ `Media:properties#getCurrentConnectionType --> missing localCandidate.protocol, candidateType=${localCandidate.candidateType}`
264
+ );
262
265
 
263
266
  return false;
264
267
  });
265
268
 
269
+ if (foundConnectionType === 'unknown') {
270
+ const candidatePairStates = allStatsReports
271
+ .filter((report) => report.type === 'candidate-pair')
272
+ .map((report) => report.state);
273
+
274
+ LoggerProxy.logger.warn(
275
+ `Media:properties#getCurrentConnectionType --> all candidate pair states: ${JSON.stringify(
276
+ candidatePairStates
277
+ )}`
278
+ );
279
+ }
280
+
266
281
  return foundConnectionType;
267
282
  }
268
283
  }