diff options
Diffstat (limited to 'castle-search/src/search.ts')
| -rw-r--r-- | castle-search/src/search.ts | 47 |
1 files changed, 45 insertions, 2 deletions
diff --git a/castle-search/src/search.ts b/castle-search/src/search.ts index c64735d..b5effb8 100644 --- a/castle-search/src/search.ts +++ b/castle-search/src/search.ts @@ -8,7 +8,8 @@ * first search and closed by an idempotent `session_shutdown`. */ -import { resolveGoogleLinks } from "./resolve.ts"; +import { isGoogleGoto } from "./google-links.ts"; +import { GoogleVerificationError } from "./errors.ts"; import { BrowserConnection, PageSession, raceAbort } from "./cdp.ts"; import { type CdpTarget, @@ -332,7 +333,7 @@ export const search = async ( } return { - results: await resolveGoogleLinks(probe.results.slice(0, request.numResults), signal), + results: probe.results.slice(0, request.numResults), searchUrl, finalUrl: probe.url || searchUrl, endpoint, @@ -350,3 +351,45 @@ export const search = async ( release(); } }; + +export type ResolveOutcome = { from: string; to: string; endpoint: string }; + +/** + * Follows one Google `/goto` link in Chrome and reports where it lands. + * + * Lazy by design: search leaves the links wrapped, and the caller resolves only + * the ones it means to open. One tab, one navigation, closed afterwards — the + * pattern of a person clicking a result, which Google serves directly. + * + * If Google answers with its "Verifying your request" page instead, the tab is + * still on google.com when the page loads. That is reported as such. The + * countdown is not waited out and nothing is clicked. + */ +export const resolveLink = async (url: string, signal: AbortSignal): Promise<ResolveOutcome> => { + if (!isGoogleGoto(url)) { + throw new Error( + "castle_cdp_resolve only follows Google result links of the form https://www.google.com/goto?url=… " + + "as printed by castle_cdp_search. Results from other engines already carry their real URL.", + ); + } + const release = await acquire(signal); + try { + const conn = await ensureConnection(signal); + const endpoint = describeCdpTarget(conn.target); + const page = await PageSession.open(conn, signal); + try { + await page.navigate(url, signal); + const landed = await page.evaluate<string>("location.href", signal); + if (!landed || isGoogleHost(landed)) { + throw new GoogleVerificationError(landed || url); + } + return { from: url, to: landed, endpoint }; + } finally { + await page.close(); + } + } finally { + release(); + } +}; + +const isGoogleHost = (url: string): boolean => /^https?:\/\/([a-z0-9-]+\.)*google\.(?:com?\.)?[a-z]{2,3}\//.test(url); |
