diff --git a/src/bot/orchestrator.ts b/src/bot/orchestrator.ts index 1c80fae..a1b725c 100644 --- a/src/bot/orchestrator.ts +++ b/src/bot/orchestrator.ts @@ -33,6 +33,7 @@ export interface OrchestratorOptions { botAccountEmail?: string; botAccountPassword?: string; transferMode?: string; + debugMode?: boolean; } /** @@ -69,6 +70,7 @@ export class BotOrchestrator { private _state: BotState = 'idle'; private _isShuttingDown: boolean = false; + private _isDebugMode: boolean = false; private _keepAliveInterval: NodeJS.Timeout | null = null; constructor( @@ -83,6 +85,7 @@ export class BotOrchestrator { this._botName = botName || config.botName; this._callbacks = callbacks; this._options = options; + this._isDebugMode = !!options.debugMode; this._logger = createSessionLogger(sessionId); } @@ -233,7 +236,7 @@ export class BotOrchestrator { ); if (!emailInput) { this._logger.warn(`No login page found, current URL: ${this._page!.url().substring(0, 150)}`); - await this._takeScreenshot('step1-no-login-page', true); + await this._takeScreenshot('step1-no-login-page', this._isDebugMode); } // STEP 2: Microsoft Authentication @@ -246,11 +249,11 @@ export class BotOrchestrator { ); if (!authSuccess) { - await this._takeScreenshot('step2-auth-failed', true); + await this._takeScreenshot('step2-auth-failed', this._isDebugMode); throw new Error('Microsoft authentication failed'); } this._logger.info('STEP 2: authentication successful'); - await this._takeScreenshot('step2-auth-done', true); + await this._takeScreenshot('step2-auth-done', this._isDebugMode); // STEP 3: Wait for Teams to load after auth this._logger.info('STEP 3: waiting for Teams to load after auth...'); @@ -262,7 +265,7 @@ export class BotOrchestrator { } catch { this._logger.warn(`Unexpected URL after auth: ${this._page!.url().substring(0, 150)}`); } - await this._takeScreenshot('step3-teams-loaded', true); + await this._takeScreenshot('step3-teams-loaded', this._isDebugMode); // STEP 4: Navigate to the meeting URL with proper launch params. // CRITICAL: The suppress params (msLaunch, suppressPrompt, directDl) must @@ -291,7 +294,7 @@ export class BotOrchestrator { timeout: 30000, }); this._logger.info(`STEP 4: URL after navigation: ${this._page!.url().substring(0, 150)}`); - await this._takeScreenshot('step4-meeting-url-loaded', true); + await this._takeScreenshot('step4-meeting-url-loaded', this._isDebugMode); // STEP 4a: Poll for first actionable button (interstitial OR pre-join) const interstitialSelectors = [ @@ -327,32 +330,32 @@ export class BotOrchestrator { if (!isPreJoin) { await firstBtn.click(); this._logger.info(`STEP 4a: clicked interstitial: "${btnText}" (data-tid="${btnTid}")`); - await this._takeScreenshot('step4a-after-interstitial', true); + await this._takeScreenshot('step4a-after-interstitial', this._isDebugMode); } else { this._logger.info(`STEP 4a: pre-join button already visible: "${btnText}"`); } } else { - await this._takeScreenshot('step4a-no-buttons-found', true); + await this._takeScreenshot('step4a-no-buttons-found', this._isDebugMode); } // STEP 5: Poll for "Join now" on the pre-join screen (mic is NOT touched) - await this._takeScreenshot('step5-before-join-now', true); + await this._takeScreenshot('step5-before-join-now', this._isDebugMode); const joinNowBtn = await this._pollForElement(preJoinSelectors, 30000, 'Join now button'); if (!joinNowBtn) { - await this._takeScreenshot('step5-no-join-now', true); + await this._takeScreenshot('step5-no-join-now', this._isDebugMode); throw new Error('"Join now" button not found on pre-join screen'); } await joinNowBtn.click(); this._logger.info('STEP 5: clicked "Join now", waiting for meeting'); - await this._takeScreenshot('step5-join-now-clicked', true); + await this._takeScreenshot('step5-join-now-clicked', this._isDebugMode); // STEP 6: Wait for meeting admission (hangup button = in meeting) await this._waitForMeetingAdmission(); this._setState('in_meeting'); this._logger.info(`STEP 6: bot joined the meeting (authenticated as ${this._options.botAccountEmail})`); - await this._takeScreenshot('step6-in-meeting', true); + await this._takeScreenshot('step6-in-meeting', this._isDebugMode); this._startKeepAlive(); await this._audioProcedure!.initialize(); diff --git a/src/index.ts b/src/index.ts index 1bfe959..5efc754 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,8 +19,8 @@ async function main(): Promise { // Start HTTP server httpServer = new HttpServer({ - onJoinRequest: async (sessionId, meetingUrl, botName, instanceId, gatewayWsUrl, language, botAccountEmail, botAccountPassword, transferMode) => { - await sessionManager.createSession(sessionId, meetingUrl, botName, instanceId, gatewayWsUrl, language, botAccountEmail, botAccountPassword, transferMode); + onJoinRequest: async (sessionId, meetingUrl, botName, instanceId, gatewayWsUrl, language, botAccountEmail, botAccountPassword, transferMode, debugMode) => { + await sessionManager.createSession(sessionId, meetingUrl, botName, instanceId, gatewayWsUrl, language, botAccountEmail, botAccountPassword, transferMode, debugMode); }, onLeaveRequest: async (sessionId) => { await sessionManager.endSession(sessionId); diff --git a/src/server/httpServer.ts b/src/server/httpServer.ts index 5ff9ed3..1811c72 100644 --- a/src/server/httpServer.ts +++ b/src/server/httpServer.ts @@ -7,7 +7,7 @@ import { config } from '../config'; import { runAuthTests, runSingleVariant, getVariantIds } from '../bot/authTestProcedure'; export interface HttpServerCallbacks { - onJoinRequest: (sessionId: string, meetingUrl: string, botName?: string, instanceId?: string, gatewayWsUrl?: string, language?: string, botAccountEmail?: string, botAccountPassword?: string, transferMode?: string) => Promise; + onJoinRequest: (sessionId: string, meetingUrl: string, botName?: string, instanceId?: string, gatewayWsUrl?: string, language?: string, botAccountEmail?: string, botAccountPassword?: string, transferMode?: string, debugMode?: boolean) => Promise; onLeaveRequest: (sessionId: string) => Promise; onStatusRequest: (sessionId: string) => { state: string; error?: string } | null; } @@ -80,14 +80,14 @@ export class HttpServer { // Deploy a new bot this._app.post('/api/bot', async (req: Request, res: Response) => { try { - const { sessionId, meetingUrl, botName, instanceId, gatewayWsUrl, language, botAccountEmail, botAccountPassword, transferMode } = req.body; + const { sessionId, meetingUrl, botName, instanceId, gatewayWsUrl, language, botAccountEmail, botAccountPassword, transferMode, debugMode } = req.body; if (!sessionId || !meetingUrl) { res.status(400).json({ error: 'Missing required fields: sessionId, meetingUrl' }); return; } - await this._callbacks.onJoinRequest(sessionId, meetingUrl, botName, instanceId, gatewayWsUrl, language, botAccountEmail, botAccountPassword, transferMode); + await this._callbacks.onJoinRequest(sessionId, meetingUrl, botName, instanceId, gatewayWsUrl, language, botAccountEmail, botAccountPassword, transferMode, debugMode); res.json({ success: true, diff --git a/src/sessionManager.ts b/src/sessionManager.ts index 81af19c..5219a39 100644 --- a/src/sessionManager.ts +++ b/src/sessionManager.ts @@ -42,6 +42,7 @@ export class SessionManager { botAccountEmail?: string, botAccountPassword?: string, transferMode?: string, + debugMode?: boolean, ): Promise { if (this._sessions.has(sessionId)) { logger.warn(`Session ${sessionId} already exists`); @@ -76,6 +77,7 @@ export class SessionManager { botAccountEmail: botAccountEmail, botAccountPassword: botAccountPassword, transferMode: transferMode, + debugMode: debugMode, }; const orchestrator = new BotOrchestrator(