Skip to content

Commit b7caba9

Browse files
committed
Polish the project setup logic a little bit.
1 parent 47fdec5 commit b7caba9

1 file changed

Lines changed: 37 additions & 31 deletions

File tree

src/services/google.ts

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
import type { Response, BrowserContext, Page } from 'playwright';
66
import { ApiCredentialStatus, ApiCredentials, OAuthCredentials } from '../apiCredentials.js';
7+
import { generateLatchkeyAppName } from '../playwrightUtils.js';
78
import { runCaptured } from '../curl.js';
89
import { Service, BrowserFollowupServiceSession, LoginFailedError } from './base.js';
910
import * as http from 'node:http';
1011
import * as url from 'node:url';
1112

12-
const DEFAULT_TIMEOUT_MS = 30000;
13+
const DEFAULT_TIMEOUT_MS = 8000;
1314
const OAUTH_CALLBACK_PORT = 8080;
1415
const OAUTH_SCOPES = [
1516
// User info (for credential validation)
@@ -259,10 +260,10 @@ class GoogleServiceSession extends BrowserFollowupServiceSession {
259260
clientSecret = oldCredentials.clientSecret;
260261
} else {
261262
// Step 1: Navigate to Google Cloud Console and create project
262-
await this.createProject(page);
263+
const projectSlug = await this.createProject(page);
263264

264265
// Step 2: Enable required APIs
265-
await this.enableGoogleApis(page);
266+
await this.enableGoogleApis(page, projectSlug);
266267

267268
// Step 3: Create OAuth client ID
268269
const credentials = await this.createOAuthClient(page);
@@ -286,34 +287,38 @@ class GoogleServiceSession extends BrowserFollowupServiceSession {
286287
);
287288
}
288289

289-
private async createProject(page: Page): Promise<void> {
290+
private async createProject(page: Page): Promise<string> {
290291
// Navigate to the projects page
291292
await page.goto('https://console.cloud.google.com/projectselector2/home/dashboard', {
292293
timeout: DEFAULT_TIMEOUT_MS,
293294
});
294295

295-
// Wait a bit for the page to load
296-
await page.waitForTimeout(2000);
297-
298296
// Always create a new project
299-
const createProjectButton = page.locator('text="Create Project"').first();
300-
301-
if (await createProjectButton.isVisible({ timeout: 5000 })) {
302-
await createProjectButton.click();
303-
await page.waitForTimeout(1000);
297+
const createProjectButton = page.locator('.projectselector-project-create');
298+
await createProjectButton.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
299+
await createProjectButton.click();
304300

305-
const projectNameInput = page.locator('input[name="projectName"]');
306-
await projectNameInput.fill('Latchkey Project');
301+
const projectNameInput = page.locator('input[name="projectName"]');
302+
await projectNameInput.waitFor({ timeout: DEFAULT_TIMEOUT_MS });
303+
await projectNameInput.fill(generateLatchkeyAppName());
307304

308-
const createButton = page.locator('button:has-text("Create")');
309-
await createButton.click();
305+
const createButton = page.locator('button[type="submit"]');
306+
await createButton.click();
310307

311-
// Wait for project creation
312-
await page.waitForTimeout(5000);
308+
// Wait until the URL is https:
309+
// https://console.cloud.google.com/home/dashboard?project=...
310+
await page.waitForURL('https://console.cloud.google.com/home/dashboard?project=**', {
311+
timeout: 16000,
312+
});
313+
const urlObj = new URL(page.url());
314+
const projectId = urlObj.searchParams.get('project');
315+
if (!projectId) {
316+
throw new LoginFailedError('Failed to create or retrieve Google Cloud project ID.');
313317
}
318+
return projectId;
314319
}
315320

316-
private async enableGoogleApis(page: Page): Promise<void> {
321+
private async enableGoogleApis(page: Page, projectSlug: string): Promise<void> {
317322
const apis = [
318323
'gmail.googleapis.com',
319324
'calendar-json.googleapis.com',
@@ -324,31 +329,32 @@ class GoogleServiceSession extends BrowserFollowupServiceSession {
324329
];
325330

326331
for (const api of apis) {
327-
await this.enableApi(page, api);
332+
await this.enableApi(page, projectSlug, api);
328333
}
329334
}
330335

331-
private async enableApi(page: Page, apiName: string): Promise<void> {
332-
// Navigate to API library
333-
await page.goto(`https://console.cloud.google.com/apis/library/${apiName}`, {
336+
private async enableApi(page: Page, projectSlug: string, apiName: string): Promise<void> {
337+
await page.goto(`https://console.cloud.google.com/apis/library/${apiName}?project=${projectSlug}`, {
334338
timeout: DEFAULT_TIMEOUT_MS,
335339
});
336340

337-
await page.waitForTimeout(2000);
341+
const manageButton = page.locator('text="Manage"');
342+
const enableButton = page.locator('button:has-text("Enable")');
343+
const manageOrEnableButton = manageButton.or(enableButton);
344+
345+
await manageOrEnableButton.isVisible({ timeout: DEFAULT_TIMEOUT_MS });
338346

339347
// Check if API is already enabled
340-
const manageButton = page.locator('text="Manage"');
341-
if (await manageButton.isVisible({ timeout: 5000 })) {
342-
// API already enabled
348+
if (await manageButton.isVisible()) {
343349
return;
344350
}
345351

346-
// Enable the API
347-
const enableButton = page.locator('button:has-text("Enable")');
348-
if (await enableButton.isVisible({ timeout: 5000 })) {
352+
if (await enableButton.isVisible()) {
349353
await enableButton.click();
350-
await page.waitForTimeout(5000);
351354
}
355+
const disableButton = page.locator('text="Disable API"');
356+
await disableButton.waitFor({ timeout: 16000 });
357+
throw new LoginFailedError(`Failed to enable API: ${apiName}`);
352358
}
353359

354360
private async createOAuthClient(page: Page): Promise<{ clientId: string; clientSecret: string }> {

0 commit comments

Comments
 (0)