@webex/plugin-meetings 3.8.1-web-workers-keepalive.1 → 3.9.0-multipleLLM.1
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/constants.js +26 -2
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +77 -95
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/parser.js +4 -1
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/media/properties.js +53 -5
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/brbState.js +14 -12
- package/dist/meeting/brbState.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +8 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +443 -225
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +2 -5
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +44 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/type.js +7 -0
- package/dist/meeting/type.js.map +1 -0
- package/dist/meeting/util.js +98 -13
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +29 -21
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +18 -10
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/members/collection.js +13 -0
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +53 -29
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +3 -3
- package/dist/members/request.js.map +1 -1
- package/dist/members/util.js +25 -8
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -1
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +34 -5
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +42 -2
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +32 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/index.js +3 -3
- package/dist/reachability/index.js.map +1 -1
- package/dist/types/constants.d.ts +24 -0
- package/dist/types/locus-info/index.d.ts +54 -10
- package/dist/types/media/properties.d.ts +21 -0
- package/dist/types/meeting/brbState.d.ts +0 -1
- package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
- package/dist/types/meeting/index.d.ts +51 -20
- package/dist/types/meeting/request.d.ts +18 -1
- package/dist/types/meeting/request.type.d.ts +74 -0
- package/dist/types/meeting/type.d.ts +9 -0
- package/dist/types/meeting/util.d.ts +13 -3
- package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
- package/dist/types/meetings/index.d.ts +3 -1
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/members/collection.d.ts +6 -0
- package/dist/types/members/index.d.ts +22 -9
- package/dist/types/members/request.d.ts +1 -1
- package/dist/types/members/util.d.ts +13 -6
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/multistream/remoteMedia.d.ts +20 -1
- package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
- package/dist/types/multistream/sendSlotManager.d.ts +16 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -24
- package/src/constants.ts +25 -2
- package/src/locus-info/index.ts +133 -96
- package/src/locus-info/parser.ts +5 -1
- package/src/media/properties.ts +43 -0
- package/src/meeting/brbState.ts +9 -7
- package/src/meeting/in-meeting-actions.ts +17 -0
- package/src/meeting/index.ts +273 -42
- package/src/meeting/muteState.ts +2 -6
- package/src/meeting/request.ts +39 -0
- package/src/meeting/request.type.ts +64 -0
- package/src/meeting/type.ts +9 -0
- package/src/meeting/util.ts +114 -22
- package/src/meeting-info/meeting-info-v2.ts +24 -5
- package/src/meetings/index.ts +12 -5
- package/src/member/index.ts +1 -0
- package/src/member/types.ts +1 -0
- package/src/members/collection.ts +11 -0
- package/src/members/index.ts +51 -15
- package/src/members/request.ts +2 -2
- package/src/members/util.ts +34 -6
- package/src/metrics/constants.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +7 -7
- package/src/multistream/remoteMedia.ts +34 -4
- package/src/multistream/remoteMediaGroup.ts +37 -2
- package/src/multistream/sendSlotManager.ts +34 -2
- package/src/reachability/index.ts +3 -3
- package/test/unit/spec/locus-info/index.js +229 -98
- package/test/unit/spec/locus-info/parser.js +3 -2
- package/test/unit/spec/media/properties.ts +137 -0
- package/test/unit/spec/meeting/brbState.ts +9 -9
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
- package/test/unit/spec/meeting/index.js +1022 -93
- package/test/unit/spec/meeting/muteState.js +32 -6
- package/test/unit/spec/meeting/request.js +92 -0
- package/test/unit/spec/meeting/utils.js +167 -17
- package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
- package/test/unit/spec/meetings/index.js +12 -1
- package/test/unit/spec/members/collection.js +120 -0
- package/test/unit/spec/members/index.js +140 -12
- package/test/unit/spec/members/request.js +57 -2
- package/test/unit/spec/members/utils.js +139 -17
- package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
- package/test/unit/spec/multistream/remoteMedia.ts +66 -2
- package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
- package/test/unit/spec/reachability/index.ts +158 -1
@@ -305,7 +305,7 @@ describe('plugin-meetings', () => {
|
|
305
305
|
{state: newControls.rdcControl}
|
306
306
|
);
|
307
307
|
});
|
308
|
-
|
308
|
+
|
309
309
|
it('should trigger the CONTROLS_POLLING_QA_CHANGED event when necessary', () => {
|
310
310
|
locusInfo.controls = {};
|
311
311
|
locusInfo.emitScoped = sinon.stub();
|
@@ -772,7 +772,7 @@ describe('plugin-meetings', () => {
|
|
772
772
|
},
|
773
773
|
};
|
774
774
|
locusInfo.emitScoped = sinon.stub();
|
775
|
-
locusInfo.updateParticipants({});
|
775
|
+
locusInfo.updateParticipants({}, []);
|
776
776
|
|
777
777
|
// if this assertion fails, double-check the attributes used in
|
778
778
|
// the updateParticipants function in locus-info/index.js
|
@@ -790,6 +790,7 @@ describe('plugin-meetings', () => {
|
|
790
790
|
selfId: '2',
|
791
791
|
hostId: '3',
|
792
792
|
isReplace: undefined,
|
793
|
+
removedParticipantIds: [],
|
793
794
|
}
|
794
795
|
);
|
795
796
|
// note: in a real use case, recordingId, selfId, and hostId would all be the same
|
@@ -814,7 +815,7 @@ describe('plugin-meetings', () => {
|
|
814
815
|
};
|
815
816
|
|
816
817
|
locusInfo.emitScoped = sinon.stub();
|
817
|
-
locusInfo.updateParticipants({}, true);
|
818
|
+
locusInfo.updateParticipants({}, [], true);
|
818
819
|
|
819
820
|
assert.calledWith(
|
820
821
|
locusInfo.emitScoped,
|
@@ -830,43 +831,11 @@ describe('plugin-meetings', () => {
|
|
830
831
|
selfId: '2',
|
831
832
|
hostId: '3',
|
832
833
|
isReplace: true,
|
834
|
+
removedParticipantIds: [],
|
833
835
|
}
|
834
836
|
);
|
835
837
|
});
|
836
838
|
|
837
|
-
it('should update the deltaParticipants object', () => {
|
838
|
-
const prev = locusInfo.deltaParticipants;
|
839
|
-
|
840
|
-
locusInfo.updateParticipantDeltas(newParticipants);
|
841
|
-
|
842
|
-
assert.notEqual(locusInfo.deltaParticipants, prev);
|
843
|
-
});
|
844
|
-
|
845
|
-
it('should update the delta property on all changed states', () => {
|
846
|
-
locusInfo.updateParticipantDeltas(newParticipants);
|
847
|
-
|
848
|
-
const [exampleParticipant] = locusInfo.deltaParticipants;
|
849
|
-
|
850
|
-
assert.isTrue(exampleParticipant.delta.audioStatus);
|
851
|
-
assert.isTrue(exampleParticipant.delta.videoSlidesStatus);
|
852
|
-
assert.isTrue(exampleParticipant.delta.videoStatus);
|
853
|
-
});
|
854
|
-
|
855
|
-
it('should include the person details of the changed participant', () => {
|
856
|
-
locusInfo.updateParticipantDeltas(newParticipants);
|
857
|
-
|
858
|
-
const [exampleParticipant] = locusInfo.deltaParticipants;
|
859
|
-
|
860
|
-
assert.equal(exampleParticipant.person, newParticipants[0].person);
|
861
|
-
});
|
862
|
-
|
863
|
-
it('should clear deltaParticipants when no changes occured', () => {
|
864
|
-
locusInfo.participants = [...newParticipants];
|
865
|
-
|
866
|
-
locusInfo.updateParticipantDeltas(locusInfo.participants);
|
867
|
-
|
868
|
-
assert.isTrue(locusInfo.deltaParticipants.length === 0);
|
869
|
-
});
|
870
839
|
|
871
840
|
it('should call with participant display name', () => {
|
872
841
|
const failureParticipant = [
|
@@ -880,7 +849,7 @@ describe('plugin-meetings', () => {
|
|
880
849
|
];
|
881
850
|
|
882
851
|
locusInfo.emitScoped = sinon.stub();
|
883
|
-
locusInfo.updateParticipants(failureParticipant);
|
852
|
+
locusInfo.updateParticipants(failureParticipant, []);
|
884
853
|
assert.calledWith(
|
885
854
|
locusInfo.emitScoped,
|
886
855
|
{
|
@@ -1674,6 +1643,28 @@ describe('plugin-meetings', () => {
|
|
1674
1643
|
);
|
1675
1644
|
});
|
1676
1645
|
|
1646
|
+
it('should trigger MEETING_INFO_UPDATED even if the roles array is empty', () => {
|
1647
|
+
const initialInfo = cloneDeep(meetingInfo);
|
1648
|
+
|
1649
|
+
const updateSelf = cloneDeep(self);
|
1650
|
+
updateSelf.controls.role.roles = [];
|
1651
|
+
|
1652
|
+
locusInfo.emitScoped = sinon.stub();
|
1653
|
+
locusInfo.updateMeetingInfo(initialInfo, updateSelf);
|
1654
|
+
|
1655
|
+
assert.calledWith(
|
1656
|
+
locusInfo.emitScoped,
|
1657
|
+
{
|
1658
|
+
file: 'locus-info',
|
1659
|
+
function: 'updateMeetingInfo',
|
1660
|
+
},
|
1661
|
+
LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
|
1662
|
+
{
|
1663
|
+
isInitializing: !self,
|
1664
|
+
}
|
1665
|
+
);
|
1666
|
+
});
|
1667
|
+
|
1677
1668
|
const checkMeetingInfoUpdatedCalled = (expected, payload) => {
|
1678
1669
|
const expectedArgs = [
|
1679
1670
|
locusInfo.emitScoped,
|
@@ -2049,6 +2040,18 @@ describe('plugin-meetings', () => {
|
|
2049
2040
|
});
|
2050
2041
|
});
|
2051
2042
|
|
2043
|
+
describe('#handleLocusAPIResponse', () => {
|
2044
|
+
it('calls handleLocusDelta', () => {
|
2045
|
+
const fakeLocus = {eventType: LOCUSEVENT.DIFFERENCE};
|
2046
|
+
|
2047
|
+
sinon.stub(locusInfo, 'handleLocusDelta');
|
2048
|
+
|
2049
|
+
locusInfo.handleLocusAPIResponse(mockMeeting, {locus: fakeLocus});
|
2050
|
+
|
2051
|
+
assert.calledWith(locusInfo.handleLocusDelta, fakeLocus, mockMeeting);
|
2052
|
+
});
|
2053
|
+
});
|
2054
|
+
|
2052
2055
|
describe('#LocusDeltaEvents', () => {
|
2053
2056
|
const fakeMeeting = 'fakeMeeting';
|
2054
2057
|
let sandbox = null;
|
@@ -2061,7 +2064,7 @@ describe('plugin-meetings', () => {
|
|
2061
2064
|
|
2062
2065
|
fakeLocus = {
|
2063
2066
|
meeting: true,
|
2064
|
-
participants:
|
2067
|
+
participants: [],
|
2065
2068
|
url: 'newLocusUrl',
|
2066
2069
|
syncUrl: 'newSyncUrl',
|
2067
2070
|
};
|
@@ -2108,6 +2111,38 @@ describe('plugin-meetings', () => {
|
|
2108
2111
|
assert.isFunction(locusParser.onDeltaAction);
|
2109
2112
|
});
|
2110
2113
|
|
2114
|
+
it("#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo", () => {
|
2115
|
+
const callOrder = [];
|
2116
|
+
sinon.stub(locusInfo, "updateControls");
|
2117
|
+
sinon.stub(locusInfo, "updateConversationUrl");
|
2118
|
+
sinon.stub(locusInfo, "updateCreated");
|
2119
|
+
sinon.stub(locusInfo, "updateFullState");
|
2120
|
+
sinon.stub(locusInfo, "updateHostInfo");
|
2121
|
+
sinon.stub(locusInfo, "updateMeetingInfo").callsFake(() => {
|
2122
|
+
callOrder.push("updateMeetingInfo");
|
2123
|
+
});
|
2124
|
+
sinon.stub(locusInfo, "updateMediaShares");
|
2125
|
+
sinon.stub(locusInfo, "updateParticipantsUrl");
|
2126
|
+
sinon.stub(locusInfo, "updateReplace");
|
2127
|
+
sinon.stub(locusInfo, "updateSelf");
|
2128
|
+
sinon.stub(locusInfo, "updateLocusUrl").callsFake(() => {
|
2129
|
+
callOrder.push("updateLocusUrl");
|
2130
|
+
});
|
2131
|
+
sinon.stub(locusInfo, "updateAclUrl");
|
2132
|
+
sinon.stub(locusInfo, "updateBasequence");
|
2133
|
+
sinon.stub(locusInfo, "updateSequence");
|
2134
|
+
sinon.stub(locusInfo, "updateMemberShip");
|
2135
|
+
sinon.stub(locusInfo, "updateIdentifiers");
|
2136
|
+
sinon.stub(locusInfo, "updateEmbeddedApps");
|
2137
|
+
sinon.stub(locusInfo, "updateResources");
|
2138
|
+
sinon.stub(locusInfo, "compareAndUpdate");
|
2139
|
+
|
2140
|
+
locusInfo.updateLocusInfo(locus);
|
2141
|
+
|
2142
|
+
// Ensure updateLocusUrl is called before updateMeetingInfo if both are called
|
2143
|
+
assert.deepEqual(callOrder, ['updateLocusUrl', 'updateMeetingInfo']);
|
2144
|
+
});
|
2145
|
+
|
2111
2146
|
it('#updateLocusInfo ignores breakout LEFT message', () => {
|
2112
2147
|
const newLocus = {
|
2113
2148
|
self: {
|
@@ -2159,10 +2194,11 @@ describe('plugin-meetings', () => {
|
|
2159
2194
|
assert.notCalled(locusInfo.compareAndUpdate);
|
2160
2195
|
});
|
2161
2196
|
|
2197
|
+
|
2198
|
+
|
2162
2199
|
it('onFullLocus() updates the working-copy of locus parser', () => {
|
2163
2200
|
const eventType = 'fakeEvent';
|
2164
2201
|
|
2165
|
-
sandbox.stub(locusInfo, 'updateParticipantDeltas');
|
2166
2202
|
sandbox.stub(locusInfo, 'updateLocusInfo');
|
2167
2203
|
sandbox.stub(locusInfo, 'updateParticipants');
|
2168
2204
|
sandbox.stub(locusInfo, 'isMeetingActive');
|
@@ -2182,7 +2218,6 @@ describe('plugin-meetings', () => {
|
|
2182
2218
|
const oldWorkingCopy = locusParser.workingCopy;
|
2183
2219
|
|
2184
2220
|
const spies = [
|
2185
|
-
sandbox.stub(locusInfo, 'updateParticipantDeltas'),
|
2186
2221
|
sandbox.stub(locusInfo, 'updateLocusInfo'),
|
2187
2222
|
sandbox.stub(locusInfo, 'updateParticipants'),
|
2188
2223
|
sandbox.stub(locusInfo, 'isMeetingActive'),
|
@@ -2257,7 +2292,7 @@ describe('plugin-meetings', () => {
|
|
2257
2292
|
|
2258
2293
|
it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl', () => {
|
2259
2294
|
const {DESYNC} = LocusDeltaParser.loci;
|
2260
|
-
const fakeDeltaLocus = {id: 'fake delta locus'};
|
2295
|
+
const fakeDeltaLocus = {baseSequence: {}, id: 'fake delta locus'};
|
2261
2296
|
const meeting = {
|
2262
2297
|
meetingRequest: {
|
2263
2298
|
getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
|
@@ -2348,23 +2383,23 @@ describe('plugin-meetings', () => {
|
|
2348
2383
|
|
2349
2384
|
it('applyLocusDeltaData handles LOCUS_URL_CHANGED action correctly', () => {
|
2350
2385
|
const {LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
|
2351
|
-
const
|
2386
|
+
const fakeFullLocus = {
|
2387
|
+
url: 'new full loci url',
|
2388
|
+
};
|
2352
2389
|
const meeting = {
|
2353
2390
|
meetingRequest: {
|
2354
|
-
getLocusDTO: sandbox.stub().resolves({body:
|
2391
|
+
getLocusDTO: sandbox.stub().resolves({body: fakeFullLocus}),
|
2355
2392
|
},
|
2356
2393
|
locusInfo: {
|
2357
2394
|
handleLocusDelta: sandbox.stub(),
|
2358
2395
|
},
|
2359
|
-
locusUrl: 'current locus url',
|
2396
|
+
locusUrl: 'current BO session locus url',
|
2360
2397
|
};
|
2361
2398
|
|
2362
|
-
locusInfo.locusParser.workingCopy =
|
2363
|
-
syncUrl: 'current sync url',
|
2364
|
-
};
|
2399
|
+
locusInfo.locusParser.workingCopy = null;
|
2365
2400
|
|
2366
2401
|
locusInfo.applyLocusDeltaData(LOCUS_URL_CHANGED, fakeLocus, meeting);
|
2367
|
-
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url:
|
2402
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: fakeLocus.url});
|
2368
2403
|
});
|
2369
2404
|
|
2370
2405
|
describe('edge cases for sync failing', () => {
|
@@ -2392,25 +2427,22 @@ describe('plugin-meetings', () => {
|
|
2392
2427
|
};
|
2393
2428
|
});
|
2394
2429
|
|
2395
|
-
it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', () => {
|
2430
|
+
it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', async () => {
|
2396
2431
|
meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
|
2397
2432
|
|
2398
2433
|
locusInfo.locusParser.workingCopy = {}; // no syncUrl
|
2399
2434
|
|
2400
|
-
|
2401
|
-
// we will wait and stub it's last function to resolve this waiting promise.
|
2402
|
-
return new Promise((resolve) => {
|
2403
|
-
webex.meetings.destroy.callsFake(() => resolve());
|
2404
|
-
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2405
|
-
}).then(() => {
|
2406
|
-
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
|
2435
|
+
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2407
2436
|
|
2408
|
-
|
2409
|
-
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2410
|
-
assert.notCalled(locusInfo.locusParser.resume);
|
2437
|
+
await testUtils.flushPromises();
|
2411
2438
|
|
2412
|
-
|
2413
|
-
|
2439
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
|
2440
|
+
|
2441
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2442
|
+
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2443
|
+
assert.notCalled(locusInfo.locusParser.resume);
|
2444
|
+
|
2445
|
+
assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
|
2414
2446
|
});
|
2415
2447
|
|
2416
2448
|
it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails, does a full locus sync', () => {
|
@@ -2447,44 +2479,67 @@ describe('plugin-meetings', () => {
|
|
2447
2479
|
});
|
2448
2480
|
});
|
2449
2481
|
|
2450
|
-
it('applyLocusDeltaData
|
2482
|
+
it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails with 403, it does not do a full locus sync', async () => {
|
2483
|
+
const fake403Error = new Error('fake error');
|
2484
|
+
fake403Error.statusCode = 403;
|
2485
|
+
|
2486
|
+
meeting.meetingRequest.getLocusDTO.onCall(0).rejects(fake403Error);
|
2487
|
+
|
2488
|
+
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2489
|
+
|
2490
|
+
await testUtils.flushPromises();
|
2491
|
+
|
2492
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'deltaSyncUrl'});
|
2493
|
+
|
2494
|
+
assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
|
2495
|
+
correlationId: meeting.correlationId,
|
2496
|
+
url: 'deltaSyncUrl',
|
2497
|
+
reason: 'fake error',
|
2498
|
+
errorName: 'Error',
|
2499
|
+
stack: sinon.match.any,
|
2500
|
+
code: sinon.match.any,
|
2501
|
+
});
|
2502
|
+
|
2503
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2504
|
+
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2505
|
+
assert.notCalled(locusInfo.locusParser.resume);
|
2506
|
+
});
|
2507
|
+
|
2508
|
+
it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', async () => {
|
2451
2509
|
meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
|
2452
2510
|
|
2453
|
-
|
2454
|
-
// we will wait and stub it's last function to resolve this waiting promise.
|
2455
|
-
return new Promise((resolve) => {
|
2456
|
-
webex.meetings.destroy.callsFake(() => resolve());
|
2457
|
-
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2458
|
-
}).then(() => {
|
2459
|
-
assert.calledTwice(meeting.meetingRequest.getLocusDTO);
|
2511
|
+
locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2460
2512
|
|
2461
|
-
|
2462
|
-
{url: 'deltaSyncUrl'},
|
2463
|
-
]);
|
2464
|
-
assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
|
2465
|
-
{url: 'fullSyncUrl'},
|
2466
|
-
]);
|
2513
|
+
await testUtils.flushPromises();
|
2467
2514
|
|
2468
|
-
|
2469
|
-
correlationId: meeting.correlationId,
|
2470
|
-
url: 'deltaSyncUrl',
|
2471
|
-
reason: 'fake error',
|
2472
|
-
errorName: 'Error',
|
2473
|
-
stack: sinon.match.any,
|
2474
|
-
code: sinon.match.any,
|
2475
|
-
});
|
2515
|
+
assert.calledTwice(meeting.meetingRequest.getLocusDTO);
|
2476
2516
|
|
2477
|
-
|
2478
|
-
|
2479
|
-
|
2517
|
+
assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [
|
2518
|
+
{url: 'deltaSyncUrl'},
|
2519
|
+
]);
|
2520
|
+
assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
|
2521
|
+
{url: 'fullSyncUrl'},
|
2522
|
+
]);
|
2480
2523
|
|
2481
|
-
|
2524
|
+
assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
|
2525
|
+
correlationId: meeting.correlationId,
|
2526
|
+
url: 'deltaSyncUrl',
|
2527
|
+
reason: 'fake error',
|
2528
|
+
errorName: 'Error',
|
2529
|
+
stack: sinon.match.any,
|
2530
|
+
code: sinon.match.any,
|
2482
2531
|
});
|
2532
|
+
|
2533
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2534
|
+
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2535
|
+
assert.notCalled(locusInfo.locusParser.resume);
|
2536
|
+
|
2537
|
+
assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
|
2483
2538
|
});
|
2484
2539
|
});
|
2485
2540
|
|
2486
2541
|
it('onDeltaLocus handle delta data', () => {
|
2487
|
-
fakeLocus.participants =
|
2542
|
+
fakeLocus.participants = [];
|
2488
2543
|
const fakeBreakout = {
|
2489
2544
|
sessionId: 'sessionId',
|
2490
2545
|
groupId: 'groupId',
|
@@ -2501,17 +2556,15 @@ describe('plugin-meetings', () => {
|
|
2501
2556
|
};
|
2502
2557
|
locusInfo.updateParticipants = sinon.stub();
|
2503
2558
|
locusInfo.onDeltaLocus(fakeLocus);
|
2504
|
-
assert.calledWith(locusInfo.updateParticipants,
|
2559
|
+
assert.calledWith(locusInfo.updateParticipants, [], undefined, false);
|
2505
2560
|
|
2506
2561
|
fakeLocus.controls.breakout.sessionId = 'sessionId2';
|
2507
2562
|
locusInfo.onDeltaLocus(fakeLocus);
|
2508
|
-
assert.calledWith(locusInfo.updateParticipants,
|
2563
|
+
assert.calledWith(locusInfo.updateParticipants, [], undefined, true);
|
2509
2564
|
});
|
2510
2565
|
|
2511
2566
|
it('onDeltaLocus merges delta participants with existing participants', () => {
|
2512
|
-
const FAKE_DELTA_PARTICIPANTS = [
|
2513
|
-
{id: '1111'}, {id: '2222'}
|
2514
|
-
]
|
2567
|
+
const FAKE_DELTA_PARTICIPANTS = [{id: '1111'}, {id: '2222'}];
|
2515
2568
|
fakeLocus.participants = FAKE_DELTA_PARTICIPANTS;
|
2516
2569
|
|
2517
2570
|
sinon.spy(locusInfo, 'mergeParticipants');
|
@@ -2519,8 +2572,86 @@ describe('plugin-meetings', () => {
|
|
2519
2572
|
const existingParticipants = locusInfo.participants;
|
2520
2573
|
|
2521
2574
|
locusInfo.onDeltaLocus(fakeLocus);
|
2522
|
-
assert.calledOnceWithExactly(
|
2523
|
-
|
2575
|
+
assert.calledOnceWithExactly(
|
2576
|
+
locusInfo.mergeParticipants,
|
2577
|
+
existingParticipants,
|
2578
|
+
FAKE_DELTA_PARTICIPANTS
|
2579
|
+
);
|
2580
|
+
assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, undefined, false);
|
2581
|
+
});
|
2582
|
+
|
2583
|
+
[true, false].forEach((isDelta) =>
|
2584
|
+
it(`applyLocusDeltaData - handles empty ${
|
2585
|
+
isDelta ? 'delta' : 'full'
|
2586
|
+
} DTO in response`, async () => {
|
2587
|
+
const {DESYNC} = LocusDeltaParser.loci;
|
2588
|
+
const fakeFullLocusDto = {};
|
2589
|
+
const meeting = {
|
2590
|
+
meetingRequest: {
|
2591
|
+
getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
|
2592
|
+
},
|
2593
|
+
locusInfo: {
|
2594
|
+
onFullLocus: sandbox.stub(),
|
2595
|
+
handleLocusDelta: sandbox.stub(),
|
2596
|
+
},
|
2597
|
+
locusUrl: 'fake locus FULL url',
|
2598
|
+
};
|
2599
|
+
|
2600
|
+
sinon.stub(locusInfo.locusParser, 'resume').resolves();
|
2601
|
+
|
2602
|
+
if (isDelta) {
|
2603
|
+
locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
|
2604
|
+
} else {
|
2605
|
+
locusInfo.locusParser.workingCopy = {}; // no syncUrl (to trigger FULL DTO request)
|
2606
|
+
}
|
2607
|
+
|
2608
|
+
await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2609
|
+
|
2610
|
+
await testUtils.flushPromises();
|
2611
|
+
|
2612
|
+
if (isDelta) {
|
2613
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
|
2614
|
+
url: 'fake locus DELTA url',
|
2615
|
+
});
|
2616
|
+
} else {
|
2617
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
|
2618
|
+
url: 'fake locus FULL url',
|
2619
|
+
});
|
2620
|
+
}
|
2621
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2622
|
+
assert.notCalled(meeting.locusInfo.onFullLocus);
|
2623
|
+
assert.calledOnce(locusInfo.locusParser.resume);
|
2624
|
+
})
|
2625
|
+
);
|
2626
|
+
|
2627
|
+
it(`applyLocusDeltaData - handles the case when we get FULL DTO when we asked for DELTA DTO`, async () => {
|
2628
|
+
const {DESYNC} = LocusDeltaParser.loci;
|
2629
|
+
const fakeFullLocusDto = {someStuff: 'data'}; // non-empty DTO, without baseSequence
|
2630
|
+
const meeting = {
|
2631
|
+
meetingRequest: {
|
2632
|
+
getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
|
2633
|
+
},
|
2634
|
+
locusInfo: {
|
2635
|
+
onFullLocus: sandbox.stub(),
|
2636
|
+
handleLocusDelta: sandbox.stub(),
|
2637
|
+
},
|
2638
|
+
locusUrl: 'fake locus FULL url',
|
2639
|
+
};
|
2640
|
+
|
2641
|
+
sinon.stub(locusInfo.locusParser, 'resume').resolves();
|
2642
|
+
|
2643
|
+
locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
|
2644
|
+
|
2645
|
+
await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
|
2646
|
+
|
2647
|
+
await testUtils.flushPromises();
|
2648
|
+
|
2649
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
|
2650
|
+
url: 'fake locus DELTA url',
|
2651
|
+
});
|
2652
|
+
assert.notCalled(meeting.locusInfo.handleLocusDelta);
|
2653
|
+
assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
|
2654
|
+
assert.calledOnce(locusInfo.locusParser.resume);
|
2524
2655
|
});
|
2525
2656
|
});
|
2526
2657
|
|
@@ -2934,10 +3065,9 @@ describe('plugin-meetings', () => {
|
|
2934
3065
|
beforeEach(() => {
|
2935
3066
|
clock = sinon.useFakeTimers();
|
2936
3067
|
|
2937
|
-
sinon.stub(locusInfo, 'updateParticipantDeltas');
|
2938
3068
|
sinon.stub(locusInfo, 'updateParticipants');
|
2939
|
-
sinon.stub(locusInfo, 'isMeetingActive')
|
2940
|
-
sinon.stub(locusInfo, 'handleOneOnOneEvent')
|
3069
|
+
sinon.stub(locusInfo, 'isMeetingActive');
|
3070
|
+
sinon.stub(locusInfo, 'handleOneOnOneEvent');
|
2941
3071
|
(updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo'));
|
2942
3072
|
syncRequestStub = sinon.stub().resolves({body: {}});
|
2943
3073
|
|
@@ -2958,6 +3088,7 @@ describe('plugin-meetings', () => {
|
|
2958
3088
|
id: 'test person id',
|
2959
3089
|
},
|
2960
3090
|
},
|
3091
|
+
participants: [],
|
2961
3092
|
});
|
2962
3093
|
|
2963
3094
|
updateLocusInfoStub.resetHistory();
|
@@ -253,7 +253,8 @@ describe('locus-info/parser', () => {
|
|
253
253
|
});
|
254
254
|
|
255
255
|
it('replaces current loci when the locus URL changes and incoming sequence is later, even when baseSequence doesn\'t match', () => {
|
256
|
-
const {
|
256
|
+
const {LOCUS_URL_CHANGED} = LocusDeltaParser.loci;
|
257
|
+
sandbox.stub(LocusDeltaParser, 'compare').returns(LOCUS_URL_CHANGED);
|
257
258
|
|
258
259
|
parser.queue.dequeue = sandbox.stub().returns(NEW_LOCI);
|
259
260
|
parser.onDeltaAction = sandbox.stub();
|
@@ -262,7 +263,7 @@ describe('locus-info/parser', () => {
|
|
262
263
|
|
263
264
|
parser.processDeltaEvent();
|
264
265
|
|
265
|
-
assert.equal(parser.workingCopy,
|
266
|
+
assert.equal(parser.workingCopy, null);
|
266
267
|
});
|
267
268
|
|
268
269
|
it('does not replace current loci when the locus URL changes but incoming sequence is not later', () => {
|
@@ -6,6 +6,8 @@ import * as tsSdpModule from '@webex/ts-sdp';
|
|
6
6
|
import MediaProperties from '@webex/plugin-meetings/src/media/properties';
|
7
7
|
import {Defer} from '@webex/common';
|
8
8
|
import MediaConnectionAwaiter from '../../../../src/media/MediaConnectionAwaiter';
|
9
|
+
import Metrics from '../../../../src/metrics';
|
10
|
+
import BEHAVIORAL_METRICS from '../../../../src/metrics/constants';
|
9
11
|
|
10
12
|
describe('MediaProperties', () => {
|
11
13
|
let mediaProperties;
|
@@ -389,4 +391,139 @@ describe('MediaProperties', () => {
|
|
389
391
|
});
|
390
392
|
});
|
391
393
|
});
|
394
|
+
|
395
|
+
// issue types and subtypes used in these tests are just examples
|
396
|
+
// they don't reflect real issue types/subtypes used in production
|
397
|
+
describe('sendMediaIssueMetric', () => {
|
398
|
+
let sendBehavioralMetricStub;
|
399
|
+
let clock;
|
400
|
+
|
401
|
+
beforeEach(() => {
|
402
|
+
clock = sinon.useFakeTimers();
|
403
|
+
sendBehavioralMetricStub = sinon.stub(Metrics, 'sendBehavioralMetric');
|
404
|
+
});
|
405
|
+
|
406
|
+
afterEach(() => {
|
407
|
+
clock.restore();
|
408
|
+
});
|
409
|
+
|
410
|
+
it('should send a behavioral metric with correct parameters', () => {
|
411
|
+
const issueType = 'audio';
|
412
|
+
const issueSubType = 'packet-loss';
|
413
|
+
const correlationId = 'test-correlation-id-123';
|
414
|
+
|
415
|
+
mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
|
416
|
+
|
417
|
+
assert.calledOnce(sendBehavioralMetricStub);
|
418
|
+
assert.calledWith(sendBehavioralMetricStub, BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED, {
|
419
|
+
correlationId,
|
420
|
+
'audio_packet-loss': 1,
|
421
|
+
});
|
422
|
+
});
|
423
|
+
|
424
|
+
it('should increment count while being throttled and reset it once metric goes out', () => {
|
425
|
+
const issueType = 'video';
|
426
|
+
const issueSubType = 'freeze';
|
427
|
+
const correlationId = 'test-correlation-id';
|
428
|
+
|
429
|
+
// Call multiple times with same issue type/subtype
|
430
|
+
mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
|
431
|
+
mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
|
432
|
+
mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
|
433
|
+
|
434
|
+
// First call should go through immediately, subsequent calls are throttled
|
435
|
+
assert.calledOnce(sendBehavioralMetricStub);
|
436
|
+
assert.calledWith(sendBehavioralMetricStub, BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED, {
|
437
|
+
correlationId,
|
438
|
+
video_freeze: 1, // Only the first call goes through due to throttling
|
439
|
+
});
|
440
|
+
sendBehavioralMetricStub.resetHistory();
|
441
|
+
|
442
|
+
assert.equal(mediaProperties.mediaIssueCounters['video_freeze'], 2); // counter should be reset after the first metric goes out, hence only 2 not 3 here
|
443
|
+
|
444
|
+
clock.tick(5 * 60 * 1000); // Advance time by 5 minutes to expire throttle
|
445
|
+
|
446
|
+
assert.calledOnceWithExactly(
|
447
|
+
sendBehavioralMetricStub,
|
448
|
+
BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
|
449
|
+
{
|
450
|
+
correlationId,
|
451
|
+
video_freeze: 2,
|
452
|
+
}
|
453
|
+
);
|
454
|
+
});
|
455
|
+
|
456
|
+
it('should track different issue types separately in counters', () => {
|
457
|
+
const correlationId = 'test-correlation-id';
|
458
|
+
|
459
|
+
// Send different issue types
|
460
|
+
mediaProperties.sendMediaIssueMetric('audio', 'packet-loss', correlationId);
|
461
|
+
mediaProperties.sendMediaIssueMetric('video', 'freeze', correlationId);
|
462
|
+
mediaProperties.sendMediaIssueMetric('audio', 'packet-loss', correlationId);
|
463
|
+
mediaProperties.sendMediaIssueMetric('audio', 'packet-loss', correlationId);
|
464
|
+
mediaProperties.sendMediaIssueMetric('audio', 'packet-loss', correlationId);
|
465
|
+
mediaProperties.sendMediaIssueMetric('video', 'freeze', correlationId);
|
466
|
+
|
467
|
+
// First call should go through immediately, subsequent calls are throttled
|
468
|
+
assert.calledOnceWithExactly(
|
469
|
+
sendBehavioralMetricStub,
|
470
|
+
BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
|
471
|
+
{
|
472
|
+
correlationId,
|
473
|
+
'audio_packet-loss': 1,
|
474
|
+
}
|
475
|
+
);
|
476
|
+
|
477
|
+
// But the counters should be tracked separately
|
478
|
+
assert.equal(mediaProperties.mediaIssueCounters['audio_packet-loss'], 3);
|
479
|
+
assert.equal(mediaProperties.mediaIssueCounters['video_freeze'], 2);
|
480
|
+
|
481
|
+
sendBehavioralMetricStub.resetHistory();
|
482
|
+
|
483
|
+
clock.tick(5 * 60 * 1000); // Advance time by 5 minutes to expire throttle
|
484
|
+
|
485
|
+
assert.calledOnceWithExactly(
|
486
|
+
sendBehavioralMetricStub,
|
487
|
+
BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
|
488
|
+
{
|
489
|
+
correlationId,
|
490
|
+
video_freeze: 2,
|
491
|
+
'audio_packet-loss': 3,
|
492
|
+
}
|
493
|
+
);
|
494
|
+
});
|
495
|
+
|
496
|
+
it('should flush throttled metrics when unsetPeerConnection is called', () => {
|
497
|
+
const issueType = 'share';
|
498
|
+
const issueSubType = 'connection-lost';
|
499
|
+
const correlationId = 'test-correlation-id';
|
500
|
+
|
501
|
+
// Send metrics multiple times
|
502
|
+
mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
|
503
|
+
mediaProperties.sendMediaIssueMetric(issueType, issueSubType, correlationId);
|
504
|
+
|
505
|
+
// First call should go through immediately
|
506
|
+
assert.calledOnceWithExactly(
|
507
|
+
sendBehavioralMetricStub,
|
508
|
+
BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
|
509
|
+
{
|
510
|
+
correlationId,
|
511
|
+
'share_connection-lost': 1,
|
512
|
+
}
|
513
|
+
);
|
514
|
+
sendBehavioralMetricStub.resetHistory();
|
515
|
+
|
516
|
+
// Call unsetPeerConnection which should flush throttled metrics
|
517
|
+
mediaProperties.unsetPeerConnection();
|
518
|
+
|
519
|
+
assert.calledOnceWithExactly(
|
520
|
+
sendBehavioralMetricStub,
|
521
|
+
BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED,
|
522
|
+
{
|
523
|
+
correlationId,
|
524
|
+
'share_connection-lost': 1,
|
525
|
+
}
|
526
|
+
);
|
527
|
+
});
|
528
|
+
});
|
392
529
|
});
|