From 8053a7fbd1d465033517fdc35715686e5665299f Mon Sep 17 00:00:00 2001 From: rcholic Date: Wed, 24 Dec 2025 19:21:42 -0800 Subject: [PATCH 1/2] Adapt SDK to wait for loading async function in chrome --- src/global.d.ts | 89 +++++++++++++++++++++++++++++++++++++++++++++++++ src/snapshot.ts | 40 ++++++++++++++++++++-- 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/global.d.ts diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 00000000..427857d0 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,89 @@ +/** + * Type definitions for Sentience Chrome Extension API + * + * This file defines the global window.sentience API that is injected + * by the Sentience Chrome Extension into every page. + * + * These types allow TypeScript code to call window.sentience methods + * without using 'as any' casts. + */ + +/** + * Sentience Chrome Extension API + * + * The actual return types match the SDK's types.ts definitions, + * but we use 'any' here to avoid conflicts between browser context + * and Node.js context types. + */ +interface SentienceAPI { + /** + * Take a snapshot of the current page + * + * Extracts interactive elements with semantic understanding, + * scores them by importance, and returns structured data. + * + * @param options Snapshot configuration options + * @returns Promise resolving to snapshot data + * + * @example + * ```typescript + * // Basic snapshot + * const result = await window.sentience.snapshot(); + * console.log(result.elements); // Top 50 elements + * + * // With options + * const result = await window.sentience.snapshot({ + * limit: 100, + * screenshot: true, + * filter: { min_area: 50 } + * }); + * ``` + */ + snapshot(options?: any): Promise; + + /** + * Click an element by its ID + * + * @param id Element ID from snapshot + * @returns true if click succeeded, false otherwise + */ + click(id: number): boolean; + + /** + * Get readable text from the page + * + * @param options Read options + * @returns Extracted text content + */ + read(options?: any): any; + + /** + * Internal: WASM module reference (may not be exposed) + * @internal + */ + _wasmModule?: any; +} + +/** + * Extend the global Window interface + */ +declare global { + interface Window { + /** + * Sentience Chrome Extension API + * + * This API is injected by the Sentience extension and provides + * programmatic access to semantic web page analysis. + */ + sentience: SentienceAPI; + + /** + * Internal: Element registry for click tracking + * @internal + */ + sentience_registry: HTMLElement[]; + } +} + +// This export makes this a module (required for declaration merging) +export {}; diff --git a/src/snapshot.ts b/src/snapshot.ts index 2854aadb..ba2a9dcb 100644 --- a/src/snapshot.ts +++ b/src/snapshot.ts @@ -2,6 +2,7 @@ * Snapshot functionality - calls window.sentience.snapshot() or server-side API */ +import './global'; // Import type definitions for window.sentience import { SentienceBrowser } from './browser'; import { Snapshot } from './types'; @@ -44,6 +45,28 @@ async function snapshotViaExtension( ): Promise { const page = browser.getPage(); + // CRITICAL: Wait for extension injection to complete (CSP-resistant architecture) + // The new architecture loads injected_api.js asynchronously, so window.sentience + // may not be immediately available after page load + try { + await page.waitForFunction( + () => typeof window.sentience !== 'undefined', + { timeout: 5000 } + ); + } catch (e) { + // Gather diagnostics if wait fails + const diag = await page.evaluate(() => ({ + sentience_defined: typeof window.sentience !== 'undefined', + extension_id: document.documentElement.dataset.sentienceExtensionId || 'not set', + url: window.location.href + })).catch(() => ({ error: 'Could not gather diagnostics' })); + + throw new Error( + `Sentience extension failed to inject window.sentience API. ` + + `Is the extension loaded? Diagnostics: ${JSON.stringify(diag)}` + ); + } + // Build options object const opts: any = {}; if (options.screenshot !== undefined) { @@ -56,9 +79,9 @@ async function snapshotViaExtension( opts.filter = options.filter; } - // Call extension API + // Call extension API (no 'as any' needed - types defined in global.d.ts) const result = await page.evaluate((opts) => { - return (window as any).sentience.snapshot(opts); + return window.sentience.snapshot(opts); }, opts); // Basic validation @@ -77,6 +100,19 @@ async function snapshotViaApi( ): Promise { const page = browser.getPage(); + // CRITICAL: Wait for extension injection to complete (CSP-resistant architecture) + // Even for API mode, we need the extension to collect raw data locally + try { + await page.waitForFunction( + () => typeof (window as any).sentience !== 'undefined', + { timeout: 5000 } + ); + } catch (e) { + throw new Error( + 'Sentience extension failed to inject. Cannot collect raw data for API processing.' + ); + } + // Step 1: Get raw data from local extension (always happens locally) const rawOpts: any = {}; if (options.screenshot !== undefined) { From f7051e1425fc8a7af654e9fb140c10c861fc31e8 Mon Sep 17 00:00:00 2001 From: rcholic Date: Wed, 24 Dec 2025 20:27:13 -0800 Subject: [PATCH 2/2] fix tests --- src/snapshot.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/snapshot.ts b/src/snapshot.ts index ba2a9dcb..85b1d47f 100644 --- a/src/snapshot.ts +++ b/src/snapshot.ts @@ -2,7 +2,6 @@ * Snapshot functionality - calls window.sentience.snapshot() or server-side API */ -import './global'; // Import type definitions for window.sentience import { SentienceBrowser } from './browser'; import { Snapshot } from './types';