Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -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<any>;

/**
* 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 {};
39 changes: 37 additions & 2 deletions src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,28 @@ async function snapshotViaExtension(
): Promise<Snapshot> {
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) {
Expand All @@ -56,9 +78,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
Expand All @@ -77,6 +99,19 @@ async function snapshotViaApi(
): Promise<Snapshot> {
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) {
Expand Down