JavaScript · 10740 bytes Raw Blame History
1 // Chrome Service Worker for LooseCannon (Manifest V3)
2 console.log('[LooseCannon] Chrome Service Worker initialized');
3
4 // Import modules
5 import { UnifiedMessageHandler } from './unified-handler-v3.js';
6 import { ScreenshotManager } from './screenshot-manager.js';
7 import { PatternLearning } from './pattern-learning.js';
8
9 class LooseCannonServiceWorker {
10 constructor() {
11 this.messageHandler = new UnifiedMessageHandler();
12 this.screenshotManager = new ScreenshotManager();
13 this.patternLearning = new PatternLearning();
14 this.serverUrl = 'http://localhost:8765';
15 this.analytics = {
16 sessionsStarted: 0,
17 messagesProcessed: 0,
18 scammersDetected: 0,
19 screenshotsCaptured: 0
20 };
21 this.init();
22 }
23
24 init() {
25 this.setupListeners();
26 this.setupAlarms();
27 this.connectToServer();
28 this.loadAnalytics();
29 }
30
31 setupListeners() {
32 // Message listener for content scripts and popup
33 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
34 console.log('[ServiceWorker] Received message:', message.type);
35
36 // Handle async operations properly in Manifest V3
37 this.handleMessage(message, sender).then(sendResponse);
38 return true; // Keep channel open for async response
39 });
40
41 // Tab events for tracking
42 chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
43 if (changeInfo.status === 'complete') {
44 this.checkForSupportedSite(tab);
45 }
46 });
47
48 // Extension install/update events
49 chrome.runtime.onInstalled.addListener((details) => {
50 if (details.reason === 'install') {
51 this.onFirstInstall();
52 } else if (details.reason === 'update') {
53 this.onUpdate(details.previousVersion);
54 }
55 });
56
57 // Alarm listener for periodic tasks
58 chrome.alarms.onAlarm.addListener((alarm) => {
59 this.handleAlarm(alarm);
60 });
61 }
62
63 async handleMessage(message, sender) {
64 try {
65 switch (message.type) {
66 case 'NEW_MESSAGE':
67 return await this.handleNewMessage(message.data, sender);
68
69 case 'CAPTURE_SCREENSHOT':
70 return await this.captureScreenshot(sender.tab);
71
72 case 'GET_ANALYTICS':
73 return await this.getAnalytics();
74
75 case 'EXPORT_DATA':
76 return await this.exportData(message.data);
77
78 case 'LEARN_PATTERN':
79 return await this.learnPattern(message.data);
80
81 case 'GET_SERVER_STATUS':
82 return await this.checkServerStatus();
83
84 case 'TOGGLE_ACTIVE':
85 return await this.handleToggleActive(message.data, sender);
86
87 default:
88 return { error: 'Unknown message type' };
89 }
90 } catch (error) {
91 console.error('[ServiceWorker] Error handling message:', error);
92 return { error: error.message };
93 }
94 }
95
96 async handleNewMessage(data, sender) {
97 this.analytics.messagesProcessed++;
98
99 // Add Chrome-specific tab info
100 data.tabId = sender.tab.id;
101 data.url = sender.url;
102
103 // Process through unified handler
104 const response = await this.messageHandler.processMessage(data);
105
106 // If scammer detected, capture screenshot
107 if (response.scammerScore > 0.7) {
108 this.analytics.scammersDetected++;
109
110 // Capture evidence
111 const screenshot = await this.captureScreenshot(sender.tab);
112 if (screenshot.success) {
113 response.evidenceId = screenshot.id;
114 }
115
116 // Learn from pattern
117 await this.patternLearning.addPattern(data, response.scammerScore);
118 }
119
120 // Update analytics
121 await this.saveAnalytics();
122
123 return response;
124 }
125
126 async captureScreenshot(tab) {
127 if (!tab) return { success: false, error: 'No tab provided' };
128
129 try {
130 // Chrome-specific screenshot API
131 const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, {
132 format: 'png',
133 quality: 90
134 });
135
136 // Store screenshot
137 const screenshot = await this.screenshotManager.store({
138 dataUrl,
139 tabId: tab.id,
140 url: tab.url,
141 timestamp: new Date().toISOString()
142 });
143
144 this.analytics.screenshotsCaptured++;
145
146 return {
147 success: true,
148 id: screenshot.id,
149 size: screenshot.size
150 };
151 } catch (error) {
152 console.error('[ServiceWorker] Screenshot error:', error);
153 return { success: false, error: error.message };
154 }
155 }
156
157 async checkForSupportedSite(tab) {
158 const supportedSites = [
159 'web.whatsapp.com',
160 'web.telegram.org',
161 'messenger.com'
162 ];
163
164 const isSupported = supportedSites.some(site => tab.url?.includes(site));
165
166 if (isSupported) {
167 // Show page action or badge
168 chrome.action.setBadgeText({
169 text: 'LC',
170 tabId: tab.id
171 });
172
173 chrome.action.setBadgeBackgroundColor({
174 color: '#4CAF50',
175 tabId: tab.id
176 });
177 }
178 }
179
180 setupAlarms() {
181 // Set up periodic tasks
182 chrome.alarms.create('analyticsSync', { periodInMinutes: 5 });
183 chrome.alarms.create('patternSync', { periodInMinutes: 30 });
184 chrome.alarms.create('cleanup', { periodInMinutes: 60 });
185 }
186
187 async handleAlarm(alarm) {
188 switch (alarm.name) {
189 case 'analyticsSync':
190 await this.syncAnalytics();
191 break;
192
193 case 'patternSync':
194 await this.syncPatterns();
195 break;
196
197 case 'cleanup':
198 await this.cleanup();
199 break;
200 }
201 }
202
203 async syncAnalytics() {
204 try {
205 // Send analytics to server
206 const response = await fetch(`${this.serverUrl}/analytics/sync`, {
207 method: 'POST',
208 headers: { 'Content-Type': 'application/json' },
209 body: JSON.stringify({
210 analytics: this.analytics,
211 timestamp: new Date().toISOString()
212 })
213 });
214
215 if (response.ok) {
216 console.log('[ServiceWorker] Analytics synced');
217 }
218 } catch (error) {
219 console.error('[ServiceWorker] Analytics sync failed:', error);
220 }
221 }
222
223 async syncPatterns() {
224 try {
225 const patterns = await this.patternLearning.getPatterns();
226
227 // Send learned patterns to server
228 const response = await fetch(`${this.serverUrl}/patterns/sync`, {
229 method: 'POST',
230 headers: { 'Content-Type': 'application/json' },
231 body: JSON.stringify({ patterns })
232 });
233
234 if (response.ok) {
235 const serverPatterns = await response.json();
236 await this.patternLearning.updateFromServer(serverPatterns);
237 console.log('[ServiceWorker] Patterns synced');
238 }
239 } catch (error) {
240 console.error('[ServiceWorker] Pattern sync failed:', error);
241 }
242 }
243
244 async cleanup() {
245 // Clean up old screenshots
246 await this.screenshotManager.cleanup(24 * 60 * 60 * 1000); // 24 hours
247
248 // Clean up old conversation data
249 const storage = await chrome.storage.local.get();
250 const now = Date.now();
251 const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
252
253 for (const key in storage) {
254 if (key.startsWith('conversation_')) {
255 const data = storage[key];
256 if (data.timestamp && (now - new Date(data.timestamp).getTime() > maxAge)) {
257 await chrome.storage.local.remove(key);
258 }
259 }
260 }
261
262 console.log('[ServiceWorker] Cleanup completed');
263 }
264
265 async connectToServer() {
266 try {
267 const response = await fetch(`${this.serverUrl}/status`);
268 if (response.ok) {
269 console.log('[ServiceWorker] Server connected');
270 chrome.action.setTitle({ title: 'LooseCannon - Connected' });
271 } else {
272 throw new Error('Server not responding');
273 }
274 } catch (error) {
275 console.error('[ServiceWorker] Server connection failed:', error);
276 chrome.action.setTitle({ title: 'LooseCannon - Server Offline' });
277
278 // Retry in 30 seconds
279 setTimeout(() => this.connectToServer(), 30000);
280 }
281 }
282
283 async getAnalytics() {
284 const stored = await chrome.storage.local.get('analytics');
285 return {
286 ...this.analytics,
287 ...stored.analytics,
288 uptime: Date.now() - this.startTime
289 };
290 }
291
292 async saveAnalytics() {
293 await chrome.storage.local.set({ analytics: this.analytics });
294 }
295
296 async loadAnalytics() {
297 const stored = await chrome.storage.local.get('analytics');
298 if (stored.analytics) {
299 this.analytics = { ...this.analytics, ...stored.analytics };
300 }
301 this.startTime = Date.now();
302 }
303
304 async exportData(options = {}) {
305 const data = {
306 version: '0.3.0',
307 timestamp: new Date().toISOString(),
308 analytics: this.analytics,
309 patterns: await this.patternLearning.getPatterns(),
310 screenshots: await this.screenshotManager.getAll()
311 };
312
313 if (options.includeConversations) {
314 const storage = await chrome.storage.local.get();
315 data.conversations = Object.entries(storage)
316 .filter(([key]) => key.startsWith('conversation_'))
317 .map(([key, value]) => value);
318 }
319
320 // Create download
321 const blob = new Blob([JSON.stringify(data, null, 2)], {
322 type: 'application/json'
323 });
324
325 const url = URL.createObjectURL(blob);
326 const filename = `loosecannon-export-${Date.now()}.json`;
327
328 await chrome.downloads.download({
329 url,
330 filename,
331 saveAs: true
332 });
333
334 return { success: true, filename };
335 }
336
337 async handleToggleActive(data, sender) {
338 this.analytics.sessionsStarted++;
339 await this.saveAnalytics();
340
341 // Notify dashboard if open
342 chrome.runtime.sendMessage({
343 type: 'DASHBOARD_UPDATE',
344 data: {
345 event: 'session_toggle',
346 active: data.isActive,
347 platform: data.platform,
348 tabId: sender.tab?.id
349 }
350 }).catch(() => {
351 // Dashboard might not be open
352 });
353
354 return { success: true };
355 }
356
357 async checkServerStatus() {
358 try {
359 const response = await fetch(`${this.serverUrl}/status`);
360 const data = await response.json();
361 return { connected: true, ...data };
362 } catch (error) {
363 return { connected: false, error: error.message };
364 }
365 }
366
367 onFirstInstall() {
368 // Open welcome page
369 chrome.tabs.create({
370 url: chrome.runtime.getURL('dashboard/welcome.html')
371 });
372 }
373
374 onUpdate(previousVersion) {
375 console.log(`[ServiceWorker] Updated from ${previousVersion} to 0.3.0`);
376
377 // Show update notification
378 chrome.notifications.create({
379 type: 'basic',
380 iconUrl: 'icons/icon-128.png',
381 title: 'LooseCannon Updated',
382 message: 'New features: Chrome support, Analytics Dashboard, and more!'
383 });
384 }
385 }
386
387 // Initialize service worker
388 const serviceWorker = new LooseCannonServiceWorker();
389
390 // Export for testing
391 if (typeof module !== 'undefined' && module.exports) {
392 module.exports = LooseCannonServiceWorker;
393 }