// background.js console.log("Background service worker loaded"); let domainsDB = {}; let SAFE_BROWSING_API_KEY = ""; const API_CALL_LIMIT = 100; // Maximum number of API calls per minute let apiCallCount = 0; let lastResetTime = Date.now(); // Error reporting service const errorReporter = { errors: [], maxErrors: 100, log(error, context = {}) { const errorEntry = { timestamp: Date.now(), error: error.message || error, stack: error.stack, context }; this.errors.push(errorEntry); if (this.errors.length > this.maxErrors) { this.errors.shift(); } // Send to monitoring service if configured if (this.monitoringEndpoint) { this.sendToMonitoring(errorEntry); } console.error('Error logged:', errorEntry); }, async sendToMonitoring(errorEntry) { try { await fetch(this.monitoringEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(errorEntry) }); } catch (e) { console.error('Failed to send error to monitoring:', e); } }, getErrors() { return [...this.errors]; }, clearErrors() { this.errors = []; } }; // Performance monitoring const performanceMonitor = { metrics: { apiCalls: 0, processingTime: 0, errors: 0 }, startTimer() { return performance.now(); }, endTimer(startTime) { return performance.now() - startTime; }, logMetric(name, value) { this.metrics[name] = (this.metrics[name] || 0) + value; }, getMetrics() { return { ...this.metrics }; }, reset() { this.metrics = { apiCalls: 0, processingTime: 0, errors: 0 }; } }; // Health check system const healthCheck = { lastCheck: Date.now(), status: 'healthy', issues: [], async check() { const startTime = performanceMonitor.startTimer(); try { // Check storage access await chrome.storage.local.get(['domainsDB']); // Check API key if (!SAFE_BROWSING_API_KEY) { throw new Error('API key not configured'); } // Check domains database if (!domainsDB || Object.keys(domainsDB).length === 0) { throw new Error('Domains database is empty'); } this.status = 'healthy'; this.issues = []; } catch (error) { this.status = 'unhealthy'; this.issues.push({ timestamp: Date.now(), error: error.message }); errorReporter.log(error, { component: 'healthCheck' }); } performanceMonitor.logMetric('processingTime', performanceMonitor.endTimer(startTime)); this.lastCheck = Date.now(); }, getStatus() { return { status: this.status, lastCheck: this.lastCheck, issues: [...this.issues] }; } }; // Default configuration const DEFAULT_CONFIG = { apiKey: '', domainsDBURL: 'https://git.365devnet.eu/365DevNet/EnterpriseAppProtection/raw/branch/main/domains.json', updateInterval: 24, // hours warningTemplate: 'Warning: This link claims to be {app} but goes to an unofficial domain.', lastUpdate: null, suspiciousCount: 0, suspiciousLinks: [] }; // Function to get configuration async function getConfig() { return new Promise((resolve) => { chrome.storage.local.get([ "safeBrowsingApiKey", "domainsDBURL", "updateInterval", "warningTemplate" ], (result) => { resolve({ apiKey: result.safeBrowsingApiKey || DEFAULT_CONFIG.apiKey, domainsDBURL: result.domainsDBURL || DEFAULT_CONFIG.domainsDBURL, updateInterval: result.updateInterval || DEFAULT_CONFIG.updateInterval, warningTemplate: result.warningTemplate || DEFAULT_CONFIG.warningTemplate }); }); }); } // Function to update last update timestamp async function updateLastUpdate() { await chrome.storage.local.set({ lastUpdate: Date.now() }); } // Function to update domains database async function updateDomainsDB() { try { const config = await getConfig(); console.log("Updating domains database from:", config.domainsDBURL); const response = await fetch(config.domainsDBURL); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); await chrome.storage.local.set({ domainsDB: data }); await updateLastUpdate(); console.log('Domains database updated successfully'); return true; } catch (error) { console.error('Failed to update domains database:', error); return false; } } // Run updates when the extension is installed or started chrome.runtime.onInstalled.addListener(() => { console.log("Extension installed"); updateDomainsDB(); }); chrome.runtime.onStartup.addListener(() => { console.log("Service worker started"); updateDomainsDB(); }); // Periodic update based on UPDATE_INTERVAL (in hours) setInterval(() => { chrome.storage.local.get(["updateInterval"], function (result) { const updateInterval = result.updateInterval || 24; console.log("Periodic update triggered"); updateDomainsDB(); }); }, 3600000); // Listen for manual update messages (e.g., from the popup) chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { console.log("onMessage: Received message:", message); if (message.action === "updateDB") { console.log("onMessage: Manual update triggered via Update Now button"); updateDomainsDB().then(() => { console.log("onMessage: Manual update completed"); sendResponse({ status: "updated" }); }); return true; // Keep message channel open for async response } if (message.action === "updatePopup") { // Store flagged links and update the total suspicious count chrome.storage.local.get(["totalSuspiciousLinks"], (result) => { let totalCount = result.totalSuspiciousLinks || 0; totalCount += message.flaggedLinks.length; // Add new links found in this session chrome.storage.local.set({ flaggedLinks: message.flaggedLinks, totalSuspiciousLinks: totalCount }, () => { console.log(`Updated totalSuspiciousLinks: ${totalCount}`); }); }); } if (message.action === "getSuspiciousLinks") { // Retrieve both flagged links and total suspicious link count chrome.storage.local.get(["flaggedLinks", "totalSuspiciousLinks"], (data) => { sendResponse({ count: data.totalSuspiciousLinks || 0, links: data.flaggedLinks || [] }); }); return true; // Keep message channel open for async response } if (message.action === "checkSafeBrowsing") { checkSafeBrowsing(message.url) .then(result => sendResponse(result)) .catch(error => { console.error("Safe Browsing API error:", error); sendResponse({ isUnsafe: false }); }); return true; // Required for async response } if (message.action === "getHealthStatus") { sendResponse(healthCheck.getStatus()); return true; } if (message.action === "getMetrics") { sendResponse({ performance: performanceMonitor.getMetrics(), errors: errorReporter.getErrors() }); return true; } }); // Debug Mode: Keep-alive mechanism for testing const DEBUG_MODE = false; // Set to 'true' for testing if (DEBUG_MODE) { chrome.alarms.create("keepAlive", { delayInMinutes: 0.1, periodInMinutes: 0.1 }); chrome.alarms.onAlarm.addListener((alarm) => { if (alarm.name === "keepAlive") { console.log("keepAlive alarm triggered, service worker is active"); } }); } // Reset API call count every minute setInterval(() => { apiCallCount = 0; lastResetTime = Date.now(); }, 60000); // Check URL against Google Safe Browsing API async function checkSafeBrowsing(url) { const startTime = performanceMonitor.startTimer(); try { // Check rate limit if (apiCallCount >= API_CALL_LIMIT) { throw new Error('API call limit reached'); } // Get API key from storage if (!SAFE_BROWSING_API_KEY) { const result = await chrome.storage.local.get(["safeBrowsingApiKey"]); SAFE_BROWSING_API_KEY = result.safeBrowsingApiKey; if (!SAFE_BROWSING_API_KEY) { throw new Error('Safe Browsing API key not set'); } } apiCallCount++; performanceMonitor.logMetric('apiCalls', 1); const response = await fetch( `https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${SAFE_BROWSING_API_KEY}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client: { clientId: "enterprise-app-protection", clientVersion: "1.0" }, threatInfo: { threatTypes: ["MALWARE", "SOCIAL_ENGINEERING"], platformTypes: ["ANY_PLATFORM"], threatEntryTypes: ["URL"], threatEntries: [{ url }] } }) } ); if (!response.ok) { throw new Error(`API responded with status: ${response.status}`); } const data = await response.json(); performanceMonitor.logMetric('processingTime', performanceMonitor.endTimer(startTime)); return { isUnsafe: data.matches ? true : false }; } catch (error) { performanceMonitor.logMetric('errors', 1); errorReporter.log(error, { url, apiCallCount }); return { isUnsafe: false }; } } // Listen for storage changes to update API key chrome.storage.onChanged.addListener((changes, namespace) => { if (namespace === "local" && changes.safeBrowsingApiKey) { SAFE_BROWSING_API_KEY = changes.safeBrowsingApiKey.newValue; } }); // Add periodic health checks setInterval(() => { healthCheck.check(); }, 300000); // Every 5 minutes