Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/@webex/plugin-meetings/src/hashTree/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ export const ObjectType = {
mediaShare: 'mediashare',
info: 'info',
fullState: 'fullstate',
links: 'links',
} as const;

export type ObjectType = Enum<typeof ObjectType>;

// mapping from ObjectType to top level LocusDTO keys
export const ObjectTypeToLocusKeyMap = {
[ObjectType.links]: 'links',
[ObjectType.info]: 'info',
[ObjectType.fullState]: 'fullState',
[ObjectType.self]: 'self',
Expand Down
40 changes: 17 additions & 23 deletions packages/@webex/plugin-meetings/src/locus-info/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import HashTreeParser, {
LocusInfoUpdateType,
} from '../hashTree/hashTreeParser';
import {ObjectType, ObjectTypeToLocusKeyMap} from '../hashTree/types';
import {LocusDTO, LocusFullState} from './types';
import {Links, LocusDTO, LocusFullState} from './types';

export type LocusLLMEvent = {
data: {
Expand Down Expand Up @@ -113,8 +113,7 @@ export default class LocusInfo extends EventsScope {
mediaShares: any;
replace: any;
url: any;
services: any;
resources: any;
links?: Links;
mainSessionLocusCache: any;
self: any;
hashTreeParser?: HashTreeParser;
Expand Down Expand Up @@ -360,8 +359,7 @@ export default class LocusInfo extends EventsScope {
this.updateSelf(locus.self);
this.updateHostInfo(locus.host);
this.updateMediaShares(locus.mediaShares);
this.updateServices(locus.links?.services);
this.updateResources(locus.links?.resources);
this.updateLinks(locus.links);
}

/**
Expand Down Expand Up @@ -596,12 +594,13 @@ export default class LocusInfo extends EventsScope {
this.hashTreeObjectId2ParticipantId.delete(object.htMeta.elementId.id);
}
break;
case ObjectType.links:
case ObjectType.info:
case ObjectType.fullState:
case ObjectType.self:
if (!object.data) {
// self without data is handled inside HashTreeParser and results in LocusInfoUpdateType.MEETING_ENDED, so we should never get here
// other types like info or fullstate - Locus should never send them without data
// all other types info, fullstate, etc - Locus should never send them without data
LoggerProxy.logger.warn(
`Locus-info:index#updateLocusFromHashTreeObject --> received ${type} object without data, this is not expected! version=${object.htMeta.elementId.version}`
);
Expand Down Expand Up @@ -1032,8 +1031,7 @@ export default class LocusInfo extends EventsScope {
this.updateMemberShip(locus.membership);
this.updateIdentifiers(locus.identities);
this.updateEmbeddedApps(locus.embeddedApps);
this.updateServices(locus.links?.services);
this.updateResources(locus.links?.resources);
this.updateLinks(locus.links);
this.compareAndUpdate();
// update which required to compare different objects from locus
}
Expand Down Expand Up @@ -1685,45 +1683,41 @@ export default class LocusInfo extends EventsScope {
}

/**
* @param {Object} services
* Updates links and emits appropriate events if services or resources have changed
* @param {Object} links
* @returns {undefined}
* @memberof LocusInfo
*/
updateServices(services: Record<'breakout' | 'record', {url: string}>) {
if (services && !isEqual(this.services, services)) {
this.services = services;
updateLinks(links?: Links) {
const {services, resources} = links || {};

if (services && !isEqual(this.links?.services, services)) {
this.emitScoped(
{
file: 'locus-info',
function: 'updateServices',
function: 'updateLinks',
},
LOCUSINFO.EVENTS.LINKS_SERVICES,
{
services,
}
);
}
}

/**
* @param {Object} resources
* @returns {undefined}
* @memberof LocusInfo
*/
updateResources(resources: Record<'webcastInstance', {url: string}>) {
if (resources && !isEqual(this.resources, resources)) {
this.resources = resources;
if (resources && !isEqual(this.links?.resources, resources)) {
this.emitScoped(
{
file: 'locus-info',
function: 'updateResources',
function: 'updateLinks',
},
LOCUSINFO.EVENTS.LINKS_RESOURCES,
{
resources,
}
);
}

this.links = links;
}

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/@webex/plugin-meetings/src/locus-info/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export type LocusFullState = {
type: string;
};

export type Links = {
services: Record<'breakout' | 'record', {url: string}>; // there exist also other services, but these are the ones we currently use
resources: Record<'webcastInstance' | 'visibleDataSets', {url: string}>; // there exist also other resources, but these are the ones we currently use
};

export type LocusDTO = {
controls?: any;
fullState?: LocusFullState;
Expand All @@ -27,7 +32,7 @@ export type LocusDTO = {
jsSdkMeta?: {
removedParticipantIds: string[]; // list of ids of participants that are removed in the last update
};
links?: any;
links?: Links;
mediaShares?: any[];
meetings?: any[];
participants: any[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,24 @@ describe('plugin-meetings', () => {
});
});

it('should process locus update correctly when called with updated links', () => {
const newLinks = {
id: 'new-links',
visibleDataSets: ['dataset1', 'dataset2'],
};

// simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
locusInfoUpdateCallback(OBJECTS_UPDATED, {
updatedObjects: [{htMeta: {elementId: {type: 'links'}}, data: newLinks}],
});

// check onDeltaLocus() was called with correctly updated locus info
assert.calledOnceWithExactly(onDeltaLocusStub, {
...expectedLocusInfo,
links: newLinks,
});
});

it('should process locus update correctly when called with updated LOCUS object', () => {
// setup new updated locus that has many things missing
const newLocusHtMeta = {elementId: {type: 'locus', version: 42}};
Expand All @@ -414,6 +432,7 @@ describe('plugin-meetings', () => {
info: {id: 'fake-info'},
fullState: {id: 'fake-full-state'},
self: {id: 'fake-self'},
links: { id: 'fake-links' },
mediaShares: expectedLocusInfo.mediaShares,
// and now the new fields
...newLocus,
Expand Down Expand Up @@ -459,6 +478,7 @@ describe('plugin-meetings', () => {
info: {id: 'fake-info'},
fullState: {id: 'fake-full-state'},
self: {id: 'fake-self'},
links: { id: 'fake-links' },
mediaShares: expectedLocusInfo.mediaShares,
participants: [], // empty means there were no participant updates
jsSdkMeta: {removedParticipantIds: []}, // no participants were removed
Expand Down Expand Up @@ -492,6 +512,7 @@ describe('plugin-meetings', () => {
info: {id: 'fake-info'},
fullState: {id: 'fake-full-state'},
self: {id: 'fake-self'},
links: {id: 'fake-links'},
mediaShares: expectedLocusInfo.mediaShares,
// and now the new fields
...newLocus,
Expand Down Expand Up @@ -2771,7 +2792,7 @@ describe('plugin-meetings', () => {
sinon.stub(locusInfo, "updateMemberShip");
sinon.stub(locusInfo, "updateIdentifiers");
sinon.stub(locusInfo, "updateEmbeddedApps");
sinon.stub(locusInfo, "updateResources");
sinon.stub(locusInfo, "updateLinks");
sinon.stub(locusInfo, "compareAndUpdate");

locusInfo.updateLocusInfo(locus);
Expand Down Expand Up @@ -2805,7 +2826,7 @@ describe('plugin-meetings', () => {
locusInfo.updateMemberShip = sinon.stub();
locusInfo.updateIdentifiers = sinon.stub();
locusInfo.updateEmbeddedApps = sinon.stub();
locusInfo.updateResources = sinon.stub();
locusInfo.updateLinks = sinon.stub();
locusInfo.compareAndUpdate = sinon.stub();

locusInfo.updateLocusInfo(newLocus);
Expand All @@ -2827,11 +2848,42 @@ describe('plugin-meetings', () => {
assert.notCalled(locusInfo.updateMemberShip);
assert.notCalled(locusInfo.updateIdentifiers);
assert.notCalled(locusInfo.updateEmbeddedApps);
assert.notCalled(locusInfo.updateResources);
assert.notCalled(locusInfo.updateLinks);
assert.notCalled(locusInfo.compareAndUpdate);
});

it('#updateLocusInfo puts the Locus DTO top level properties at the right place in LocusInfo class', () => {
// this test verifies that the top-level properties of Locus DTO are copied
// into LocusInfo class and set as top level properties too
// this is important, because the code handling Locus hass trees relies on it, see updateFromHashTree()
const info = {id: 'info id'};
const fullState = {id: 'fullState id'};
const links = {services: {id: 'service links'}, resources: {id: 'resource links'}};
const self = {id: 'self id'};
const mediaShares = [{id: 'fake media share'}];

sinon.stub(SelfUtils, 'getSelves').returns({
current: {},
previous: {},
updates: {},
});

const newLocus = {
info,
fullState,
links,
self,
mediaShares,
};

locusInfo.updateLocusInfo(newLocus);

assert.deepEqual(locusInfo.info, newLocus.info);
assert.deepEqual(locusInfo.fullState, newLocus.fullState);
assert.deepEqual(locusInfo.links, newLocus.links);
assert.deepEqual(locusInfo.self, newLocus.self);
assert.deepEqual(locusInfo.mediaShares, newLocus.mediaShares);
});

it('onFullLocus() updates the working-copy of locus parser', () => {
const eventType = 'fakeEvent';
Expand Down
Loading