From 3823b75cfe4e90520656acca90441632b3ef5a44 Mon Sep 17 00:00:00 2001 From: rstachof Date: Fri, 12 Dec 2025 00:44:41 -0500 Subject: [PATCH 1/5] fix(init): make webex.ready depend on services init --- .../src/lib/credentials/credentials.js | 30 +++++++++++++ .../webex-core/src/lib/credentials/index.js | 2 +- .../webex-core/src/lib/services/services.js | 42 +++++++++++++++---- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/packages/@webex/webex-core/src/lib/credentials/credentials.js b/packages/@webex/webex-core/src/lib/credentials/credentials.js index 59ada43c61a..beacce0ca6b 100644 --- a/packages/@webex/webex-core/src/lib/credentials/credentials.js +++ b/packages/@webex/webex-core/src/lib/credentials/credentials.js @@ -38,6 +38,12 @@ const Credentials = WebexPlugin.extend({ return Boolean((this.supertoken && this.supertoken.canAuthorize) || this.canRefresh); }, }, + canMakeRequests: { + deps: ['canAuthorize', 'servicesReady'], + fn() { + return Boolean(this.canAuthorize && this.servicesReady); + }, + }, canRefresh: { deps: ['supertoken', 'supertoken.canRefresh'], fn() { @@ -92,6 +98,17 @@ const Credentials = WebexPlugin.extend({ default: false, type: 'boolean', }, + /** + * Becomes `true` once services initialization has run. + * Resets to `false` on logout/token invalidation. + * @instance + * @memberof Credentials + * @type {boolean} + */ + servicesReady: { + default: false, + type: 'boolean', + }, refreshTimer: { default: undefined, type: 'any', @@ -424,6 +441,16 @@ const Credentials = WebexPlugin.extend({ this.webex.once('loaded', () => { this.ready = true; }); + + // Listen for services initialization events after storage is loaded. + // Services will emit 'services:initialized' when its init completes. + this.listenToOnce(this.webex, 'loaded', () => { + if (this.webex.internal.services) { + this.listenTo(this.webex.internal.services, 'services:initialized', () => { + this.servicesReady = true; + }); + } + }); }, @oneFlight @@ -462,6 +489,9 @@ const Credentials = WebexPlugin.extend({ this.logger.info('credentials: finished removing tokens'); + // Reset servicesReady state on logout + this.servicesReady = false; + // Return a promise to give the storage layer a tick or two to clear // localStorage return Promise.resolve(); diff --git a/packages/@webex/webex-core/src/lib/credentials/index.js b/packages/@webex/webex-core/src/lib/credentials/index.js index f407e7b7328..158db1be553 100644 --- a/packages/@webex/webex-core/src/lib/credentials/index.js +++ b/packages/@webex/webex-core/src/lib/credentials/index.js @@ -7,7 +7,7 @@ import {registerPlugin} from '../../webex-core'; import Credentials from './credentials'; registerPlugin('credentials', Credentials, { - proxies: ['canAuthorize', 'canRefresh'], + proxies: ['canAuthorize', 'canMakeRequests', 'canRefresh', 'servicesReady'], }); export {default as Credentials} from './credentials'; diff --git a/packages/@webex/webex-core/src/lib/services/services.js b/packages/@webex/webex-core/src/lib/services/services.js index 32f3120dcea..74c7280dfd2 100644 --- a/packages/@webex/webex-core/src/lib/services/services.js +++ b/packages/@webex/webex-core/src/lib/services/services.js @@ -55,6 +55,20 @@ const Services = WebexPlugin.extend({ initFailed: ['boolean', false, false], }, + session: { + /** + * Becomes `true` once service catalog initialization has completed. + * Blocks `webex.ready` until services are initialized. + * @instance + * @memberof Services + * @type {boolean} + */ + ready: { + default: false, + type: 'boolean', + }, + }, + _catalogs: new WeakMap(), _serviceUrls: null, @@ -1121,9 +1135,10 @@ const Services = WebexPlugin.extend({ this.initConfig(); }); - // wait for webex instance to be ready before attempting - // to update the service catalogs - this.listenToOnce(this.webex, 'ready', () => { + // Wait for storage to be loaded before attempting to update the service catalogs. + // We listen for 'loaded' instead of 'ready' because services.ready is a dependency + // of webex.ready - listening to 'ready' would cause a deadlock. + this.listenToOnce(this.webex, 'loaded', () => { const {supertoken} = this.webex.credentials; // Validate if the supertoken exists. if (supertoken && supertoken.access_token) { @@ -1136,16 +1151,25 @@ const Services = WebexPlugin.extend({ this.logger.error( `services: failed to init initial services when credentials available, ${error?.message}` ); + }) + .finally(() => { + this.ready = true; + this.trigger('services:initialized'); }); } else { const {email} = this.webex.config; - this.collectPreauthCatalog(email ? {email} : undefined).catch((error) => { - this.initFailed = true; - this.logger.error( - `services: failed to init initial services when no credentials available, ${error?.message}` - ); - }); + this.collectPreauthCatalog(email ? {email} : undefined) + .catch((error) => { + this.initFailed = true; + this.logger.error( + `services: failed to init initial services when no credentials available, ${error?.message}` + ); + }) + .finally(() => { + this.ready = true; + this.trigger('services:initialized'); + }); } }); }, From d0bab4fd78d54f1f55a9184493f8f20206170c25 Mon Sep 17 00:00:00 2001 From: rstachof Date: Fri, 12 Dec 2025 15:31:16 -0500 Subject: [PATCH 2/5] fix(services): init services ready and deploy --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index efbe7b8b2a2..493f3c5af60 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,7 +4,7 @@ run-name: ${{ github.actor }} is running Deploy CD on: push: branches: # White-list of deployable tags and branches. Note that all white-listed branches cannot include any `/` characters - - next + - webex-ready-services env: rid: ${{ github.run_id }}-${{ github.run_number }} From 258991a7077a2a5e3b1099a24612ab936fe0750e Mon Sep 17 00:00:00 2001 From: rstachof Date: Fri, 12 Dec 2025 15:39:02 -0500 Subject: [PATCH 3/5] fix(init): use services ready --- .../src/lib/credentials/credentials.js | 30 ------------------- .../webex-core/src/lib/credentials/index.js | 2 +- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/packages/@webex/webex-core/src/lib/credentials/credentials.js b/packages/@webex/webex-core/src/lib/credentials/credentials.js index beacce0ca6b..59ada43c61a 100644 --- a/packages/@webex/webex-core/src/lib/credentials/credentials.js +++ b/packages/@webex/webex-core/src/lib/credentials/credentials.js @@ -38,12 +38,6 @@ const Credentials = WebexPlugin.extend({ return Boolean((this.supertoken && this.supertoken.canAuthorize) || this.canRefresh); }, }, - canMakeRequests: { - deps: ['canAuthorize', 'servicesReady'], - fn() { - return Boolean(this.canAuthorize && this.servicesReady); - }, - }, canRefresh: { deps: ['supertoken', 'supertoken.canRefresh'], fn() { @@ -98,17 +92,6 @@ const Credentials = WebexPlugin.extend({ default: false, type: 'boolean', }, - /** - * Becomes `true` once services initialization has run. - * Resets to `false` on logout/token invalidation. - * @instance - * @memberof Credentials - * @type {boolean} - */ - servicesReady: { - default: false, - type: 'boolean', - }, refreshTimer: { default: undefined, type: 'any', @@ -441,16 +424,6 @@ const Credentials = WebexPlugin.extend({ this.webex.once('loaded', () => { this.ready = true; }); - - // Listen for services initialization events after storage is loaded. - // Services will emit 'services:initialized' when its init completes. - this.listenToOnce(this.webex, 'loaded', () => { - if (this.webex.internal.services) { - this.listenTo(this.webex.internal.services, 'services:initialized', () => { - this.servicesReady = true; - }); - } - }); }, @oneFlight @@ -489,9 +462,6 @@ const Credentials = WebexPlugin.extend({ this.logger.info('credentials: finished removing tokens'); - // Reset servicesReady state on logout - this.servicesReady = false; - // Return a promise to give the storage layer a tick or two to clear // localStorage return Promise.resolve(); diff --git a/packages/@webex/webex-core/src/lib/credentials/index.js b/packages/@webex/webex-core/src/lib/credentials/index.js index 158db1be553..f407e7b7328 100644 --- a/packages/@webex/webex-core/src/lib/credentials/index.js +++ b/packages/@webex/webex-core/src/lib/credentials/index.js @@ -7,7 +7,7 @@ import {registerPlugin} from '../../webex-core'; import Credentials from './credentials'; registerPlugin('credentials', Credentials, { - proxies: ['canAuthorize', 'canMakeRequests', 'canRefresh', 'servicesReady'], + proxies: ['canAuthorize', 'canRefresh'], }); export {default as Credentials} from './credentials'; From 3ca098d662f2cff5f7cccb5ddb3df1c17635ab8f Mon Sep 17 00:00:00 2001 From: rstachof Date: Fri, 12 Dec 2025 15:40:02 -0500 Subject: [PATCH 4/5] fix(ci): change deploy yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 493f3c5af60..339c34d03b8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,7 +4,7 @@ run-name: ${{ github.actor }} is running Deploy CD on: push: branches: # White-list of deployable tags and branches. Note that all white-listed branches cannot include any `/` characters - - webex-ready-services + - webex-services-ready env: rid: ${{ github.run_id }}-${{ github.run_number }} From fb4ae9135fa7ff8d07e5d3f4a21bd0f8c52e5093 Mon Sep 17 00:00:00 2001 From: rstachof Date: Fri, 12 Dec 2025 15:53:56 -0500 Subject: [PATCH 5/5] fix(services): add similar init logic to services-v2 --- .../src/lib/services-v2/services-v2.ts | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) 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 118c4d230fd..6e072da74c9 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 @@ -40,6 +40,20 @@ const Services = WebexPlugin.extend({ initFailed: ['boolean', false, false], }, + session: { + /** + * Becomes `true` once services initialization has completed. + * This blocks `webex.ready` until services are initialized. + * @instance + * @memberof Services + * @type {boolean} + */ + ready: { + default: false, + type: 'boolean', + }, + }, + _catalogs: new WeakMap(), _activeServices: {}, @@ -1091,9 +1105,10 @@ const Services = WebexPlugin.extend({ this.initConfig(); }); - // wait for webex instance to be ready before attempting - // to update the service catalogs - this.listenToOnce(this.webex, 'ready', () => { + // wait for webex instance storage to be loaded before attempting + // to update the service catalogs. Using 'loaded' instead of 'ready' + // to avoid deadlock since webex.ready depends on this plugin's ready. + this.listenToOnce(this.webex, 'loaded', () => { const {supertoken} = this.webex.credentials; // Validate if the supertoken exists. if (supertoken && supertoken.access_token) { @@ -1106,16 +1121,25 @@ const Services = WebexPlugin.extend({ this.logger.error( `services: failed to init initial services when credentials available, ${error?.message}` ); + }) + .finally(() => { + this.ready = true; + this.trigger('services:initialized'); }); } else { const {email} = this.webex.config; - this.collectPreauthCatalog(email ? {email} : undefined).catch((error) => { - this.initFailed = true; - this.logger.error( - `services: failed to init initial services when no credentials available, ${error?.message}` - ); - }); + this.collectPreauthCatalog(email ? {email} : undefined) + .catch((error) => { + this.initFailed = true; + this.logger.error( + `services: failed to init initial services when no credentials available, ${error?.message}` + ); + }) + .finally(() => { + this.ready = true; + this.trigger('services:initialized'); + }); } }); },