feat: anonymous fallback when authenticated join fails (lobby timeout, external tenant)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
1db83a805e
commit
c420987dcb
1 changed files with 98 additions and 54 deletions
|
|
@ -91,7 +91,7 @@ export class BotOrchestrator {
|
|||
throw new Error(`Invalid meeting URL: ${this._meetingUrl}`);
|
||||
}
|
||||
|
||||
const isAuthenticated = !!(this._options.botAccountEmail && this._options.botAccountPassword);
|
||||
let useAuthentication = !!(this._options.botAccountEmail && this._options.botAccountPassword);
|
||||
|
||||
try {
|
||||
this._setState('launching');
|
||||
|
|
@ -99,61 +99,24 @@ export class BotOrchestrator {
|
|||
// Connect to Gateway WebSocket first
|
||||
await this._connectToGateway();
|
||||
|
||||
// Launch browser
|
||||
await this._launchBrowser();
|
||||
|
||||
// Authenticate with Microsoft if bot account is configured
|
||||
if (isAuthenticated) {
|
||||
const { AuthProcedure } = await import('./authProcedure');
|
||||
const authProcedure = new AuthProcedure(this._page!, this._logger);
|
||||
const authSuccess = await authProcedure.authenticateWithMicrosoft(
|
||||
this._options.botAccountEmail!,
|
||||
this._options.botAccountPassword!
|
||||
);
|
||||
if (!authSuccess) {
|
||||
this._logger.warn('Microsoft authentication failed - falling back to anonymous join');
|
||||
}
|
||||
}
|
||||
|
||||
this._setState('navigating');
|
||||
|
||||
// Navigate to meeting and handle launcher
|
||||
await this._joinProcedure!.startMeetingLauncherFlow(this._meetingUrl);
|
||||
|
||||
// Set virtual background if configured (must be done on pre-join screen, before "Join now")
|
||||
if (this._options.backgroundImageUrl && this._page) {
|
||||
try {
|
||||
const { BackgroundProcedure } = await import('./backgroundProcedure');
|
||||
const bgProcedure = new BackgroundProcedure(this._page, this._logger);
|
||||
await bgProcedure.setBackgroundFromUrl(this._options.backgroundImageUrl);
|
||||
} catch (error) {
|
||||
this._logger.warn(`Background image setup failed (non-fatal): ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Join the meeting (enter lobby for anonymous, direct join for authenticated)
|
||||
await this._joinProcedure!.joinMeetingLobbyFlow();
|
||||
|
||||
// Check if we're in lobby
|
||||
const inLobby = await this._joinProcedure!.isInMeetingLobby({ waitForSeconds: 10 });
|
||||
if (inLobby) {
|
||||
this._setState('in_lobby');
|
||||
this._logger.info('Bot is in lobby, waiting to be admitted...');
|
||||
}
|
||||
|
||||
// Wait to be admitted to the meeting
|
||||
await this._waitForMeetingAdmission();
|
||||
|
||||
this._setState('in_meeting');
|
||||
this._logger.info('Bot joined the meeting!');
|
||||
|
||||
// Initialize audio
|
||||
await this._audioProcedure!.initialize();
|
||||
|
||||
// Enable and subscribe to captions
|
||||
await this._enableCaptions();
|
||||
// Try joining (authenticated first, then anonymous fallback)
|
||||
await this._attemptJoin(useAuthentication);
|
||||
|
||||
} catch (error) {
|
||||
// If authenticated join failed, retry as anonymous
|
||||
if (useAuthentication) {
|
||||
this._logger.warn(`Authenticated join failed: ${(error as Error).message}. Retrying as anonymous guest...`);
|
||||
try {
|
||||
await this._cleanup();
|
||||
await this._attemptJoin(false);
|
||||
return;
|
||||
} catch (retryError) {
|
||||
this._logger.error('Anonymous fallback also failed:', retryError);
|
||||
this._setState('error', (retryError as Error).message);
|
||||
await this._takeScreenshot('error-fallback');
|
||||
throw retryError;
|
||||
}
|
||||
}
|
||||
this._logger.error('Error starting bot:', error);
|
||||
this._setState('error', (error as Error).message);
|
||||
await this._takeScreenshot('error');
|
||||
|
|
@ -161,6 +124,87 @@ export class BotOrchestrator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to join a meeting (authenticated or anonymous).
|
||||
*/
|
||||
private async _attemptJoin(authenticate: boolean): Promise<void> {
|
||||
// Launch browser
|
||||
await this._launchBrowser();
|
||||
|
||||
// Authenticate with Microsoft if requested
|
||||
if (authenticate) {
|
||||
const { AuthProcedure } = await import('./authProcedure');
|
||||
const authProcedure = new AuthProcedure(this._page!, this._logger);
|
||||
const authSuccess = await authProcedure.authenticateWithMicrosoft(
|
||||
this._options.botAccountEmail!,
|
||||
this._options.botAccountPassword!
|
||||
);
|
||||
if (!authSuccess) {
|
||||
throw new Error('Microsoft authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
// Update JoinProcedure with correct auth state
|
||||
this._joinProcedure = new JoinProcedure(this._page!, this._logger, this._botName, authenticate);
|
||||
|
||||
this._setState('navigating');
|
||||
|
||||
// Navigate to meeting and handle launcher
|
||||
await this._joinProcedure.startMeetingLauncherFlow(this._meetingUrl);
|
||||
|
||||
// Set virtual background if configured (must be done on pre-join screen, before "Join now")
|
||||
if (this._options.backgroundImageUrl && this._page && authenticate) {
|
||||
try {
|
||||
const { BackgroundProcedure } = await import('./backgroundProcedure');
|
||||
const bgProcedure = new BackgroundProcedure(this._page, this._logger);
|
||||
await bgProcedure.setBackgroundFromUrl(this._options.backgroundImageUrl);
|
||||
} catch (error) {
|
||||
this._logger.warn(`Background image setup failed (non-fatal): ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Join the meeting
|
||||
await this._joinProcedure.joinMeetingLobbyFlow();
|
||||
|
||||
// Check if we're in lobby
|
||||
const inLobby = await this._joinProcedure.isInMeetingLobby({ waitForSeconds: 10 });
|
||||
if (inLobby) {
|
||||
this._setState('in_lobby');
|
||||
this._logger.info('Bot is in lobby, waiting to be admitted...');
|
||||
}
|
||||
|
||||
// Wait to be admitted to the meeting
|
||||
await this._waitForMeetingAdmission();
|
||||
|
||||
this._setState('in_meeting');
|
||||
this._logger.info(`Bot joined the meeting! (authenticated: ${authenticate})`);
|
||||
|
||||
// Initialize audio
|
||||
await this._audioProcedure!.initialize();
|
||||
|
||||
// Enable and subscribe to captions
|
||||
await this._enableCaptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up browser for retry (close browser without full shutdown).
|
||||
*/
|
||||
private async _cleanup(): Promise<void> {
|
||||
try {
|
||||
if (this._page) await this._page.close().catch(() => {});
|
||||
if (this._context) await this._context.close().catch(() => {});
|
||||
if (this._browser) await this._browser.close().catch(() => {});
|
||||
this._page = null;
|
||||
this._context = null;
|
||||
this._browser = null;
|
||||
this._joinProcedure = null;
|
||||
this._captionsProcedure = null;
|
||||
this._audioProcedure = null;
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the Gateway WebSocket for this session.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue