@webex/plugin-meetings 3.0.0-stream-classes.3 → 3.0.0-stream-classes.5

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 (77) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +5 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/interpretation/index.js +1 -1
  6. package/dist/interpretation/siLanguage.js +1 -1
  7. package/dist/locus-info/index.js +25 -1
  8. package/dist/locus-info/index.js.map +1 -1
  9. package/dist/locus-info/parser.js +5 -0
  10. package/dist/locus-info/parser.js.map +1 -1
  11. package/dist/meeting/index.js +72 -15
  12. package/dist/meeting/index.js.map +1 -1
  13. package/dist/meeting/locusMediaRequest.js +1 -1
  14. package/dist/meeting/locusMediaRequest.js.map +1 -1
  15. package/dist/meeting/request.js +24 -14
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.js +33 -3
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/meetings/index.js +3 -4
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/metrics/constants.js +4 -1
  22. package/dist/metrics/constants.js.map +1 -1
  23. package/dist/multistream/remoteMediaManager.js +46 -9
  24. package/dist/multistream/remoteMediaManager.js.map +1 -1
  25. package/dist/reachability/index.js +2 -11
  26. package/dist/reachability/index.js.map +1 -1
  27. package/dist/reachability/request.js.map +1 -1
  28. package/dist/reconnection-manager/index.js +26 -22
  29. package/dist/reconnection-manager/index.js.map +1 -1
  30. package/dist/roap/index.js +5 -7
  31. package/dist/roap/index.js.map +1 -1
  32. package/dist/roap/request.js +1 -0
  33. package/dist/roap/request.js.map +1 -1
  34. package/dist/roap/turnDiscovery.js +3 -4
  35. package/dist/roap/turnDiscovery.js.map +1 -1
  36. package/dist/types/constants.d.ts +1 -0
  37. package/dist/types/meeting/index.d.ts +16 -2
  38. package/dist/types/meeting/locusMediaRequest.d.ts +1 -2
  39. package/dist/types/meeting/request.d.ts +2 -1
  40. package/dist/types/meeting/util.d.ts +9 -1
  41. package/dist/types/metrics/constants.d.ts +3 -0
  42. package/dist/types/multistream/remoteMediaManager.d.ts +9 -1
  43. package/dist/types/reachability/index.d.ts +0 -6
  44. package/dist/types/reachability/request.d.ts +1 -1
  45. package/dist/types/roap/request.d.ts +2 -1
  46. package/package.json +1 -1
  47. package/src/constants.ts +3 -2
  48. package/src/locus-info/index.ts +33 -2
  49. package/src/locus-info/parser.ts +7 -0
  50. package/src/meeting/index.ts +59 -16
  51. package/src/meeting/locusMediaRequest.ts +2 -3
  52. package/src/meeting/request.ts +16 -6
  53. package/src/meeting/util.ts +36 -2
  54. package/src/meetings/index.ts +2 -2
  55. package/src/metrics/constants.ts +3 -0
  56. package/src/multistream/remoteMediaManager.ts +41 -4
  57. package/src/reachability/index.ts +4 -10
  58. package/src/reachability/request.ts +1 -1
  59. package/src/reconnection-manager/index.ts +13 -11
  60. package/src/roap/index.ts +2 -4
  61. package/src/roap/request.ts +2 -1
  62. package/src/roap/turnDiscovery.ts +2 -3
  63. package/test/integration/spec/converged-space-meetings.js +7 -7
  64. package/test/integration/spec/journey.js +85 -103
  65. package/test/integration/spec/space-meeting.js +9 -9
  66. package/test/unit/spec/locus-info/index.js +118 -1
  67. package/test/unit/spec/meeting/index.js +95 -30
  68. package/test/unit/spec/meeting/locusMediaRequest.ts +1 -2
  69. package/test/unit/spec/meeting/request.js +23 -0
  70. package/test/unit/spec/meeting/utils.js +73 -4
  71. package/test/unit/spec/meetings/index.js +68 -3
  72. package/test/unit/spec/multistream/remoteMediaManager.ts +10 -2
  73. package/test/unit/spec/reachability/index.ts +8 -8
  74. package/test/unit/spec/reconnection-manager/index.js +21 -0
  75. package/test/unit/spec/roap/index.ts +5 -1
  76. package/test/unit/spec/roap/turnDiscovery.ts +11 -4
  77. package/test/utils/integrationTestUtils.js +4 -4
@@ -22,6 +22,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
22
22
  * @param {String} options.mediaId
23
23
  * @param {String} options.correlationId
24
24
  * @param {String} options.meetingId
25
+ * @param {IP_VERSION} options.ipVersion only required for offers
25
26
  * @returns {Promise} returns the response/failure of the request
26
27
  */
27
28
  sendRoap(options: {
@@ -29,7 +30,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
29
30
  locusSelfUrl: string;
30
31
  mediaId: string;
31
32
  meetingId: string;
32
- ipVersion: IP_VERSION;
33
+ ipVersion?: IP_VERSION;
33
34
  locusMediaRequest?: LocusMediaRequest;
34
35
  }): Promise<{
35
36
  mediaConnections: any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "3.0.0-stream-classes.3",
3
+ "version": "3.0.0-stream-classes.5",
4
4
  "description": "",
5
5
  "license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
6
6
  "contributors": [
package/src/constants.ts CHANGED
@@ -388,6 +388,7 @@ export const MEETING_REMOVED_REASON = {
388
388
  USER_ENDED_SHARE_STREAMS: 'USER_ENDED_SHARE_STREAMS', // user triggered stop share
389
389
  NO_MEETINGS_TO_SYNC: 'NO_MEETINGS_TO_SYNC', // After the syncMeeting no meeting exists
390
390
  MEETING_CONNECTION_FAILED: 'MEETING_CONNECTION_FAILED', // meeting failed to connect due to ice failures or firewall issue
391
+ LOCUS_DTO_SYNC_FAILED: 'LOCUS_DTO_SYNC_FAILED', // failed to get any Locus DTO for that meeting
391
392
  };
392
393
 
393
394
  // One one one calls ends for the following reasons
@@ -1242,8 +1243,8 @@ export const DEFAULT_MEETING_INFO_REQUEST_BODY = {
1242
1243
  /** the values for IP_VERSION are fixed and defined in Orpheus API */
1243
1244
  export const IP_VERSION = {
1244
1245
  unknown: 0,
1245
- only_ipv4: 4,
1246
- only_ipv6: 6,
1246
+ only_ipv4: 4, // we know we have ipv4, we don't know or we don't have ipv6
1247
+ only_ipv6: 6, // we know we have ipv6, we don't know or we don't have ipv4
1247
1248
  ipv4_and_ipv6: 1,
1248
1249
  } as const;
1249
1250
 
@@ -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();
@@ -3,6 +3,9 @@ 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
@@ -293,6 +296,10 @@ export default class Parser {
293
296
  // the incoming locus has baseSequence from the future, so it is out-of-order,
294
297
  // we are missing 1 or more locus that should be in front of it, we need to wait for it
295
298
  comparison = WAIT;
299
+
300
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LOCUS_DELTA_OUT_OF_ORDER, {
301
+ stack: new Error().stack,
302
+ });
296
303
  }
297
304
  break;
298
305
  default:
@@ -544,7 +544,8 @@ export default class Meeting extends StatelessWebexPlugin {
544
544
  meetingJoinUrl: any;
545
545
  meetingNumber: any;
546
546
  meetingState: any;
547
- permissionToken: any;
547
+ permissionToken: string;
548
+ permissionTokenPayload: any;
548
549
  resourceId: any;
549
550
  resourceUrl: string;
550
551
  selfId: string;
@@ -3024,7 +3025,8 @@ export default class Meeting extends StatelessWebexPlugin {
3024
3025
  webexMeetingInfo?.hostId ||
3025
3026
  this.owner;
3026
3027
  this.permissionToken = webexMeetingInfo?.permissionToken;
3027
- this.setSelfUserPolicies(this.permissionToken);
3028
+ this.setPermissionTokenPayload(webexMeetingInfo?.permissionToken);
3029
+ this.setSelfUserPolicies();
3028
3030
  // Need to populate environment when sending CA event
3029
3031
  this.environment = locusMeetingObject?.info.channel || webexMeetingInfo?.channel;
3030
3032
  }
@@ -3288,11 +3290,20 @@ export default class Meeting extends StatelessWebexPlugin {
3288
3290
 
3289
3291
  /**
3290
3292
  * Sets the self user policies based on the contents of the permission token
3293
+ * @returns {void}
3294
+ */
3295
+ setSelfUserPolicies() {
3296
+ this.selfUserPolicies = this.permissionTokenPayload?.permission?.userPolicies;
3297
+ }
3298
+
3299
+ /**
3300
+ * Sets the permission token payload on the class instance
3301
+ *
3291
3302
  * @param {String} permissionToken
3292
3303
  * @returns {void}
3293
3304
  */
3294
- setSelfUserPolicies(permissionToken: string) {
3295
- this.selfUserPolicies = jwt.decode(permissionToken)?.permission?.userPolicies;
3305
+ public setPermissionTokenPayload(permissionToken: string) {
3306
+ this.permissionTokenPayload = jwt.decode(permissionToken);
3296
3307
  }
3297
3308
 
3298
3309
  /**
@@ -5100,7 +5111,9 @@ export default class Meeting extends StatelessWebexPlugin {
5100
5111
  errors: [
5101
5112
  // @ts-ignore
5102
5113
  this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
5103
- CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE
5114
+ {
5115
+ clientErrorCode: CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE,
5116
+ }
5104
5117
  ),
5105
5118
  ],
5106
5119
  },
@@ -5636,7 +5649,9 @@ export default class Meeting extends StatelessWebexPlugin {
5636
5649
  errors: [
5637
5650
  // @ts-ignore
5638
5651
  this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
5639
- CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE
5652
+ {
5653
+ clientErrorCode: CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE,
5654
+ }
5640
5655
  ),
5641
5656
  ],
5642
5657
  },
@@ -5671,8 +5686,16 @@ export default class Meeting extends StatelessWebexPlugin {
5671
5686
  meetingId: this.id,
5672
5687
  },
5673
5688
  });
5689
+ LoggerProxy.logger.info(
5690
+ `${LOG_HEADER} successfully established media connection, type=${connectionType}`
5691
+ );
5692
+
5693
+ // We can log ReceiveSlot SSRCs only after the SDP exchange, so doing it here:
5694
+ this.remoteMediaManager?.logAllReceiveSlots();
5674
5695
  })
5675
5696
  .catch((error) => {
5697
+ LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
5698
+
5676
5699
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
5677
5700
  correlation_id: this.correlationId,
5678
5701
  locus_id: this.locusUrl.split('/').pop(),
@@ -5712,11 +5735,6 @@ export default class Meeting extends StatelessWebexPlugin {
5712
5735
  this.unsetPeerConnections();
5713
5736
  }
5714
5737
 
5715
- LoggerProxy.logger.error(
5716
- `${LOG_HEADER} Error adding media failed to initiate PC and send request, `,
5717
- error
5718
- );
5719
-
5720
5738
  // Upload logs on error while adding media
5721
5739
  Trigger.trigger(
5722
5740
  this,
@@ -5872,12 +5890,15 @@ export default class Meeting extends StatelessWebexPlugin {
5872
5890
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.UPDATE_MEDIA, options);
5873
5891
  }
5874
5892
 
5875
- if (
5876
- this.isMultistream &&
5877
- (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined)
5878
- ) {
5893
+ if (this.isMultistream) {
5894
+ if (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined) {
5895
+ throw new Error(
5896
+ 'toggling shareAudioEnabled or shareVideoEnabled in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
5897
+ );
5898
+ }
5899
+ } else if (shareAudioEnabled !== undefined) {
5879
5900
  throw new Error(
5880
- 'toggling shareAudioEnabled or shareVideoEnabled in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
5901
+ 'toggling shareAudioEnabled in a transcoded meeting is not supported as of now'
5881
5902
  );
5882
5903
  }
5883
5904
 
@@ -7252,4 +7273,26 @@ export default class Meeting extends StatelessWebexPlugin {
7252
7273
  }
7253
7274
  }
7254
7275
  }
7276
+
7277
+ /**
7278
+ * Gets the time left in seconds till the permission token expires
7279
+ * (from the time the function has been fired)
7280
+ *
7281
+ * @returns {number} time left in seconds
7282
+ */
7283
+ public getPermissionTokenTimeLeftInSec(): number | undefined {
7284
+ if (!this.permissionTokenPayload) {
7285
+ return undefined;
7286
+ }
7287
+
7288
+ const permissionTokenExpValue = Number(this.permissionTokenPayload.exp);
7289
+
7290
+ // using new Date instead of Date.now() to allow for accurate unit testing
7291
+ // https://github.com/sinonjs/fake-timers/issues/321
7292
+ const now = new Date().getTime();
7293
+
7294
+ // substract current time from the permissionTokenExp
7295
+ // (permissionTokenExp is a epoch timestamp, not a time to live duration)
7296
+ return (permissionTokenExpValue - now) / 1000;
7297
+ }
7255
7298
  }
@@ -16,7 +16,7 @@ export type RoapRequest = {
16
16
  reachability: any;
17
17
  sequence?: any;
18
18
  joinCookie: any; // any, because this is opaque to the client, we pass whatever object we got from one backend component (Orpheus) to the other (Locus)
19
- ipVersion: IP_VERSION;
19
+ ipVersion?: IP_VERSION;
20
20
  };
21
21
 
22
22
  export type LocalMuteRequest = {
@@ -28,7 +28,6 @@ export type LocalMuteRequest = {
28
28
  audioMuted?: boolean;
29
29
  videoMuted?: boolean;
30
30
  };
31
- ipVersion: IP_VERSION;
32
31
  };
33
32
 
34
33
  export type Request = RoapRequest | LocalMuteRequest;
@@ -204,7 +203,7 @@ export class LocusMediaRequest extends WebexPlugin {
204
203
  correlationId: this.config.correlationId,
205
204
  clientMediaPreferences: {
206
205
  preferTranscoding: this.config.preferTranscoding,
207
- ipver: request.ipVersion,
206
+ ipver: request.type === 'RoapMessage' ? request.ipVersion : undefined,
208
207
  },
209
208
  };
210
209
 
@@ -108,6 +108,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
108
108
  sipUri: string;
109
109
  deviceUrl: string;
110
110
  locusUrl: string;
111
+ locusClusterUrl: string;
111
112
  resourceId: string;
112
113
  correlationId: string;
113
114
  ensureConversation: boolean;
@@ -124,7 +125,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
124
125
  locale?: string;
125
126
  deviceCapabilities?: Array<string>;
126
127
  liveAnnotationSupported: boolean;
127
- ipVersion: IP_VERSION;
128
+ ipVersion?: IP_VERSION;
128
129
  }) {
129
130
  const {
130
131
  asResourceOccupant,
@@ -133,6 +134,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
133
134
  permissionToken,
134
135
  deviceUrl,
135
136
  locusUrl,
137
+ locusClusterUrl,
136
138
  resourceId,
137
139
  correlationId,
138
140
  ensureConversation,
@@ -214,10 +216,18 @@ export default class MeetingRequest extends StatelessWebexPlugin {
214
216
  url = `${locusUrl}/${PARTICIPANT}`;
215
217
  } else if (inviteeAddress || meetingNumber) {
216
218
  try {
217
- // @ts-ignore
218
- await this.webex.internal.services.waitForCatalog('postauth');
219
- // @ts-ignore
220
- url = `${this.webex.internal.services.get('locus')}/${LOCI}/${CALL}`;
219
+ let clusterUrl;
220
+
221
+ if (locusClusterUrl) {
222
+ clusterUrl = `https://${locusClusterUrl}/locus/api/v1`;
223
+ } else {
224
+ // @ts-ignore
225
+ await this.webex.internal.services.waitForCatalog('postauth');
226
+ // @ts-ignore
227
+ clusterUrl = this.webex.internal.services.get('locus');
228
+ }
229
+
230
+ url = `${clusterUrl}/${LOCI}/${CALL}`;
221
231
  body.invitee = {
222
232
  address: inviteeAddress || `wbxmn:${meetingNumber}`,
223
233
  };
@@ -406,7 +416,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
406
416
  `Meeting:request#getLocusDTO --> Error getting latest locus, error ${err}`
407
417
  );
408
418
 
409
- return err;
419
+ throw err;
410
420
  });
411
421
  }
412
422
 
@@ -13,7 +13,9 @@ import {
13
13
  FULL_STATE,
14
14
  SELF_POLICY,
15
15
  EVENT_TRIGGERS,
16
+ IP_VERSION,
16
17
  } from '../constants';
18
+ import BrowserDetection from '../common/browser-detection';
17
19
  import IntentToJoinError from '../common/errors/intent-to-join';
18
20
  import JoinMeetingError from '../common/errors/join-meeting';
19
21
  import ParameterError from '../common/errors/parameter';
@@ -73,7 +75,6 @@ const MeetingUtil = {
73
75
  audioMuted,
74
76
  videoMuted,
75
77
  },
76
- ipVersion: meeting.getWebexObject().meetings.reachability.getIpVersion(),
77
78
  })
78
79
  .then((response) => {
79
80
  // @ts-ignore
@@ -92,6 +93,38 @@ const MeetingUtil = {
92
93
 
93
94
  isPinOrGuest: (err) => err?.body?.errorCode && INTENT_TO_JOIN.includes(err.body.errorCode),
94
95
 
96
+ /**
97
+ * Returns the current state of knowledge about whether we are on an ipv4-only or ipv6-only or mixed (ipv4 and ipv6) network.
98
+ * The return value matches the possible values of "ipver" parameter used by the backend APIs.
99
+ *
100
+ * @param {Object} webex webex instance
101
+ * @returns {IP_VERSION|undefined} ipver value to be passed to the backend APIs or undefined if we should not pass any value to the backend
102
+ */
103
+ getIpVersion(webex: any): IP_VERSION | undefined {
104
+ const {supportsIpV4, supportsIpV6} = webex.internal.device.ipNetworkDetector;
105
+
106
+ if (BrowserDetection().isBrowser('firefox')) {
107
+ // our ipv6 solution relies on FQDN ICE candidates, but Firefox doesn't support them,
108
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1713128
109
+ // so for Firefox we don't want the backend to activate the "ipv6 feature"
110
+ return undefined;
111
+ }
112
+
113
+ if (supportsIpV4 && supportsIpV6) {
114
+ return IP_VERSION.ipv4_and_ipv6;
115
+ }
116
+
117
+ if (supportsIpV4) {
118
+ return IP_VERSION.only_ipv4;
119
+ }
120
+
121
+ if (supportsIpV6) {
122
+ return IP_VERSION.only_ipv6;
123
+ }
124
+
125
+ return IP_VERSION.unknown;
126
+ },
127
+
95
128
  joinMeeting: (meeting, options) => {
96
129
  if (!meeting) {
97
130
  return Promise.reject(new ParameterError('You need a meeting object.'));
@@ -113,6 +146,7 @@ const MeetingUtil = {
113
146
  meetingNumber: meeting.meetingNumber,
114
147
  deviceUrl: meeting.deviceUrl,
115
148
  locusUrl: meeting.locusUrl,
149
+ locusClusterUrl: meeting.meetingInfo?.locusClusterUrl,
116
150
  correlationId: meeting.correlationId,
117
151
  roapMessage: options.roapMessage,
118
152
  permissionToken: meeting.permissionToken,
@@ -126,7 +160,7 @@ const MeetingUtil = {
126
160
  locale: options.locale,
127
161
  deviceCapabilities: options.deviceCapabilities,
128
162
  liveAnnotationSupported: options.liveAnnotationSupported,
129
- ipVersion: meeting.getWebexObject().meetings.reachability.getIpVersion(),
163
+ ipVersion: MeetingUtil.getIpVersion(meeting.getWebexObject()),
130
164
  })
131
165
  .then((res) => {
132
166
  // @ts-ignore
@@ -887,6 +887,7 @@ export default class Meetings extends WebexPlugin {
887
887
  'Meetings:index#uploadLogs --> Upload logs for meeting completed.',
888
888
  uploadResult
889
889
  );
890
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPLOAD_LOGS_SUCCESS, options);
890
891
  Trigger.trigger(
891
892
  this,
892
893
  {
@@ -921,8 +922,7 @@ export default class Meetings extends WebexPlugin {
921
922
  );
922
923
 
923
924
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPLOAD_LOGS_FAILURE, {
924
- // @ts-ignore - seems like typo
925
- meetingId: options.meetingsId,
925
+ ...options,
926
926
  reason: uploadError.message,
927
927
  stack: uploadError.stack,
928
928
  code: uploadError.code,
@@ -40,6 +40,7 @@ const BEHAVIORAL_METRICS = {
40
40
  PEERCONNECTION_FAILURE: 'js_sdk_peerConnection_failures',
41
41
  INVALID_ICE_CANDIDATE: 'js_sdk_invalid_ice_candidate',
42
42
  UPLOAD_LOGS_FAILURE: 'js_sdk_upload_logs_failure',
43
+ UPLOAD_LOGS_SUCCESS: 'js_sdk_upload_logs_success',
43
44
  RECEIVE_TRANSCRIPTION_FAILURE: 'js_sdk_receive_transcription_failure',
44
45
  FETCH_MEETING_INFO_V1_SUCCESS: 'js_sdk_fetch_meeting_info_v1_success',
45
46
  FETCH_MEETING_INFO_V1_FAILURE: 'js_sdk_fetch_meeting_info_v1_failure',
@@ -54,6 +55,8 @@ const BEHAVIORAL_METRICS = {
54
55
  MOVE_FROM_FAILURE: 'js_sdk_move_from_failure',
55
56
  TURN_DISCOVERY_FAILURE: 'js_sdk_turn_discovery_failure',
56
57
  MEETING_INFO_POLICY_ERROR: 'js_sdk_meeting_info_policy_error',
58
+ LOCUS_DELTA_SYNC_FAILED: 'js_sdk_locus_delta_sync_failed',
59
+ LOCUS_DELTA_OUT_OF_ORDER: 'js_sdk_locus_delta_ooo',
57
60
  };
58
61
 
59
62
  export {BEHAVIORAL_METRICS as default};
@@ -473,6 +473,8 @@ export class RemoteMediaManager extends EventsScope {
473
473
  if (!this.started) {
474
474
  throw new Error('setLayout() called before start()');
475
475
  }
476
+ LoggerProxy.logger.log(`RemoteMediaManager#setLayout --> new layout selected: ${layoutId}`);
477
+
476
478
  this.currentLayoutId = layoutId;
477
479
  this.currentLayout = cloneDeep(this.config.video.layouts[this.currentLayoutId]);
478
480
 
@@ -725,10 +727,12 @@ export class RemoteMediaManager extends EventsScope {
725
727
  /**
726
728
  * Logs the state of the receive slots
727
729
  */
728
- private logReceieveSlots() {
730
+ private logMainVideoReceiveSlots() {
729
731
  let logMessage = '';
730
732
  forEach(this.receiveSlotAllocations.activeSpeaker, (group, groupName) => {
731
- logMessage += `group: ${groupName}\n${group.slots.map((slot) => slot.logString).join(' ')}`;
733
+ logMessage += `\ngroup: ${groupName}\n${group.slots
734
+ .map((slot) => slot.logString)
735
+ .join(', ')}`;
732
736
  });
733
737
 
734
738
  logMessage += '\nreceiverSelected:\n';
@@ -737,10 +741,43 @@ export class RemoteMediaManager extends EventsScope {
737
741
  });
738
742
 
739
743
  LoggerProxy.logger.log(
740
- `RemoteMediaManager#updateVideoReceiveSlots --> receive slots updated: unused=${this.slots.video.unused.length}, activeSpeaker=${this.slots.video.activeSpeaker.length}, receiverSelected=${this.slots.video.receiverSelected.length}\n${logMessage}`
744
+ `RemoteMediaManager#logMainVideoReceiveSlots --> MAIN VIDEO receive slots: unused=${this.slots.video.unused.length}, activeSpeaker=${this.slots.video.activeSpeaker.length}, receiverSelected=${this.slots.video.receiverSelected.length}${logMessage}`
745
+ );
746
+ }
747
+
748
+ /** logs main audio slots */
749
+ private logMainAudioReceiveSlots() {
750
+ LoggerProxy.logger.log(
751
+ `RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio
752
+ .map((slot) => slot.logString)
753
+ .join(', ')}`
741
754
  );
742
755
  }
743
756
 
757
+ /** logs slides video slots */
758
+ private logSlidesVideoReceiveSlots() {
759
+ LoggerProxy.logger.log(
760
+ `RemoteMediaManager#logSlidesVideoReceiveSlots --> SLIDES VIDEO receive slot: ${this.slots.screenShare.video?.logString}`
761
+ );
762
+ }
763
+
764
+ /** logs slides audio slots */
765
+ private logSlidesAudioReceiveSlots() {
766
+ LoggerProxy.logger.log(
767
+ `RemoteMediaManager#logSlidesAudioReceiveSlots --> SLIDES AUDIO receive slots: ${this.slots.screenShare.audio
768
+ .map((slot) => slot.logString)
769
+ .join(', ')}`
770
+ );
771
+ }
772
+
773
+ /** Logs all current receive slots */
774
+ public logAllReceiveSlots() {
775
+ this.logMainVideoReceiveSlots();
776
+ this.logMainAudioReceiveSlots();
777
+ this.logSlidesVideoReceiveSlots();
778
+ this.logSlidesAudioReceiveSlots();
779
+ }
780
+
744
781
  /**
745
782
  * Makes sure we have the right number of receive slots created for the current layout
746
783
  * and allocates them to the right video panes / pane groups
@@ -765,7 +802,7 @@ export class RemoteMediaManager extends EventsScope {
765
802
  // allocate receiver selected
766
803
  this.allocateSlotsToReceiverSelectedVideoPaneGroups();
767
804
 
768
- this.logReceieveSlots();
805
+ this.logMainVideoReceiveSlots();
769
806
 
770
807
  // If this is the initial layout, there may be some "unused" slots left because of the preallocation
771
808
  // done in this.preallocateVideoReceiveSlots(), so release them now
@@ -7,7 +7,9 @@
7
7
  import _ from 'lodash';
8
8
 
9
9
  import LoggerProxy from '../common/logs/logger-proxy';
10
- import {ICE_GATHERING_STATE, CONNECTION_STATE, REACHABILITY, IP_VERSION} from '../constants';
10
+ import MeetingUtil from '../meeting/util';
11
+
12
+ import {ICE_GATHERING_STATE, CONNECTION_STATE, REACHABILITY} from '../constants';
11
13
 
12
14
  import ReachabilityRequest from './request';
13
15
 
@@ -71,7 +73,7 @@ export default class Reachability {
71
73
  // Fetch clusters and measure latency
72
74
  try {
73
75
  const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
74
- this.getIpVersion()
76
+ MeetingUtil.getIpVersion(this.webex)
75
77
  );
76
78
 
77
79
  // Perform Reachability Check
@@ -134,14 +136,6 @@ export default class Reachability {
134
136
  return reachable;
135
137
  }
136
138
 
137
- /**
138
- * Returns what we know about the IP version of the networks we're connected to.
139
- * @returns {IP_VERSION}
140
- */
141
- getIpVersion(): IP_VERSION {
142
- return IP_VERSION.unknown;
143
- }
144
-
145
139
  /**
146
140
  * Generate peerConnection config settings
147
141
  * @param {object} cluster
@@ -33,7 +33,7 @@ class ReachabilityRequest {
33
33
  * @param {IP_VERSION} ipVersion information about current ip network we're on
34
34
  * @returns {Promise}
35
35
  */
36
- getClusters = (ipVersion: IP_VERSION): Promise<{clusters: ClusterList; joinCookie: any}> =>
36
+ getClusters = (ipVersion?: IP_VERSION): Promise<{clusters: ClusterList; joinCookie: any}> =>
37
37
  this.webex
38
38
  .request({
39
39
  method: HTTP_VERBS.GET,
@@ -443,17 +443,19 @@ export default class ReconnectionManager {
443
443
  }
444
444
  }
445
445
 
446
- try {
447
- LoggerProxy.logger.info(
448
- 'ReconnectionManager:index#executeReconnection --> Updating meeting data from server.'
449
- );
450
- await this.webex.meetings.syncMeetings();
451
- } catch (syncError) {
452
- LoggerProxy.logger.info(
453
- 'ReconnectionManager:index#executeReconnection --> Unable to sync meetings, reconnecting.',
454
- syncError
455
- );
456
- throw new NeedsRetryError(syncError);
446
+ if (!this.webex.credentials.isUnverifiedGuest) {
447
+ try {
448
+ LoggerProxy.logger.info(
449
+ 'ReconnectionManager:index#executeReconnection --> Updating meeting data from server.'
450
+ );
451
+ await this.webex.meetings.syncMeetings();
452
+ } catch (syncError) {
453
+ LoggerProxy.logger.info(
454
+ 'ReconnectionManager:index#executeReconnection --> Unable to sync meetings, reconnecting.',
455
+ syncError
456
+ );
457
+ throw new NeedsRetryError(syncError);
458
+ }
457
459
  }
458
460
 
459
461
  // TODO: try to improve this logic as the reconnection manager saves the instance of deleted meeting object
package/src/roap/index.ts CHANGED
@@ -7,6 +7,7 @@ import LoggerProxy from '../common/logs/logger-proxy';
7
7
  import RoapRequest from './request';
8
8
  import TurnDiscovery from './turnDiscovery';
9
9
  import Meeting from '../meeting';
10
+ import MeetingUtil from '../meeting/util';
10
11
 
11
12
  /**
12
13
  * Roap options
@@ -100,7 +101,6 @@ export default class Roap extends StatelessWebexPlugin {
100
101
  mediaId: options.mediaId,
101
102
  meetingId: meeting.id,
102
103
  locusMediaRequest: meeting.locusMediaRequest,
103
- ipVersion: meeting.webex.meetings.reachability.getIpVersion(),
104
104
  })
105
105
  .then(() => {
106
106
  LoggerProxy.logger.log(`Roap:index#sendRoapOK --> ROAP OK sent with seq ${options.seq}`);
@@ -135,7 +135,6 @@ export default class Roap extends StatelessWebexPlugin {
135
135
  mediaId: options.mediaId,
136
136
  meetingId: meeting.id,
137
137
  locusMediaRequest: meeting.locusMediaRequest,
138
- ipVersion: meeting.webex.meetings.reachability.getIpVersion(),
139
138
  });
140
139
  }
141
140
 
@@ -165,7 +164,6 @@ export default class Roap extends StatelessWebexPlugin {
165
164
  mediaId: options.mediaId,
166
165
  meetingId: meeting.id,
167
166
  locusMediaRequest: meeting.locusMediaRequest,
168
- ipVersion: meeting.webex.meetings.reachability.getIpVersion(),
169
167
  })
170
168
  .then(() => {
171
169
  LoggerProxy.logger.log(
@@ -204,7 +202,7 @@ export default class Roap extends StatelessWebexPlugin {
204
202
  meetingId: meeting.id,
205
203
  preferTranscoding: !meeting.isMultistream,
206
204
  locusMediaRequest: meeting.locusMediaRequest,
207
- ipVersion: meeting.webex.meetings.reachability.getIpVersion(),
205
+ ipVersion: MeetingUtil.getIpVersion(meeting.webex),
208
206
  })
209
207
  .then(({locus, mediaConnections}) => {
210
208
  if (mediaConnections) {
@@ -63,6 +63,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
63
63
  * @param {String} options.mediaId
64
64
  * @param {String} options.correlationId
65
65
  * @param {String} options.meetingId
66
+ * @param {IP_VERSION} options.ipVersion only required for offers
66
67
  * @returns {Promise} returns the response/failure of the request
67
68
  */
68
69
  async sendRoap(options: {
@@ -70,7 +71,7 @@ export default class RoapRequest extends StatelessWebexPlugin {
70
71
  locusSelfUrl: string;
71
72
  mediaId: string;
72
73
  meetingId: string;
73
- ipVersion: IP_VERSION;
74
+ ipVersion?: IP_VERSION;
74
75
  locusMediaRequest?: LocusMediaRequest;
75
76
  }) {
76
77
  const {roapMessage, locusSelfUrl, mediaId, meetingId, locusMediaRequest, ipVersion} = options;