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>

df2dee84820f957c4a70182150cea4bd62d97fd7

Wolfsblvt <wolfsblvt@gmail.com>

Signed
2 files changed, +23 -4Ignore whitespace
public/scripts/extensions/connection-manager/index.js+16 -2
@@ -1,6 +1,6 @@
1import { DOMPurify, Fuse } from '../../../lib.js';1import { DOMPurify, Fuse } from '../../../lib.js';
22
3import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';3import { event_types, eventSource, main_api, online_status, saveSettingsDebounced } from '../../../script.js';
4import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';4import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
5import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';5import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
6import { SlashCommand } from '../../slash-commands/SlashCommand.js';6import { SlashCommand } from '../../slash-commands/SlashCommand.js';
@@ -11,7 +11,7 @@ import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDe
11import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';11import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
12import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';12import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
13import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';13import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';
14import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4 } from '../../utils.js';14import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4, waitUntilCondition } from '../../utils.js';
15import { t } from '../../i18n.js';15import { t } from '../../i18n.js';
16import { getSecretLabelById } from '../../secrets.js';16import { getSecretLabelById } from '../../secrets.js';
1717
@@ -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) {
715722
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 }
719733
720 return profile.name;734 return profile.name;
public/scripts/utils.js+7 -2
@@ -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 */
1642export async function waitUntilCondition(condition, timeout = 1000, interval = 100) {1644export 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);
16481653
1649 const intervalId = setInterval(() => {1654 const intervalId = setInterval(() => {