Skip to content

Commit e30eb1a

Browse files
authored
Merge pull request #121 from SentienceAPI/sync-extension-v2.2.0
Sync Extension: v2.2.0
2 parents 728a599 + c808b93 commit e30eb1a

File tree

7 files changed

+1292
-2531
lines changed

7 files changed

+1292
-2531
lines changed

sentience/extension/background.js

Lines changed: 86 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -1,242 +1,104 @@
1-
// Sentience Chrome Extension - Background Service Worker
2-
// Auto-generated from modular source
3-
import init, { analyze_page_with_options, analyze_page, prune_for_api } from '../pkg/sentience_core.js';
1+
import init, { analyze_page_with_options, analyze_page, prune_for_api } from "../pkg/sentience_core.js";
42

5-
// background.js - Service Worker with WASM (CSP-Immune!)
6-
// This runs in an isolated environment, completely immune to page CSP policies
3+
let wasmReady = !1, wasmInitPromise = null;
74

8-
9-
console.log('[Sentience Background] Initializing...');
10-
11-
// Global WASM initialization state
12-
let wasmReady = false;
13-
let wasmInitPromise = null;
14-
15-
/**
16-
* Initialize WASM module - called once on service worker startup
17-
* Uses static imports (not dynamic import()) which is required for Service Workers
18-
*/
195
async function initWASM() {
20-
if (wasmReady) return;
21-
if (wasmInitPromise) return wasmInitPromise;
6+
if (!wasmReady) return wasmInitPromise || (wasmInitPromise = (async () => {
7+
try {
8+
globalThis.js_click_element = () => {}, await init(), wasmReady = !0;
9+
} catch (error) {
10+
throw error;
11+
}
12+
})(), wasmInitPromise);
13+
}
2214

23-
wasmInitPromise = (async () => {
15+
async function handleScreenshotCapture(_tabId, options = {}) {
2416
try {
25-
console.log('[Sentience Background] Loading WASM module...');
26-
27-
// Define the js_click_element function that WASM expects
28-
// In Service Workers, use 'globalThis' instead of 'window'
29-
// In background context, we can't actually click, so we log a warning
30-
globalThis.js_click_element = () => {
31-
console.warn('[Sentience Background] js_click_element called in background (ignored)');
32-
};
33-
34-
// Initialize WASM - this calls the init() function from the static import
35-
// The init() function handles fetching and instantiating the .wasm file
36-
await init();
37-
38-
wasmReady = true;
39-
console.log('[Sentience Background] ✓ WASM ready!');
40-
console.log(
41-
'[Sentience Background] Available functions: analyze_page, analyze_page_with_options, prune_for_api'
42-
);
17+
const {format: format = "png", quality: quality = 90} = options;
18+
return await chrome.tabs.captureVisibleTab(null, {
19+
format: format,
20+
quality: quality
21+
});
4322
} catch (error) {
44-
console.error('[Sentience Background] WASM initialization failed:', error);
45-
throw error;
23+
throw new Error(`Failed to capture screenshot: ${error.message}`);
4624
}
47-
})();
48-
49-
return wasmInitPromise;
5025
}
5126

52-
// Initialize WASM on service worker startup
53-
initWASM().catch((err) => {
54-
console.error('[Sentience Background] Failed to initialize WASM:', err);
55-
});
56-
57-
/**
58-
* Message handler for all extension communication
59-
* Includes global error handling to prevent extension crashes
60-
*/
61-
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
62-
// Global error handler to prevent extension crashes
63-
try {
64-
// Handle screenshot requests (existing functionality)
65-
if (request.action === 'captureScreenshot') {
66-
handleScreenshotCapture(sender.tab.id, request.options)
67-
.then((screenshot) => {
68-
sendResponse({ success: true, screenshot });
69-
})
70-
.catch((error) => {
71-
console.error('[Sentience Background] Screenshot capture failed:', error);
72-
sendResponse({
73-
success: false,
74-
error: error.message || 'Screenshot capture failed',
75-
});
76-
});
77-
return true; // Async response
78-
}
79-
80-
// Handle WASM processing requests (NEW!)
81-
if (request.action === 'processSnapshot') {
82-
handleSnapshotProcessing(request.rawData, request.options)
83-
.then((result) => {
84-
sendResponse({ success: true, result });
85-
})
86-
.catch((error) => {
87-
console.error('[Sentience Background] Snapshot processing failed:', error);
88-
sendResponse({
89-
success: false,
90-
error: error.message || 'Snapshot processing failed',
91-
});
92-
});
93-
return true; // Async response
94-
}
95-
96-
// Unknown action
97-
console.warn('[Sentience Background] Unknown action:', request.action);
98-
sendResponse({ success: false, error: 'Unknown action' });
99-
return false;
100-
} catch (error) {
101-
// Catch any synchronous errors that might crash the extension
102-
console.error('[Sentience Background] Fatal error in message handler:', error);
103-
try {
104-
sendResponse({
105-
success: false,
106-
error: `Fatal error: ${error.message || 'Unknown error'}`,
107-
});
108-
} catch (e) {
109-
// If sendResponse already called, ignore
110-
}
111-
return false;
112-
}
113-
});
114-
115-
/**
116-
* Handle screenshot capture (existing functionality)
117-
*/
118-
async function handleScreenshotCapture(_tabId, options = {}) {
119-
try {
120-
const { format = 'png', quality = 90 } = options;
121-
122-
const dataUrl = await chrome.tabs.captureVisibleTab(null, {
123-
format,
124-
quality,
125-
});
126-
127-
console.log(
128-
`[Sentience Background] Screenshot captured: ${format}, size: ${dataUrl.length} bytes`
129-
);
130-
return dataUrl;
131-
} catch (error) {
132-
console.error('[Sentience Background] Screenshot error:', error);
133-
throw new Error(`Failed to capture screenshot: ${error.message}`);
134-
}
135-
}
136-
137-
/**
138-
* Handle snapshot processing with WASM (NEW!)
139-
* This is where the magic happens - completely CSP-immune!
140-
* Includes safeguards to prevent crashes and hangs.
141-
*
142-
* @param {Array} rawData - Raw element data from injected_api.js
143-
* @param {Object} options - Snapshot options (limit, filter, etc.)
144-
* @returns {Promise<Object>} Processed snapshot result
145-
*/
14627
async function handleSnapshotProcessing(rawData, options = {}) {
147-
const MAX_ELEMENTS = 10000; // Safety limit to prevent hangs
148-
const startTime = performance.now();
149-
150-
try {
151-
// Safety check: limit element count to prevent hangs
152-
if (!Array.isArray(rawData)) {
153-
throw new Error('rawData must be an array');
154-
}
155-
156-
if (rawData.length > MAX_ELEMENTS) {
157-
console.warn(
158-
`[Sentience Background] ⚠️ Large dataset: ${rawData.length} elements. Limiting to ${MAX_ELEMENTS} to prevent hangs.`
159-
);
160-
rawData = rawData.slice(0, MAX_ELEMENTS);
161-
}
162-
163-
// Ensure WASM is initialized
164-
await initWASM();
165-
if (!wasmReady) {
166-
throw new Error('WASM module not initialized');
167-
}
168-
169-
console.log(
170-
`[Sentience Background] Processing ${rawData.length} elements with options:`,
171-
options
172-
);
173-
174-
// Run WASM processing using the imported functions directly
175-
// Wrap in try-catch with timeout protection
176-
let analyzedElements;
28+
const startTime = performance.now();
17729
try {
178-
// Use a timeout wrapper to prevent infinite hangs
179-
const wasmPromise = new Promise((resolve, reject) => {
30+
if (!Array.isArray(rawData)) throw new Error("rawData must be an array");
31+
if (rawData.length > 1e4 && (rawData = rawData.slice(0, 1e4)), await initWASM(),
32+
!wasmReady) throw new Error("WASM module not initialized");
33+
let analyzedElements, prunedRawData;
18034
try {
181-
let result;
182-
if (options.limit || options.filter) {
183-
result = analyze_page_with_options(rawData, options);
184-
} else {
185-
result = analyze_page(rawData);
186-
}
187-
resolve(result);
35+
const wasmPromise = new Promise((resolve, reject) => {
36+
try {
37+
let result;
38+
result = options.limit || options.filter ? analyze_page_with_options(rawData, options) : analyze_page(rawData),
39+
resolve(result);
40+
} catch (e) {
41+
reject(e);
42+
}
43+
});
44+
analyzedElements = await Promise.race([ wasmPromise, new Promise((_, reject) => setTimeout(() => reject(new Error("WASM processing timeout (>18s)")), 18e3)) ]);
18845
} catch (e) {
189-
reject(e);
46+
const errorMsg = e.message || "Unknown WASM error";
47+
throw new Error(`WASM analyze_page failed: ${errorMsg}`);
19048
}
191-
});
192-
193-
// Add timeout protection (18 seconds - less than content.js timeout)
194-
analyzedElements = await Promise.race([
195-
wasmPromise,
196-
new Promise((_, reject) =>
197-
setTimeout(() => reject(new Error('WASM processing timeout (>18s)')), 18000)
198-
),
199-
]);
200-
} catch (e) {
201-
const errorMsg = e.message || 'Unknown WASM error';
202-
console.error(`[Sentience Background] WASM analyze_page failed: ${errorMsg}`, e);
203-
throw new Error(`WASM analyze_page failed: ${errorMsg}`);
49+
try {
50+
prunedRawData = prune_for_api(rawData);
51+
} catch (e) {
52+
prunedRawData = rawData;
53+
}
54+
performance.now();
55+
return {
56+
elements: analyzedElements,
57+
raw_elements: prunedRawData
58+
};
59+
} catch (error) {
60+
performance.now();
61+
throw error;
20462
}
63+
}
20564

206-
// Prune elements for API (prevents 413 errors on large sites)
207-
let prunedRawData;
65+
initWASM().catch(err => {}), chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
20866
try {
209-
prunedRawData = prune_for_api(rawData);
210-
} catch (e) {
211-
console.warn('[Sentience Background] prune_for_api failed, using original data:', e);
212-
prunedRawData = rawData;
67+
return "captureScreenshot" === request.action ? (handleScreenshotCapture(sender.tab.id, request.options).then(screenshot => {
68+
sendResponse({
69+
success: !0,
70+
screenshot: screenshot
71+
});
72+
}).catch(error => {
73+
sendResponse({
74+
success: !1,
75+
error: error.message || "Screenshot capture failed"
76+
});
77+
}), !0) : "processSnapshot" === request.action ? (handleSnapshotProcessing(request.rawData, request.options).then(result => {
78+
sendResponse({
79+
success: !0,
80+
result: result
81+
});
82+
}).catch(error => {
83+
sendResponse({
84+
success: !1,
85+
error: error.message || "Snapshot processing failed"
86+
});
87+
}), !0) : (sendResponse({
88+
success: !1,
89+
error: "Unknown action"
90+
}), !1);
91+
} catch (error) {
92+
try {
93+
sendResponse({
94+
success: !1,
95+
error: `Fatal error: ${error.message || "Unknown error"}`
96+
});
97+
} catch (e) {}
98+
return !1;
21399
}
214-
215-
const duration = performance.now() - startTime;
216-
console.log(
217-
`[Sentience Background] ✓ Processed: ${analyzedElements.length} analyzed, ${prunedRawData.length} pruned (${duration.toFixed(1)}ms)`
218-
);
219-
220-
return {
221-
elements: analyzedElements,
222-
raw_elements: prunedRawData,
223-
};
224-
} catch (error) {
225-
const duration = performance.now() - startTime;
226-
console.error(`[Sentience Background] Processing error after ${duration.toFixed(1)}ms:`, error);
227-
throw error;
228-
}
229-
}
230-
231-
console.log('[Sentience Background] Service worker ready');
232-
233-
// Global error handlers to prevent extension crashes
234-
self.addEventListener('error', (event) => {
235-
console.error('[Sentience Background] Global error caught:', event.error);
236-
event.preventDefault(); // Prevent extension crash
237-
});
238-
239-
self.addEventListener('unhandledrejection', (event) => {
240-
console.error('[Sentience Background] Unhandled promise rejection:', event.reason);
241-
event.preventDefault(); // Prevent extension crash
242-
});
100+
}), self.addEventListener("error", event => {
101+
event.preventDefault();
102+
}), self.addEventListener("unhandledrejection", event => {
103+
event.preventDefault();
104+
});

0 commit comments

Comments
 (0)