Skip to content
Open
9 changes: 5 additions & 4 deletions public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
"version": "1.0.0",
"description": "A multipurpose browser extension for enhanced privacy, usability, and productivity",
"permissions": ["storage", "activeTab", "scripting"],
"host_permissions": ["<all_urls>"],
"host_permissions": ["http://*/*", "https://*/*"],
"action": {
"default_popup": "index.html",
"default_title": "Safe-Web"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"],
"css": ["content.css"],
"run_at": "document_end"
"run_at": "document_idle",
"all_frames": false
}
],
"background": {
Expand All @@ -23,7 +24,7 @@
"web_accessible_resources": [
{
"resources": ["assets/*"],
"matches": ["<all_urls>"]
"matches": ["http://*/*", "https://*/*"]
}
]
}
42 changes: 25 additions & 17 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback, useMemo } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useSettings } from "./hooks/useSettings.js";
import { ChromeAPI } from "./utils/chrome.js";
Expand Down Expand Up @@ -54,13 +54,13 @@ function App() {
isVisible: false,
});

const showToaster = (message, type = "info") => {
const showToaster = useCallback((message, type = "info") => {
setToaster({ message, type, isVisible: true });
};
}, []);

const hideToaster = () => {
const hideToaster = useCallback(() => {
setToaster((prev) => ({ ...prev, isVisible: false }));
};
}, []);

// Load current tab info and check permissions
useEffect(() => {
Expand All @@ -79,7 +79,7 @@ function App() {
loadTabInfo();
}, []);

const handleToggleMasking = async () => {
const handleToggleMasking = useCallback(async () => {
if (!hasPermission) {
showToaster("Cannot activate on this page", "warning");
return;
Expand Down Expand Up @@ -119,18 +119,26 @@ function App() {
} else {
showToaster("Failed to toggle protection", "error");
}
};
}, [hasPermission, settings, updateSettings, showToaster]);

const handleMaskingStyleChange = async (style) => {
const success = await setMaskingStyle(style);
if (success) {
showToaster(`Masking style changed to ${style}`, "success");
}
};
const handleMaskingStyleChange = useCallback(
async (style) => {
const success = await setMaskingStyle(style);
if (success) {
showToaster(`Masking style changed to ${style}`, "success");
}
},
[setMaskingStyle, showToaster]
);

const handleIntensityChange = async (intensity) => {
await setMaskingIntensity(intensity);
};
const handleIntensityChange = useCallback(
async (intensity) => {
await setMaskingIntensity(intensity);
},
[setMaskingIntensity]
);

const currentYear = useMemo(() => new Date().getFullYear(), []);

if (loading) {
return (
Expand Down Expand Up @@ -263,7 +271,7 @@ function App() {
>
<div className="text-center space-y-1">
<p className="text-xs text-[var(--text-muted)]">
© {new Date().getFullYear()} Safe-Web. All rights reserved.
© {currentYear} Safe-Web. All rights reserved.
</p>
<p className="text-xs text-[var(--text-muted)]">
Built with{" "}
Expand Down
118 changes: 80 additions & 38 deletions src/background/background.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Safe-Web Background Service Worker
// Safe-Web Background Service Worker - Optimized
// Handles extension lifecycle, storage, and communication with content scripts

class SafeWebBackground {
constructor() {
this.tabUpdateThrottle = new Map();
this.initializeExtension();
this.setupEventListeners();
}
Expand Down Expand Up @@ -48,7 +49,7 @@ class SafeWebBackground {
chrome.runtime.onMessage.addListener(this.handleMessage.bind(this));

// Tab update handling
chrome.tabs.onUpdated.addListener(this.handleTabUpdate.bind(this));
chrome.tabs.onUpdated.addListener(this.throttledTabUpdate.bind(this));

// Storage change handling
chrome.storage.onChanged.addListener(this.handleStorageChange.bind(this));
Expand Down Expand Up @@ -85,17 +86,17 @@ class SafeWebBackground {
break;

case "TOGGLE_MASKING":
await this.toggleMasking(sender.tab.id);
await this.toggleMasking(sender.tab?.id);
sendResponse({ success: true });
break;

case "GET_TAB_STATE":
const tabState = await this.getTabState(sender.tab.id);
const tabState = await this.getTabState(sender.tab?.id);
sendResponse({ success: true, data: tabState });
break;

case "UPDATE_TAB_STATE":
await this.updateTabState(sender.tab.id, data);
await this.updateTabState(sender.tab?.id, data);
sendResponse({ success: true });
break;

Expand All @@ -117,6 +118,20 @@ class SafeWebBackground {
return true;
}

throttledTabUpdate(tabId, changeInfo, tab) {

Copilot AI Jun 1, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If tabId is undefined, you end up storing and clearing a key of undefined in the throttle map. Add an early return guard when tabId is falsy to avoid unintended behavior.

Suggested change
throttledTabUpdate(tabId, changeInfo, tab) {
throttledTabUpdate(tabId, changeInfo, tab) {
if (!tabId) {
console.warn("Safe-Web: Received falsy tabId in throttledTabUpdate");
return;
}

Copilot uses AI. Check for mistakes.
if (this.tabUpdateThrottle.has(tabId)) {
clearTimeout(this.tabUpdateThrottle.get(tabId));
}

this.tabUpdateThrottle.set(
tabId,
setTimeout(() => {
this.handleTabUpdate(tabId, changeInfo, tab);
this.tabUpdateThrottle.delete(tabId);
}, 100)
);
}

async handleTabUpdate(tabId, changeInfo, tab) {
if (changeInfo.status === "complete" && tab.url) {
// Inject content script if needed
Expand Down Expand Up @@ -153,12 +168,12 @@ class SafeWebBackground {
async getSettings() {
try {
const result = await chrome.storage.sync.get("safeWebSettings");

// Return default settings if none exist
if (!result.safeWebSettings) {
const defaultSettings = {
maskingEnabled: false,
maskingStyle: 'blur',
maskingStyle: "blur",
maskingIntensity: 5,
sensitivePatterns: {
email: true,
Expand All @@ -167,20 +182,20 @@ class SafeWebBackground {
creditCard: true,
names: false,
addresses: false,
customPatterns: []
customPatterns: [],
},
shortcuts: {
toggleMasking: 'Ctrl+Shift+M'
toggleMasking: "Ctrl+Shift+M",
},
theme: 'dark',
animations: true
theme: "dark",
animations: true,
};

// Initialize with default settings
await chrome.storage.sync.set({ safeWebSettings: defaultSettings });
return defaultSettings;
}

return result.safeWebSettings;
} catch (error) {
console.error("Safe-Web: Failed to get settings:", error);
Expand All @@ -200,6 +215,8 @@ class SafeWebBackground {
}

async toggleMasking(tabId) {
if (!tabId) return;

const settings = await this.getSettings();
const newState = !settings.maskingEnabled;

Expand All @@ -217,16 +234,29 @@ class SafeWebBackground {
}

async getTabState(tabId) {
const result = await chrome.storage.local.get(`tab_${tabId}`);
return (
result[`tab_${tabId}`] || { maskingActive: false, detectedElements: [] }
);
if (!tabId) return { maskingActive: false, detectedElements: [] };

try {
const result = await chrome.storage.local.get(`tab_${tabId}`);
return (
result[`tab_${tabId}`] || { maskingActive: false, detectedElements: [] }
);
} catch (error) {
console.error("Safe-Web: Failed to get tab state:", error);
return { maskingActive: false, detectedElements: [] };
}
}

async updateTabState(tabId, state) {
const currentState = await this.getTabState(tabId);
const newState = { ...currentState, ...state };
await chrome.storage.local.set({ [`tab_${tabId}`]: newState });
if (!tabId) return;

try {
const currentState = await this.getTabState(tabId);
const newState = { ...currentState, ...state };
await chrome.storage.local.set({ [`tab_${tabId}`]: newState });
} catch (error) {
console.error("Safe-Web: Failed to update tab state:", error);
}
}

async ensureContentScript(tabId, url) {
Expand All @@ -250,6 +280,8 @@ class SafeWebBackground {
target: { tabId },
files: ["content.css"],
});

console.log("Safe-Web: Content script injected into tab", tabId);
} catch (injectionError) {
console.error(
"Safe-Web: Failed to inject content script:",
Expand All @@ -262,32 +294,42 @@ class SafeWebBackground {
async analyzeContent(content) {
// Basic content analysis for sensitive information
const patterns = {
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
phone: /(\+1\s?)?(\([0-9]{3}\)|[0-9]{3})[\s\-]?[0-9]{3}[\s\-]?[0-9]{4}/g,
ssn: /\b\d{3}-?\d{2}-?\d{4}\b/g,
creditCard: /\b\d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4}\b/g,
emails: (

Copilot AI Jun 1, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keys returned by analyzeContent (emails, phones, ssns, creditCards) don’t match the sensitivePatterns setting keys (email, phone, ssn, creditCard). Consider aligning these names or mapping them consistently.

Copilot uses AI. Check for mistakes.
content.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g) || []
).length,
phones: (
content.match(
/(\+1\s?)?(\([0-9]{3}\)|[0-9]{3})[\s\-]?[0-9]{3}[\s\-]?[0-9]{4}/g
) || []
).length,
ssns: (content.match(/\b\d{3}-?\d{2}-?\d{4}\b/g) || []).length,
creditCards: (
content.match(/\b\d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4}\b/g) || []
).length,
};

const detected = {};

for (const [type, pattern] of Object.entries(patterns)) {
const matches = content.match(pattern);
if (matches) {
detected[type] = matches.length;
}
}

return detected;
return {
totalMatches: Object.values(patterns).reduce(
(sum, count) => sum + count,
0
),
breakdown: patterns,
timestamp: Date.now(),
};
}

async showWelcome() {
await chrome.tabs.create({
url: chrome.runtime.getURL("welcome.html"),
});
try {
await chrome.tabs.create({
url: chrome.runtime.getURL("welcome.html"),
});
} catch (error) {
console.error("Safe-Web: Failed to show welcome page:", error);
}
}

async handleUpdate(previousVersion) {
console.log(`Safe-Web updated from ${previousVersion}`);
console.log(`Safe-Web updated from version ${previousVersion}`);
// Handle any migration logic here
}
}
Expand Down
Loading