@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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/hashTree/types.js +4 -3
- package/dist/hashTree/types.js.map +1 -1
- package/dist/interceptors/locusRouteToken.js +20 -4
- package/dist/interceptors/locusRouteToken.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +16 -2
- package/dist/locus-info/index.js.map +1 -1
- package/dist/types/hashTree/types.d.ts +2 -0
- package/dist/types/interceptors/locusRouteToken.d.ts +1 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +1 -1
- package/src/hashTree/types.ts +3 -1
- package/src/interceptors/locusRouteToken.ts +16 -4
- package/src/locus-info/index.ts +18 -0
- package/test/unit/spec/interceptors/locusRouteToken.ts +27 -0
- package/test/unit/spec/locus-info/index.js +60 -4
|
@@ -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
|
package/dist/webinar/index.js
CHANGED
package/package.json
CHANGED
package/src/hashTree/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Enum} from '../constants';
|
|
2
2
|
|
|
3
|
-
// todo: Locus docs have now more types like
|
|
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 =
|
|
37
|
-
|
|
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
|
-
|
|
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
|
/**
|
package/src/locus-info/index.ts
CHANGED
|
@@ -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: {
|
|
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: {
|
|
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
|