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>
46 lines
2.0 KiB
TypeScript
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();
|
|
}
|