diff --git a/packages/@webex/internal-plugin-mercury/src/mercury.js b/packages/@webex/internal-plugin-mercury/src/mercury.js index 506815b086c..a87a29b13d1 100644 --- a/packages/@webex/internal-plugin-mercury/src/mercury.js +++ b/packages/@webex/internal-plugin-mercury/src/mercury.js @@ -68,6 +68,17 @@ const Mercury = WebexPlugin.extend({ this.webex.internal.feature.updateFeature(envelope.data.featureToggle); } }); + + // subject to change + this.on('event:ActiveClusterStatusEvent', (envelope) => { + if ( + typeof this.webex.internal.services?.switchActiveClusterIds === 'function' && + envelope && + envelope.data + ) { + this.webex.internal.services.switchActiveClusterIds(envelope.data?.activeClusters); + } + }); }, /** diff --git a/packages/@webex/internal-plugin-mercury/test/unit/spec/mercury.js b/packages/@webex/internal-plugin-mercury/test/unit/spec/mercury.js index 7bfa76e6890..50381ae1693 100644 --- a/packages/@webex/internal-plugin-mercury/test/unit/spec/mercury.js +++ b/packages/@webex/internal-plugin-mercury/test/unit/spec/mercury.js @@ -75,6 +75,7 @@ describe('plugin-mercury', () => { webex.internal.services = { convertUrlToPriorityHostUrl: sinon.stub().returns(Promise.resolve('ws://example-2.com')), markFailedUrl: sinon.stub().returns(Promise.resolve()), + switchActiveClusterIds: sinon.stub(), }; webex.internal.metrics.submitClientMetrics = sinon.stub(); webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus = sinon.stub(); @@ -155,13 +156,20 @@ describe('plugin-mercury', () => { it('connects to Mercury using default url', () => { webex.internal.feature.updateFeature = sinon.stub(); const promise = mercury.connect(); - const envelope = { + const featureToggleEnvelope = { data: { featureToggle: { 'feature-name': true, }, }, }; + const activeClusterEventEnvelope = { + data: { + activeClusters: { + wdm: 'wdm-cluster-id.com', + }, + }, + }; assert.isFalse(mercury.connected, 'Mercury is not connected'); assert.isTrue(mercury.connecting, 'Mercury is connecting'); @@ -171,23 +179,32 @@ describe('plugin-mercury', () => { assert.isTrue(mercury.connected, 'Mercury is connected'); assert.isFalse(mercury.connecting, 'Mercury is not connecting'); assert.calledWith(socketOpenStub, sinon.match(/ws:\/\/example.com/), sinon.match.any); - mercury._emit('event:featureToggle_update', envelope); + mercury._emit('event:featureToggle_update', featureToggleEnvelope); assert.calledOnceWithExactly( webex.internal.feature.updateFeature, - envelope.data.featureToggle + featureToggleEnvelope.data.featureToggle + ); + + mercury._emit('event:ActiveClusterStatusEvent', activeClusterEventEnvelope); + assert.calledOnceWithExactly( + webex.internal.services.switchActiveClusterIds, + activeClusterEventEnvelope.data.activeClusters ); sinon.restore(); }); }); - it('connects to Mercury but does not call updateFeature', () => { + it('connects to Mercury but does not call updateFeature or switchActiveClusterIds', () => { webex.internal.feature.updateFeature = sinon.stub(); const promise = mercury.connect(); const envelope = {}; return promise.then(() => { mercury._emit('event:featureToggle_update', envelope); + mercury._emit('event:ActiveClusterStatusEvent', envelope); assert.notCalled(webex.internal.feature.updateFeature); + assert.notCalled(webex.internal.services.switchActiveClusterIds); + sinon.restore(); }); }); diff --git a/packages/@webex/webex-core/src/lib/services-v2/services-v2.ts b/packages/@webex/webex-core/src/lib/services-v2/services-v2.ts index 7c7a3842745..e42771df394 100644 --- a/packages/@webex/webex-core/src/lib/services-v2/services-v2.ts +++ b/packages/@webex/webex-core/src/lib/services-v2/services-v2.ts @@ -346,6 +346,41 @@ const Services = WebexPlugin.extend({ ); }, + /** + * Update cluster id via mercury service update. If the cluster id does not exist, + * fetch new catalog. + * + * @param {ActiveServices} newActiveClusters - The new active clusters to switch to. + * @returns {Promsie} + * */ + switchActiveClusterIds(newActiveClusters: ActiveServices): Promise { + this.logger.info('services: switching active cluster ids'); + + const newActiveClusterIds = Object.values(newActiveClusters); + let missingClusterIds = false; + + newActiveClusterIds.forEach((clusterId) => { + // if the clusterId does not exist in the catalog, fetch the catalog + if (!this._services.find((service) => service.id === clusterId)) { + missingClusterIds = true; + } + }); + + if (missingClusterIds) { + this.logger.warn( + 'services: some cluster ids do not exist in the catalog, fetching the catalog' + ); + + // fetch the catalog + return this.updateServices({forceRefresh: true}); + } + // update the active services + this._updateActiveServices(newActiveClusters); + this.logger.info('services: active cluster ids updated successfully'); + + return Promise.resolve(); + }, + /** * Get user meeting preferences (preferred webex site). * diff --git a/packages/@webex/webex-core/test/integration/spec/services-v2/services-v2.js b/packages/@webex/webex-core/test/integration/spec/services-v2/services-v2.js index 5e2d2c59ff7..5ecc90733e9 100644 --- a/packages/@webex/webex-core/test/integration/spec/services-v2/services-v2.js +++ b/packages/@webex/webex-core/test/integration/spec/services-v2/services-v2.js @@ -19,7 +19,10 @@ import WebexCore, { import testUsers from '@webex/test-helper-test-users'; import uuid from 'uuid'; import sinon from 'sinon'; -import {formattedServiceHostmapEntryConv} from '../../../fixtures/host-catalog-v2'; +import { + formattedServiceHostmapEntryConv, + serviceHostmapV2, +} from '../../../fixtures/host-catalog-v2'; // /* eslint-disable no-underscore-dangle */ describe('webex-core', () => { @@ -494,6 +497,54 @@ describe('webex-core', () => { }); }); + describe('#switchActiveClusterIds', () => { + let requestStub; + + beforeEach(() => { + services._formatReceivedHostmap(serviceHostmapV2); + }); + + afterEach(() => { + requestStub.restore(); + }); + + it('fetches new catalog when id does not exist', () => { + requestStub = sinon + .stub(webex.internal.newMetrics.callDiagnosticLatencies, 'measureLatency') + .returns( + Promise.resolve({ + body: { + activeServices: { + ...serviceHostmapV2.activeServices, + conversation: 'urn:TEAM:me-central-1_asdf:conversation', + }, + services: [ + ...serviceHostmapV2.services, + { + id: 'urn:TEAM:me-central-1_asdf:conversation', + serviceName: 'conversation', + serviceUrls: [{baseUrl: 'baseurl.com', priority: 1}], + }, + ], + }, + }) + ); + + services + .switchActiveClusterIds({ + conversation: 'urn:TEAM:me-central-1_asdf:conversation', + }) + .then(() => { + assert.equal( + !!services._services.find( + (service) => service.id === 'urn:TEAM:me-central-1_asdf:conversation' + ), + true + ); + }); + }); + }); + describe('#updateServices()', () => { it('returns a Promise that and resolves on success', (done) => { const servicesPromise = services.updateServices(); diff --git a/packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts b/packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts index e0b67333ae4..e3b01bd9e54 100644 --- a/packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts +++ b/packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts @@ -268,6 +268,48 @@ describe('webex-core', () => { }); }); + describe('#switchActiveClusterIds', () => { + let serviceHostmap; + let formattedHM; + + beforeEach(() => { + serviceHostmap = serviceHostmapV2; + formattedHM = services._formatReceivedHostmap(serviceHostmap); + }); + + it('switches properly when id exists', async () => { + services.updateServices = sinon.stub(); + services._updateActiveServices = sinon.stub().callsFake((data) => { + Object.assign(services._activeServices, data); + }); + + await services.switchActiveClusterIds({ + conversation: 'urn:TEAM:me-central-1_d:conversation', + }); + + assert.notCalled(services.updateServices); + + assert.calledWith(services._updateActiveServices, { + conversation: 'urn:TEAM:me-central-1_d:conversation', + }); + + assert.equal(services._activeServices.conversation, 'urn:TEAM:me-central-1_d:conversation'); + }); + + it('makes request to fetch when id does not exist', async () => { + services.updateServices = sinon.stub(); + services._updateActiveServices = sinon.stub().callsFake((data) => { + Object.assign(services._activeServices, data); + }); + + await services.switchActiveClusterIds({ + conversation: 'urn:TEAM:me-central-1_asdf:conversation', + }); + + assert.calledWith(services.updateServices, {forceRefresh: true}); + }); + }); + describe('#updateCatalog', () => { it('updates the catalog', async () => { const serviceGroup = 'postauth';