← back to Google Places Key Provisioner
all-in-one.js
140 lines
#!/usr/bin/env node
/**
* One-shot Google Places API key provisioner via Browserbase.
* Creates a session, navigates to GCP, prints live URL, polls every 10s
* for "user has logged in" (URL no longer on accounts.google.com),
* then drives the rest: project create → enable Places → create key → extract.
*
* Run: node all-in-one.js
* Times out after 6 minutes if user never logs in.
*/
const Browserbase = require('@browserbasehq/sdk').default;
const { chromium } = require('playwright-core');
const fs = require('fs');
const path = require('path');
require('dotenv').config({ path: require('os').homedir() + '/.claude/skills/browserbase/.env' });
(async () => {
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create({
projectId: process.env.BROWSERBASE_PROJECT_ID,
keepAlive: true,
proxies: false,
});
const live = `https://www.browserbase.com/devtools-fullscreen/inspector.html?wss=connect.browserbase.com/debug/${session.id}/devtools/browser/`;
console.log('SESSION_ID =', session.id);
console.log('LIVE_URL =', live);
fs.writeFileSync(path.join(__dirname, 'session.json'),
JSON.stringify({ id: session.id, connectUrl: session.connectUrl, debugUrl: live }, null, 2));
const browser = await chromium.connectOverCDP(session.connectUrl);
const ctx = browser.contexts()[0];
const page = ctx.pages()[0] || await ctx.newPage();
await page.goto('https://console.cloud.google.com/projectcreate', { waitUntil: 'domcontentloaded', timeout: 60000 });
console.log('parked at:', page.url());
console.log('\n→ OPEN THE LIVE_URL ABOVE AND LOG IN. I will poll every 10s for up to 6 min.');
// Poll for logged-in state
let loggedIn = false;
for (let i = 0; i < 36; i++) {
await page.waitForTimeout(10000);
const url = page.url();
process.stdout.write(` [${i+1}/36] url=${url.slice(0,80)}\n`);
if (!url.includes('accounts.google.com') && url.includes('console.cloud.google.com')) {
loggedIn = true;
console.log(' ✓ logged in detected');
break;
}
}
if (!loggedIn) {
console.error('TIMEOUT — did not detect login within 6 min. Session left alive at', live);
process.exit(1);
}
// STEP 1: project create
console.log('\n--- STEP 1: create project ---');
try {
await page.goto('https://console.cloud.google.com/projectcreate', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(4000);
const projectName = 'lacountyeats-' + Date.now().toString(36).slice(-5);
const nameInput = page.locator('input[aria-label*="roject name" i]').first();
await nameInput.waitFor({ timeout: 10000 });
await nameInput.click();
await nameInput.fill('');
await nameInput.fill(projectName);
console.log(' filled project name:', projectName);
await page.waitForTimeout(1500);
const createBtn = page.locator('button:has-text("Create"), button:has-text("CREATE")').first();
await createBtn.click();
console.log(' clicked Create — waiting 25s for provision…');
await page.waitForTimeout(25000);
console.log(' url after create:', page.url());
} catch (e) {
console.error(' step1 error:', e.message);
}
// STEP 2: enable Places API
console.log('\n--- STEP 2: enable Places API ---');
try {
await page.goto('https://console.cloud.google.com/apis/library/places-backend.googleapis.com', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(7000);
// Try several enable-button shapes
const enableSelectors = [
'button:has-text("Enable")',
'button:has-text("ENABLE")',
'cfc-progress-button button',
];
let clicked = false;
for (const sel of enableSelectors) {
const btn = page.locator(sel).first();
if (await btn.isVisible({ timeout: 3000 }).catch(() => false)) {
const txt = await btn.textContent();
if (txt && txt.toLowerCase().includes('enable')) {
await btn.click();
console.log(' clicked Enable — waiting 20s…');
await page.waitForTimeout(20000);
clicked = true;
break;
}
}
}
if (!clicked) console.log(' Places API likely already enabled (no Enable button found)');
} catch (e) {
console.error(' step2 error:', e.message);
}
// STEP 3: create API key
console.log('\n--- STEP 3: create API key ---');
let keyValue = null;
try {
await page.goto('https://console.cloud.google.com/apis/credentials', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(7000);
// Try header "+ Create credentials" button
const cc = page.locator('button:has-text("Create credentials"), button:has-text("CREATE CREDENTIALS")').first();
await cc.waitFor({ timeout: 10000 });
await cc.click();
await page.waitForTimeout(2000);
// Menu item "API key"
const apiItem = page.locator('text=/^API key$/i').first();
await apiItem.click();
console.log(' clicked Create Credentials → API key — waiting for modal…');
await page.waitForTimeout(10000);
// Extract key value
const html = await page.content();
const m = html.match(/AIzaSy[A-Za-z0-9_-]{30,}/);
if (m) {
keyValue = m[0];
console.log('\n=== KEY ===\n' + keyValue + '\n=== /KEY ===');
fs.writeFileSync(path.join(__dirname, 'key.txt'), keyValue + '\n', { mode: 0o600 });
} else {
console.error(' could not extract key from modal — open live URL and copy manually');
}
} catch (e) {
console.error(' step3 error:', e.message);
}
console.log('\nLive URL still alive for manual cleanup:', live);
console.log('Done.');
// Don't close — leave session alive for manual inspection if needed
})();