Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
10 changes: 10 additions & 0 deletions packages/@webex/webex-core/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ export default {
*/
useCatalogOverride: false,

/**
* When true, skips fetching the preauth catalog during initialization
* while the user is unauthenticated (no existing token). The catalog is
* expected to be collected manually later instead. When false (default),
* the preauth catalog is collected automatically during init.
*
* @type {boolean}
*/
skipPreauthCatalogOnUnauthenticated: false,

/**
* Maximum time (in milliseconds) to wait for the initial service catalog
* collection when `waitForCatalogInit` is enabled, before letting
Expand Down
79 changes: 54 additions & 25 deletions packages/@webex/webex-core/src/lib/services-v2/services-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,28 @@ const Services = WebexPlugin.extend({
this.ready = true;
},

/**
* Build a promise that rejects once the catalog init timeout elapses. Race
* this against catalog collection so a hung request never leaves
* `services.ready` false forever - that would stall `webex.ready` and leave
* consumers waiting on it indefinitely. Timeout is configurable via
* `config.services.catalogInitTimeout` (defaults to 15s in config). Created
* lazily so paths that skip catalog collection never schedule a stray timer.
*
* @private
* @returns {Promise<never>}
*/
_makeInitTimeout(): Promise<never> {
const initTimeoutMs = this.webex.config?.services?.catalogInitTimeout;

return new Promise<never>((_, reject) => {
setTimeout(
() => reject(new Error(`services: init timed out after ${initTimeoutMs}ms`)),
initTimeoutMs
);
});
},

/**
* Initializer
*
Expand Down Expand Up @@ -1455,6 +1477,14 @@ const Services = WebexPlugin.extend({
} else {
const {email} = this.webex.config;

if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
this.logger.info(
'services: skipping preauth catalog collection while unauthenticated as per the config'
);

return;
}

this.collectPreauthCatalog(email ? {email} : undefined).catch((error) => {
this.initFailed = true;
this.logger.error(
Expand Down Expand Up @@ -1489,24 +1519,12 @@ const Services = WebexPlugin.extend({
}
const {supertoken} = this.webex.credentials;

// Race init against a hard timeout so a hung request never leaves
// `services.ready` false forever - that would stall `webex.ready` and
// leave consumers waiting on it indefinitely. Timeout is configurable via
// `config.services.catalogInitTimeout` (defaults to 15s in config).
const initTimeoutMs = this.webex.config?.services?.catalogInitTimeout;
const initServiceCatalogsTimeout = new Promise<never>((_, reject) => {
setTimeout(
() => reject(new Error(`services: init timed out after ${initTimeoutMs}ms`)),
initTimeoutMs
);
});

// Validate if the supertoken exists.
if (supertoken && supertoken.access_token) {
// `initServiceCatalogs` marks the catalog ready internally once the
// postauth catalog is collected - even if it loses the timeout race
// above, so a slow fetch still eventually flips `catalog.isReady`.
Promise.race([this.initServiceCatalogs(), initServiceCatalogsTimeout])
// below, so a slow fetch still eventually flips `catalog.isReady`.
Promise.race([this.initServiceCatalogs(), this._makeInitTimeout()])
.catch((error) => {
this.initFailed = true;
this.logger.error(
Expand All @@ -1517,17 +1535,28 @@ const Services = WebexPlugin.extend({
} else {
const {email} = this.webex.config;

Promise.race([
this.collectPreauthCatalog(email ? {email} : undefined),
initServiceCatalogsTimeout,
])
.catch((error) => {
this.initFailed = true;
this.logger.error(
`services: failed to init initial services when no credentials available, ${error?.message}`
);
})
.finally(() => this._finalizeReady());
if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
// Skip the preauth catalog fetch (it will be collected manually
// later), but still finalize `services.ready` so `webex.ready` is not
// stalled while unauthenticated. No timeout is created here so there
// is no stray timer or unhandled rejection.
this.logger.info(
'services: skipping preauth catalog collection while unauthenticated as per the config'
);
this._finalizeReady();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register the auth listener before finalizing readiness

When gated initialization and skipPreauthCatalogOnUnauthenticated are enabled without a credentials refresh in progress, _finalizeReady() sets services.ready synchronously, which can synchronously emit webex.ready. If a consumer's ready handler installs a token immediately, change:canAuthorize fires before the listener below is registered, so initServiceCatalogs() never runs and the authenticated catalog remains unavailable. Register the fresh-login listener before calling _finalizeReady() in this branch.

Useful? React with 👍 / 👎.

} else {
Promise.race([
this.collectPreauthCatalog(email ? {email} : undefined),
this._makeInitTimeout(),
])
.catch((error) => {
this.initFailed = true;
this.logger.error(
`services: failed to init initial services when no credentials available, ${error?.message}`
);
})
.finally(() => this._finalizeReady());
}

// Handle fresh login: 'loaded' fires before OAuth completes, so listen
// for `canAuthorize` flipping true and then collect the postauth catalog.
Expand Down
80 changes: 54 additions & 26 deletions packages/@webex/webex-core/src/lib/services/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,28 @@ const Services = WebexPlugin.extend({
this.ready = true;
},

/**
* Build a promise that rejects once the catalog init timeout elapses. Race
* this against catalog collection so a hung request never leaves
* `services.ready` false forever - that would stall `webex.ready` and leave
* consumers waiting on it indefinitely. Timeout is configurable via
* `config.services.catalogInitTimeout` (defaults to 15s in config). Created
* lazily so paths that skip catalog collection never schedule a stray timer.
*
* @private
* @returns {Promise<never>}
*/
_makeInitTimeout() {
const initTimeoutMs = this.webex.config?.services?.catalogInitTimeout;

return new Promise((_, reject) => {
setTimeout(
() => reject(new Error(`services: init timed out after ${initTimeoutMs}ms`)),
initTimeoutMs
);
});
},

/**
* Initializer
*
Expand Down Expand Up @@ -1492,6 +1514,14 @@ const Services = WebexPlugin.extend({
} else {
const {email} = this.webex.config;

if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
this.logger.info(
'services: skipping preauth catalog collection while unauthenticated as per the config'
);

return;
}

this.collectPreauthCatalog(email ? {email} : undefined).catch((error) => {
this.initFailed = true;
this.logger.error(
Expand Down Expand Up @@ -1526,25 +1556,12 @@ const Services = WebexPlugin.extend({
}
const {supertoken} = this.webex.credentials;

// Race init against a hard timeout so a hung request never leaves
// `services.ready` false forever - that would stall `webex.ready` and
// leave consumers waiting on it indefinitely. Timeout is configurable via
// `config.services.catalogInitTimeout` (defaults to 15s in config).
const initTimeoutMs = this.webex.config?.services?.catalogInitTimeout;

const initServiceCatalogsTimeout = new Promise((_, reject) => {
setTimeout(
() => reject(new Error(`services: init timed out after ${initTimeoutMs}ms`)),
initTimeoutMs
);
});

// Validate if the supertoken exists.
if (supertoken && supertoken.access_token) {
// `initServiceCatalogs` marks the catalog ready internally once the
// postauth catalog is collected - even if it loses the timeout race
// above, so a slow fetch still eventually flips `catalog.isReady`.
Promise.race([this.initServiceCatalogs(), initServiceCatalogsTimeout])
// below, so a slow fetch still eventually flips `catalog.isReady`.
Promise.race([this.initServiceCatalogs(), this._makeInitTimeout()])
.catch((error) => {
this.initFailed = true;
this.logger.error(
Expand All @@ -1555,17 +1572,28 @@ const Services = WebexPlugin.extend({
} else {
const {email} = this.webex.config;

Promise.race([
this.collectPreauthCatalog(email ? {email} : undefined),
initServiceCatalogsTimeout,
])
.catch((error) => {
this.initFailed = true;
this.logger.error(
`services: failed to init initial services when no credentials available, ${error?.message}`
);
})
.finally(() => this._finalizeReady());
if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
// Skip the preauth catalog fetch (it will be collected manually
// later), but still finalize `services.ready` so `webex.ready` is not
// stalled while unauthenticated. No timeout is created here so there
// is no stray timer or unhandled rejection.
this.logger.info(
'services: skipping preauth catalog collection while unauthenticated as per the config'
);
this._finalizeReady();
} else {
Promise.race([
this.collectPreauthCatalog(email ? {email} : undefined),
this._makeInitTimeout(),
])
.catch((error) => {
this.initFailed = true;
this.logger.error(
`services: failed to init initial services when no credentials available, ${error?.message}`
);
})
.finally(() => this._finalizeReady());
}

// Handle fresh login: 'loaded' fires before OAuth completes, so listen
// for `canAuthorize` flipping true and then collect the postauth catalog.
Expand Down
Loading
Loading