← back to Lawyer Directory Builder

src/scripts/probe_calbar.ts

73 lines

/**
 * Network probe — open one CalBar Detail/{id} page in Playwright and log every
 * XHR/fetch request and response so we can identify the data endpoint that
 * powers the SPA. Once found we'll replay it via plain fetch for all ~330k
 * bar numbers without keeping a browser session per request.
 */
import { chromium } from 'playwright';

const SAMPLE_BAR_NUMBER = process.argv[2] || '29960';
const URL = `https://apps.calbar.ca.gov/attorney/Licensee/Detail/${SAMPLE_BAR_NUMBER}`;

(async () => {
  const browser = await chromium.launch({ headless: true });
  try {
  const ctx = await browser.newContext({
    userAgent: 'LawyerDirectoryBuilder/0.1 (research; contact: steveabramsdesigns@gmail.com)',
  });
  const page = await ctx.newPage();

  const requests: Array<{ url: string; method: string; resourceType: string; headers: Record<string,string> }> = [];
  page.on('request', req => {
    const u = req.url();
    if (u.includes('calbar') || u.includes('apps.calbar')) {
      requests.push({ url: u, method: req.method(), resourceType: req.resourceType(), headers: req.headers() });
    }
  });

  const responses: Array<{ url: string; status: number; ct: string; bodyPreview: string; bodyLen: number }> = [];
  page.on('response', async res => {
    const u = res.url();
    if (!u.includes('calbar') && !u.includes('apps.calbar')) return;
    const ct = res.headers()['content-type'] || '';
    if (!ct.includes('json') && !ct.includes('xml') && !ct.includes('javascript')) return;
    try {
      const body = await res.text();
      responses.push({
        url: u, status: res.status(), ct,
        bodyPreview: body.slice(0, 600),
        bodyLen: body.length,
      });
    } catch {}
  });

  console.log(`probing ${URL}…`);
  await page.goto(URL, { waitUntil: 'networkidle', timeout: 30000 });
  await page.waitForTimeout(2000); // catch lazy XHRs

  console.log('\n=== JSON/XML/JS responses (calbar domain) ===');
  for (const r of responses) {
    console.log(`\n${r.status}  ${r.url}`);
    console.log(`  content-type: ${r.ct}  size: ${r.bodyLen}`);
    console.log(`  body preview:\n  ${r.bodyPreview.replace(/\n/g, '\n  ').slice(0, 500)}`);
  }

  console.log('\n=== content of the rendered page ===');
  const title = await page.title();
  console.log(`title: ${title}`);
  // Grab the innerText of the rendered detail panel if present
  const text = await page.evaluate(`(function(){
    var sel = ['#moduleAttorneySearchList', '#detail', '.lic-info', '.attorney-detail', 'main'];
    for (var i=0;i<sel.length;i++) {
      var el = document.querySelector(sel[i]);
      if (el && el.innerText) return { selector: sel[i], text: el.innerText.slice(0, 800) };
    }
    return { selector: 'body', text: (document.body.innerText||'').slice(0, 800) };
  })()`) as any;
  console.log(`from "${text.selector}":\n${text.text}`);

  } finally {
    await browser.close();
  }
})().catch(e => { console.error('probe fail:', e); process.exit(1); });