aboutsummaryrefslogtreecommitdiff
path: root/castle-search/src/search.ts
diff options
context:
space:
mode:
Diffstat (limited to 'castle-search/src/search.ts')
-rw-r--r--castle-search/src/search.ts47
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);