Skip to content

Commit 8afaa5c

Browse files
authored
Merge pull request #54 from SentienceAPI/sync-extension-v2.0.1
Sync Extension: v2.0.1
2 parents 7f324e0 + cbd9ed7 commit 8afaa5c

File tree

6 files changed

+580
-128
lines changed

6 files changed

+580
-128
lines changed

sentience/extension/background.js

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -53,44 +53,60 @@ initWASM().catch(err => {
5353

5454
/**
5555
* Message handler for all extension communication
56+
* Includes global error handling to prevent extension crashes
5657
*/
5758
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
58-
// Handle screenshot requests (existing functionality)
59-
if (request.action === 'captureScreenshot') {
60-
handleScreenshotCapture(sender.tab.id, request.options)
61-
.then(screenshot => {
62-
sendResponse({ success: true, screenshot });
63-
})
64-
.catch(error => {
65-
console.error('[Sentience Background] Screenshot capture failed:', error);
66-
sendResponse({
67-
success: false,
68-
error: error.message || 'Screenshot capture failed'
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+
});
6973
});
70-
});
71-
return true; // Async response
72-
}
74+
return true; // Async response
75+
}
7376

74-
// Handle WASM processing requests (NEW!)
75-
if (request.action === 'processSnapshot') {
76-
handleSnapshotProcessing(request.rawData, request.options)
77-
.then(result => {
78-
sendResponse({ success: true, result });
79-
})
80-
.catch(error => {
81-
console.error('[Sentience Background] Snapshot processing failed:', error);
82-
sendResponse({
83-
success: false,
84-
error: error.message || 'Snapshot processing failed'
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+
});
8589
});
90+
return true; // Async response
91+
}
92+
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'}`
86104
});
87-
return true; // Async response
105+
} catch (e) {
106+
// If sendResponse already called, ignore
107+
}
108+
return false;
88109
}
89-
90-
// Unknown action
91-
console.warn('[Sentience Background] Unknown action:', request.action);
92-
sendResponse({ success: false, error: 'Unknown action' });
93-
return false;
94110
});
95111

96112
/**
@@ -119,13 +135,27 @@ async function handleScreenshotCapture(_tabId, options = {}) {
119135
/**
120136
* Handle snapshot processing with WASM (NEW!)
121137
* This is where the magic happens - completely CSP-immune!
138+
* Includes safeguards to prevent crashes and hangs.
122139
*
123140
* @param {Array} rawData - Raw element data from injected_api.js
124141
* @param {Object} options - Snapshot options (limit, filter, etc.)
125142
* @returns {Promise<Object>} Processed snapshot result
126143
*/
127144
async function handleSnapshotProcessing(rawData, options = {}) {
145+
const MAX_ELEMENTS = 10000; // Safety limit to prevent hangs
146+
const startTime = performance.now();
147+
128148
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+
129159
// Ensure WASM is initialized
130160
await initWASM();
131161
if (!wasmReady) {
@@ -135,15 +165,35 @@ async function handleSnapshotProcessing(rawData, options = {}) {
135165
console.log(`[Sentience Background] Processing ${rawData.length} elements with options:`, options);
136166

137167
// Run WASM processing using the imported functions directly
168+
// Wrap in try-catch with timeout protection
138169
let analyzedElements;
139170
try {
140-
if (options.limit || options.filter) {
141-
analyzedElements = analyze_page_with_options(rawData, options);
142-
} else {
143-
analyzedElements = analyze_page(rawData);
144-
}
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+
]);
145193
} catch (e) {
146-
throw new Error(`WASM analyze_page failed: ${e.message}`);
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}`);
147197
}
148198

149199
// Prune elements for API (prevents 413 errors on large sites)
@@ -155,16 +205,29 @@ async function handleSnapshotProcessing(rawData, options = {}) {
155205
prunedRawData = rawData;
156206
}
157207

158-
console.log(`[Sentience Background] ✓ Processed: ${analyzedElements.length} analyzed, ${prunedRawData.length} pruned`);
208+
const duration = performance.now() - startTime;
209+
console.log(`[Sentience Background] ✓ Processed: ${analyzedElements.length} analyzed, ${prunedRawData.length} pruned (${duration.toFixed(1)}ms)`);
159210

160211
return {
161212
elements: analyzedElements,
162213
raw_elements: prunedRawData
163214
};
164215
} catch (error) {
165-
console.error('[Sentience Background] Processing error:', error);
216+
const duration = performance.now() - startTime;
217+
console.error(`[Sentience Background] Processing error after ${duration.toFixed(1)}ms:`, error);
166218
throw error;
167219
}
168220
}
169221

170222
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+
});

sentience/extension/content.js

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,39 +45,88 @@ function handleScreenshotRequest(data) {
4545
/**
4646
* Handle snapshot processing requests (NEW!)
4747
* Sends raw DOM data to background worker for WASM processing
48+
* Includes timeout protection to prevent extension crashes
4849
*/
4950
function handleSnapshotRequest(data) {
5051
const startTime = performance.now();
52+
const TIMEOUT_MS = 20000; // 20 seconds (longer than injected_api timeout)
53+
let responded = false;
5154

52-
chrome.runtime.sendMessage(
53-
{
54-
action: 'processSnapshot',
55-
rawData: data.rawData,
56-
options: data.options
57-
},
58-
(response) => {
55+
// Timeout protection: if background doesn't respond, send error
56+
const timeoutId = setTimeout(() => {
57+
if (!responded) {
58+
responded = true;
5959
const duration = performance.now() - startTime;
60+
console.error(`[Sentience Bridge] ⚠️ WASM processing timeout after ${duration.toFixed(1)}ms`);
61+
window.postMessage({
62+
type: 'SENTIENCE_SNAPSHOT_RESULT',
63+
requestId: data.requestId,
64+
error: 'WASM processing timeout - background script may be unresponsive',
65+
duration: duration
66+
}, '*');
67+
}
68+
}, TIMEOUT_MS);
69+
70+
try {
71+
chrome.runtime.sendMessage(
72+
{
73+
action: 'processSnapshot',
74+
rawData: data.rawData,
75+
options: data.options
76+
},
77+
(response) => {
78+
if (responded) return; // Already responded via timeout
79+
responded = true;
80+
clearTimeout(timeoutId);
81+
82+
const duration = performance.now() - startTime;
6083

61-
if (response?.success) {
62-
console.log(`[Sentience Bridge] ✓ WASM processing complete in ${duration.toFixed(1)}ms`);
63-
window.postMessage({
64-
type: 'SENTIENCE_SNAPSHOT_RESULT',
65-
requestId: data.requestId,
66-
elements: response.result.elements,
67-
raw_elements: response.result.raw_elements,
68-
duration: duration
69-
}, '*');
70-
} else {
71-
console.error('[Sentience Bridge] WASM processing failed:', response?.error);
72-
window.postMessage({
73-
type: 'SENTIENCE_SNAPSHOT_RESULT',
74-
requestId: data.requestId,
75-
error: response?.error || 'Processing failed',
76-
duration: duration
77-
}, '*');
84+
// Handle Chrome extension errors (e.g., background script crashed)
85+
if (chrome.runtime.lastError) {
86+
console.error('[Sentience Bridge] Chrome runtime error:', chrome.runtime.lastError.message);
87+
window.postMessage({
88+
type: 'SENTIENCE_SNAPSHOT_RESULT',
89+
requestId: data.requestId,
90+
error: `Chrome runtime error: ${chrome.runtime.lastError.message}`,
91+
duration: duration
92+
}, '*');
93+
return;
94+
}
95+
96+
if (response?.success) {
97+
console.log(`[Sentience Bridge] ✓ WASM processing complete in ${duration.toFixed(1)}ms`);
98+
window.postMessage({
99+
type: 'SENTIENCE_SNAPSHOT_RESULT',
100+
requestId: data.requestId,
101+
elements: response.result.elements,
102+
raw_elements: response.result.raw_elements,
103+
duration: duration
104+
}, '*');
105+
} else {
106+
console.error('[Sentience Bridge] WASM processing failed:', response?.error);
107+
window.postMessage({
108+
type: 'SENTIENCE_SNAPSHOT_RESULT',
109+
requestId: data.requestId,
110+
error: response?.error || 'Processing failed',
111+
duration: duration
112+
}, '*');
113+
}
78114
}
115+
);
116+
} catch (error) {
117+
if (!responded) {
118+
responded = true;
119+
clearTimeout(timeoutId);
120+
const duration = performance.now() - startTime;
121+
console.error('[Sentience Bridge] Exception sending message:', error);
122+
window.postMessage({
123+
type: 'SENTIENCE_SNAPSHOT_RESULT',
124+
requestId: data.requestId,
125+
error: `Failed to send message: ${error.message}`,
126+
duration: duration
127+
}, '*');
79128
}
80-
);
129+
}
81130
}
82131

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

0 commit comments

Comments
 (0)