diff --git a/src/bot/chatProcedure.ts b/src/bot/chatProcedure.ts index 4b8e716..7368964 100644 --- a/src/bot/chatProcedure.ts +++ b/src/bot/chatProcedure.ts @@ -24,6 +24,8 @@ export class ChatProcedure { private _botJoinedAtMs: number = 0; private _recentMessageKeyTimestamps: Map = new Map(); private _scanIntervalId: ReturnType | null = null; + private _consecutiveOpenFailures: number = 0; + private static readonly _MAX_OPEN_FAILURES = 5; constructor( page: Page, @@ -116,10 +118,10 @@ export class ChatProcedure { * In authenticated Teams, the chat panel may already be open (meeting loads * from a chat thread). Clicking again would TOGGLE it closed. */ - private async _openChatPanel(): Promise { + private async _openChatPanel(): Promise { if (await this._isChatPanelOpen()) { this._logger.info('Chat panel already open - skipping toggle'); - return; + return true; } const chatButtonSelectors = [ @@ -131,20 +133,15 @@ export class ChatProcedure { 'button[aria-label*="Meeting chat" i]', ]; - // Poll for the chat button (toolbar renders asynchronously after join). - // IMPORTANT: click only ONCE per attempt, then wait for the panel to appear. - // Multiple clicks on the same toggle button would open/close/open/close. const maxAttempts = 12; const pollIntervalMs = 2000; for (let attempt = 1; attempt <= maxAttempts; attempt++) { - // Check first - a previous click might have opened it by now if (await this._isChatPanelOpen()) { this._logger.info(`Chat panel detected as open (attempt ${attempt})`); - return; + return true; } - // Try to find and click the button (only one click per attempt) let clicked = false; for (const selector of chatButtonSelectors) { try { @@ -163,14 +160,11 @@ export class ChatProcedure { } if (clicked) { - // Wait for panel to render after click await this._page.waitForTimeout(2500); if (await this._isChatPanelOpen()) { this._logger.info('Chat panel opened successfully'); - return; + return true; } - // Panel didn't open despite click - DON'T click again immediately, - // wait for next attempt cycle (avoids toggle oscillation) this._logger.info('Chat button clicked but panel not detected yet, waiting before next attempt'); await this._page.waitForTimeout(pollIntervalMs); } else { @@ -182,6 +176,7 @@ export class ChatProcedure { } this._logger.warn('Could not open chat panel after polling - chat will not work'); + return false; } /** @@ -445,9 +440,31 @@ export class ChatProcedure { if (!this._isSubscribed || !this._page) return; try { if (!(await this._isChatPanelOpen())) { + if (this._consecutiveOpenFailures >= ChatProcedure._MAX_OPEN_FAILURES) { + this._logger.warn( + `Chat panel failed to open ${this._consecutiveOpenFailures} consecutive times - ` + + 'stopping periodic chat scan to avoid log noise', + ); + if (this._scanIntervalId) { + clearInterval(this._scanIntervalId); + this._scanIntervalId = null; + } + return; + } this._logger.info('Chat panel closed, reopening'); - await this._openChatPanel(); + const opened = await this._openChatPanel(); + if (opened) { + this._consecutiveOpenFailures = 0; + } else { + this._consecutiveOpenFailures++; + this._logger.info( + `Chat reopen failed (${this._consecutiveOpenFailures}/${ChatProcedure._MAX_OPEN_FAILURES} before giving up)`, + ); + return; + } await this._page.waitForTimeout(1000); + } else { + this._consecutiveOpenFailures = 0; } const knownKeys = Array.from(this._recentMessageKeyTimestamps.keys());