fix: auth join via Teams v2 'Join a meeting' form with Meeting ID + Passcode fields
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d0cbcbcb29
commit
1972f698b4
1 changed files with 123 additions and 71 deletions
|
|
@ -178,90 +178,142 @@ export class BotOrchestrator {
|
|||
this._setState('navigating');
|
||||
|
||||
if (authenticate) {
|
||||
// AUTHENTICATED JOIN: Navigate directly to the meeting URL within the
|
||||
// Teams v2 web app context. The external launcher (/dl/launcher/) and its
|
||||
// "Continue on this browser" button always redirect to the anonymous
|
||||
// light-meetings experience, even with an active auth session.
|
||||
//
|
||||
// Strategy: Use the Teams v2 internal URL format that keeps the user
|
||||
// within the authenticated Teams web app. The key is to NOT go through
|
||||
// the launcher dialog at all.
|
||||
this._logger.info(`Authenticated: navigating to meeting within Teams v2 app...`);
|
||||
// AUTHENTICATED JOIN: Use the Teams v2 "Join a meeting" form.
|
||||
// The Teams v2 app (already loaded after auth) has a "Join a meeting" page
|
||||
// with Meeting ID and Passcode fields. This keeps the user authenticated
|
||||
// and avoids the external launcher which always redirects to anonymous mode.
|
||||
this._logger.info(`Authenticated: joining meeting via Teams v2 "Join a meeting" form...`);
|
||||
|
||||
// Parse the meeting URL to extract meeting code and passcode
|
||||
const { parseMeetingUrl } = await import('./meetingUrlParser');
|
||||
const parsed = parseMeetingUrl(this._meetingUrl);
|
||||
const meetingId = parsed.meetingId || '';
|
||||
const passcode = parsed.passcode || '';
|
||||
|
||||
// Try multiple URL formats to find one that works within Teams v2
|
||||
const meetingUrlFormats = [
|
||||
// Format 1: Teams v2 internal pre-join (most likely to keep auth)
|
||||
`https://teams.microsoft.com/v2/#/meeting-join/${parsed.meetingId || ''}${parsed.passcode ? '?p=' + parsed.passcode : ''}`,
|
||||
// Format 2: Direct /meet/ URL (original meeting URL)
|
||||
this._meetingUrl,
|
||||
// Format 3: Teams v2 hash-based deep link
|
||||
`https://teams.microsoft.com/_#/meet/${parsed.meetingId || ''}${parsed.passcode ? '?p=' + parsed.passcode : ''}`,
|
||||
];
|
||||
this._logger.info(`Meeting ID: ${meetingId}, Passcode: ${passcode ? '***' : '(none)'}`);
|
||||
|
||||
let joinedSuccessfully = false;
|
||||
let joinedViaForm = false;
|
||||
|
||||
for (const url of meetingUrlFormats) {
|
||||
this._logger.info(`Trying auth join URL: ${url}`);
|
||||
try {
|
||||
// Navigate to the Teams v2 "Join a meeting" page
|
||||
// This page is available within the authenticated Teams v2 app
|
||||
await this._page!.goto('https://teams.microsoft.com/v2/', {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 20000,
|
||||
});
|
||||
await this._page!.waitForTimeout(3000);
|
||||
|
||||
try {
|
||||
await this._page!.goto(url, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 20000,
|
||||
});
|
||||
await this._page!.waitForTimeout(3000);
|
||||
// Look for "Join a meeting" link/button in the Teams v2 sidebar or header
|
||||
const joinMeetingSelectors = [
|
||||
'button:has-text("Join a meeting")',
|
||||
'a:has-text("Join a meeting")',
|
||||
'button:has-text("Join with an ID")',
|
||||
'a:has-text("Join with an ID")',
|
||||
'[data-tid="join-meeting-button"]',
|
||||
'button:has-text("An Besprechung teilnehmen")',
|
||||
];
|
||||
|
||||
// Check if we landed on the authenticated pre-join page
|
||||
const currentUrl = this._page!.url();
|
||||
const pageText = await this._page!.evaluate(() => document.body?.innerText?.substring(0, 500) || '');
|
||||
|
||||
this._logger.info(`After navigation - URL: ${currentUrl.substring(0, 100)}`);
|
||||
|
||||
// If we see the launcher, DON'T click "Continue on this browser" -- that leads to anon
|
||||
if (pageText.includes('Continue on this browser') || pageText.includes('Join on the Teams app')) {
|
||||
this._logger.info('Launcher dialog detected - skipping it (would redirect to anonymous mode)');
|
||||
// Instead, try to find a "Use the web app" or direct join link
|
||||
const useWebAppButton = await this._page!.$('a:has-text("Use the web app"), button:has-text("Use the web app")');
|
||||
if (useWebAppButton) {
|
||||
await useWebAppButton.click();
|
||||
await this._page!.waitForTimeout(3000);
|
||||
let foundJoinPage = false;
|
||||
for (const sel of joinMeetingSelectors) {
|
||||
try {
|
||||
const btn = await this._page!.$(sel);
|
||||
if (btn) {
|
||||
await btn.click();
|
||||
this._logger.info(`Clicked: ${sel}`);
|
||||
await this._page!.waitForTimeout(2000);
|
||||
foundJoinPage = true;
|
||||
break;
|
||||
}
|
||||
continue; // Try next URL format
|
||||
}
|
||||
|
||||
// Check if we're on the authenticated pre-join (no name input, has "Join now")
|
||||
if (!pageText.includes('Enter the name') && !pageText.includes('Type your name') &&
|
||||
(pageText.includes('Join now') || pageText.includes('Join'))) {
|
||||
this._logger.info('On authenticated pre-join page (no name input required)');
|
||||
joinedSuccessfully = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if we ended up on anon page (light-meetings)
|
||||
if (currentUrl.includes('anon=true') || currentUrl.includes('light-meetings')) {
|
||||
this._logger.warn(`URL redirected to anonymous mode: ${currentUrl.substring(0, 80)}`);
|
||||
continue; // Try next URL format
|
||||
}
|
||||
|
||||
// Check if we're already in the meeting (Teams v2 loaded meeting directly)
|
||||
if (pageText.includes('Leave') && pageText.includes('Mute')) {
|
||||
this._logger.info('Already in meeting after navigation (Teams v2 joined directly)');
|
||||
joinedSuccessfully = true;
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (navError) {
|
||||
this._logger.warn(`Navigation failed for URL ${url.substring(0, 60)}: ${navError}`);
|
||||
continue;
|
||||
} catch { /* continue */ }
|
||||
}
|
||||
|
||||
// Check if we're on the "Join a meeting" form page
|
||||
const pageText = await this._page!.evaluate(() => document.body?.innerText?.substring(0, 500) || '');
|
||||
|
||||
if (pageText.includes('Meeting ID') || pageText.includes('Join a meeting') || pageText.includes('Besprechungs-ID')) {
|
||||
this._logger.info('On "Join a meeting" form page - filling in meeting details');
|
||||
|
||||
// Fill in the Meeting ID field
|
||||
const meetingIdSelectors = [
|
||||
'input[placeholder*="Meeting ID" i]',
|
||||
'input[placeholder*="Besprechungs-ID" i]',
|
||||
'input[aria-label*="Meeting ID" i]',
|
||||
'input[aria-label*="Besprechungs-ID" i]',
|
||||
'input[name*="meetingId" i]',
|
||||
'input[type="text"]:first-of-type',
|
||||
];
|
||||
|
||||
for (const sel of meetingIdSelectors) {
|
||||
try {
|
||||
const input = await this._page!.$(sel);
|
||||
if (input) {
|
||||
await input.click();
|
||||
await input.fill(meetingId);
|
||||
this._logger.info(`Filled Meeting ID: ${meetingId}`);
|
||||
break;
|
||||
}
|
||||
} catch { /* continue */ }
|
||||
}
|
||||
|
||||
// Fill in the Passcode field (if we have one)
|
||||
if (passcode) {
|
||||
const passcodeSelectors = [
|
||||
'input[placeholder*="passcode" i]',
|
||||
'input[placeholder*="Passcode" i]',
|
||||
'input[placeholder*="Kennung" i]',
|
||||
'input[aria-label*="passcode" i]',
|
||||
'input[aria-label*="Kennung" i]',
|
||||
'input[name*="passcode" i]',
|
||||
'input[type="text"]:nth-of-type(2)',
|
||||
'input[type="password"]',
|
||||
];
|
||||
|
||||
for (const sel of passcodeSelectors) {
|
||||
try {
|
||||
const input = await this._page!.$(sel);
|
||||
if (input) {
|
||||
await input.click();
|
||||
await input.fill(passcode);
|
||||
this._logger.info('Filled Passcode');
|
||||
break;
|
||||
}
|
||||
} catch { /* continue */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Click "Join meeting" button
|
||||
await this._page!.waitForTimeout(500);
|
||||
const joinBtnSelectors = [
|
||||
'button:has-text("Join meeting")',
|
||||
'button:has-text("An Besprechung teilnehmen")',
|
||||
'button:has-text("Beitreten")',
|
||||
'button[data-tid="join-meeting-submit"]',
|
||||
];
|
||||
|
||||
for (const sel of joinBtnSelectors) {
|
||||
try {
|
||||
const btn = await this._page!.$(sel);
|
||||
if (btn) {
|
||||
await btn.click();
|
||||
this._logger.info(`Clicked: ${sel}`);
|
||||
joinedViaForm = true;
|
||||
break;
|
||||
}
|
||||
} catch { /* continue */ }
|
||||
}
|
||||
|
||||
if (joinedViaForm) {
|
||||
// Wait for the pre-join/meeting page to load
|
||||
await this._page!.waitForTimeout(5000);
|
||||
this._logger.info('Submitted meeting join form - waiting for pre-join page');
|
||||
}
|
||||
}
|
||||
} catch (formError) {
|
||||
this._logger.warn(`Teams v2 form join failed: ${formError}`);
|
||||
}
|
||||
|
||||
if (!joinedSuccessfully) {
|
||||
this._logger.warn('All authenticated URL formats failed - falling back to launcher flow');
|
||||
// Last resort: use the launcher flow (will end up anonymous, but at least in the meeting)
|
||||
if (!joinedViaForm) {
|
||||
this._logger.warn('Teams v2 form join did not work - falling back to launcher flow');
|
||||
await this._joinProcedure.startMeetingLauncherFlow(this._meetingUrl);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue