@webex/plugin-meetings 3.10.0-next.24 → 3.10.0-next.26

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.
@@ -7,6 +7,7 @@ export declare const ObjectType: {
7
7
  readonly info: "info";
8
8
  readonly fullState: "fullstate";
9
9
  readonly links: "links";
10
+ readonly control: "controlentry";
10
11
  };
11
12
  export type ObjectType = Enum<typeof ObjectType>;
12
13
  export declare const ObjectTypeToLocusKeyMap: {
@@ -16,6 +17,7 @@ export declare const ObjectTypeToLocusKeyMap: {
16
17
  self: string;
17
18
  participant: string;
18
19
  mediashare: string;
20
+ controlentry: string;
19
21
  };
20
22
  export interface HtMeta {
21
23
  elementId: {
@@ -11,6 +11,7 @@ export default class LocusRouteTokenInterceptor extends Interceptor {
11
11
  */
12
12
  static create(): LocusRouteTokenInterceptor;
13
13
  getLocusIdByRequestUrl(url: string): string;
14
+ getHeader(headers: Record<string, string>, name: string): string;
14
15
  /**
15
16
  * @param {Object} options
16
17
  * @param {HttpResponse} response
@@ -448,7 +448,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
448
448
  }, _callee7);
449
449
  }))();
450
450
  },
451
- version: "3.10.0-next.24"
451
+ version: "3.10.0-next.26"
452
452
  });
453
453
  var _default = exports.default = Webinar;
454
454
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -93,5 +93,5 @@
93
93
  "//": [
94
94
  "TODO: upgrade jwt-decode when moving to node 18"
95
95
  ],
96
- "version": "3.10.0-next.24"
96
+ "version": "3.10.0-next.26"
97
97
  }
@@ -1,6 +1,6 @@
1
1
  import {Enum} from '../constants';
2
2
 
3
- // todo: Locus docs have now more types like CONTROL_ENTRY, EMBEDDED_APP - need to add support for them once Locus implements them
3
+ // todo: Locus docs have now more types like EMBEDDED_APP - need to add support for them once Locus implements them
4
4
  export const ObjectType = {
5
5
  participant: 'participant',
6
6
  self: 'self',
@@ -9,6 +9,7 @@ export const ObjectType = {
9
9
  info: 'info',
10
10
  fullState: 'fullstate',
11
11
  links: 'links',
12
+ control: 'controlentry',
12
13
  } as const;
13
14
 
14
15
  export type ObjectType = Enum<typeof ObjectType>;
@@ -21,6 +22,7 @@ export const ObjectTypeToLocusKeyMap = {
21
22
  [ObjectType.self]: 'self',
22
23
  [ObjectType.participant]: 'participants', // note: each object is a single participant in participants array
23
24
  [ObjectType.mediaShare]: 'mediaShares', // note: each object is a single mediaShare in mediaShares array
25
+ [ObjectType.control]: 'controls', // note: each object is a single control entry in controls object
24
26
  };
25
27
  export interface HtMeta {
26
28
  elementId: {
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import {Interceptor} from '@webex/http-core';
6
- import {has} from 'lodash';
7
6
 
8
7
  const LOCUS_ID_REGEX = /\/locus\/api\/v1\/loci\/([a-f0-9-]{36})/i;
9
8
  const X_CISCO_PART_ROUTE_TOKEN = 'X-Cisco-Part-Route-Token';
@@ -25,6 +24,13 @@ export default class LocusRouteTokenInterceptor extends Interceptor {
25
24
  return url?.match(LOCUS_ID_REGEX)?.[1];
26
25
  }
27
26
 
27
+ // Helper function to get header value case insensitively
28
+ getHeader(headers: Record<string, string>, name: string) {
29
+ const key = Object.keys(headers).find((k) => k.toLowerCase() === name.toLowerCase());
30
+
31
+ return key ? headers[key] : undefined;
32
+ }
33
+
28
34
  /**
29
35
  * @param {Object} options
30
36
  * @param {HttpResponse} response
@@ -33,8 +39,10 @@ export default class LocusRouteTokenInterceptor extends Interceptor {
33
39
  onResponse(options, response) {
34
40
  const locusId = this.getLocusIdByRequestUrl(options.uri);
35
41
  if (locusId) {
36
- const hasRouteToken = has(response.headers, X_CISCO_PART_ROUTE_TOKEN);
37
- const token = response.headers[X_CISCO_PART_ROUTE_TOKEN];
42
+ const hasRouteToken = Object.keys(response.headers).some(
43
+ (key) => key.toLowerCase() === X_CISCO_PART_ROUTE_TOKEN.toLowerCase()
44
+ );
45
+ const token = this.getHeader(response.headers, X_CISCO_PART_ROUTE_TOKEN);
38
46
  if (hasRouteToken) {
39
47
  this.updateToken(locusId, token);
40
48
  }
@@ -66,7 +74,11 @@ export default class LocusRouteTokenInterceptor extends Interceptor {
66
74
  * @returns {void}
67
75
  */
68
76
  updateToken(locusId, token) {
69
- ROUTE_TOKEN[locusId] = token;
77
+ if (token === 'null' || token === null) {
78
+ delete ROUTE_TOKEN[locusId];
79
+ } else {
80
+ ROUTE_TOKEN[locusId] = token;
81
+ }
70
82
  }
71
83
 
72
84
  /**
@@ -590,6 +590,24 @@ export default class LocusInfo extends EventsScope {
590
590
  this.hashTreeObjectId2ParticipantId.delete(object.htMeta.elementId.id);
591
591
  }
592
592
  break;
593
+ case ObjectType.control:
594
+ if (object.data) {
595
+ Object.keys(object.data).forEach((controlKey) => {
596
+ LoggerProxy.logger.info(
597
+ `Locus-info:index#updateLocusFromHashTreeObject --> control ${controlKey} updated:`,
598
+ object.data[controlKey]
599
+ );
600
+ if (!locus.controls) {
601
+ locus.controls = {};
602
+ }
603
+ locus.controls[controlKey] = object.data[controlKey];
604
+ });
605
+ } else {
606
+ LoggerProxy.logger.warn(
607
+ `Locus-info:index#updateLocusFromHashTreeObject --> control object update without data - this is not expected!`
608
+ );
609
+ }
610
+ break;
593
611
  case ObjectType.links:
594
612
  case ObjectType.info:
595
613
  case ObjectType.fullState:
@@ -54,6 +54,23 @@ describe('LocusRouteTokenInterceptor', () => {
54
54
  assert.equal(interceptor.getToken(TEST_LOCUS_ID), 'test-token');
55
55
  });
56
56
 
57
+ it('get route token case insensitively ', async () => {
58
+ const response = {
59
+ headers: {
60
+ ['x-cisco-part-route-token']: 'test-token',
61
+ },
62
+ };
63
+
64
+ const result = await interceptor.onResponse(
65
+ {
66
+ uri: `https://locus-test.webex.com/locus/api/v1/loci/${TEST_LOCUS_ID}/foo`,
67
+ },
68
+ response
69
+ );
70
+ assert.equal(result, response);
71
+ assert.equal(interceptor.getToken(TEST_LOCUS_ID), 'test-token');
72
+ });
73
+
57
74
  it('onResponse should not store token when header missing', async () => {
58
75
  interceptor.updateToken(TEST_LOCUS_ID);
59
76
  const response = {headers: {}};
@@ -84,4 +101,14 @@ describe('LocusRouteTokenInterceptor', () => {
84
101
  interceptor.updateToken(TEST_LOCUS_ID, 'abc456');
85
102
  assert.equal(interceptor.getToken(TEST_LOCUS_ID), 'abc456');
86
103
  });
104
+
105
+ it('should delete token when updateToken called with "null"', () => {
106
+ interceptor.updateToken(TEST_LOCUS_ID, 'null');
107
+ assert.isUndefined(interceptor.getToken(TEST_LOCUS_ID));
108
+ });
109
+
110
+ it('should delete token when updateToken called with null', () => {
111
+ interceptor.updateToken(TEST_LOCUS_ID, null);
112
+ assert.isUndefined(interceptor.getToken(TEST_LOCUS_ID));
113
+ });
87
114
  });
@@ -486,7 +486,6 @@ describe('plugin-meetings', () => {
486
486
  // setup new updated locus that has many things missing
487
487
  const newLocusHtMeta = {elementId: {type: 'locus', version: 42}};
488
488
  const newLocus = {
489
- controls: 'new-controls',
490
489
  host: 'new-host',
491
490
  htMeta: newLocusHtMeta,
492
491
  };
@@ -499,10 +498,11 @@ describe('plugin-meetings', () => {
499
498
  // check onDeltaLocus() was called with correctly updated locus info
500
499
  assert.calledOnceWithExactly(onDeltaLocusStub, {
501
500
  // these fields are not part of Locus object, so should keep their old values:
501
+ controls: {id: 'fake-controls'},
502
502
  info: {id: 'fake-info'},
503
503
  fullState: {id: 'fake-full-state'},
504
504
  self: {id: 'fake-self'},
505
- links: { id: 'fake-links' },
505
+ links: {id: 'fake-links'},
506
506
  mediaShares: expectedLocusInfo.mediaShares,
507
507
  // and now the new fields
508
508
  ...newLocus,
@@ -518,7 +518,6 @@ describe('plugin-meetings', () => {
518
518
  // setup new updated locus that has many things missing
519
519
  const newLocusHtMeta = {elementId: {type: 'locus', version: 42}};
520
520
  const newLocus = {
521
- controls: 'new-controls',
522
521
  host: 'new-host',
523
522
  htMeta: newLocusHtMeta,
524
523
  };
@@ -531,6 +530,7 @@ describe('plugin-meetings', () => {
531
530
  data: {
532
531
  ...newLocus,
533
532
  // all these fields below should be ignored and not override the existing ones in our "old" Locus
533
+ controls: {id: 'new-controls'},
534
534
  info: 'new-info',
535
535
  fullState: 'new-fullState',
536
536
  self: 'new-self',
@@ -545,10 +545,11 @@ describe('plugin-meetings', () => {
545
545
  // with old values for the fields that should be ignored (like "info" or "fullState")
546
546
  assert.calledOnceWithExactly(onDeltaLocusStub, {
547
547
  // these fields have the "old" values:
548
+ controls: {id: 'fake-controls'},
548
549
  info: {id: 'fake-info'},
549
550
  fullState: {id: 'fake-full-state'},
550
551
  self: {id: 'fake-self'},
551
- links: { id: 'fake-links' },
552
+ links: {id: 'fake-links'},
552
553
  mediaShares: expectedLocusInfo.mediaShares,
553
554
  participants: [], // empty means there were no participant updates
554
555
  jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
@@ -579,6 +580,7 @@ describe('plugin-meetings', () => {
579
580
  // check onDeltaLocus() was called with correctly updated locus info
580
581
  assert.calledOnceWithExactly(onDeltaLocusStub, {
581
582
  // these fields are not part of Locus object, so should keep their old values:
583
+ controls: {id: 'fake-controls'},
582
584
  info: {id: 'fake-info'},
583
585
  fullState: {id: 'fake-full-state'},
584
586
  self: {id: 'fake-self'},
@@ -768,6 +770,60 @@ describe('plugin-meetings', () => {
768
770
  });
769
771
  });
770
772
 
773
+ it('should process locus update correctly when called with multiple CONTROL object updates', () => {
774
+ const firstControl = {
775
+ muteOnEntry: {enabled: true},
776
+ lock: {locked: true, meta: {lastModified: 'YESTERDAY', modifiedBy: 'John Doe'}},
777
+ };
778
+ const secondControl = {
779
+ reactions: {enabled: true},
780
+ };
781
+
782
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
783
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
784
+ updatedObjects: [
785
+ {
786
+ htMeta: {elementId: {type: 'controlentry', id: 'control-1'}},
787
+ data: firstControl,
788
+ },
789
+ {
790
+ htMeta: {elementId: {type: 'controlentry', id: 'control-2'}},
791
+ data: secondControl,
792
+ },
793
+ ],
794
+ });
795
+
796
+ // check onDeltaLocus() was called with correctly updated locus info
797
+ // all keys from both controls should be merged into the controls object
798
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
799
+ ...expectedLocusInfo,
800
+ controls: {
801
+ id: 'fake-controls',
802
+ muteOnEntry: {enabled: true},
803
+ lock: {locked: true, meta: {lastModified: 'YESTERDAY', modifiedBy: 'John Doe'}},
804
+ reactions: {enabled: true},
805
+ },
806
+ });
807
+ });
808
+
809
+ it('should process locus update correctly when CONTROL object is received with no data', () => {
810
+ // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
811
+ locusInfoUpdateCallback(OBJECTS_UPDATED, {
812
+ updatedObjects: [
813
+ {
814
+ htMeta: {elementId: {type: 'controlentry', id: 'some-control-id'}},
815
+ data: null,
816
+ },
817
+ ],
818
+ });
819
+
820
+ // check onDeltaLocus() was called with correctly updated locus info
821
+ // when data is null, it should be ignored and not change the controls
822
+ assert.calledOnceWithExactly(onDeltaLocusStub, {
823
+ ...expectedLocusInfo,
824
+ });
825
+ });
826
+
771
827
  it('should handle MEETING_ENDED correctly', () => {
772
828
  const fakeMeeting = {id: 'fake-meeting-from-collection'};
773
829
  const collectionGetStub = sinon