@webex/plugin-meetings 3.1.0-next.9 → 3.2.0

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 (181) hide show
  1. package/dist/annotation/annotation.types.d.ts +42 -0
  2. package/dist/annotation/constants.d.ts +31 -0
  3. package/dist/annotation/index.d.ts +117 -0
  4. package/dist/breakouts/breakout.d.ts +8 -0
  5. package/dist/breakouts/breakout.js +1 -1
  6. package/dist/breakouts/collection.d.ts +5 -0
  7. package/dist/breakouts/edit-lock-error.d.ts +15 -0
  8. package/dist/breakouts/events.d.ts +8 -0
  9. package/dist/breakouts/index.d.ts +5 -0
  10. package/dist/breakouts/index.js +1 -1
  11. package/dist/breakouts/request.d.ts +22 -0
  12. package/dist/breakouts/utils.d.ts +15 -0
  13. package/dist/common/browser-detection.d.ts +9 -0
  14. package/dist/common/collection.d.ts +48 -0
  15. package/dist/common/config.d.ts +2 -0
  16. package/dist/common/errors/captcha-error.d.ts +15 -0
  17. package/dist/common/errors/intent-to-join.d.ts +16 -0
  18. package/dist/common/errors/join-meeting.d.ts +17 -0
  19. package/dist/common/errors/media.d.ts +15 -0
  20. package/dist/common/errors/no-meeting-info.d.ts +14 -0
  21. package/dist/common/errors/parameter.d.ts +15 -0
  22. package/dist/common/errors/password-error.d.ts +15 -0
  23. package/dist/common/errors/permission.d.ts +14 -0
  24. package/dist/common/errors/reclaim-host-role-errors.d.ts +60 -0
  25. package/dist/common/errors/reconnection-in-progress.d.ts +9 -0
  26. package/dist/common/errors/reconnection-in-progress.js +34 -0
  27. package/dist/common/errors/reconnection-in-progress.js.map +1 -0
  28. package/dist/common/errors/reconnection.d.ts +15 -0
  29. package/dist/common/errors/stats.d.ts +15 -0
  30. package/dist/common/errors/webex-errors.d.ts +93 -0
  31. package/dist/common/errors/webex-meetings-error.d.ts +20 -0
  32. package/dist/common/events/events-scope.d.ts +17 -0
  33. package/dist/common/events/events.d.ts +12 -0
  34. package/dist/common/events/trigger-proxy.d.ts +2 -0
  35. package/dist/common/events/util.d.ts +2 -0
  36. package/dist/common/logs/logger-config.d.ts +2 -0
  37. package/dist/common/logs/logger-proxy.d.ts +2 -0
  38. package/dist/common/logs/request.d.ts +36 -0
  39. package/dist/common/queue.d.ts +34 -0
  40. package/dist/config.d.ts +73 -0
  41. package/dist/constants.d.ts +1088 -0
  42. package/dist/constants.js +6 -3
  43. package/dist/constants.js.map +1 -1
  44. package/dist/controls-options-manager/constants.d.ts +4 -0
  45. package/dist/controls-options-manager/enums.d.ts +15 -0
  46. package/dist/controls-options-manager/index.d.ts +136 -0
  47. package/dist/controls-options-manager/types.d.ts +43 -0
  48. package/dist/controls-options-manager/util.d.ts +1 -0
  49. package/dist/index.d.ts +7 -0
  50. package/dist/interceptors/index.d.ts +2 -0
  51. package/dist/interceptors/locusRetry.d.ts +27 -0
  52. package/dist/interpretation/collection.d.ts +5 -0
  53. package/dist/interpretation/index.d.ts +5 -0
  54. package/dist/interpretation/index.js +1 -1
  55. package/dist/interpretation/siLanguage.d.ts +5 -0
  56. package/dist/interpretation/siLanguage.js +1 -1
  57. package/dist/locus-info/controlsUtils.d.ts +2 -0
  58. package/dist/locus-info/embeddedAppsUtils.d.ts +2 -0
  59. package/dist/locus-info/fullState.d.ts +2 -0
  60. package/dist/locus-info/hostUtils.d.ts +2 -0
  61. package/dist/locus-info/index.d.ts +322 -0
  62. package/dist/locus-info/infoUtils.d.ts +2 -0
  63. package/dist/locus-info/mediaSharesUtils.d.ts +2 -0
  64. package/dist/locus-info/parser.d.ts +272 -0
  65. package/dist/locus-info/selfUtils.d.ts +2 -0
  66. package/dist/media/MediaConnectionAwaiter.d.ts +61 -0
  67. package/dist/media/index.d.ts +34 -0
  68. package/dist/media/properties.d.ts +93 -0
  69. package/dist/media/util.d.ts +2 -0
  70. package/dist/mediaQualityMetrics/config.d.ts +241 -0
  71. package/dist/mediaQualityMetrics/config.js +10 -10
  72. package/dist/mediaQualityMetrics/config.js.map +1 -1
  73. package/dist/meeting/in-meeting-actions.d.ts +167 -0
  74. package/dist/meeting/index.d.ts +1825 -0
  75. package/dist/meeting/index.js +112 -64
  76. package/dist/meeting/index.js.map +1 -1
  77. package/dist/meeting/locusMediaRequest.d.ts +74 -0
  78. package/dist/meeting/locusMediaRequest.js +27 -0
  79. package/dist/meeting/locusMediaRequest.js.map +1 -1
  80. package/dist/meeting/muteState.d.ts +178 -0
  81. package/dist/meeting/request.d.ts +295 -0
  82. package/dist/meeting/request.type.d.ts +11 -0
  83. package/dist/meeting/state.d.ts +9 -0
  84. package/dist/meeting/util.d.ts +119 -0
  85. package/dist/meeting/util.js +0 -16
  86. package/dist/meeting/util.js.map +1 -1
  87. package/dist/meeting/voicea-meeting.d.ts +16 -0
  88. package/dist/meeting/voicea-meeting.js +37 -49
  89. package/dist/meeting/voicea-meeting.js.map +1 -1
  90. package/dist/meeting-info/collection.d.ts +20 -0
  91. package/dist/meeting-info/index.d.ts +69 -0
  92. package/dist/meeting-info/meeting-info-v2.d.ts +123 -0
  93. package/dist/meeting-info/request.d.ts +22 -0
  94. package/dist/meeting-info/util.d.ts +2 -0
  95. package/dist/meeting-info/utilv2.d.ts +2 -0
  96. package/dist/meetings/collection.d.ts +40 -0
  97. package/dist/meetings/index.d.ts +398 -0
  98. package/dist/meetings/index.js +12 -28
  99. package/dist/meetings/index.js.map +1 -1
  100. package/dist/meetings/meetings.types.d.ts +4 -0
  101. package/dist/meetings/request.d.ts +27 -0
  102. package/dist/meetings/util.d.ts +18 -0
  103. package/dist/member/index.d.ts +160 -0
  104. package/dist/member/types.d.ts +32 -0
  105. package/dist/member/util.d.ts +2 -0
  106. package/dist/members/collection.d.ts +29 -0
  107. package/dist/members/index.d.ts +353 -0
  108. package/dist/members/request.d.ts +114 -0
  109. package/dist/members/types.d.ts +25 -0
  110. package/dist/members/util.d.ts +215 -0
  111. package/dist/metrics/constants.d.ts +70 -0
  112. package/dist/metrics/index.d.ts +45 -0
  113. package/dist/multistream/mediaRequestManager.d.ts +119 -0
  114. package/dist/multistream/receiveSlot.d.ts +68 -0
  115. package/dist/multistream/receiveSlotManager.d.ts +56 -0
  116. package/dist/multistream/remoteMedia.d.ts +72 -0
  117. package/dist/multistream/remoteMediaGroup.d.ts +49 -0
  118. package/dist/multistream/remoteMediaManager.d.ts +300 -0
  119. package/dist/multistream/sendSlotManager.d.ts +69 -0
  120. package/dist/networkQualityMonitor/index.d.ts +70 -0
  121. package/dist/personal-meeting-room/index.d.ts +47 -0
  122. package/dist/personal-meeting-room/request.d.ts +14 -0
  123. package/dist/personal-meeting-room/util.d.ts +2 -0
  124. package/dist/reachability/clusterReachability.d.ts +110 -0
  125. package/dist/reachability/index.d.ts +109 -0
  126. package/dist/reachability/index.js +88 -9
  127. package/dist/reachability/index.js.map +1 -1
  128. package/dist/reachability/request.d.ts +39 -0
  129. package/dist/reachability/util.d.ts +15 -0
  130. package/dist/reactions/constants.d.ts +3 -0
  131. package/dist/reactions/reactions.d.ts +4 -0
  132. package/dist/reactions/reactions.type.d.ts +52 -0
  133. package/dist/reconnection-manager/index.d.ts +136 -0
  134. package/dist/recording-controller/enums.d.ts +7 -0
  135. package/dist/recording-controller/index.d.ts +207 -0
  136. package/dist/recording-controller/util.d.ts +14 -0
  137. package/dist/roap/index.d.ts +86 -0
  138. package/dist/roap/request.d.ts +39 -0
  139. package/dist/roap/request.js +3 -27
  140. package/dist/roap/request.js.map +1 -1
  141. package/dist/roap/turnDiscovery.d.ts +155 -0
  142. package/dist/rtcMetrics/constants.d.ts +4 -0
  143. package/dist/rtcMetrics/index.d.ts +61 -0
  144. package/dist/statsAnalyzer/global.d.ts +36 -0
  145. package/dist/statsAnalyzer/index.d.ts +217 -0
  146. package/dist/statsAnalyzer/index.js +4 -2
  147. package/dist/statsAnalyzer/index.js.map +1 -1
  148. package/dist/statsAnalyzer/mqaUtil.d.ts +48 -0
  149. package/dist/statsAnalyzer/mqaUtil.js +14 -0
  150. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  151. package/dist/transcription/index.d.ts +64 -0
  152. package/dist/types/constants.d.ts +3 -1
  153. package/dist/types/mediaQualityMetrics/config.d.ts +8 -2
  154. package/dist/types/meeting/index.d.ts +10 -1
  155. package/dist/types/meeting/locusMediaRequest.d.ts +1 -0
  156. package/dist/types/meeting/voicea-meeting.d.ts +3 -2
  157. package/dist/types/meetings/index.d.ts +1 -16
  158. package/dist/types/reachability/index.d.ts +11 -0
  159. package/dist/webinar/collection.d.ts +16 -0
  160. package/dist/webinar/index.d.ts +5 -0
  161. package/dist/webinar/index.js +1 -1
  162. package/package.json +21 -21
  163. package/src/constants.ts +3 -2
  164. package/src/mediaQualityMetrics/config.ts +13 -7
  165. package/src/meeting/index.ts +73 -30
  166. package/src/meeting/locusMediaRequest.ts +31 -0
  167. package/src/meeting/util.ts +1 -16
  168. package/src/meeting/voicea-meeting.ts +44 -46
  169. package/src/meetings/index.ts +15 -27
  170. package/src/reachability/index.ts +60 -0
  171. package/src/roap/request.ts +1 -24
  172. package/src/statsAnalyzer/index.ts +6 -3
  173. package/src/statsAnalyzer/mqaUtil.ts +18 -0
  174. package/test/unit/spec/meeting/index.js +70 -33
  175. package/test/unit/spec/meeting/locusMediaRequest.ts +49 -0
  176. package/test/unit/spec/meeting/utils.js +0 -10
  177. package/test/unit/spec/meeting/voicea-meeting.ts +5 -14
  178. package/test/unit/spec/meetings/index.js +59 -17
  179. package/test/unit/spec/reachability/index.ts +266 -0
  180. package/test/unit/spec/roap/request.ts +0 -37
  181. package/test/unit/spec/stats-analyzer/index.js +89 -8
@@ -296,6 +296,63 @@ export default class Reachability {
296
296
  return reachable;
297
297
  }
298
298
 
299
+ /**
300
+ * Returns true only if ALL protocols (UDP, TCP and TLS) have been tested and none
301
+ * of the media clusters where reachable with any of the protocols. This is done
302
+ * irrespective of the config, so for example:
303
+ * if config.meetings.experimental.enableTlsReachability === false,
304
+ * it will return false, because TLS reachability won't be tested,
305
+ * so we can't say for sure that media backend is unreachable over TLS.
306
+ *
307
+ * @returns {boolean}
308
+ */
309
+ async isWebexMediaBackendUnreachable() {
310
+ let unreachable = false;
311
+
312
+ // @ts-ignore
313
+ const reachabilityData = await this.webex.boundedStorage
314
+ .get(this.namespace, REACHABILITY.localStorageResult)
315
+ .catch(() => {});
316
+
317
+ if (reachabilityData) {
318
+ try {
319
+ const reachabilityResults: ReachabilityResults = JSON.parse(reachabilityData);
320
+
321
+ const protocols = {
322
+ udp: {tested: false, reachable: undefined},
323
+ tcp: {tested: false, reachable: undefined},
324
+ xtls: {tested: false, reachable: undefined},
325
+ };
326
+
327
+ Object.values(reachabilityResults).forEach((result) => {
328
+ Object.keys(protocols).forEach((protocol) => {
329
+ if (
330
+ result[protocol]?.result === 'reachable' ||
331
+ result[protocol]?.result === 'unreachable'
332
+ ) {
333
+ protocols[protocol].tested = true;
334
+
335
+ // we need at least 1 'reachable' result to mark the whole protocol as reachable
336
+ if (result[protocol].result === 'reachable') {
337
+ protocols[protocol].reachable = true;
338
+ }
339
+ }
340
+ });
341
+ });
342
+
343
+ unreachable = Object.values(protocols).every(
344
+ (protocol) => protocol.tested && !protocol.reachable
345
+ );
346
+ } catch (e) {
347
+ LoggerProxy.logger.error(
348
+ `Roap:request#attachReachabilityData --> Error in parsing reachability data: ${e}`
349
+ );
350
+ }
351
+ }
352
+
353
+ return unreachable;
354
+ }
355
+
299
356
  /**
300
357
  * Get list of all unreachable clusters
301
358
  * @returns {array} Unreachable clusters
@@ -314,6 +371,9 @@ export default class Reachability {
314
371
  if (result.tcp.result === 'unreachable') {
315
372
  unreachableList.push({name: key, protocol: 'tcp'});
316
373
  }
374
+ if (result.xtls.result === 'unreachable') {
375
+ unreachableList.push({name: key, protocol: 'xtls'});
376
+ }
317
377
  });
318
378
 
319
379
  return unreachableList;
@@ -61,7 +61,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
61
61
  ipVersion?: IP_VERSION;
62
62
  locusMediaRequest?: LocusMediaRequest;
63
63
  }) {
64
- const {roapMessage, locusSelfUrl, mediaId, meetingId, locusMediaRequest, ipVersion} = options;
64
+ const {roapMessage, locusSelfUrl, mediaId, locusMediaRequest, ipVersion} = options;
65
65
 
66
66
  if (!mediaId) {
67
67
  LoggerProxy.logger.info('Roap:request#sendRoap --> sending empty mediaID');
@@ -82,14 +82,6 @@ export default class RoapRequest extends StatelessWebexPlugin {
82
82
  `Roap:request#sendRoap --> ${locusSelfUrl} \n ${roapMessage.messageType} \n seq:${roapMessage.seq}`
83
83
  );
84
84
 
85
- // @ts-ignore
86
- this.webex.internal.newMetrics.submitClientEvent({
87
- name: 'client.locus.media.request',
88
- options: {
89
- meetingId,
90
- },
91
- });
92
-
93
85
  return locusMediaRequest
94
86
  .send({
95
87
  type: 'RoapMessage',
@@ -101,13 +93,6 @@ export default class RoapRequest extends StatelessWebexPlugin {
101
93
  ipVersion,
102
94
  })
103
95
  .then((res) => {
104
- // @ts-ignore
105
- this.webex.internal.newMetrics.submitClientEvent({
106
- name: 'client.locus.media.response',
107
- options: {
108
- meetingId,
109
- },
110
- });
111
96
  // always it will be the first mediaConnection Object
112
97
  const mediaConnections =
113
98
  res.body.mediaConnections &&
@@ -131,14 +116,6 @@ export default class RoapRequest extends StatelessWebexPlugin {
131
116
  };
132
117
  })
133
118
  .catch((err) => {
134
- // @ts-ignore
135
- this.webex.internal.newMetrics.submitClientEvent({
136
- name: 'client.locus.media.response',
137
- options: {
138
- meetingId,
139
- rawError: err,
140
- },
141
- });
142
119
  LoggerProxy.logger.error(`Roap:request#sendRoap --> Error:`, err);
143
120
  LoggerProxy.logger.error(
144
121
  `Roap:request#sendRoapRequest --> roapMessage that caused error:${JSON.stringify(
@@ -998,12 +998,11 @@ export class StatsAnalyzer extends EventsScope {
998
998
  result.qualityLimitationReason;
999
999
  this.statsResults[mediaType][sendrecvType].qualityLimitationResolutionChanges =
1000
1000
  result.qualityLimitationResolutionChanges;
1001
- this.statsResults[mediaType][sendrecvType].retransmittedPacketsSent =
1001
+ this.statsResults[mediaType][sendrecvType].totalRtxPacketsSent =
1002
1002
  result.retransmittedPacketsSent;
1003
+ this.statsResults[mediaType][sendrecvType].totalRtxBytesSent = result.retransmittedBytesSent;
1003
1004
  this.statsResults[mediaType][sendrecvType].totalBytesSent = result.bytesSent;
1004
1005
  this.statsResults[mediaType][sendrecvType].headerBytesSent = result.headerBytesSent;
1005
- this.statsResults[mediaType][sendrecvType].retransmittedBytesSent =
1006
- result.retransmittedBytesSent;
1007
1006
  this.statsResults[mediaType][sendrecvType].requestedBitrate = result.requestedBitrate;
1008
1007
  this.statsResults[mediaType][sendrecvType].requestedFrameSize = result.requestedFrameSize;
1009
1008
  }
@@ -1136,6 +1135,10 @@ export class StatsAnalyzer extends EventsScope {
1136
1135
  this.statsResults[mediaType][sendrecvType].fecPacketsReceived = result.fecPacketsReceived;
1137
1136
  this.statsResults[mediaType][sendrecvType].totalBytesReceived = result.bytesReceived;
1138
1137
  this.statsResults[mediaType][sendrecvType].headerBytesReceived = result.headerBytesReceived;
1138
+ this.statsResults[mediaType][sendrecvType].totalRtxPacketsReceived =
1139
+ result.retransmittedPacketsReceived;
1140
+ this.statsResults[mediaType][sendrecvType].totalRtxBytesReceived =
1141
+ result.retransmittedBytesReceived;
1139
1142
 
1140
1143
  this.statsResults[mediaType][sendrecvType].meanRtpJitter.push(result.jitter);
1141
1144
 
@@ -237,10 +237,16 @@ export const getVideoReceiverMqa = ({
237
237
  const lastPacketsLost = getLastTotalValue('totalPacketsLost');
238
238
  const lastBytesReceived = getLastTotalValue('totalBytesReceived');
239
239
 
240
+ const lastRtxPacketsReceived = getLastTotalValue('totalRtxPacketsReceived');
241
+ const lastRtxBytesReceived = getLastTotalValue('totalRtxBytesReceived');
242
+
240
243
  const packetsLost = getTotalValue('totalPacketsLost');
241
244
  const totalPacketsReceived = getTotalValue('totalPacketsReceived');
242
245
  const totalBytesReceived = getTotalValue('totalBytesReceived');
243
246
 
247
+ const totalRtxPacketsReceived = getTotalValue('totalRtxPacketsReceived');
248
+ const totalRtxBytesReceived = getTotalValue('totalRtxBytesReceived');
249
+
244
250
  const meanRemoteJitter = Object.keys(statsResults)
245
251
  .filter((mt) => mt.includes(baseMediaType))
246
252
  .reduce((acc, mt) => acc.concat(statsResults[mt][sendrecvType].meanRemoteJitter), []);
@@ -266,10 +272,15 @@ export const getVideoReceiverMqa = ({
266
272
 
267
273
  // Calculate the outgoing bitrate
268
274
  const totalBytesReceivedInaMin = totalBytesReceived - lastBytesReceived;
275
+ const totalRtxBytesReceivedInaMin = totalRtxBytesReceived - lastRtxBytesReceived;
269
276
 
270
277
  videoReceiver.common.rtpBitrate = totalBytesReceivedInaMin
271
278
  ? (totalBytesReceivedInaMin * 8) / 60
272
279
  : 0;
280
+ videoReceiver.common.rtxPackets = totalRtxPacketsReceived - lastRtxPacketsReceived;
281
+ videoReceiver.common.rtxBitrate = totalRtxBytesReceivedInaMin
282
+ ? (totalRtxBytesReceivedInaMin * 8) / 60
283
+ : 0;
273
284
  };
274
285
 
275
286
  export const getVideoReceiverStreamMqa = ({
@@ -349,11 +360,15 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, b
349
360
  const lastPacketsSent = getLastTotalValue('totalPacketsSent');
350
361
  const lastBytesSent = getLastTotalValue('totalBytesSent');
351
362
  const lastPacketsLostTotal = getLastTotalValue('totalPacketsLostOnReceiver');
363
+ const lastRtxPacketsSent = getLastTotalValue('totalRtxPacketsSent');
364
+ const lastRtxBytesSent = getLastTotalValue('totalRtxBytesSent');
352
365
 
353
366
  const totalPacketsLostOnReceiver = getTotalValue('totalPacketsLostOnReceiver');
354
367
  const totalPacketsSent = getTotalValue('totalPacketsSent');
355
368
  const totalBytesSent = getTotalValue('totalBytesSent');
356
369
  const availableOutgoingBitrate = getTotalValue('availableOutgoingBitrate');
370
+ const totalRtxPacketsSent = getTotalValue('totalRtxPacketsSent');
371
+ const totalRtxBytesSent = getTotalValue('totalRtxBytesSent');
357
372
 
358
373
  videoSender.common.common.direction =
359
374
  statsResults[Object.keys(statsResults).find((mediaType) => mediaType.includes(baseMediaType))]
@@ -389,8 +404,11 @@ export const getVideoSenderMqa = ({videoSender, statsResults, lastMqaDataSent, b
389
404
 
390
405
  // Calculate the outgoing bitrate
391
406
  const totalBytesSentInaMin = totalBytesSent - lastBytesSent;
407
+ const totalRtxBytesSentInaMin = totalRtxBytesSent - lastRtxBytesSent;
392
408
 
393
409
  videoSender.common.rtpBitrate = totalBytesSentInaMin ? (totalBytesSentInaMin * 8) / 60 : 0;
410
+ videoSender.common.rtxPackets = totalRtxPacketsSent - lastRtxPacketsSent;
411
+ videoSender.common.rtxBitrate = totalRtxBytesSentInaMin ? (totalRtxBytesSentInaMin * 8) / 60 : 0;
394
412
  };
395
413
 
396
414
  export const getVideoSenderStreamMqa = ({
@@ -906,12 +906,12 @@ describe('plugin-meetings', () => {
906
906
 
907
907
  describe('#isTranscriptionSupported', () => {
908
908
  it('should return false if the feature is not supported for the meeting', () => {
909
- meeting.locusInfo.controls = {transcribe: {transcribing: false}};
909
+ meeting.locusInfo.controls = {transcribe: {caption: false}};
910
910
 
911
911
  assert.equal(meeting.isTranscriptionSupported(), false);
912
912
  });
913
913
  it('should return true if webex assitant is enabled', () => {
914
- meeting.locusInfo.controls = {transcribe: {transcribing: true}};
914
+ meeting.locusInfo.controls = {transcribe: {caption: true}};
915
915
 
916
916
  assert.equal(meeting.isTranscriptionSupported(), true);
917
917
  });
@@ -1302,6 +1302,31 @@ describe('plugin-meetings', () => {
1302
1302
  );
1303
1303
  });
1304
1304
  });
1305
+
1306
+ describe('#handleLLMOnline', () => {
1307
+ beforeEach(() => {
1308
+ webex.internal.llm.off = sinon.stub();
1309
+ });
1310
+
1311
+ it('turns off llm online, emits transcription connected events', () => {
1312
+ meeting.handleLLMOnline();
1313
+ assert.calledOnceWithExactly(
1314
+ webex.internal.llm.off,
1315
+ 'online',
1316
+ meeting.handleLLMOnline
1317
+ );
1318
+ assert.calledWith(
1319
+ TriggerProxy.trigger,
1320
+ sinon.match.instanceOf(Meeting),
1321
+ {
1322
+ file: 'meeting/index',
1323
+ function: 'handleLLMOnline',
1324
+ },
1325
+ EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
1326
+ );
1327
+ });
1328
+ });
1329
+
1305
1330
  describe('#join', () => {
1306
1331
  let sandbox = null;
1307
1332
  let setCorrelationIdSpy;
@@ -1351,15 +1376,10 @@ describe('plugin-meetings', () => {
1351
1376
  assert.calledOnce(MeetingUtil.joinMeeting);
1352
1377
  assert.calledOnce(meeting.setLocus);
1353
1378
  assert.equal(result, joinMeetingResult);
1354
-
1355
1379
  assert.calledWith(
1356
- TriggerProxy.trigger,
1357
- sinon.match.instanceOf(Meeting),
1358
- {
1359
- file: 'meeting/index',
1360
- function: 'join',
1361
- },
1362
- EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
1380
+ webex.internal.llm.on,
1381
+ 'online',
1382
+ meeting.handleLLMOnline
1363
1383
  );
1364
1384
  });
1365
1385
 
@@ -6166,11 +6186,22 @@ describe('plugin-meetings', () => {
6166
6186
 
6167
6187
  beforeEach(() => {
6168
6188
  sandbox = sinon.createSandbox();
6169
- sandbox.stub(meeting, 'cleanupLocalStreams');
6189
+ meeting.statsAnalyzer = {
6190
+ stopAnalyzer: sinon.stub().returns(Promise.resolve())
6191
+ };
6192
+
6193
+ meeting.reconnectionManager = {
6194
+ cleanUp: sinon.stub()
6195
+ };
6170
6196
 
6171
- sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
6197
+ meeting.cleanupLocalStreams=sinon.stub();
6198
+ meeting.closeRemoteStreams = sinon.stub().returns(Promise.resolve());
6199
+ meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
6200
+ meeting.unsetRemoteStreams = sinon.stub();
6201
+ meeting.unsetPeerConnections = sinon.stub();
6202
+ meeting.addMedia = sinon.stub().returns(Promise.resolve());
6203
+ meeting.mediaProperties.setMediaDirection = sinon.stub();
6172
6204
 
6173
- sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
6174
6205
  sandbox
6175
6206
  .stub(MeetingUtil, 'joinMeeting')
6176
6207
  .returns(
@@ -6232,9 +6263,11 @@ describe('plugin-meetings', () => {
6232
6263
  });
6233
6264
  });
6234
6265
 
6235
- it('should reconnectMedia after DX joins after moveTo', async () => {
6266
+ it('should cleanup on moveTo & addMedia after', async () => {
6236
6267
  await meeting.moveTo('resourceId');
6237
6268
 
6269
+ assert.equal(meeting.isMoveToInProgress, true);
6270
+
6238
6271
  await meeting.locusInfo.emitScoped(
6239
6272
  {
6240
6273
  file: 'locus-info',
@@ -6242,26 +6275,26 @@ describe('plugin-meetings', () => {
6242
6275
  },
6243
6276
  'SELF_OBSERVING'
6244
6277
  );
6278
+
6245
6279
 
6246
- // beacuse we are calling callback so we need to wait
6247
-
6248
- assert.called(meeting.cleanupLocalStreams);
6249
-
6250
- // give queued Promise callbacks a chance to run
6251
- await Promise.resolve();
6252
-
6253
- assert.called(meeting.mediaProperties.setMediaDirection);
6254
-
6255
- assert.calledWith(meeting.reconnectionManager.reconnectMedia, {
6256
- mediaDirection: {
6257
- sendVideo: false,
6258
- receiveVideo: false,
6259
- sendAudio: false,
6260
- receiveAudio: false,
6261
- sendShare: false,
6262
- receiveShare: true,
6263
- },
6264
- });
6280
+ // Verify that the event handler behaves as expected
6281
+ expect(meeting.statsAnalyzer.stopAnalyzer.calledOnce).to.be.true;
6282
+ expect(meeting.closeRemoteStreams.calledOnce).to.be.true;
6283
+ await testUtils.flushPromises();
6284
+ expect(meeting.closePeerConnections.calledOnce).to.be.true;
6285
+ await testUtils.flushPromises();
6286
+ expect(meeting.cleanupLocalStreams.calledOnce).to.be.true;
6287
+ expect(meeting.unsetRemoteStreams.calledOnce).to.be.true;
6288
+ expect(meeting.unsetPeerConnections.calledOnce).to.be.true;
6289
+ expect(meeting.reconnectionManager.cleanUp.calledOnce).to.be.true;
6290
+ expect(meeting.mediaProperties.setMediaDirection.calledOnce).to.be.true;
6291
+ expect(meeting.addMedia.calledOnceWithExactly({
6292
+ audioEnabled: false,
6293
+ videoEnabled: false,
6294
+ shareVideoEnabled: true
6295
+ })).to.be.true;
6296
+ await testUtils.flushPromises();
6297
+ assert.equal(meeting.isMoveToInProgress, false);
6265
6298
  });
6266
6299
 
6267
6300
  it('should throw an error if moveTo call fails', async () => {
@@ -6269,6 +6302,7 @@ describe('plugin-meetings', () => {
6269
6302
  try {
6270
6303
  await meeting.moveTo('resourceId');
6271
6304
  } catch {
6305
+ assert.equal(meeting.isMoveToInProgress, false);
6272
6306
  assert.calledOnce(Metrics.sendBehavioralMetric);
6273
6307
  assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_TO_FAILURE, {
6274
6308
  correlation_id: meeting.correlationId,
@@ -6290,6 +6324,7 @@ describe('plugin-meetings', () => {
6290
6324
  'SELF_OBSERVING'
6291
6325
  );
6292
6326
  } catch {
6327
+ assert.equal(meeting.isMoveToInProgress, false);
6293
6328
  assert.calledOnce(Metrics.sendBehavioralMetric);
6294
6329
  assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_TO_FAILURE, {
6295
6330
  correlation_id: meeting.correlationId,
@@ -7852,6 +7887,7 @@ describe('plugin-meetings', () => {
7852
7887
  describe('#setUpLocusInfoSelfListener', () => {
7853
7888
  it('listens to the self unadmitted guest event', (done) => {
7854
7889
  meeting.startKeepAlive = sinon.stub();
7890
+ meeting.updateLLMConnection = sinon.stub();
7855
7891
  meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_UNADMITTED_GUEST', test1);
7856
7892
  assert.calledOnceWithExactly(meeting.startKeepAlive);
7857
7893
  assert.calledThrice(TriggerProxy.trigger);
@@ -7862,6 +7898,7 @@ describe('plugin-meetings', () => {
7862
7898
  'meeting:self:lobbyWaiting',
7863
7899
  {payload: test1}
7864
7900
  );
7901
+ assert.calledOnce(meeting.updateLLMConnection);
7865
7902
  done();
7866
7903
  });
7867
7904
  it('listens to the self admitted guest event', (done) => {
@@ -106,6 +106,12 @@ describe('LocusMediaRequest.send()', () => {
106
106
  },
107
107
  });
108
108
 
109
+ mockWebex.internal = {
110
+ newMetrics: {
111
+ submitClientEvent: sinon.stub()
112
+ },
113
+ };
114
+
109
115
  locusMediaRequest = new LocusMediaRequest({
110
116
  device: {
111
117
  url: 'deviceUrl',
@@ -113,6 +119,7 @@ describe('LocusMediaRequest.send()', () => {
113
119
  regionCode: 'regionCode',
114
120
  },
115
121
  correlationId: 'correlationId',
122
+ meetingId: 'meetingId',
116
123
  preferTranscoding: true,
117
124
  }, {
118
125
  parent: mockWebex,
@@ -134,6 +141,27 @@ describe('LocusMediaRequest.send()', () => {
134
141
  await sendRoapMessage('OFFER');
135
142
 
136
143
  webexRequestStub.resetHistory();
144
+ mockWebex.internal.newMetrics.submitClientEvent.resetHistory();
145
+ }
146
+
147
+ const checkMetrics = (expectedMetrics: boolean = true) => {
148
+ if (expectedMetrics) {
149
+ assert.calledWith(mockWebex.internal.newMetrics.submitClientEvent, {
150
+ name: 'client.locus.media.request',
151
+ options: {
152
+ meetingId: 'meetingId',
153
+ },
154
+ });
155
+
156
+ assert.calledWith(mockWebex.internal.newMetrics.submitClientEvent, {
157
+ name: 'client.locus.media.response',
158
+ options: {
159
+ meetingId: 'meetingId',
160
+ },
161
+ });
162
+ } else {
163
+ assert.notCalled(mockWebex.internal.newMetrics.submitClientEvent);
164
+ }
137
165
  }
138
166
 
139
167
  it('sends a roap message', async () => {
@@ -146,6 +174,21 @@ describe('LocusMediaRequest.send()', () => {
146
174
  uri: 'fakeMeetingSelfUrl/media',
147
175
  body: createExpectedRoapBody('OFFER', {audioMuted: true, videoMuted: true}),
148
176
  });
177
+
178
+ checkMetrics();
179
+ });
180
+
181
+ it('sends correct metric event when roap message fails', async () => {
182
+ webexRequestStub.rejects({code: 300, message: 'fake error'});
183
+ await assert.isRejected(sendRoapMessage('OFFER'));
184
+
185
+ assert.calledWith(mockWebex.internal.newMetrics.submitClientEvent, {
186
+ name: 'client.locus.media.response',
187
+ options: {
188
+ meetingId: 'meetingId',
189
+ rawError: {code: 300, message: 'fake error'},
190
+ },
191
+ });
149
192
  });
150
193
 
151
194
  it('sends a local mute request', async () => {
@@ -160,6 +203,8 @@ describe('LocusMediaRequest.send()', () => {
160
203
  uri: 'fakeMeetingSelfUrl/media',
161
204
  body: createExpectedLocalMuteBody({audioMuted: false, videoMuted: false}),
162
205
  });
206
+
207
+ checkMetrics(false);
163
208
  });
164
209
 
165
210
  it('sends a local mute request with sequence', async () => {
@@ -207,6 +252,7 @@ describe('LocusMediaRequest.send()', () => {
207
252
  body: createExpectedLocalMuteBody({audioMuted: false, videoMuted: true}),
208
253
  });
209
254
 
255
+ checkMetrics(false);
210
256
  });
211
257
 
212
258
  it('sends a local mute request with the last audio/video mute values', async () => {
@@ -225,6 +271,7 @@ describe('LocusMediaRequest.send()', () => {
225
271
  body: createExpectedLocalMuteBody({audioMuted: true, videoMuted: false}),
226
272
  });
227
273
 
274
+ checkMetrics(false);
228
275
  });
229
276
 
230
277
  it('sends only roap when roap and local mute are requested', async () => {
@@ -242,6 +289,8 @@ describe('LocusMediaRequest.send()', () => {
242
289
  uri: 'fakeMeetingSelfUrl/media',
243
290
  body: createExpectedRoapBody('OFFER', {audioMuted: true, videoMuted: false}),
244
291
  });
292
+
293
+ checkMetrics();
245
294
  });
246
295
 
247
296
  describe('queueing', () => {
@@ -370,16 +370,6 @@ describe('plugin-meetings', () => {
370
370
  sequence: {},
371
371
  type: 'LocalMute',
372
372
  });
373
-
374
- assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
375
- name: 'client.locus.media.request',
376
- options: {meetingId: meeting.id},
377
- });
378
-
379
- assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
380
- name: 'client.locus.media.response',
381
- options: {meetingId: meeting.id},
382
- });
383
373
  });
384
374
  });
385
375
 
@@ -101,14 +101,7 @@ describe('plugin-meetings', () => {
101
101
  ],
102
102
  transcript_language_code: "en"
103
103
  }
104
- ],
105
- transcript: {
106
- text: "Don't bother me talking I'm just going to get the transcript data that is interim and I needed if I keep talking, I get the interim data",
107
- csis: [
108
- 1234867712
109
- ],
110
- transcript_language_code: "en"
111
- }
104
+ ]
112
105
  };
113
106
  });
114
107
 
@@ -160,7 +153,6 @@ describe('plugin-meetings', () => {
160
153
  it('should process new final captions correctly', () => {
161
154
  let transcriptData = fakeMeeting.transcription;
162
155
  let transcriptId = fakeVoiceaPayload.transcriptId;
163
- delete fakeVoiceaPayload.transcripts;
164
156
 
165
157
  // Assuming that processNewCaptions is a pure function that doesn't mutate the input but returns a new state
166
158
  processNewCaptions({
@@ -169,7 +161,7 @@ describe('plugin-meetings', () => {
169
161
  });
170
162
 
171
163
  // Check if speaker details are cached if needed
172
- const csisKey = fakeVoiceaPayload.transcript.csis[0];
164
+ const csisKey = fakeVoiceaPayload.transcripts[0].csis[0];
173
165
  const speaker = transcriptData.speakerProxy[csisKey];
174
166
  expect(speaker).to.exist;
175
167
 
@@ -178,6 +170,7 @@ describe('plugin-meetings', () => {
178
170
 
179
171
  //check if the interim caption is removed
180
172
  const oldInterimCaption = transcriptData.captions.find(caption => caption.id === `${transcriptId}_${speaker.speakerId}`);
173
+ console.log(oldInterimCaption);
181
174
  expect(oldInterimCaption).to.not.exist;
182
175
 
183
176
  // Check the final caption data
@@ -186,8 +179,8 @@ describe('plugin-meetings', () => {
186
179
  expect(newCaption).to.include({
187
180
  id: transcriptId,
188
181
  isFinal: fakeVoiceaPayload.isFinal,
189
- text: fakeVoiceaPayload.transcript.text,
190
- currentSpokenLanguage: fakeVoiceaPayload.transcript.transcript_language_code,
182
+ text: fakeVoiceaPayload.transcripts[0].text,
183
+ currentSpokenLanguage: fakeVoiceaPayload.transcripts[0].transcript_language_code,
191
184
  });
192
185
 
193
186
  // Check the speaker data in the new caption
@@ -197,7 +190,6 @@ describe('plugin-meetings', () => {
197
190
  it('should process new interim captions correctly', () => {
198
191
  let transcriptData = fakeMeeting.transcription;
199
192
  let transcriptId = fakeVoiceaPayload.transcriptId;
200
- delete fakeVoiceaPayload.transcript;
201
193
 
202
194
  transcriptData.captions.splice(transcriptData.length - 1, 1);
203
195
  fakeVoiceaPayload.isFinal = false;
@@ -232,7 +224,6 @@ describe('plugin-meetings', () => {
232
224
  it('should process interim captions with an existing one correctly', () => {
233
225
  let transcriptData = fakeMeeting.transcription;
234
226
  let transcriptId = fakeVoiceaPayload.transcriptId;
235
- delete fakeVoiceaPayload.transcript;
236
227
  fakeVoiceaPayload.isFinal = false;
237
228
 
238
229
  processNewCaptions({