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
211 changes: 159 additions & 52 deletions sentience/extension/background.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,170 @@
// background.js - Service Worker for screenshot capture
// Chrome extensions can only capture screenshots from the background script
// Listen for screenshot requests from content script
// background.js - Service Worker with WASM (CSP-Immune!)
// This runs in an isolated environment, completely immune to page CSP policies

// ✅ STATIC IMPORTS at top level - Required for Service Workers!
// Dynamic import() is FORBIDDEN in ServiceWorkerGlobalScope
import init, { analyze_page, analyze_page_with_options, prune_for_api } from './pkg/sentience_core.js';

console.log('[Sentience Background] Initializing...');

// Global WASM initialization state
let wasmReady = false;
let wasmInitPromise = null;

/**
* Initialize WASM module - called once on service worker startup
* Uses static imports (not dynamic import()) which is required for Service Workers
*/
async function initWASM() {
if (wasmReady) return;
if (wasmInitPromise) return wasmInitPromise;

wasmInitPromise = (async () => {
try {
console.log('[Sentience Background] Loading WASM module...');

// Define the js_click_element function that WASM expects
// In Service Workers, use 'globalThis' instead of 'window'
// In background context, we can't actually click, so we log a warning
globalThis.js_click_element = (_id) => {
console.warn('[Sentience Background] js_click_element called in background (ignored)');
};

// Initialize WASM - this calls the init() function from the static import
// The init() function handles fetching and instantiating the .wasm file
await init();

wasmReady = true;
console.log('[Sentience Background] ✓ WASM ready!');
console.log('[Sentience Background] Available functions: analyze_page, analyze_page_with_options, prune_for_api');
} catch (error) {
console.error('[Sentience Background] WASM initialization failed:', error);
throw error;
}
})();

return wasmInitPromise;
}

// Initialize WASM on service worker startup
initWASM().catch(err => {
console.error('[Sentience Background] Failed to initialize WASM:', err);
});

/**
* Message handler for all extension communication
*/
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'captureScreenshot') {
handleScreenshotCapture(sender.tab.id, request.options)
.then(screenshot => {
sendResponse({ success: true, screenshot });
})
.catch(error => {
console.error('[Sentience] Screenshot capture failed:', error);
sendResponse({
success: false,
error: error.message || 'Screenshot capture failed'
});
});
// Handle screenshot requests (existing functionality)
if (request.action === 'captureScreenshot') {
handleScreenshotCapture(sender.tab.id, request.options)
.then(screenshot => {
sendResponse({ success: true, screenshot });
})
.catch(error => {
console.error('[Sentience Background] Screenshot capture failed:', error);
sendResponse({
success: false,
error: error.message || 'Screenshot capture failed'
});
});
return true; // Async response
}

// Return true to indicate we'll send response asynchronously
return true;
}
// Handle WASM processing requests (NEW!)
if (request.action === 'processSnapshot') {
handleSnapshotProcessing(request.rawData, request.options)
.then(result => {
sendResponse({ success: true, result });
})
.catch(error => {
console.error('[Sentience Background] Snapshot processing failed:', error);
sendResponse({
success: false,
error: error.message || 'Snapshot processing failed'
});
});
return true; // Async response
}

// Unknown action
console.warn('[Sentience Background] Unknown action:', request.action);
sendResponse({ success: false, error: 'Unknown action' });
return false;
});

/**
* Capture screenshot of the active tab
* @param {number} tabId - Tab ID to capture
* @param {Object} options - Screenshot options
* @returns {Promise<string>} Base64-encoded PNG data URL
* Handle screenshot capture (existing functionality)
*/
async function handleScreenshotCapture(tabId, options = {}) {
try {
const {
format = 'png', // 'png' or 'jpeg'
quality = 90 // JPEG quality (0-100), ignored for PNG
} = options;

// Capture visible tab as data URL
const dataUrl = await chrome.tabs.captureVisibleTab(null, {
format: format,
quality: quality
});

console.log(`[Sentience] Screenshot captured: ${format}, size: ${dataUrl.length} bytes`);

return dataUrl;
} catch (error) {
console.error('[Sentience] Screenshot error:', error);
throw new Error(`Failed to capture screenshot: ${error.message}`);
}
async function handleScreenshotCapture(_tabId, options = {}) {
try {
const {
format = 'png',
quality = 90
} = options;

const dataUrl = await chrome.tabs.captureVisibleTab(null, {
format: format,
quality: quality
});

console.log(`[Sentience Background] Screenshot captured: ${format}, size: ${dataUrl.length} bytes`);
return dataUrl;
} catch (error) {
console.error('[Sentience Background] Screenshot error:', error);
throw new Error(`Failed to capture screenshot: ${error.message}`);
}
}

/**
* Optional: Add viewport-specific capture (requires additional setup)
* This would allow capturing specific regions, not just visible area
* Handle snapshot processing with WASM (NEW!)
* This is where the magic happens - completely CSP-immune!
*
* @param {Array} rawData - Raw element data from injected_api.js
* @param {Object} options - Snapshot options (limit, filter, etc.)
* @returns {Promise<Object>} Processed snapshot result
*/
async function captureRegion(tabId, region) {
// For region capture, you'd need to:
// 1. Capture full visible tab
// 2. Use Canvas API to crop to region
// 3. Return cropped image

// Not implemented in this basic version
throw new Error('Region capture not yet implemented');
async function handleSnapshotProcessing(rawData, options = {}) {
try {
// Ensure WASM is initialized
await initWASM();
if (!wasmReady) {
throw new Error('WASM module not initialized');
}

console.log(`[Sentience Background] Processing ${rawData.length} elements with options:`, options);

// Run WASM processing using the imported functions directly
let analyzedElements;
try {
if (options.limit || options.filter) {
analyzedElements = analyze_page_with_options(rawData, options);
} else {
analyzedElements = analyze_page(rawData);
}
} catch (e) {
throw new Error(`WASM analyze_page failed: ${e.message}`);
}

// Prune elements for API (prevents 413 errors on large sites)
let prunedRawData;
try {
prunedRawData = prune_for_api(rawData);
} catch (e) {
console.warn('[Sentience Background] prune_for_api failed, using original data:', e);
prunedRawData = rawData;
}

console.log(`[Sentience Background] ✓ Processed: ${analyzedElements.length} analyzed, ${prunedRawData.length} pruned`);

return {
elements: analyzedElements,
raw_elements: prunedRawData
};
} catch (error) {
console.error('[Sentience Background] Processing error:', error);
throw error;
}
}

console.log('[Sentience Background] Service worker ready');
79 changes: 70 additions & 9 deletions sentience/extension/content.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,83 @@
// content.js - ISOLATED WORLD
console.log('[Sentience] Bridge loaded.');
// content.js - ISOLATED WORLD (Bridge between Main World and Background)
console.log('[Sentience Bridge] Loaded.');

// 1. Pass Extension ID to Main World (So WASM knows where to load from)
// 1. Pass Extension ID to Main World (So API knows where to find resources)
document.documentElement.dataset.sentienceExtensionId = chrome.runtime.id;

// 2. Proxy for Screenshots (The only thing Isolated World needs to do)
// 2. Message Router - Handles all communication between page and background
window.addEventListener('message', (event) => {
// Security check: only accept messages from same window
if (event.source !== window || event.data.type !== 'SENTIENCE_SCREENSHOT_REQUEST') return;
if (event.source !== window) return;

// Route different message types
switch (event.data.type) {
case 'SENTIENCE_SCREENSHOT_REQUEST':
handleScreenshotRequest(event.data);
break;

case 'SENTIENCE_SNAPSHOT_REQUEST':
handleSnapshotRequest(event.data);
break;

default:
// Ignore unknown message types
break;
}
});

/**
* Handle screenshot requests (existing functionality)
*/
function handleScreenshotRequest(data) {
chrome.runtime.sendMessage(
{ action: 'captureScreenshot', options: event.data.options },
{ action: 'captureScreenshot', options: data.options },
(response) => {
window.postMessage({
type: 'SENTIENCE_SCREENSHOT_RESULT',
requestId: event.data.requestId,
screenshot: response?.success ? response.screenshot : null
requestId: data.requestId,
screenshot: response?.success ? response.screenshot : null,
error: response?.error
}, '*');
}
);
});
}

/**
* Handle snapshot processing requests (NEW!)
* Sends raw DOM data to background worker for WASM processing
*/
function handleSnapshotRequest(data) {
const startTime = performance.now();

chrome.runtime.sendMessage(
{
action: 'processSnapshot',
rawData: data.rawData,
options: data.options
},
(response) => {
const duration = performance.now() - startTime;

if (response?.success) {
console.log(`[Sentience Bridge] ✓ WASM processing complete in ${duration.toFixed(1)}ms`);
window.postMessage({
type: 'SENTIENCE_SNAPSHOT_RESULT',
requestId: data.requestId,
elements: response.result.elements,
raw_elements: response.result.raw_elements,
duration: duration
}, '*');
} else {
console.error('[Sentience Bridge] WASM processing failed:', response?.error);
window.postMessage({
type: 'SENTIENCE_SNAPSHOT_RESULT',
requestId: data.requestId,
error: response?.error || 'Processing failed',
duration: duration
}, '*');
}
}
);
}

console.log('[Sentience Bridge] Ready - Extension ID:', chrome.runtime.id);
Loading