Files
autofirmer-expanded/app/api/autobuyer/locate/route.ts
T
Brandon LiandClaude Opus 5 3cc7ddcc5c Add scrollToLoad and an absent variant of waitFor
scrollToLoad walks a progressively-loading list to the bottom before the steps
that act on its items run. Stopping is two-part: no new matches appeared AND the
container was already pinned to the bottom — counting alone stops early on a slow
fetch. Hitting the scroll cap is reported rather than passed off as done, so a
later step never works quietly on a partial list.

The scrolling element is usually not the window. Lists like this live in a div
with its own overflow, and scrolling the document does nothing at all, so the
step walks up from a matched item to the ancestor that actually scrolls —
overflow allows it and there is more content than fits — with containerSelector
to name one outright when the guess is wrong. Verified against a page whose
document also scrolls, which is the case that tells the two apart: it found the
inner div and pulled 12 items up to 60 in 7 scrolls.

waitFor gains `absent`, for waiting on something to go rather than arrive — a
modal closing after a reset. It only accepts a genuine "selector matched
nothing"; an unreachable extension looks the same from a distance and would
otherwise satisfy the gate for the wrong reason, sending the next iteration into
a page that still has the modal open.

The locate queue carries a free-form options blob now, so a new kind of request
stops meaning a new column each time.

Also fixes a missing comma in the reset flow that broke the build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 14:31:36 -05:00

46 lines
2.0 KiB
TypeScript

import { NextRequest } from 'next/server';
import { createLocateRequest, getLocateRequest } from '@/lib/db';
import { corsJson, corsPreflight } from '../cors';
/** Python enqueues "find this selector and tell me where it is on screen". */
export async function POST(req: NextRequest) {
try {
const body = await req.json() as { selector?: unknown; index?: unknown; urlPattern?: unknown; openUrl?: unknown; navigateUrl?: unknown; options?: unknown };
if (typeof body.selector !== 'string' || !body.selector.trim()) {
return corsJson({ error: '`selector` is required' }, { status: 400 });
}
const index = Number.isInteger(body.index) ? Number(body.index) : 0;
const urlPattern = typeof body.urlPattern === 'string' ? body.urlPattern : '';
const openUrl = typeof body.openUrl === 'string' ? body.openUrl : '';
const navigateUrl = typeof body.navigateUrl === 'string' ? body.navigateUrl : '';
const options = body.options && typeof body.options === 'object' ? JSON.stringify(body.options) : '{}';
const row = createLocateRequest(body.selector.trim(), index, urlPattern, openUrl, navigateUrl, options);
return corsJson({ id: row.id, status: row.status });
} catch (err: any) {
return corsJson({ error: err?.message ?? 'Failed to queue request' }, { status: 500 });
}
}
/** Python polls `?id=` until the extension resolves it. */
export async function GET(req: NextRequest) {
const id = Number(req.nextUrl.searchParams.get('id'));
if (!Number.isInteger(id) || id <= 0) {
return corsJson({ error: '`id` is required' }, { status: 400 });
}
const row = getLocateRequest(id);
if (!row) return corsJson({ error: 'No such request' }, { status: 404 });
return corsJson({
id: row.id,
selector: row.selector,
status: row.status,
result: row.result ? JSON.parse(row.result) : null,
error: row.error,
});
}
export async function OPTIONS() {
return corsPreflight();
}