1- import init , { analyze_page_with_options , analyze_page , prune_for_api } from "../pkg/sentience_core.js" ;
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' ;
24
3- let wasmReady = ! 1 , wasmInitPromise = null ;
5+ // background.js - Service Worker with WASM (CSP-Immune!)
6+ // This runs in an isolated environment, completely immune to page CSP policies
47
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+ */
519async 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- }
20+ if ( wasmReady ) return ;
21+ if ( wasmInitPromise ) return wasmInitPromise ;
1422
15- async function handleScreenshotCapture ( _tabId , options = { } ) {
23+ wasmInitPromise = ( async ( ) => {
1624 try {
17- const { format : format = "png" , quality : quality = 90 } = options ;
18- return await chrome . tabs . captureVisibleTab ( null , {
19- format : format ,
20- quality : quality
21- } ) ;
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+ ) ;
2243 } catch ( error ) {
23- throw new Error ( `Failed to capture screenshot: ${ error . message } ` ) ;
44+ console . error ( '[Sentience Background] WASM initialization failed:' , error ) ;
45+ throw error ;
2446 }
47+ } ) ( ) ;
48+
49+ return wasmInitPromise ;
2550}
2651
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+ */
27146async function handleSnapshotProcessing ( rawData , options = { } ) {
28- const startTime = performance . now ( ) ;
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 ;
29177 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- }
178+ // Use a timeout wrapper to prevent infinite hangs
179+ const wasmPromise = new Promise ( ( resolve , reject ) => {
49180 try {
50- prunedRawData = prune_for_api ( rawData ) ;
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 ) ;
51188 } catch ( e ) {
52- prunedRawData = rawData ;
189+ reject ( e ) ;
53190 }
54- performance . now ( ) ;
55- return {
56- elements : analyzedElements ,
57- raw_elements : prunedRawData
58- } ;
59- } catch ( error ) {
60- performance . now ( ) ;
61- throw error ;
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 } ` ) ;
62204 }
63- }
64205
65- initWASM ( ) . catch ( err => { } ) , chrome . runtime . onMessage . addListener ( ( request , sender , sendResponse ) => {
206+ // Prune elements for API (prevents 413 errors on large sites)
207+ let prunedRawData ;
66208 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 ;
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 ;
99213 }
100- } ) , self . addEventListener ( "error" , event => {
101- event . preventDefault ( ) ;
102- } ) , self . addEventListener ( "unhandledrejection" , event => {
103- event . preventDefault ( ) ;
104- } ) ;
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+ } ) ;
0 commit comments