@webex/plugin-meetings 3.10.0-webex-services-ready.1 → 3.10.0-webex-services-ready.2

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.
@@ -6,9 +6,11 @@ export declare const ObjectType: {
6
6
  readonly mediaShare: "mediashare";
7
7
  readonly info: "info";
8
8
  readonly fullState: "fullstate";
9
+ readonly links: "links";
9
10
  };
10
11
  export type ObjectType = Enum<typeof ObjectType>;
11
12
  export declare const ObjectTypeToLocusKeyMap: {
13
+ links: string;
12
14
  info: string;
13
15
  fullstate: string;
14
16
  self: string;
@@ -1,7 +1,7 @@
1
1
  import EventsScope from '../common/events/events-scope';
2
2
  import { LOCUSEVENT } from '../constants';
3
3
  import HashTreeParser, { DataSet, HashTreeMessage, HashTreeObject } from '../hashTree/hashTreeParser';
4
- import { LocusDTO } from './types';
4
+ import { Links, LocusDTO } from './types';
5
5
  export type LocusLLMEvent = {
6
6
  data: {
7
7
  eventType: typeof LOCUSEVENT.HASH_TREE_DATA_UPDATED;
@@ -46,8 +46,7 @@ export default class LocusInfo extends EventsScope {
46
46
  mediaShares: any;
47
47
  replace: any;
48
48
  url: any;
49
- services: any;
50
- resources: any;
49
+ links?: Links;
51
50
  mainSessionLocusCache: any;
52
51
  self: any;
53
52
  hashTreeParser?: HashTreeParser;
@@ -278,21 +277,12 @@ export default class LocusInfo extends EventsScope {
278
277
  */
279
278
  updateCreated(created: object): void;
280
279
  /**
281
- * @param {Object} services
280
+ * Updates links and emits appropriate events if services or resources have changed
281
+ * @param {Object} links
282
282
  * @returns {undefined}
283
283
  * @memberof LocusInfo
284
284
  */
285
- updateServices(services: Record<'breakout' | 'record', {
286
- url: string;
287
- }>): void;
288
- /**
289
- * @param {Object} resources
290
- * @returns {undefined}
291
- * @memberof LocusInfo
292
- */
293
- updateResources(resources: Record<'webcastInstance', {
294
- url: string;
295
- }>): void;
285
+ updateLinks(links?: Links): void;
296
286
  /**
297
287
  * @param {Object} fullState
298
288
  * @returns {undefined}
@@ -10,6 +10,14 @@ export type LocusFullState = {
10
10
  state: string;
11
11
  type: string;
12
12
  };
13
+ export type Links = {
14
+ services: Record<'breakout' | 'record', {
15
+ url: string;
16
+ }>;
17
+ resources: Record<'webcastInstance' | 'visibleDataSets', {
18
+ url: string;
19
+ }>;
20
+ };
13
21
  export type LocusDTO = {
14
22
  controls?: any;
15
23
  fullState?: LocusFullState;
@@ -25,7 +33,7 @@ export type LocusDTO = {
25
33
  jsSdkMeta?: {
26
34
  removedParticipantIds: string[];
27
35
  };
28
- links?: any;
36
+ links?: Links;
29
37
  mediaShares?: any[];
30
38
  meetings?: any[];
31
39
  participants: any[];
@@ -448,7 +448,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
448
448
  }, _callee7);
449
449
  }))();
450
450
  },
451
- version: "3.10.0-webex-services-ready.1"
451
+ version: "3.10.0-webex-services-ready.2"
452
452
  });
453
453
  var _default = exports.default = Webinar;
454
454
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -43,12 +43,12 @@
43
43
  "@webex/eslint-config-legacy": "0.0.0",
44
44
  "@webex/jest-config-legacy": "0.0.0",
45
45
  "@webex/legacy-tools": "0.0.0",
46
- "@webex/plugin-rooms": "3.10.0-webex-services-ready.1",
47
- "@webex/test-helper-chai": "3.10.0-webex-services-ready.0",
48
- "@webex/test-helper-mocha": "3.10.0-webex-services-ready.0",
49
- "@webex/test-helper-mock-webex": "3.10.0-webex-services-ready.0",
50
- "@webex/test-helper-retry": "3.10.0-webex-services-ready.0",
51
- "@webex/test-helper-test-users": "3.10.0-webex-services-ready.0",
46
+ "@webex/plugin-rooms": "3.10.0-webex-services-ready.2",
47
+ "@webex/test-helper-chai": "3.10.0-webex-services-ready.1",
48
+ "@webex/test-helper-mocha": "3.10.0-webex-services-ready.1",
49
+ "@webex/test-helper-mock-webex": "3.10.0-webex-services-ready.1",
50
+ "@webex/test-helper-retry": "3.10.0-webex-services-ready.1",
51
+ "@webex/test-helper-test-users": "3.10.0-webex-services-ready.1",
52
52
  "chai": "^4.3.4",
53
53
  "chai-as-promised": "^7.1.1",
54
54
  "eslint": "^8.24.0",
@@ -60,23 +60,23 @@
60
60
  "typescript": "^4.7.4"
61
61
  },
62
62
  "dependencies": {
63
- "@webex/common": "3.10.0-webex-services-ready.0",
63
+ "@webex/common": "3.10.0-webex-services-ready.1",
64
64
  "@webex/event-dictionary-ts": "^1.0.1930",
65
65
  "@webex/internal-media-core": "2.20.3",
66
- "@webex/internal-plugin-conversation": "3.10.0-webex-services-ready.1",
67
- "@webex/internal-plugin-device": "3.10.0-webex-services-ready.1",
68
- "@webex/internal-plugin-llm": "3.10.0-webex-services-ready.1",
69
- "@webex/internal-plugin-mercury": "3.10.0-webex-services-ready.1",
70
- "@webex/internal-plugin-metrics": "3.10.0-webex-services-ready.1",
71
- "@webex/internal-plugin-support": "3.10.0-webex-services-ready.1",
72
- "@webex/internal-plugin-user": "3.10.0-webex-services-ready.1",
73
- "@webex/internal-plugin-voicea": "3.10.0-webex-services-ready.1",
74
- "@webex/media-helpers": "3.10.0-webex-services-ready.0",
75
- "@webex/plugin-people": "3.10.0-webex-services-ready.1",
76
- "@webex/plugin-rooms": "3.10.0-webex-services-ready.1",
66
+ "@webex/internal-plugin-conversation": "3.10.0-webex-services-ready.2",
67
+ "@webex/internal-plugin-device": "3.10.0-webex-services-ready.2",
68
+ "@webex/internal-plugin-llm": "3.10.0-webex-services-ready.2",
69
+ "@webex/internal-plugin-mercury": "3.10.0-webex-services-ready.2",
70
+ "@webex/internal-plugin-metrics": "3.10.0-webex-services-ready.2",
71
+ "@webex/internal-plugin-support": "3.10.0-webex-services-ready.2",
72
+ "@webex/internal-plugin-user": "3.10.0-webex-services-ready.2",
73
+ "@webex/internal-plugin-voicea": "3.10.0-webex-services-ready.2",
74
+ "@webex/media-helpers": "3.10.0-webex-services-ready.1",
75
+ "@webex/plugin-people": "3.10.0-webex-services-ready.2",
76
+ "@webex/plugin-rooms": "3.10.0-webex-services-ready.2",
77
77
  "@webex/ts-sdp": "^1.8.1",
78
78
  "@webex/web-capabilities": "^1.7.1",
79
- "@webex/webex-core": "3.10.0-webex-services-ready.1",
79
+ "@webex/webex-core": "3.10.0-webex-services-ready.2",
80
80
  "ampersand-collection": "^2.0.2",
81
81
  "bowser": "^2.11.0",
82
82
  "btoa": "^1.2.1",
@@ -93,5 +93,5 @@
93
93
  "//": [
94
94
  "TODO: upgrade jwt-decode when moving to node 18"
95
95
  ],
96
- "version": "3.10.0-webex-services-ready.1"
96
+ "version": "3.10.0-webex-services-ready.2"
97
97
  }
@@ -8,12 +8,14 @@ export const ObjectType = {
8
8
  mediaShare: 'mediashare',
9
9
  info: 'info',
10
10
  fullState: 'fullstate',
11
+ links: 'links',
11
12
  } as const;
12
13
 
13
14
  export type ObjectType = Enum<typeof ObjectType>;
14
15
 
15
16
  // mapping from ObjectType to top level LocusDTO keys
16
17
  export const ObjectTypeToLocusKeyMap = {
18
+ [ObjectType.links]: 'links',
17
19
  [ObjectType.info]: 'info',
18
20
  [ObjectType.fullState]: 'fullState',
19
21
  [ObjectType.self]: 'self',
@@ -38,7 +38,7 @@ import HashTreeParser, {
38
38
  LocusInfoUpdateType,
39
39
  } from '../hashTree/hashTreeParser';
40
40
  import {ObjectType, ObjectTypeToLocusKeyMap} from '../hashTree/types';
41
- import {LocusDTO, LocusFullState} from './types';
41
+ import {Links, LocusDTO, LocusFullState} from './types';
42
42
 
43
43
  export type LocusLLMEvent = {
44
44
  data: {
@@ -113,8 +113,7 @@ export default class LocusInfo extends EventsScope {
113
113
  mediaShares: any;
114
114
  replace: any;
115
115
  url: any;
116
- services: any;
117
- resources: any;
116
+ links?: Links;
118
117
  mainSessionLocusCache: any;
119
118
  self: any;
120
119
  hashTreeParser?: HashTreeParser;
@@ -360,8 +359,7 @@ export default class LocusInfo extends EventsScope {
360
359
  this.updateSelf(locus.self);
361
360
  this.updateHostInfo(locus.host);
362
361
  this.updateMediaShares(locus.mediaShares);
363
- this.updateServices(locus.links?.services);
364
- this.updateResources(locus.links?.resources);
362
+ this.updateLinks(locus.links);
365
363
  }
366
364
 
367
365
  /**
@@ -596,12 +594,13 @@ export default class LocusInfo extends EventsScope {
596
594
  this.hashTreeObjectId2ParticipantId.delete(object.htMeta.elementId.id);
597
595
  }
598
596
  break;
597
+ case ObjectType.links:
599
598
  case ObjectType.info:
600
599
  case ObjectType.fullState:
601
600
  case ObjectType.self:
602
601
  if (!object.data) {
603
602
  // self without data is handled inside HashTreeParser and results in LocusInfoUpdateType.MEETING_ENDED, so we should never get here
604
- // other types like info or fullstate - Locus should never send them without data
603
+ // all other types info, fullstate, etc - Locus should never send them without data
605
604
  LoggerProxy.logger.warn(
606
605
  `Locus-info:index#updateLocusFromHashTreeObject --> received ${type} object without data, this is not expected! version=${object.htMeta.elementId.version}`
607
606
  );
@@ -1032,8 +1031,7 @@ export default class LocusInfo extends EventsScope {
1032
1031
  this.updateMemberShip(locus.membership);
1033
1032
  this.updateIdentifiers(locus.identities);
1034
1033
  this.updateEmbeddedApps(locus.embeddedApps);
1035
- this.updateServices(locus.links?.services);
1036
- this.updateResources(locus.links?.resources);
1034
+ this.updateLinks(locus.links);
1037
1035
  this.compareAndUpdate();
1038
1036
  // update which required to compare different objects from locus
1039
1037
  }
@@ -1685,17 +1683,19 @@ export default class LocusInfo extends EventsScope {
1685
1683
  }
1686
1684
 
1687
1685
  /**
1688
- * @param {Object} services
1686
+ * Updates links and emits appropriate events if services or resources have changed
1687
+ * @param {Object} links
1689
1688
  * @returns {undefined}
1690
1689
  * @memberof LocusInfo
1691
1690
  */
1692
- updateServices(services: Record<'breakout' | 'record', {url: string}>) {
1693
- if (services && !isEqual(this.services, services)) {
1694
- this.services = services;
1691
+ updateLinks(links?: Links) {
1692
+ const {services, resources} = links || {};
1693
+
1694
+ if (services && !isEqual(this.links?.services, services)) {
1695
1695
  this.emitScoped(
1696
1696
  {
1697
1697
  file: 'locus-info',
1698
- function: 'updateServices',
1698
+ function: 'updateLinks',
1699
1699
  },
1700
1700
  LOCUSINFO.EVENTS.LINKS_SERVICES,
1701
1701
  {
@@ -1703,20 +1703,12 @@ export default class LocusInfo extends EventsScope {
1703
1703
  }
1704
1704
  );
1705
1705
  }
1706
- }
1707
1706
 
1708
- /**
1709
- * @param {Object} resources
1710
- * @returns {undefined}
1711
- * @memberof LocusInfo
1712
- */
1713
- updateResources(resources: Record<'webcastInstance', {url: string}>) {
1714
- if (resources && !isEqual(this.resources, resources)) {
1715
- this.resources = resources;
1707
+ if (resources && !isEqual(this.links?.resources, resources)) {
1716
1708
  this.emitScoped(
1717
1709
  {
1718
1710
  file: 'locus-info',
1719
- function: 'updateResources',
1711
+ function: 'updateLinks',
1720
1712
  },
1721
1713
  LOCUSINFO.EVENTS.LINKS_RESOURCES,
1722
1714
  {
@@ -1724,6 +1716,8 @@ export default class LocusInfo extends EventsScope {
1724
1716
  }
1725
1717
  );
1726
1718
  }
1719
+
1720
+ this.links = links;
1727
1721
  }
1728
1722
 
1729
1723
  /**
@@ -12,6 +12,11 @@ export type LocusFullState = {
12
12
  type: string;
13
13
  };
14
14
 
15
+ export type Links = {
16
+ services: Record<'breakout' | 'record', {url: string}>; // there exist also other services, but these are the ones we currently use
17
+ resources: Record<'webcastInstance' | 'visibleDataSets', {url: string}>; // there exist also other resources, but these are the ones we currently use
18
+ };
19
+
15
20
  export type LocusDTO = {
16
21
  controls?: any;
17
22
  fullState?: LocusFullState;
@@ -27,7 +32,7 @@ export type LocusDTO = {
27
32
  jsSdkMeta?: {
28
33
  removedParticipantIds: string[]; // list of ids of participants that are removed in the last update
29
34
  };
30
- links?: any;
35
+ links?: Links;
31
36
  mediaShares?: any[];
32
37
  meetings?: any[];
33
38
  participants: any[];
@@ -1,5 +1,5 @@
1
1
  import uuid from 'uuid';
2
- import {cloneDeep, isEqual, isEmpty} from 'lodash';
2
+ import {cloneDeep, isEqual, isEmpty, merge} from 'lodash';
3
3
  import jwtDecode from 'jwt-decode';
4
4
  // @ts-ignore - Fix this
5
5
  import {StatelessWebexPlugin} from '@webex/webex-core';
@@ -50,6 +50,11 @@ import {
50
50
  type MeetingTranscriptPayload,
51
51
  } from '@webex/internal-plugin-voicea';
52
52
 
53
+ import {
54
+ getBrowserMediaErrorCode,
55
+ isBrowserMediaError,
56
+ isBrowserMediaErrorName,
57
+ } from '@webex/internal-plugin-metrics/src/call-diagnostic/call-diagnostic-metrics.util';
53
58
  import {processNewCaptions} from './voicea-meeting';
54
59
 
55
60
  import {
@@ -5440,6 +5445,20 @@ export default class Meeting extends StatelessWebexPlugin {
5440
5445
  shouldRetry = false;
5441
5446
  }
5442
5447
 
5448
+ if (CallDiagnosticUtils.isBrowserMediaError(error)) {
5449
+ shouldRetry = false;
5450
+ // eslint-disable-next-line no-ex-assign
5451
+ error = merge({
5452
+ error: {
5453
+ body: {
5454
+ errorCode: CallDiagnosticUtils.getBrowserMediaErrorCode(error),
5455
+ message: error?.message,
5456
+ name: error?.name,
5457
+ },
5458
+ },
5459
+ });
5460
+ }
5461
+
5443
5462
  // we only want to call leave if join was successful and this was a retry or we won't be doing any more retries
5444
5463
  if (joined && (isRetry || !shouldRetry)) {
5445
5464
  try {
@@ -394,6 +394,24 @@ describe('plugin-meetings', () => {
394
394
  });
395
395
  });
396
396
 
397
+ it('should process locus update correctly when called with updated links', () => {
398
+ const newLinks = {
399
+ id: 'new-links',
400
+ visibleDataSets: ['dataset1', 'dataset2'],
401
+ };
402
+
403
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
404
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
405
+ updatedObjects: [{htMeta: {elementId: {type: 'links'}}, data: newLinks}],
406
+ });
407
+
408
+ // check onDeltaLocus() was called with correctly updated locus info
409
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
410
+ ...expectedLocusInfo,
411
+ links: newLinks,
412
+ });
413
+ });
414
+
397
415
  it('should process locus update correctly when called with updated LOCUS object', () => {
398
416
  // setup new updated locus that has many things missing
399
417
  const newLocusHtMeta = {elementId: {type: 'locus', version: 42}};
@@ -414,6 +432,7 @@ describe('plugin-meetings', () => {
414
432
  info: {id: 'fake-info'},
415
433
  fullState: {id: 'fake-full-state'},
416
434
  self: {id: 'fake-self'},
435
+ links: { id: 'fake-links' },
417
436
  mediaShares: expectedLocusInfo.mediaShares,
418
437
  // and now the new fields
419
438
  ...newLocus,
@@ -459,6 +478,7 @@ describe('plugin-meetings', () => {
459
478
  info: {id: 'fake-info'},
460
479
  fullState: {id: 'fake-full-state'},
461
480
  self: {id: 'fake-self'},
481
+ links: { id: 'fake-links' },
462
482
  mediaShares: expectedLocusInfo.mediaShares,
463
483
  participants: [], // empty means there were no participant updates
464
484
  jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
@@ -492,6 +512,7 @@ describe('plugin-meetings', () => {
492
512
  info: {id: 'fake-info'},
493
513
  fullState: {id: 'fake-full-state'},
494
514
  self: {id: 'fake-self'},
515
+ links: {id: 'fake-links'},
495
516
  mediaShares: expectedLocusInfo.mediaShares,
496
517
  // and now the new fields
497
518
  ...newLocus,
@@ -2771,7 +2792,7 @@ describe('plugin-meetings', () => {
2771
2792
  sinon.stub(locusInfo, "updateMemberShip");
2772
2793
  sinon.stub(locusInfo, "updateIdentifiers");
2773
2794
  sinon.stub(locusInfo, "updateEmbeddedApps");
2774
- sinon.stub(locusInfo, "updateResources");
2795
+ sinon.stub(locusInfo, "updateLinks");
2775
2796
  sinon.stub(locusInfo, "compareAndUpdate");
2776
2797
 
2777
2798
  locusInfo.updateLocusInfo(locus);
@@ -2805,7 +2826,7 @@ describe('plugin-meetings', () => {
2805
2826
  locusInfo.updateMemberShip = sinon.stub();
2806
2827
  locusInfo.updateIdentifiers = sinon.stub();
2807
2828
  locusInfo.updateEmbeddedApps = sinon.stub();
2808
- locusInfo.updateResources = sinon.stub();
2829
+ locusInfo.updateLinks = sinon.stub();
2809
2830
  locusInfo.compareAndUpdate = sinon.stub();
2810
2831
 
2811
2832
  locusInfo.updateLocusInfo(newLocus);
@@ -2827,11 +2848,42 @@ describe('plugin-meetings', () => {
2827
2848
  assert.notCalled(locusInfo.updateMemberShip);
2828
2849
  assert.notCalled(locusInfo.updateIdentifiers);
2829
2850
  assert.notCalled(locusInfo.updateEmbeddedApps);
2830
- assert.notCalled(locusInfo.updateResources);
2851
+ assert.notCalled(locusInfo.updateLinks);
2831
2852
  assert.notCalled(locusInfo.compareAndUpdate);
2832
2853
  });
2833
2854
 
2855
+ it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
2856
+ // this test verifies that the top-level properties of Locus DTO are copied
2857
+ // into LocusInfo class and set as top level properties too
2858
+ // this is important, because the code handling Locus hass trees relies on it, see updateFromHashTree()
2859
+ const info = {id: 'info id'};
2860
+ const fullState = {id: 'fullState id'};
2861
+ const links = {services: {id: 'service links'}, resources: {id: 'resource links'}};
2862
+ const self = {id: 'self id'};
2863
+ const mediaShares = [{id: 'fake media share'}];
2834
2864
 
2865
+ sinon.stub(SelfUtils, 'getSelves').returns({
2866
+ current: {},
2867
+ previous: {},
2868
+ updates: {},
2869
+ });
2870
+
2871
+ const newLocus = {
2872
+ info,
2873
+ fullState,
2874
+ links,
2875
+ self,
2876
+ mediaShares,
2877
+ };
2878
+
2879
+ locusInfo.updateLocusInfo(newLocus);
2880
+
2881
+ assert.deepEqual(locusInfo.info, newLocus.info);
2882
+ assert.deepEqual(locusInfo.fullState, newLocus.fullState);
2883
+ assert.deepEqual(locusInfo.links, newLocus.links);
2884
+ assert.deepEqual(locusInfo.self, newLocus.self);
2885
+ assert.deepEqual(locusInfo.mediaShares, newLocus.mediaShares);
2886
+ });
2835
2887
 
2836
2888
  it('onFullLocus() updates the working-copy of locus parser', () => {
2837
2889
  const eventType = 'fakeEvent';
@@ -1003,6 +1003,35 @@ describe('plugin-meetings', () => {
1003
1003
  );
1004
1004
  });
1005
1005
 
1006
+ it('should call leave() if addMediaInternal() fails ', async () => {
1007
+ const addMediaError = new Error('fake addMedia error');
1008
+ addMediaError.name = 'TypeError';
1009
+
1010
+ const rejectError = {
1011
+ error: {
1012
+ body: {
1013
+ errorCode: 2729,
1014
+ message: 'fake addMedia error',
1015
+ name: 'TypeError'
1016
+ }
1017
+ }
1018
+ };
1019
+ meeting.addMediaInternal.rejects(addMediaError);
1020
+ sinon.stub(meeting, 'leave').resolves();
1021
+
1022
+ await assert.isRejected(
1023
+ meeting.joinWithMedia({
1024
+ joinOptions,
1025
+ mediaOptions,
1026
+ }),
1027
+ rejectError
1028
+ );
1029
+
1030
+ assert.calledOnce(meeting.join);
1031
+ assert.calledOnce(meeting.addMediaInternal);
1032
+ assert.calledOnce(Metrics.sendBehavioralMetric);
1033
+ });
1034
+
1006
1035
  it('should not call leave() if addMediaInternal() fails the first time and succeeds the second time and should only call join() once', async () => {
1007
1036
  const addMediaError = new Error('fake addMedia error');
1008
1037
  const leaveStub = sinon.stub(meeting, 'leave');