Skip to content

Commit 315be84

Browse files
author
SentienceDEV
committed
Merge pull request #75 from SentienceAPI/sync-extension-v2.0.7
Sync Extension: v2.0.7
2 parents 2bef691 + dcc94a8 commit 315be84

File tree

11 files changed

+2049
-256
lines changed

11 files changed

+2049
-256
lines changed

src/extension/background.js

Lines changed: 222 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,233 @@
1-
// background.js - Service Worker for screenshot capture
2-
// Chrome extensions can only capture screenshots from the background script
3-
// Listen for screenshot requests from content script
1+
// background.js - Service Worker with WASM (CSP-Immune!)
2+
// This runs in an isolated environment, completely immune to page CSP policies
3+
4+
// ✅ STATIC IMPORTS at top level - Required for Service Workers!
5+
// Dynamic import() is FORBIDDEN in ServiceWorkerGlobalScope
6+
import init, { analyze_page, analyze_page_with_options, prune_for_api } from './pkg/sentience_core.js';
7+
8+
console.log('[Sentience Background] Initializing...');
9+
10+
// Global WASM initialization state
11+
let wasmReady = false;
12+
let wasmInitPromise = null;
13+
14+
/**
15+
* Initialize WASM module - called once on service worker startup
16+
* Uses static imports (not dynamic import()) which is required for Service Workers
17+
*/
18+
async function initWASM() {
19+
if (wasmReady) return;
20+
if (wasmInitPromise) return wasmInitPromise;
21+
22+
wasmInitPromise = (async () => {
23+
try {
24+
console.log('[Sentience Background] Loading WASM module...');
25+
26+
// Define the js_click_element function that WASM expects
27+
// In Service Workers, use 'globalThis' instead of 'window'
28+
// In background context, we can't actually click, so we log a warning
29+
globalThis.js_click_element = (_id) => {
30+
console.warn('[Sentience Background] js_click_element called in background (ignored)');
31+
};
32+
33+
// Initialize WASM - this calls the init() function from the static import
34+
// The init() function handles fetching and instantiating the .wasm file
35+
await init();
36+
37+
wasmReady = true;
38+
console.log('[Sentience Background] ✓ WASM ready!');
39+
console.log('[Sentience Background] Available functions: analyze_page, analyze_page_with_options, prune_for_api');
40+
} catch (error) {
41+
console.error('[Sentience Background] WASM initialization failed:', error);
42+
throw error;
43+
}
44+
})();
45+
46+
return wasmInitPromise;
47+
}
48+
49+
// Initialize WASM on service worker startup
50+
initWASM().catch(err => {
51+
console.error('[Sentience Background] Failed to initialize WASM:', err);
52+
});
53+
54+
/**
55+
* Message handler for all extension communication
56+
* Includes global error handling to prevent extension crashes
57+
*/
458
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
5-
if (request.action === 'captureScreenshot') {
6-
handleScreenshotCapture(sender.tab.id, request.options)
7-
.then(screenshot => {
8-
sendResponse({ success: true, screenshot });
9-
})
10-
.catch(error => {
11-
console.error('[Sentience] Screenshot capture failed:', error);
12-
sendResponse({
13-
success: false,
14-
error: error.message || 'Screenshot capture failed'
15-
});
16-
});
59+
// Global error handler to prevent extension crashes
60+
try {
61+
// Handle screenshot requests (existing functionality)
62+
if (request.action === 'captureScreenshot') {
63+
handleScreenshotCapture(sender.tab.id, request.options)
64+
.then(screenshot => {
65+
sendResponse({ success: true, screenshot });
66+
})
67+
.catch(error => {
68+
console.error('[Sentience Background] Screenshot capture failed:', error);
69+
sendResponse({
70+
success: false,
71+
error: error.message || 'Screenshot capture failed'
72+
});
73+
});
74+
return true; // Async response
75+
}
76+
77+
// Handle WASM processing requests (NEW!)
78+
if (request.action === 'processSnapshot') {
79+
handleSnapshotProcessing(request.rawData, request.options)
80+
.then(result => {
81+
sendResponse({ success: true, result });
82+
})
83+
.catch(error => {
84+
console.error('[Sentience Background] Snapshot processing failed:', error);
85+
sendResponse({
86+
success: false,
87+
error: error.message || 'Snapshot processing failed'
88+
});
89+
});
90+
return true; // Async response
91+
}
1792

18-
// Return true to indicate we'll send response asynchronously
19-
return true;
20-
}
93+
// Unknown action
94+
console.warn('[Sentience Background] Unknown action:', request.action);
95+
sendResponse({ success: false, error: 'Unknown action' });
96+
return false;
97+
} catch (error) {
98+
// Catch any synchronous errors that might crash the extension
99+
console.error('[Sentience Background] Fatal error in message handler:', error);
100+
try {
101+
sendResponse({
102+
success: false,
103+
error: `Fatal error: ${error.message || 'Unknown error'}`
104+
});
105+
} catch (e) {
106+
// If sendResponse already called, ignore
107+
}
108+
return false;
109+
}
21110
});
22111

23112
/**
24-
* Capture screenshot of the active tab
25-
* @param {number} tabId - Tab ID to capture
26-
* @param {Object} options - Screenshot options
27-
* @returns {Promise<string>} Base64-encoded PNG data URL
113+
* Handle screenshot capture (existing functionality)
28114
*/
29-
async function handleScreenshotCapture(tabId, options = {}) {
30-
try {
31-
const {
32-
format = 'png', // 'png' or 'jpeg'
33-
quality = 90 // JPEG quality (0-100), ignored for PNG
34-
} = options;
35-
36-
// Capture visible tab as data URL
37-
const dataUrl = await chrome.tabs.captureVisibleTab(null, {
38-
format: format,
39-
quality: quality
40-
});
41-
42-
console.log(`[Sentience] Screenshot captured: ${format}, size: ${dataUrl.length} bytes`);
43-
44-
return dataUrl;
45-
} catch (error) {
46-
console.error('[Sentience] Screenshot error:', error);
47-
throw new Error(`Failed to capture screenshot: ${error.message}`);
48-
}
115+
async function handleScreenshotCapture(_tabId, options = {}) {
116+
try {
117+
const {
118+
format = 'png',
119+
quality = 90
120+
} = options;
121+
122+
const dataUrl = await chrome.tabs.captureVisibleTab(null, {
123+
format: format,
124+
quality: quality
125+
});
126+
127+
console.log(`[Sentience Background] Screenshot captured: ${format}, size: ${dataUrl.length} bytes`);
128+
return dataUrl;
129+
} catch (error) {
130+
console.error('[Sentience Background] Screenshot error:', error);
131+
throw new Error(`Failed to capture screenshot: ${error.message}`);
132+
}
49133
}
50134

51135
/**
52-
* Optional: Add viewport-specific capture (requires additional setup)
53-
* This would allow capturing specific regions, not just visible area
136+
* Handle snapshot processing with WASM (NEW!)
137+
* This is where the magic happens - completely CSP-immune!
138+
* Includes safeguards to prevent crashes and hangs.
139+
*
140+
* @param {Array} rawData - Raw element data from injected_api.js
141+
* @param {Object} options - Snapshot options (limit, filter, etc.)
142+
* @returns {Promise<Object>} Processed snapshot result
54143
*/
55-
async function captureRegion(tabId, region) {
56-
// For region capture, you'd need to:
57-
// 1. Capture full visible tab
58-
// 2. Use Canvas API to crop to region
59-
// 3. Return cropped image
60-
61-
// Not implemented in this basic version
62-
throw new Error('Region capture not yet implemented');
144+
async function handleSnapshotProcessing(rawData, options = {}) {
145+
const MAX_ELEMENTS = 10000; // Safety limit to prevent hangs
146+
const startTime = performance.now();
147+
148+
try {
149+
// Safety check: limit element count to prevent hangs
150+
if (!Array.isArray(rawData)) {
151+
throw new Error('rawData must be an array');
152+
}
153+
154+
if (rawData.length > MAX_ELEMENTS) {
155+
console.warn(`[Sentience Background] ⚠️ Large dataset: ${rawData.length} elements. Limiting to ${MAX_ELEMENTS} to prevent hangs.`);
156+
rawData = rawData.slice(0, MAX_ELEMENTS);
157+
}
158+
159+
// Ensure WASM is initialized
160+
await initWASM();
161+
if (!wasmReady) {
162+
throw new Error('WASM module not initialized');
163+
}
164+
165+
console.log(`[Sentience Background] Processing ${rawData.length} elements with options:`, options);
166+
167+
// Run WASM processing using the imported functions directly
168+
// Wrap in try-catch with timeout protection
169+
let analyzedElements;
170+
try {
171+
// Use a timeout wrapper to prevent infinite hangs
172+
const wasmPromise = new Promise((resolve, reject) => {
173+
try {
174+
let result;
175+
if (options.limit || options.filter) {
176+
result = analyze_page_with_options(rawData, options);
177+
} else {
178+
result = analyze_page(rawData);
179+
}
180+
resolve(result);
181+
} catch (e) {
182+
reject(e);
183+
}
184+
});
185+
186+
// Add timeout protection (18 seconds - less than content.js timeout)
187+
analyzedElements = await Promise.race([
188+
wasmPromise,
189+
new Promise((_, reject) =>
190+
setTimeout(() => reject(new Error('WASM processing timeout (>18s)')), 18000)
191+
)
192+
]);
193+
} catch (e) {
194+
const errorMsg = e.message || 'Unknown WASM error';
195+
console.error(`[Sentience Background] WASM analyze_page failed: ${errorMsg}`, e);
196+
throw new Error(`WASM analyze_page failed: ${errorMsg}`);
197+
}
198+
199+
// Prune elements for API (prevents 413 errors on large sites)
200+
let prunedRawData;
201+
try {
202+
prunedRawData = prune_for_api(rawData);
203+
} catch (e) {
204+
console.warn('[Sentience Background] prune_for_api failed, using original data:', e);
205+
prunedRawData = rawData;
206+
}
207+
208+
const duration = performance.now() - startTime;
209+
console.log(`[Sentience Background] ✓ Processed: ${analyzedElements.length} analyzed, ${prunedRawData.length} pruned (${duration.toFixed(1)}ms)`);
210+
211+
return {
212+
elements: analyzedElements,
213+
raw_elements: prunedRawData
214+
};
215+
} catch (error) {
216+
const duration = performance.now() - startTime;
217+
console.error(`[Sentience Background] Processing error after ${duration.toFixed(1)}ms:`, error);
218+
throw error;
219+
}
63220
}
221+
222+
console.log('[Sentience Background] Service worker ready');
223+
224+
// Global error handlers to prevent extension crashes
225+
self.addEventListener('error', (event) => {
226+
console.error('[Sentience Background] Global error caught:', event.error);
227+
event.preventDefault(); // Prevent extension crash
228+
});
229+
230+
self.addEventListener('unhandledrejection', (event) => {
231+
console.error('[Sentience Background] Unhandled promise rejection:', event.reason);
232+
event.preventDefault(); // Prevent extension crash
233+
});

0 commit comments

Comments
 (0)