Fix make `/profile` await the for an actual successful API connection (#4589) * fix: add connection status check after profile switch in connection manager Fixes #4262 * feat: add timeout parameter to connection manager await functionality & catch timout error * Add rejectOnTimeout option to waitUntilCondition --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -1,6 +1,6 @@ | |||
| 1 | import { DOMPurify, Fuse } from '../../../lib.js'; | 1 | import { DOMPurify, Fuse } from '../../../lib.js'; |
| 2 | 2 | ||
| 3 | import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js'; | 3 | import { event_types, eventSource, main_api, online_status, saveSettingsDebounced } from '../../../script.js'; |
| 4 | import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; | 4 | import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 5 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; | 5 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; |
| 6 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 6 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| @@ -11,7 +11,7 @@ import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDe | |||
| 11 | import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; | 11 | import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 12 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | 12 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| 13 | import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js'; | 13 | import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js'; |
| 14 | import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4 } from '../../utils.js'; | 14 | import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4, waitUntilCondition } from '../../utils.js'; |
| 15 | import { t } from '../../i18n.js'; | 15 | import { t } from '../../i18n.js'; |
| 16 | import { getSecretLabelById } from '../../secrets.js'; | 16 | import { getSecretLabelById } from '../../secrets.js'; |
| 17 | 17 | ||
| @@ -684,6 +684,13 @@ async function renderDetailsContent(detailsContent) { | |||
| 684 | defaultValue: 'true', | 684 | defaultValue: 'true', |
| 685 | enumList: commonEnumProviders.boolean('trueFalse')(), | 685 | enumList: commonEnumProviders.boolean('trueFalse')(), |
| 686 | }), | 686 | }), |
| 687 | SlashCommandNamedArgument.fromProps({ | ||
| 688 | name: 'timeout', | ||
| 689 | description: 'Maximum time to wait for the API connection to be established, in milliseconds. Set to 0 to disable. Only applies when await=true.', | ||
| 690 | isRequired: false, | ||
| 691 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 692 | defaultValue: '2000', | ||
| 693 | }), | ||
| 687 | ], | 694 | ], |
| 688 | callback: async (args, value) => { | 695 | callback: async (args, value) => { |
| 689 | if (!value || typeof value !== 'string') { | 696 | if (!value || typeof value !== 'string') { |
| @@ -715,6 +722,13 @@ async function renderDetailsContent(detailsContent) { | |||
| 715 | 722 | ||
| 716 | if (shouldAwait) { | 723 | if (shouldAwait) { |
| 717 | await awaitPromise; | 724 | await awaitPromise; |
| 725 | |||
| 726 | // We should also await the connection to be established | ||
| 727 | const parsedTimeout = parseInt(args?.timeout?.toString()); | ||
| 728 | const timeout = !isNaN(parsedTimeout) ? Math.max(0, parsedTimeout) : 2000; | ||
| 729 | if (timeout > 0) { | ||
| 730 | await waitUntilCondition(() => online_status !== 'no_connection', timeout, 100, { rejectOnTimeout: false }); | ||
| 731 | } | ||
| 718 | } | 732 | } |
| 719 | 733 | ||
| 720 | return profile.name; | 734 | return profile.name; |
| @@ -1637,13 +1637,18 @@ export function createThumbnail(dataUrl, maxWidth = null, maxHeight = null, type | |||
| 1637 | * @param {{ (): boolean; }} condition The condition to wait for. | 1637 | * @param {{ (): boolean; }} condition The condition to wait for. |
| 1638 | * @param {number} [timeout=1000] The timeout in milliseconds. | 1638 | * @param {number} [timeout=1000] The timeout in milliseconds. |
| 1639 | * @param {number} [interval=100] The interval in milliseconds. | 1639 | * @param {number} [interval=100] The interval in milliseconds. |
| 1640 | * @param {object} [options] Options object | ||
| 1641 | * @param {boolean} [options.rejectOnTimeout=true] Whether to reject the promise on timeout or resolve it. | ||
| 1640 | * @returns {Promise<void>} A promise that resolves when the condition is true. | 1642 | * @returns {Promise<void>} A promise that resolves when the condition is true. |
| 1641 | */ | 1643 | */ |
| 1642 | export async function waitUntilCondition(condition, timeout = 1000, interval = 100) { | 1644 | export async function waitUntilCondition(condition, timeout = 1000, interval = 100, options = {}) { |
| 1645 | const { rejectOnTimeout = true } = options; | ||
| 1646 | |||
| 1643 | return new Promise((resolve, reject) => { | 1647 | return new Promise((resolve, reject) => { |
| 1644 | const timeoutId = setTimeout(() => { | 1648 | const timeoutId = setTimeout(() => { |
| 1645 | clearInterval(intervalId); | 1649 | clearInterval(intervalId); |
| 1646 | reject(new Error('Timed out waiting for condition to be true')); | 1650 | const timeoutFn = rejectOnTimeout ? reject : resolve; |
| 1651 | timeoutFn(new Error('Timed out waiting for condition to be true')); | ||
| 1647 | }, timeout); | 1652 | }, timeout); |
| 1648 | 1653 | ||
| 1649 | const intervalId = setInterval(() => { | 1654 | const intervalId = setInterval(() => { |