11import init , { analyze_page_with_options , analyze_page , prune_for_api } from "../pkg/sentience_core.js" ;
22
3- let wasmReady = ! 1 , wasmInitPromise = null ;
3+ // background.js - Service Worker with WASM (CSP-Immune!)
4+ // This runs in an isolated environment, completely immune to page CSP policies
45
6+
7+ console . log ( '[Sentience Background] Initializing...' ) ;
8+
9+ // Global WASM initialization state
10+ let wasmReady = false ;
11+ let wasmInitPromise = null ;
12+
13+ /**
14+ * Initialize WASM module - called once on service worker startup
15+ * Uses static imports (not dynamic import()) which is required for Service Workers
16+ */
517async function initWASM ( ) {
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- }
18+ if ( wasmReady ) return ;
19+ if ( wasmInitPromise ) return wasmInitPromise ;
1420
15- async function handleScreenshotCapture ( _tabId , options = { } ) {
21+ wasmInitPromise = ( async ( ) => {
1622 try {
17- const { format : format = "png" , quality : quality = 90 } = options ;
18- return await chrome . tabs . captureVisibleTab ( null , {
19- format : format ,
20- quality : quality
21- } ) ;
23+ console . log ( '[Sentience Background] Loading WASM module...' ) ;
24+
25+ // Define the js_click_element function that WASM expects
26+ // In Service Workers, use 'globalThis' instead of 'window'
27+ // In background context, we can't actually click, so we log a warning
28+ globalThis . js_click_element = ( ) => {
29+ console . warn ( '[Sentience Background] js_click_element called in background (ignored)' ) ;
30+ } ;
31+
32+ // Initialize WASM - this calls the init() function from the static import
33+ // The init() function handles fetching and instantiating the .wasm file
34+ await init ( ) ;
35+
36+ wasmReady = true ;
37+ console . log ( '[Sentience Background] ✓ WASM ready!' ) ;
38+ console . log (
39+ '[Sentience Background] Available functions: analyze_page, analyze_page_with_options, prune_for_api'
40+ ) ;
2241 } catch ( error ) {
23- throw new Error ( `Failed to capture screenshot: ${ error . message } ` ) ;
42+ console . error ( '[Sentience Background] WASM initialization failed:' , error ) ;
43+ throw error ;
2444 }
45+ } ) ( ) ;
46+
47+ return wasmInitPromise ;
2548}
2649
50+ // Initialize WASM on service worker startup
51+ initWASM ( ) . catch ( ( err ) => {
52+ console . error ( '[Sentience Background] Failed to initialize WASM:' , err ) ;
53+ } ) ;
54+
55+ /**
56+ * Message handler for all extension communication
57+ * Includes global error handling to prevent extension crashes
58+ */
59+ chrome . runtime . onMessage . addListener ( ( request , sender , sendResponse ) => {
60+ // Global error handler to prevent extension crashes
61+ try {
62+ // Handle screenshot requests (existing functionality)
63+ if ( request . action === 'captureScreenshot' ) {
64+ handleScreenshotCapture ( sender . tab . id , request . options )
65+ . then ( ( screenshot ) => {
66+ sendResponse ( { success : true , screenshot } ) ;
67+ } )
68+ . catch ( ( error ) => {
69+ console . error ( '[Sentience Background] Screenshot capture failed:' , error ) ;
70+ sendResponse ( {
71+ success : false ,
72+ error : error . message || 'Screenshot capture failed' ,
73+ } ) ;
74+ } ) ;
75+ return true ; // Async response
76+ }
77+
78+ // Handle WASM processing requests (NEW!)
79+ if ( request . action === 'processSnapshot' ) {
80+ handleSnapshotProcessing ( request . rawData , request . options )
81+ . then ( ( result ) => {
82+ sendResponse ( { success : true , result } ) ;
83+ } )
84+ . catch ( ( error ) => {
85+ console . error ( '[Sentience Background] Snapshot processing failed:' , error ) ;
86+ sendResponse ( {
87+ success : false ,
88+ error : error . message || 'Snapshot processing failed' ,
89+ } ) ;
90+ } ) ;
91+ return true ; // Async response
92+ }
93+
94+ // Unknown action
95+ console . warn ( '[Sentience Background] Unknown action:' , request . action ) ;
96+ sendResponse ( { success : false , error : 'Unknown action' } ) ;
97+ return false ;
98+ } catch ( error ) {
99+ // Catch any synchronous errors that might crash the extension
100+ console . error ( '[Sentience Background] Fatal error in message handler:' , error ) ;
101+ try {
102+ sendResponse ( {
103+ success : false ,
104+ error : `Fatal error: ${ error . message || 'Unknown error' } ` ,
105+ } ) ;
106+ } catch ( e ) {
107+ // If sendResponse already called, ignore
108+ }
109+ return false ;
110+ }
111+ } ) ;
112+
113+ /**
114+ * Handle screenshot capture (existing functionality)
115+ */
116+ async function handleScreenshotCapture ( _tabId , options = { } ) {
117+ try {
118+ const { format = 'png' , quality = 90 } = options ;
119+
120+ const dataUrl = await chrome . tabs . captureVisibleTab ( null , {
121+ format,
122+ quality,
123+ } ) ;
124+
125+ console . log (
126+ `[Sentience Background] Screenshot captured: ${ format } , size: ${ dataUrl . length } bytes`
127+ ) ;
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+ }
133+ }
134+
135+ /**
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
143+ */
27144async function handleSnapshotProcessing ( rawData , options = { } ) {
28- const startTime = performance . now ( ) ;
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 (
156+ `[Sentience Background] ⚠️ Large dataset: ${ rawData . length } elements. Limiting to ${ MAX_ELEMENTS } to prevent hangs.`
157+ ) ;
158+ rawData = rawData . slice ( 0 , MAX_ELEMENTS ) ;
159+ }
160+
161+ // Ensure WASM is initialized
162+ await initWASM ( ) ;
163+ if ( ! wasmReady ) {
164+ throw new Error ( 'WASM module not initialized' ) ;
165+ }
166+
167+ console . log (
168+ `[Sentience Background] Processing ${ rawData . length } elements with options:` ,
169+ options
170+ ) ;
171+
172+ // Run WASM processing using the imported functions directly
173+ // Wrap in try-catch with timeout protection
174+ let analyzedElements ;
29175 try {
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 ;
34- try {
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 ) ) ] ) ;
45- } catch ( e ) {
46- const errorMsg = e . message || "Unknown WASM error" ;
47- throw new Error ( `WASM analyze_page failed: ${ errorMsg } ` ) ;
48- }
176+ // Use a timeout wrapper to prevent infinite hangs
177+ const wasmPromise = new Promise ( ( resolve , reject ) => {
49178 try {
50- prunedRawData = prune_for_api ( rawData ) ;
179+ let result ;
180+ if ( options . limit || options . filter ) {
181+ result = analyze_page_with_options ( rawData , options ) ;
182+ } else {
183+ result = analyze_page ( rawData ) ;
184+ }
185+ resolve ( result ) ;
51186 } catch ( e ) {
52- prunedRawData = rawData ;
187+ reject ( e ) ;
53188 }
54- performance . now ( ) ;
55- return {
56- elements : analyzedElements ,
57- raw_elements : prunedRawData
58- } ;
59- } catch ( error ) {
60- performance . now ( ) ;
61- throw error ;
189+ } ) ;
190+
191+ // Add timeout protection (18 seconds - less than content.js timeout)
192+ analyzedElements = await Promise . race ( [
193+ wasmPromise ,
194+ new Promise ( ( _ , reject ) =>
195+ setTimeout ( ( ) => reject ( new Error ( 'WASM processing timeout (>18s)' ) ) , 18000 )
196+ ) ,
197+ ] ) ;
198+ } catch ( e ) {
199+ const errorMsg = e . message || 'Unknown WASM error' ;
200+ console . error ( `[Sentience Background] WASM analyze_page failed: ${ errorMsg } ` , e ) ;
201+ throw new Error ( `WASM analyze_page failed: ${ errorMsg } ` ) ;
62202 }
63- }
64203
65- initWASM ( ) . catch ( err => { } ) , chrome . runtime . onMessage . addListener ( ( request , sender , sendResponse ) => {
204+ // Prune elements for API (prevents 413 errors on large sites)
205+ let prunedRawData ;
66206 try {
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 ;
207+ prunedRawData = prune_for_api ( rawData ) ;
208+ } catch ( e ) {
209+ console . warn ( '[Sentience Background] prune_for_api failed, using original data:' , e ) ;
210+ prunedRawData = rawData ;
99211 }
100- } ) , self . addEventListener ( "error" , event => {
101- event . preventDefault ( ) ;
102- } ) , self . addEventListener ( "unhandledrejection" , event => {
103- event . preventDefault ( ) ;
104- } ) ;
212+
213+ const duration = performance . now ( ) - startTime ;
214+ console . log (
215+ `[Sentience Background] ✓ Processed: ${ analyzedElements . length } analyzed, ${ prunedRawData . length } pruned (${ duration . toFixed ( 1 ) } ms)`
216+ ) ;
217+
218+ return {
219+ elements : analyzedElements ,
220+ raw_elements : prunedRawData ,
221+ } ;
222+ } catch ( error ) {
223+ const duration = performance . now ( ) - startTime ;
224+ console . error ( `[Sentience Background] Processing error after ${ duration . toFixed ( 1 ) } ms:` , error ) ;
225+ throw error ;
226+ }
227+ }
228+
229+ console . log ( '[Sentience Background] Service worker ready' ) ;
230+
231+ // Global error handlers to prevent extension crashes
232+ self . addEventListener ( 'error' , ( event ) => {
233+ console . error ( '[Sentience Background] Global error caught:' , event . error ) ;
234+ event . preventDefault ( ) ; // Prevent extension crash
235+ } ) ;
236+
237+ self . addEventListener ( 'unhandledrejection' , ( event ) => {
238+ console . error ( '[Sentience Background] Unhandled promise rejection:' , event . reason ) ;
239+ event . preventDefault ( ) ; // Prevent extension crash
240+ } ) ;
0 commit comments