diff --git a/packages/@webex/internal-plugin-mercury/src/mercury.js b/packages/@webex/internal-plugin-mercury/src/mercury.js index 506815b086c..e90a123266d 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:u2c.cache-invalidation', (envelope) => { + if ( + typeof this.webex.internal.services?.invalidateCache === 'function' && + envelope && + envelope.data + ) { + this.webex.internal.services.invalidateCache(envelope.data?.timestamp); + } + }); }, /** 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..be9e3c4e1a0 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()), + invalidateCache: sinon.stub(), }; webex.internal.metrics.submitClientMetrics = sinon.stub(); webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus = sinon.stub(); @@ -162,6 +163,11 @@ describe('plugin-mercury', () => { }, }, }; + const cacheInvalidationEventEnvelope = { + data: { + timestamp: '1', + }, + }; assert.isFalse(mercury.connected, 'Mercury is not connected'); assert.isTrue(mercury.connecting, 'Mercury is connecting'); @@ -176,18 +182,25 @@ describe('plugin-mercury', () => { webex.internal.feature.updateFeature, envelope.data.featureToggle ); + mercury._emit('event:u2c.cache-invalidation', cacheInvalidationEventEnvelope); + assert.calledOnceWithExactly( + webex.internal.services.invalidateCache, + cacheInvalidationEventEnvelope.data.timestamp + ); sinon.restore(); }); }); - it('connects to Mercury but does not call updateFeature', () => { + it('connects to Mercury but does not call updateFeature or invalidateCache', () => { webex.internal.feature.updateFeature = sinon.stub(); const promise = mercury.connect(); const envelope = {}; return promise.then(() => { mercury._emit('event:featureToggle_update', envelope); + mercury._emit('event:u2c.cache-invalidation', envelope); assert.notCalled(webex.internal.feature.updateFeature); + assert.notCalled(webex.internal.services.invalidateCache); 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..a9f6dd9630c 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 @@ -45,6 +45,8 @@ const Services = WebexPlugin.extend({ _services: [], + _timestamp: 0, + /** * @private * Get the current catalog based on the assocaited @@ -122,6 +124,15 @@ const Services = WebexPlugin.extend({ this._services = unionBy(services, this._services, 'id'); }, + /** + * saves the timestamp of the last catalog update + * @param {number} timestamp + * @returns {void} + */ + _updateTimestamp(timestamp: number): void { + this._timestamp = timestamp; + }, + /** * Update a list of `serviceUrls` to the most current * catalog via the defined `discoveryUrl` then returns the current @@ -698,10 +709,11 @@ const Services = WebexPlugin.extend({ * catalog endpoint. * @returns {Array} */ - _formatReceivedHostmap({services, activeServices}) { + _formatReceivedHostmap({services, activeServices, timestamp}) { const formattedHostmap = services.map((service) => this._formatHostMapEntry(service)); this._updateActiveServices(activeServices); this._updateServices(services); + this._updateTimestamp(timestamp); return formattedHostmap; }, @@ -821,6 +833,22 @@ const Services = WebexPlugin.extend({ return newUrl.href; }, + /** + * @param {number} timestamp - the timestamp to compare against + * @returns {Promise} + */ + invalidateCache(timestamp: number): Promise { + if (timestamp > this._timestamp) { + this.logger.info('services: invalidating cache, timestamp is newer than current timestamp'); + + return this.updateServices({forceRefresh: true}); + } + + this.logger.info('services: not invalidating cache, timestamp is older than current timestamp'); + + return Promise.resolve(); + }, + /** * @private * Simplified method wrapper for sending a request to get 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..83bd10e30eb 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,28 @@ describe('webex-core', () => { }); }); + describe('#invalidateCache', () => { + let requestStub; + + beforeEach(() => { + services._formatReceivedHostmap(serviceHostmapV2); + }); + + afterEach(() => { + requestStub.restore(); + }); + + it('fetches new catalog when timestamp is newer than current timestamp', () => { + requestStub = sinon + .stub(webex.internal.newMetrics.callDiagnosticLatencies, 'measureLatency') + .returns(Promise.resolve()); + + services.invalidateCache(Date.now()); + + assert.calledOnce(requestStub); + }); + }); + 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..8fb916e953a 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,32 @@ describe('webex-core', () => { }); }); + describe('#invalidateCache', () => { + let serviceHostmap; + let formattedHM; + + beforeEach(() => { + serviceHostmap = serviceHostmapV2; + formattedHM = services._formatReceivedHostmap(serviceHostmap); + }); + + it('makes request to fetch when timestamp is newer', async () => { + services.updateServices = sinon.stub(); + + await services.invalidateCache(Date.now()); + + assert.calledWith(services.updateServices, {forceRefresh: true}); + }); + + it('does not make request when timestamp is older', async () => { + services.updateServices = sinon.stub(); + + await services.invalidateCache(0); + + assert.notCalled(services.updateServices); + }); + }); + describe('#updateCatalog', () => { it('updates the catalog', async () => { const serviceGroup = 'postauth';