mirror of
https://github.com/fmhy/FMHY-SafeGuard.git
synced 2026-03-11 08:55:40 +00:00
Add files via upload
This commit is contained in:
parent
37dba338af
commit
8171528bfc
5 changed files with 1876 additions and 959 deletions
1336
src/js/background.js
1336
src/js/background.js
File diff suppressed because it is too large
Load diff
388
src/js/content.js
Normal file
388
src/js/content.js
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
// FMHY SafeLink Guard - Content Script
|
||||
// Implements visual marking of safe/unsafe links similar to the userscript
|
||||
|
||||
"use strict";
|
||||
|
||||
// Cross-browser compatibility shim
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
|
||||
// Track processed elements to avoid reprocessing
|
||||
const processedLinks = new WeakSet();
|
||||
const processedDomains = new Set();
|
||||
const highlightCountTrusted = new Map();
|
||||
const highlightCountUntrusted = new Map();
|
||||
|
||||
// Default settings
|
||||
let settings = {
|
||||
highlightTrusted: true,
|
||||
highlightUntrusted: true,
|
||||
showWarningBanners: true,
|
||||
trustedColor: "#32cd32",
|
||||
untrustedColor: "#ff4444",
|
||||
};
|
||||
|
||||
// Domain lists
|
||||
let unsafeDomains = new Set();
|
||||
let safeDomains = new Set();
|
||||
let userTrusted = new Set();
|
||||
let userUntrusted = new Set();
|
||||
|
||||
// Search engines where highlighting should be applied
|
||||
const searchEngines = [
|
||||
"google.com",
|
||||
"google.", // Covers all Google country domains like google.co.uk, google.fr, etc.
|
||||
"bing.com",
|
||||
"duckduckgo.com",
|
||||
"librey.org",
|
||||
"4get.ca",
|
||||
"mojeek.com",
|
||||
"qwant.com",
|
||||
"swisscows.com",
|
||||
"yacy.net",
|
||||
"startpage.com",
|
||||
"search.brave.com",
|
||||
"ekoru.org",
|
||||
"gibiru.com",
|
||||
"searx.org",
|
||||
"searx.", // Covers all SearX instances
|
||||
"searxng.", // Covers all SearXNG instances
|
||||
"whoogle.", // Covers all Whoogle instances
|
||||
"metager.org",
|
||||
"ecosia.org",
|
||||
"yandex.com",
|
||||
"yandex.", // Covers all Yandex country domains
|
||||
"yahoo.com",
|
||||
"yahoo.", // Covers all Yahoo country domains
|
||||
"baidu.com",
|
||||
"naver.com",
|
||||
"seznam.cz",
|
||||
];
|
||||
|
||||
// FMHY domains to exclude
|
||||
const fmhyDomains = [
|
||||
"fmhy.net",
|
||||
"fmhy.pages.dev",
|
||||
"fmhy.lol",
|
||||
"fmhy.vercel.app",
|
||||
"fmhy.xyz",
|
||||
];
|
||||
|
||||
// CSS for warning banners
|
||||
const warningStyle = `
|
||||
background-color: #ff0000;
|
||||
color: #fff;
|
||||
padding: 2px 6px;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
margin-left: 6px;
|
||||
z-index: 9999;
|
||||
`;
|
||||
|
||||
// Main initialization
|
||||
function init() {
|
||||
loadSettings()
|
||||
.then(() => loadDomainLists())
|
||||
.then(() => {
|
||||
processPage();
|
||||
setupObserver();
|
||||
})
|
||||
.catch((err) =>
|
||||
console.error("[FMHY SafeGuard] Error initializing content script:", err)
|
||||
);
|
||||
}
|
||||
|
||||
// Check if current site is a search engine where we should apply highlighting
|
||||
function isSupportedSite(domain) {
|
||||
// Don't highlight on FMHY sites
|
||||
if (fmhyDomains.some((fmhyDomain) => domain.endsWith(fmhyDomain))) {
|
||||
console.log(
|
||||
`[FMHY SafeGuard] Skipping highlighting on FMHY domain: ${domain}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only highlight on search engines
|
||||
return searchEngines.some((searchDomain) => domain.includes(searchDomain));
|
||||
}
|
||||
|
||||
// Load user settings from storage
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const data = await browserAPI.storage.local.get([
|
||||
"highlightTrusted",
|
||||
"highlightUntrusted",
|
||||
"showWarningBanners",
|
||||
"trustedColor",
|
||||
"untrustedColor",
|
||||
"userTrustedDomains",
|
||||
"userUntrustedDomains",
|
||||
]);
|
||||
|
||||
// Apply stored settings or use defaults
|
||||
settings.highlightTrusted =
|
||||
data.highlightTrusted !== undefined
|
||||
? data.highlightTrusted
|
||||
: settings.highlightTrusted;
|
||||
settings.highlightUntrusted =
|
||||
data.highlightUntrusted !== undefined
|
||||
? data.highlightUntrusted
|
||||
: settings.highlightUntrusted;
|
||||
settings.showWarningBanners =
|
||||
data.showWarningBanners !== undefined
|
||||
? data.showWarningBanners
|
||||
: settings.showWarningBanners;
|
||||
|
||||
if (data.trustedColor) settings.trustedColor = data.trustedColor;
|
||||
if (data.untrustedColor) settings.untrustedColor = data.untrustedColor;
|
||||
|
||||
// Load user trusted/untrusted domains
|
||||
if (data.userTrustedDomains) {
|
||||
userTrusted = new Set(data.userTrustedDomains);
|
||||
}
|
||||
|
||||
if (data.userUntrustedDomains) {
|
||||
userUntrusted = new Set(data.userUntrustedDomains);
|
||||
}
|
||||
|
||||
console.log("[FMHY SafeGuard] Settings loaded:", settings);
|
||||
} catch (error) {
|
||||
console.error("[FMHY SafeGuard] Error loading settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load domain lists from extension storage
|
||||
async function loadDomainLists() {
|
||||
try {
|
||||
const { unsafeSites, safeSiteList } = await browserAPI.storage.local.get([
|
||||
"unsafeSites",
|
||||
"safeSiteList",
|
||||
]);
|
||||
|
||||
if (unsafeSites && Array.isArray(unsafeSites)) {
|
||||
unsafeDomains = new Set(
|
||||
unsafeSites.map((site) => normalizeDomain(new URL(site).hostname))
|
||||
);
|
||||
}
|
||||
|
||||
if (safeSiteList && Array.isArray(safeSiteList)) {
|
||||
safeDomains = new Set(
|
||||
safeSiteList.map((site) => normalizeDomain(new URL(site).hostname))
|
||||
);
|
||||
}
|
||||
|
||||
// Apply user overrides
|
||||
applyUserOverrides();
|
||||
|
||||
console.log(
|
||||
`[FMHY SafeGuard] Loaded ${unsafeDomains.size} unsafe domains and ${safeDomains.size} safe domains`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("[FMHY SafeGuard] Error loading domain lists:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply user trusted/untrusted overrides
|
||||
function applyUserOverrides() {
|
||||
userTrusted.forEach((domain) => {
|
||||
safeDomains.add(domain);
|
||||
unsafeDomains.delete(domain);
|
||||
});
|
||||
|
||||
userUntrusted.forEach((domain) => {
|
||||
unsafeDomains.add(domain);
|
||||
safeDomains.delete(domain);
|
||||
});
|
||||
}
|
||||
|
||||
// Process all links in the page
|
||||
function processPage() {
|
||||
const currentDomain = normalizeDomain(window.location.hostname);
|
||||
|
||||
// Only process links on search engines and not on FMHY sites
|
||||
if (!isSupportedSite(currentDomain)) {
|
||||
console.log(
|
||||
`[FMHY SafeGuard] Skipping highlighting on non-search engine: ${currentDomain}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[FMHY SafeGuard] Processing links on search engine: ${currentDomain}`
|
||||
);
|
||||
document
|
||||
.querySelectorAll("a[href]")
|
||||
.forEach((link) => processLink(link, currentDomain));
|
||||
}
|
||||
|
||||
// Set up mutation observer to handle dynamically added content
|
||||
function setupObserver() {
|
||||
const currentDomain = normalizeDomain(window.location.hostname);
|
||||
|
||||
// Skip setting up observer if not on a supported site
|
||||
if (!isSupportedSite(currentDomain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const { addedNodes } of mutations) {
|
||||
for (const node of addedNodes) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
// If it's a link itself
|
||||
if (node.tagName === "A" && node.href) {
|
||||
processLink(node, currentDomain);
|
||||
}
|
||||
|
||||
// Process any links inside the added node
|
||||
node
|
||||
.querySelectorAll("a[href]")
|
||||
.forEach((link) => processLink(link, currentDomain));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
// Process a single link
|
||||
function processLink(link, currentDomain) {
|
||||
// Skip if already processed
|
||||
if (processedLinks.has(link)) return;
|
||||
processedLinks.add(link);
|
||||
|
||||
try {
|
||||
// Skip links without proper URLs
|
||||
if (
|
||||
!link.href ||
|
||||
link.href.startsWith("javascript:") ||
|
||||
link.href.startsWith("#")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linkDomain = normalizeDomain(new URL(link.href).hostname);
|
||||
|
||||
// Skip if the current site is safe AND the link is internal
|
||||
if (
|
||||
(safeDomains.has(currentDomain) || userTrusted.has(currentDomain)) &&
|
||||
linkDomain === currentDomain
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle untrusted links
|
||||
if (
|
||||
userUntrusted.has(linkDomain) ||
|
||||
(!userTrusted.has(linkDomain) && unsafeDomains.has(linkDomain))
|
||||
) {
|
||||
if (
|
||||
settings.highlightUntrusted &&
|
||||
getHighlightCount(highlightCountUntrusted, linkDomain) < 2
|
||||
) {
|
||||
highlightLink(link, "untrusted");
|
||||
incrementHighlightCount(highlightCountUntrusted, linkDomain);
|
||||
}
|
||||
|
||||
if (settings.showWarningBanners && !processedDomains.has(linkDomain)) {
|
||||
addWarningBanner(link);
|
||||
processedDomains.add(linkDomain);
|
||||
}
|
||||
}
|
||||
// Handle trusted links
|
||||
else if (userTrusted.has(linkDomain) || safeDomains.has(linkDomain)) {
|
||||
if (
|
||||
settings.highlightTrusted &&
|
||||
getHighlightCount(highlightCountTrusted, linkDomain) < 2
|
||||
) {
|
||||
highlightLink(link, "trusted");
|
||||
incrementHighlightCount(highlightCountTrusted, linkDomain);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[FMHY SafeGuard] Error processing link:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight a link based on its trustworthiness
|
||||
function highlightLink(link, type) {
|
||||
const color =
|
||||
type === "trusted" ? settings.trustedColor : settings.untrustedColor;
|
||||
link.style.textShadow = `0 0 4px ${color}`;
|
||||
link.style.fontWeight = "bold";
|
||||
}
|
||||
|
||||
// Add a warning banner after an unsafe link
|
||||
function addWarningBanner(link) {
|
||||
const warning = document.createElement("span");
|
||||
warning.textContent = "⚠️ FMHY Unsafe Site";
|
||||
warning.style = warningStyle;
|
||||
link.after(warning);
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function normalizeDomain(hostname) {
|
||||
return hostname.replace(/^www\./, "").toLowerCase();
|
||||
}
|
||||
|
||||
function getHighlightCount(map, domain) {
|
||||
return map.get(domain) || 0;
|
||||
}
|
||||
|
||||
function incrementHighlightCount(map, domain) {
|
||||
if (map.size > 1000) map.clear(); // Reset if too large
|
||||
map.set(domain, getHighlightCount(map, domain) + 1);
|
||||
}
|
||||
|
||||
// Listen for settings changes
|
||||
browserAPI.storage.onChanged.addListener((changes, area) => {
|
||||
if (area === "local") {
|
||||
let settingsChanged = false;
|
||||
|
||||
for (let key in changes) {
|
||||
if (
|
||||
key === "highlightTrusted" ||
|
||||
key === "highlightUntrusted" ||
|
||||
key === "showWarningBanners" ||
|
||||
key === "trustedColor" ||
|
||||
key === "untrustedColor" ||
|
||||
key === "userTrustedDomains" ||
|
||||
key === "userUntrustedDomains"
|
||||
) {
|
||||
settingsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsChanged) {
|
||||
refreshPage();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle messages from background script
|
||||
browserAPI.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.action === "refreshSettings") {
|
||||
refreshPage();
|
||||
sendResponse({ status: "success" });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Function to refresh page highlighting
|
||||
function refreshPage() {
|
||||
// Reload settings and reprocess the page
|
||||
loadSettings()
|
||||
.then(() => loadDomainLists())
|
||||
.then(() => {
|
||||
// Clear processed state and reprocess
|
||||
processedLinks.clear();
|
||||
processedDomains.clear();
|
||||
highlightCountTrusted.clear();
|
||||
highlightCountUntrusted.clear();
|
||||
processPage();
|
||||
});
|
||||
}
|
||||
|
||||
// Start the script
|
||||
init();
|
||||
304
src/pub/index.js
304
src/pub/index.js
|
|
@ -1,152 +1,152 @@
|
|||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
console.log("Popup loaded, preparing to check site status...");
|
||||
|
||||
const statusIcon = document.getElementById("status-icon");
|
||||
const statusMessage = document.getElementById("status-message");
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
|
||||
const warningPageUrl = browserAPI.runtime.getURL("pub/warning-page.html");
|
||||
const settingsPageUrl = browserAPI.runtime.getURL("pub/settings-page.html");
|
||||
const welcomePageUrl = browserAPI.runtime.getURL("pub/welcome-page.html");
|
||||
|
||||
async function applyTheme() {
|
||||
try {
|
||||
const { theme } = await browserAPI.storage.sync.get("theme");
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
document.body.setAttribute(
|
||||
"data-theme",
|
||||
theme || (prefersDark ? "dark" : "light")
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error applying theme:", error);
|
||||
}
|
||||
}
|
||||
await applyTheme();
|
||||
|
||||
try {
|
||||
const [activeTab] = await browserAPI.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
if (!activeTab || !activeTab.url) {
|
||||
throw new Error("No active tab found or URL is unavailable.");
|
||||
}
|
||||
|
||||
const currentUrl = activeTab.url;
|
||||
const rootUrl = extractRootUrl(currentUrl);
|
||||
|
||||
if (
|
||||
currentUrl.startsWith(warningPageUrl) ||
|
||||
currentUrl === settingsPageUrl ||
|
||||
currentUrl === welcomePageUrl
|
||||
) {
|
||||
handleStatusUpdate("extension_page", currentUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send both the full URL and root URL to the background for status checking
|
||||
const response = await browserAPI.runtime.sendMessage({
|
||||
action: "checkSiteStatus",
|
||||
url: currentUrl, // full path URL
|
||||
rootUrl: rootUrl, // root domain URL
|
||||
});
|
||||
|
||||
if (!response || !response.status) {
|
||||
throw new Error(
|
||||
"Failed to retrieve site status from the background script."
|
||||
);
|
||||
}
|
||||
|
||||
// Display the appropriate URL in the popup
|
||||
const displayUrl = response.matchedUrl || rootUrl;
|
||||
handleStatusUpdate(response.status, displayUrl);
|
||||
} catch (error) {
|
||||
console.error("Error while checking site status:", error);
|
||||
errorMessage.textContent = `Error: ${error.message}`;
|
||||
updateUI("error", "An error occurred while retrieving the site status.");
|
||||
}
|
||||
|
||||
function handleStatusUpdate(status, displayUrl) {
|
||||
let message;
|
||||
|
||||
switch (status) {
|
||||
case "unsafe":
|
||||
message = `${displayUrl} is flagged as <strong>unsafe</strong>. Be cautious when interacting with this site.`;
|
||||
break;
|
||||
case "potentially_unsafe":
|
||||
message = `${displayUrl} is <strong>potentially unsafe</strong>. Proceed with caution.`;
|
||||
break;
|
||||
case "fmhy":
|
||||
message = `${displayUrl} is an <strong>FMHY</strong> related site. Proceed confidently.`;
|
||||
break;
|
||||
case "safe":
|
||||
message = `${displayUrl} is <strong>safe</strong> to browse.`;
|
||||
break;
|
||||
case "starred":
|
||||
message = `${displayUrl} is a <strong>starred</strong> site.`;
|
||||
break;
|
||||
case "extension_page":
|
||||
if (displayUrl.startsWith(warningPageUrl)) {
|
||||
message =
|
||||
"You are on the <strong>Warning Page</strong>. This page warns you about potentially unsafe sites.";
|
||||
} else if (displayUrl === settingsPageUrl) {
|
||||
message =
|
||||
"This is the <strong>Settings Page</strong> of the extension. Customize your preferences here.";
|
||||
} else if (displayUrl === welcomePageUrl) {
|
||||
message =
|
||||
"Welcome to <strong>FMHY SafeGuard</strong>! Explore the extension's features and get started.";
|
||||
} else {
|
||||
message = "This is an <strong>extension page</strong>.";
|
||||
}
|
||||
break;
|
||||
case "no_data":
|
||||
message = `No data available for <strong>${displayUrl}</strong>.`;
|
||||
break;
|
||||
default:
|
||||
message = `An unknown status was received for <strong>${displayUrl}</strong>.`;
|
||||
}
|
||||
|
||||
updateUI(status, message);
|
||||
}
|
||||
|
||||
function updateUI(status, message) {
|
||||
const icons = {
|
||||
unsafe: "../res/icons/unsafe.png",
|
||||
potentially_unsafe: "../res/icons/potentially_unsafe.png",
|
||||
fmhy: "../res/icons/fmhy.png",
|
||||
safe: "../res/icons/safe.png",
|
||||
starred: "../res/icons/starred.png",
|
||||
extension_page: "../res/ext_icon_144.png",
|
||||
no_data: "../res/ext_icon_144.png",
|
||||
error: "../res/icons/error.png",
|
||||
unknown: "../res/ext_icon_144.png",
|
||||
};
|
||||
|
||||
statusIcon.src = icons[status] || icons["unknown"];
|
||||
statusMessage.innerHTML = message || "An unknown error occurred.";
|
||||
|
||||
statusIcon.classList.add("active");
|
||||
setTimeout(() => statusIcon.classList.remove("active"), 300);
|
||||
|
||||
console.log(`UI updated: ${message}`);
|
||||
}
|
||||
|
||||
document.getElementById("settingsButton").addEventListener("click", () => {
|
||||
browserAPI.runtime.openOptionsPage();
|
||||
});
|
||||
|
||||
function extractRootUrl(url) {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return `${urlObj.protocol}//${urlObj.hostname}`;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to extract root URL from: ${url}`);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
console.log("Popup loaded, preparing to check site status...");
|
||||
|
||||
const statusIcon = document.getElementById("status-icon");
|
||||
const statusMessage = document.getElementById("status-message");
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
|
||||
const warningPageUrl = browserAPI.runtime.getURL("pub/warning-page.html");
|
||||
const settingsPageUrl = browserAPI.runtime.getURL("pub/settings-page.html");
|
||||
const welcomePageUrl = browserAPI.runtime.getURL("pub/welcome-page.html");
|
||||
|
||||
async function applyTheme() {
|
||||
try {
|
||||
const { theme } = await browserAPI.storage.sync.get("theme");
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
document.body.setAttribute(
|
||||
"data-theme",
|
||||
theme || (prefersDark ? "dark" : "light")
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error applying theme:", error);
|
||||
}
|
||||
}
|
||||
await applyTheme();
|
||||
|
||||
try {
|
||||
const [activeTab] = await browserAPI.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
if (!activeTab || !activeTab.url) {
|
||||
throw new Error("No active tab found or URL is unavailable.");
|
||||
}
|
||||
|
||||
const currentUrl = activeTab.url;
|
||||
const rootUrl = extractRootUrl(currentUrl);
|
||||
|
||||
if (
|
||||
currentUrl.startsWith(warningPageUrl) ||
|
||||
currentUrl === settingsPageUrl ||
|
||||
currentUrl === welcomePageUrl
|
||||
) {
|
||||
handleStatusUpdate("extension_page", currentUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send both the full URL and root URL to the background for status checking
|
||||
const response = await browserAPI.runtime.sendMessage({
|
||||
action: "checkSiteStatus",
|
||||
url: currentUrl, // full path URL
|
||||
rootUrl: rootUrl, // root domain URL
|
||||
});
|
||||
|
||||
if (!response || !response.status) {
|
||||
throw new Error(
|
||||
"Failed to retrieve site status from the background script."
|
||||
);
|
||||
}
|
||||
|
||||
// Display the appropriate URL in the popup
|
||||
const displayUrl = response.matchedUrl || rootUrl;
|
||||
handleStatusUpdate(response.status, displayUrl);
|
||||
} catch (error) {
|
||||
console.error("Error while checking site status:", error);
|
||||
errorMessage.textContent = `Error: ${error.message}`;
|
||||
updateUI("error", "An error occurred while retrieving the site status.");
|
||||
}
|
||||
|
||||
function handleStatusUpdate(status, displayUrl) {
|
||||
let message;
|
||||
|
||||
switch (status) {
|
||||
case "unsafe":
|
||||
message = `${displayUrl} is flagged as <strong>unsafe</strong>. Be cautious when interacting with this site.`;
|
||||
break;
|
||||
case "potentially_unsafe":
|
||||
message = `${displayUrl} is <strong>potentially unsafe</strong>. Proceed with caution.`;
|
||||
break;
|
||||
case "fmhy":
|
||||
message = `${displayUrl} is an <strong>FMHY</strong> related site. Proceed confidently.`;
|
||||
break;
|
||||
case "safe":
|
||||
message = `${displayUrl} is <strong>safe</strong> to browse.`;
|
||||
break;
|
||||
case "starred":
|
||||
message = `${displayUrl} is a <strong>starred</strong> site.`;
|
||||
break;
|
||||
case "extension_page":
|
||||
if (displayUrl.startsWith(warningPageUrl)) {
|
||||
message =
|
||||
"You are on the <strong>Warning Page</strong>. This page warns you about potentially unsafe sites.";
|
||||
} else if (displayUrl === settingsPageUrl) {
|
||||
message =
|
||||
"This is the <strong>Settings Page</strong> of the extension. Customize your preferences here.";
|
||||
} else if (displayUrl === welcomePageUrl) {
|
||||
message =
|
||||
"Welcome to <strong>FMHY SafeGuard</strong>! Explore the extension's features and get started.";
|
||||
} else {
|
||||
message = "This is an <strong>extension page</strong>.";
|
||||
}
|
||||
break;
|
||||
case "no_data":
|
||||
message = `No data available for <strong>${displayUrl}</strong>.`;
|
||||
break;
|
||||
default:
|
||||
message = `An unknown status was received for <strong>${displayUrl}</strong>.`;
|
||||
}
|
||||
|
||||
updateUI(status, message);
|
||||
}
|
||||
|
||||
function updateUI(status, message) {
|
||||
const icons = {
|
||||
unsafe: "../res/icons/unsafe.png",
|
||||
potentially_unsafe: "../res/icons/potentially_unsafe.png",
|
||||
fmhy: "../res/icons/fmhy.png",
|
||||
safe: "../res/icons/safe.png",
|
||||
starred: "../res/icons/starred.png",
|
||||
extension_page: "../res/ext_icon_144.png",
|
||||
no_data: "../res/ext_icon_144.png",
|
||||
error: "../res/icons/error.png",
|
||||
unknown: "../res/ext_icon_144.png",
|
||||
};
|
||||
|
||||
statusIcon.src = icons[status] || icons["unknown"];
|
||||
statusMessage.innerHTML = message || "An unknown error occurred.";
|
||||
|
||||
statusIcon.classList.add("active");
|
||||
setTimeout(() => statusIcon.classList.remove("active"), 300);
|
||||
|
||||
console.log(`UI updated: ${message}`);
|
||||
}
|
||||
|
||||
document.getElementById("settingsButton").addEventListener("click", () => {
|
||||
browserAPI.runtime.openOptionsPage();
|
||||
});
|
||||
|
||||
function extractRootUrl(url) {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return `${urlObj.protocol}//${urlObj.hostname}`;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to extract root URL from: ${url}`);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -520,6 +520,9 @@
|
|||
</div>
|
||||
|
||||
<div class="update-status" id="updateStatus">
|
||||
<svg class="update-icon" viewBox="0 0 24 24" width="16" height="16">
|
||||
<path fill="currentColor" d="M12 4V2C6.477 2 2 6.477 2 12C2 17.523 6.477 22 12 22C17.523 22 22 17.523 22 12H20C20 16.418 16.418 20 12 20C7.582 20 4 16.418 4 12C4 7.582 7.582 4 12 4Z"/>
|
||||
</svg>
|
||||
Next update scheduled for: Checking...
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -559,6 +562,68 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Link Highlighting Settings -->
|
||||
<div class="settings-section">
|
||||
<h2 class="section-title">Link Highlighting</h2>
|
||||
<p class="section-description">
|
||||
Visually highlight safe and unsafe links in web pages
|
||||
</p>
|
||||
|
||||
<div class="settings-grid" style="margin-top: 10px;">
|
||||
<div>
|
||||
<p class="section-description" style="margin: 0;">
|
||||
Highlight safe links (green)
|
||||
</p>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="highlightTrustedToggle" checked />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-grid" style="margin-top: 15px;">
|
||||
<div>
|
||||
<p class="section-description" style="margin: 0;">
|
||||
Highlight unsafe links (red)
|
||||
</p>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="highlightUntrustedToggle" checked />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-grid" style="margin-top: 15px;">
|
||||
<div>
|
||||
<p class="section-description" style="margin: 0;">
|
||||
Show warning banners next to unsafe links
|
||||
</p>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="showWarningBannersToggle" checked />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-grid" style="margin-top: 15px;">
|
||||
<div>
|
||||
<p class="section-description" style="margin: 0;">
|
||||
Safe link color
|
||||
</p>
|
||||
</div>
|
||||
<input type="color" id="trustedColor" value="#32cd32" style="width: 50px; height: 30px; border: none; border-radius: 4px;">
|
||||
</div>
|
||||
|
||||
<div class="settings-grid" style="margin-top: 15px;">
|
||||
<div>
|
||||
<p class="section-description" style="margin: 0;">
|
||||
Unsafe link color
|
||||
</p>
|
||||
</div>
|
||||
<input type="color" id="untrustedColor" value="#ff4444" style="width: 50px; height: 30px; border: none; border-radius: 4px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto-Update Settings -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-grid">
|
||||
|
|
@ -577,13 +642,48 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Domain Management -->
|
||||
<div class="settings-section">
|
||||
<h2 class="section-title">Domain Management</h2>
|
||||
<p class="section-description">
|
||||
Manually add domains to safe or unsafe lists
|
||||
</p>
|
||||
|
||||
<div style="display: flex; gap: 20px; margin-top: 15px;">
|
||||
<div style="flex: 1;">
|
||||
<p class="section-description" style="margin-bottom: 5px; font-weight: bold;">
|
||||
Safe Domains (one per line)
|
||||
</p>
|
||||
<textarea id="trustedDomains" rows="6" style="width: 100%; background: rgba(255, 255, 255, 0.05); color: var(--text-primary); border: 1px solid var(--section-border); border-radius: 8px; padding: 8px; font-family: monospace; font-size: 14px;"></textarea>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1;">
|
||||
<p class="section-description" style="margin-bottom: 5px; font-weight: bold;">
|
||||
Unsafe Domains (one per line)
|
||||
</p>
|
||||
<textarea id="untrustedDomains" rows="6" style="width: 100%; background: rgba(255, 255, 255, 0.05); color: var(--text-primary); border: 1px solid var(--section-border); border-radius: 8px; padding: 8px; font-family: monospace; font-size: 14px;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div style="text-align: center">
|
||||
<button class="btn" id="saveSettings">Save Settings</button>
|
||||
<!-- Save Button Section -->
|
||||
<div style="text-align: center; margin-top: 20px;">
|
||||
<button
|
||||
id="saveSettings"
|
||||
class="btn"
|
||||
>
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="notification"
|
||||
class="notification"
|
||||
style="display: none"
|
||||
></div>
|
||||
|
||||
<div class="footer">
|
||||
<p>FMHY SafeGuard <span id="versionNumber"></p>
|
||||
<p>
|
||||
|
|
@ -595,11 +695,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Toast -->
|
||||
<div class="notification" id="notification">
|
||||
Settings saved successfully!
|
||||
</div>
|
||||
|
||||
<script src="settings-page.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,273 +1,423 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Cross-browser compatibility shim
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
// Load and display the extension version from manifest.json
|
||||
const manifest = browserAPI.runtime.getManifest();
|
||||
document.getElementById("versionNumber").textContent = manifest.version;
|
||||
|
||||
// Get all DOM elements
|
||||
const themeSelect = document.getElementById("themeSelect");
|
||||
const warningToggle = document.getElementById("warningToggle");
|
||||
const updateFrequency = document.getElementById("updateFrequency");
|
||||
const saveButton = document.getElementById("saveSettings");
|
||||
const notification = document.getElementById("notification");
|
||||
const lastUpdated = document.getElementById("lastUpdated");
|
||||
const updateStatus = document.getElementById("updateStatus");
|
||||
|
||||
// Theme application function
|
||||
function applyTheme(theme) {
|
||||
if (theme === "system") {
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
document.body.setAttribute("data-theme", prefersDark ? "dark" : "light");
|
||||
} else {
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
}
|
||||
}
|
||||
|
||||
// Format date function
|
||||
function formatDate(date) {
|
||||
if (!date) return "Never";
|
||||
const d = new Date(date);
|
||||
const now = new Date();
|
||||
const diffTime = Math.abs(now - d);
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) {
|
||||
return (
|
||||
"Today at " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
} else if (diffDays === 1) {
|
||||
return (
|
||||
"Yesterday at " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
d.toLocaleDateString() +
|
||||
" " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate next update time
|
||||
function calculateNextUpdate(lastUpdate, frequency) {
|
||||
if (!lastUpdate) return "Not scheduled";
|
||||
const lastUpdateDate = new Date(lastUpdate);
|
||||
let nextUpdate = new Date(lastUpdateDate);
|
||||
|
||||
switch (frequency) {
|
||||
case "daily":
|
||||
nextUpdate.setDate(nextUpdate.getDate() + 1);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
case "weekly":
|
||||
nextUpdate.setDate(nextUpdate.getDate() + 7);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
case "monthly":
|
||||
nextUpdate.setMonth(nextUpdate.getMonth() + 1);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
default:
|
||||
return "Not scheduled";
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
if (nextUpdate < now) {
|
||||
return "Update pending...";
|
||||
}
|
||||
|
||||
const timeUntil = nextUpdate - now;
|
||||
const hoursUntil = Math.floor(timeUntil / (1000 * 60 * 60));
|
||||
const minutesUntil = Math.floor(
|
||||
(timeUntil % (1000 * 60 * 60)) / (1000 * 60)
|
||||
);
|
||||
|
||||
if (hoursUntil < 24) {
|
||||
if (hoursUntil === 0) {
|
||||
return `in ${minutesUntil} minutes`;
|
||||
} else {
|
||||
return `in ${hoursUntil}h ${minutesUntil}m`;
|
||||
}
|
||||
} else {
|
||||
const days = Math.floor(hoursUntil / 24);
|
||||
if (days === 1) {
|
||||
return "tomorrow";
|
||||
} else {
|
||||
return `in ${days} days`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the UI with next update time
|
||||
async function updateNextUpdateStatus() {
|
||||
try {
|
||||
const stats = await browserAPI.storage.local.get({
|
||||
lastUpdated: null,
|
||||
});
|
||||
const settings = await browserAPI.storage.sync.get({
|
||||
updateFrequency: "daily",
|
||||
});
|
||||
|
||||
const nextUpdateText = calculateNextUpdate(
|
||||
stats.lastUpdated,
|
||||
settings.updateFrequency
|
||||
);
|
||||
|
||||
if (updateStatus) {
|
||||
updateStatus.innerHTML = `
|
||||
<svg class="update-icon" viewBox="0 0 24 24" width="16" height="16">
|
||||
<path fill="currentColor" d="M12 4V2C6.477 2 2 6.477 2 12C2 17.523 6.477 22 12 22C17.523 22 22 17.523 22 12H20C20 16.418 16.418 20 12 20C7.582 20 4 16.418 4 12C4 7.582 7.582 4 12 4Z"/>
|
||||
</svg>
|
||||
Next update ${nextUpdateText}
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating next update status:", error);
|
||||
if (updateStatus) {
|
||||
updateStatus.textContent = "Unable to check next update time";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load filterlist stats
|
||||
async function loadFilterlistStats() {
|
||||
try {
|
||||
const stats = await browserAPI.storage.local.get({
|
||||
unsafeFilterCount: 0,
|
||||
potentiallyUnsafeFilterCount: 0,
|
||||
safeSiteCount: 0,
|
||||
lastUpdated: null,
|
||||
});
|
||||
|
||||
console.log("Fetched stats:", stats);
|
||||
|
||||
document.getElementById("unsafeFilterCount").textContent =
|
||||
stats.unsafeFilterCount;
|
||||
document.getElementById("potentiallyUnsafeFilterCount").textContent =
|
||||
stats.potentiallyUnsafeFilterCount;
|
||||
document.getElementById("safeSiteCount").textContent =
|
||||
stats.safeSiteCount;
|
||||
document.getElementById("lastUpdated").textContent = formatDate(
|
||||
stats.lastUpdated
|
||||
);
|
||||
|
||||
await updateNextUpdateStatus();
|
||||
} catch (error) {
|
||||
console.error("Error loading filterlist stats:", error);
|
||||
document.getElementById("unsafeFilterCount").textContent = "Error";
|
||||
document.getElementById("potentiallyUnsafeFilterCount").textContent =
|
||||
"Error";
|
||||
document.getElementById("safeSiteCount").textContent = "Error";
|
||||
document.getElementById("lastUpdated").textContent = "Error";
|
||||
}
|
||||
}
|
||||
|
||||
// Load settings function
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const savedSettings = await browserAPI.storage.sync.get({
|
||||
theme: "system",
|
||||
warningPage: true,
|
||||
updateFrequency: "daily",
|
||||
});
|
||||
|
||||
if (themeSelect) themeSelect.value = savedSettings.theme;
|
||||
if (warningToggle) warningToggle.checked = savedSettings.warningPage;
|
||||
if (updateFrequency)
|
||||
updateFrequency.value = savedSettings.updateFrequency;
|
||||
|
||||
applyTheme(savedSettings.theme);
|
||||
await loadFilterlistStats();
|
||||
} catch (error) {
|
||||
console.error("Error loading settings:", error);
|
||||
showNotification("Error loading settings", true);
|
||||
}
|
||||
}
|
||||
|
||||
// Show notification function
|
||||
function showNotification(message, isError = false) {
|
||||
if (notification) {
|
||||
notification.textContent = message;
|
||||
if (isError) {
|
||||
notification.style.background =
|
||||
"linear-gradient(120deg, #ff6b6b, #ff8787)";
|
||||
} else {
|
||||
notification.style.background =
|
||||
"linear-gradient(120deg, var(--accent-purple), var(--accent-blue))";
|
||||
}
|
||||
notification.classList.add("show");
|
||||
setTimeout(() => {
|
||||
notification.classList.remove("show");
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Save settings function
|
||||
async function saveSettings() {
|
||||
try {
|
||||
const settings = {
|
||||
theme: themeSelect.value,
|
||||
warningPage: warningToggle.checked,
|
||||
updateFrequency: updateFrequency.value,
|
||||
};
|
||||
|
||||
console.log("Saving settings:", settings);
|
||||
|
||||
// Save settings to storage
|
||||
await browserAPI.storage.sync.set(settings);
|
||||
showNotification("Settings saved successfully!");
|
||||
applyTheme(settings.theme);
|
||||
await updateNextUpdateStatus();
|
||||
|
||||
console.log(
|
||||
"Settings saved to storage, sending message to background script..."
|
||||
);
|
||||
|
||||
// Send updated settings to background script
|
||||
await browserAPI.runtime.sendMessage({
|
||||
type: "settingsUpdated",
|
||||
settings: settings,
|
||||
});
|
||||
|
||||
console.log(
|
||||
"Settings update message sent to background script successfully."
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error occurred during saveSettings:", error);
|
||||
showNotification("Error saving settings", true);
|
||||
}
|
||||
}
|
||||
|
||||
// Event Listeners
|
||||
if (saveButton) {
|
||||
saveButton.addEventListener("click", saveSettings);
|
||||
}
|
||||
|
||||
if (themeSelect) {
|
||||
themeSelect.addEventListener("change", (e) => {
|
||||
applyTheme(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", (e) => {
|
||||
if (themeSelect && themeSelect.value === "system") {
|
||||
applyTheme("system");
|
||||
}
|
||||
});
|
||||
|
||||
browserAPI.runtime.onMessage.addListener((message) => {
|
||||
if (message.type === "filterlistUpdated") {
|
||||
loadFilterlistStats();
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(updateNextUpdateStatus, 60000);
|
||||
loadSettings();
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Cross-browser compatibility shim
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
// Load and display the extension version from manifest.json
|
||||
const manifest = browserAPI.runtime.getManifest();
|
||||
document.getElementById("versionNumber").textContent = manifest.version;
|
||||
|
||||
// Get all DOM elements
|
||||
const themeSelect = document.getElementById("themeSelect");
|
||||
const warningToggle = document.getElementById("warningToggle");
|
||||
const updateFrequency = document.getElementById("updateFrequency");
|
||||
const saveButton = document.getElementById("saveSettings");
|
||||
const notification = document.getElementById("notification");
|
||||
const lastUpdated = document.getElementById("lastUpdated");
|
||||
const updateStatus = document.getElementById("updateStatus");
|
||||
const forceRefreshButton = document.getElementById("forceRefresh");
|
||||
|
||||
// Get link highlighting elements
|
||||
const highlightTrustedToggle = document.getElementById(
|
||||
"highlightTrustedToggle"
|
||||
);
|
||||
const highlightUntrustedToggle = document.getElementById(
|
||||
"highlightUntrustedToggle"
|
||||
);
|
||||
const showWarningBannersToggle = document.getElementById(
|
||||
"showWarningBannersToggle"
|
||||
);
|
||||
const trustedColor = document.getElementById("trustedColor");
|
||||
const untrustedColor = document.getElementById("untrustedColor");
|
||||
|
||||
// Get domain management elements
|
||||
const trustedDomains = document.getElementById("trustedDomains");
|
||||
const untrustedDomains = document.getElementById("untrustedDomains");
|
||||
|
||||
// Theme application function
|
||||
function applyTheme(theme) {
|
||||
if (theme === "system") {
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
document.body.setAttribute("data-theme", prefersDark ? "dark" : "light");
|
||||
} else {
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
}
|
||||
}
|
||||
|
||||
// Format date function
|
||||
function formatDate(date) {
|
||||
if (!date) return "Never";
|
||||
const d = new Date(date);
|
||||
const now = new Date();
|
||||
const diffTime = Math.abs(now - d);
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) {
|
||||
return (
|
||||
"Today at " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
} else if (diffDays === 1) {
|
||||
return (
|
||||
"Yesterday at " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
d.toLocaleDateString() +
|
||||
" " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate next update time
|
||||
function calculateNextUpdate(lastUpdate, frequency) {
|
||||
if (!lastUpdate) return "Not scheduled";
|
||||
const lastUpdateDate = new Date(lastUpdate);
|
||||
let nextUpdate = new Date(lastUpdateDate);
|
||||
|
||||
switch (frequency) {
|
||||
case "daily":
|
||||
nextUpdate.setDate(nextUpdate.getDate() + 1);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
case "weekly":
|
||||
nextUpdate.setDate(nextUpdate.getDate() + 7);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
case "monthly":
|
||||
nextUpdate.setMonth(nextUpdate.getMonth() + 1);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
default:
|
||||
return "Not scheduled";
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
if (nextUpdate < now) {
|
||||
return "Update pending...";
|
||||
}
|
||||
|
||||
const timeUntil = nextUpdate - now;
|
||||
const hoursUntil = Math.floor(timeUntil / (1000 * 60 * 60));
|
||||
const minutesUntil = Math.floor(
|
||||
(timeUntil % (1000 * 60 * 60)) / (1000 * 60)
|
||||
);
|
||||
|
||||
if (hoursUntil < 24) {
|
||||
if (hoursUntil === 0) {
|
||||
return `in ${minutesUntil} minutes`;
|
||||
} else {
|
||||
return `in ${hoursUntil}h ${minutesUntil}m`;
|
||||
}
|
||||
} else {
|
||||
const days = Math.floor(hoursUntil / 24);
|
||||
if (days === 1) {
|
||||
return "tomorrow";
|
||||
} else {
|
||||
return `in ${days} days`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the UI with next update time
|
||||
async function updateNextUpdateStatus() {
|
||||
try {
|
||||
// Get both lastUpdated and updateFrequency in a single storage call
|
||||
const data = await browserAPI.storage.local.get({
|
||||
lastUpdated: null,
|
||||
updateFrequency: "daily",
|
||||
});
|
||||
|
||||
// Use the stored frequency setting directly
|
||||
const nextUpdateText = calculateNextUpdate(
|
||||
data.lastUpdated,
|
||||
data.updateFrequency
|
||||
);
|
||||
|
||||
if (updateStatus) {
|
||||
// Clear current content
|
||||
while (updateStatus.firstChild) {
|
||||
updateStatus.removeChild(updateStatus.firstChild);
|
||||
}
|
||||
|
||||
// Add spinning icon
|
||||
const svgIcon = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"svg"
|
||||
);
|
||||
svgIcon.setAttribute("class", "update-icon");
|
||||
svgIcon.setAttribute("viewBox", "0 0 24 24");
|
||||
svgIcon.setAttribute("width", "16");
|
||||
svgIcon.setAttribute("height", "16");
|
||||
|
||||
const path = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"path"
|
||||
);
|
||||
path.setAttribute("fill", "currentColor");
|
||||
path.setAttribute(
|
||||
"d",
|
||||
"M12 4V2C6.477 2 2 6.477 2 12C2 17.523 6.477 22 12 22C17.523 22 22 17.523 22 12H20C20 16.418 16.418 20 12 20C7.582 20 4 16.418 4 12C4 7.582 7.582 4 12 4Z"
|
||||
);
|
||||
|
||||
svgIcon.appendChild(path);
|
||||
updateStatus.appendChild(svgIcon);
|
||||
|
||||
// Add text
|
||||
updateStatus.appendChild(
|
||||
document.createTextNode(`Next update ${nextUpdateText}`)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating next update status:", error);
|
||||
if (updateStatus) {
|
||||
updateStatus.textContent = "Unable to check next update time";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load filterlist stats
|
||||
async function loadFilterlistStats() {
|
||||
try {
|
||||
const stats = await browserAPI.storage.local.get({
|
||||
unsafeFilterCount: 0,
|
||||
potentiallyUnsafeFilterCount: 0,
|
||||
safeSiteCount: 0,
|
||||
lastUpdated: null,
|
||||
});
|
||||
|
||||
console.log("Fetched stats:", stats);
|
||||
|
||||
document.getElementById("unsafeFilterCount").textContent =
|
||||
stats.unsafeFilterCount;
|
||||
document.getElementById("potentiallyUnsafeFilterCount").textContent =
|
||||
stats.potentiallyUnsafeFilterCount;
|
||||
document.getElementById("safeSiteCount").textContent =
|
||||
stats.safeSiteCount;
|
||||
document.getElementById("lastUpdated").textContent = formatDate(
|
||||
stats.lastUpdated
|
||||
);
|
||||
|
||||
await updateNextUpdateStatus();
|
||||
} catch (error) {
|
||||
console.error("Error loading filterlist stats:", error);
|
||||
document.getElementById("unsafeFilterCount").textContent = "Error";
|
||||
document.getElementById("potentiallyUnsafeFilterCount").textContent =
|
||||
"Error";
|
||||
document.getElementById("safeSiteCount").textContent = "Error";
|
||||
document.getElementById("lastUpdated").textContent = "Error";
|
||||
}
|
||||
}
|
||||
|
||||
// Load settings function
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const settings = await browserAPI.storage.local.get([
|
||||
"theme",
|
||||
"showWarning",
|
||||
"updateFrequency",
|
||||
"lastUpdated",
|
||||
"nextUpdate",
|
||||
"highlightTrusted",
|
||||
"highlightUntrusted",
|
||||
"showWarningBanners",
|
||||
"trustedColor",
|
||||
"untrustedColor",
|
||||
"userTrustedDomains",
|
||||
"userUntrustedDomains",
|
||||
]);
|
||||
|
||||
console.log("Loaded settings:", settings);
|
||||
|
||||
themeSelect.value = settings.theme || "system";
|
||||
applyTheme(settings.theme || "system");
|
||||
|
||||
warningToggle.checked = settings.showWarning !== false;
|
||||
|
||||
// Set updateFrequency with fallback to "daily"
|
||||
updateFrequency.value = settings.updateFrequency || "daily";
|
||||
console.log("Set updateFrequency to:", updateFrequency.value);
|
||||
|
||||
// Set link highlighting settings
|
||||
highlightTrustedToggle.checked = settings.highlightTrusted !== false;
|
||||
highlightUntrustedToggle.checked = settings.highlightUntrusted !== false;
|
||||
showWarningBannersToggle.checked = settings.showWarningBanners !== false;
|
||||
|
||||
if (settings.trustedColor) {
|
||||
trustedColor.value = settings.trustedColor;
|
||||
}
|
||||
|
||||
if (settings.untrustedColor) {
|
||||
untrustedColor.value = settings.untrustedColor;
|
||||
}
|
||||
|
||||
// Set domain lists
|
||||
if (
|
||||
settings.userTrustedDomains &&
|
||||
Array.isArray(settings.userTrustedDomains)
|
||||
) {
|
||||
trustedDomains.value = settings.userTrustedDomains.join("\n");
|
||||
}
|
||||
|
||||
if (
|
||||
settings.userUntrustedDomains &&
|
||||
Array.isArray(settings.userUntrustedDomains)
|
||||
) {
|
||||
untrustedDomains.value = settings.userUntrustedDomains.join("\n");
|
||||
}
|
||||
|
||||
if (settings.lastUpdated) {
|
||||
lastUpdated.textContent = formatDate(settings.lastUpdated);
|
||||
}
|
||||
|
||||
if (settings.nextUpdate) {
|
||||
updateNextUpdateStatus();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading settings:", error);
|
||||
showNotification("Error loading settings", true);
|
||||
}
|
||||
}
|
||||
|
||||
// Show notification function
|
||||
function showNotification(message, isError = false) {
|
||||
if (notification) {
|
||||
notification.textContent = message;
|
||||
if (isError) {
|
||||
notification.style.background =
|
||||
"linear-gradient(120deg, #ff6b6b, #ff8787)";
|
||||
} else {
|
||||
notification.style.background =
|
||||
"linear-gradient(120deg, var(--accent-purple), var(--accent-blue))";
|
||||
}
|
||||
notification.classList.add("show");
|
||||
setTimeout(() => {
|
||||
notification.classList.remove("show");
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse domains from textarea
|
||||
* @param {string} text - Textarea content
|
||||
* @returns {string[]} - Array of normalized domains
|
||||
*/
|
||||
function parseDomainList(text) {
|
||||
if (!text) return [];
|
||||
|
||||
return text
|
||||
.split("\n")
|
||||
.map((line) => line.trim().toLowerCase())
|
||||
.filter((line) => line && !line.startsWith("#") && !line.startsWith("//"))
|
||||
.map((domain) => {
|
||||
// Remove protocols and paths
|
||||
if (domain.includes("://")) {
|
||||
try {
|
||||
return new URL(domain).hostname.replace(/^www\./, "");
|
||||
} catch (e) {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
// Just remove www prefix if no protocol
|
||||
return domain.replace(/^www\./, "");
|
||||
});
|
||||
}
|
||||
|
||||
// Save settings function
|
||||
async function saveSettings() {
|
||||
try {
|
||||
const newSettings = {
|
||||
theme: themeSelect.value,
|
||||
showWarning: warningToggle.checked,
|
||||
updateFrequency: updateFrequency.value,
|
||||
highlightTrusted: highlightTrustedToggle.checked,
|
||||
highlightUntrusted: highlightUntrustedToggle.checked,
|
||||
showWarningBanners: showWarningBannersToggle.checked,
|
||||
trustedColor: trustedColor.value,
|
||||
untrustedColor: untrustedColor.value,
|
||||
userTrustedDomains: parseDomainList(trustedDomains.value),
|
||||
userUntrustedDomains: parseDomainList(untrustedDomains.value),
|
||||
};
|
||||
|
||||
// Save all settings explicitly to local storage
|
||||
await browserAPI.storage.local.set(newSettings);
|
||||
|
||||
// Calculate the next update time based on the new frequency
|
||||
const nextUpdate = calculateNextUpdate(
|
||||
new Date().toISOString(),
|
||||
newSettings.updateFrequency
|
||||
);
|
||||
|
||||
// Update lastUpdated to now and store the next update time
|
||||
await browserAPI.storage.local.set({
|
||||
nextUpdate,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Tell the background script to update its alarm
|
||||
await browserAPI.runtime.sendMessage({ action: "updateAlarm" });
|
||||
|
||||
// Apply settings to all open tabs
|
||||
await browserAPI.runtime.sendMessage({ action: "refreshAllTabs" });
|
||||
|
||||
// Show notification and update status
|
||||
showNotification("Settings saved and applied to all tabs!");
|
||||
await updateNextUpdateStatus();
|
||||
} catch (error) {
|
||||
console.error("Error saving settings:", error);
|
||||
showNotification("Error saving settings. Please try again.", true);
|
||||
}
|
||||
}
|
||||
|
||||
// For debugging - can be called from the browser console
|
||||
window.checkCurrentSettings = async function () {
|
||||
try {
|
||||
const data = await browserAPI.storage.local.get(null); // Get all storage
|
||||
console.log("All stored settings:", data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error retrieving settings:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Event listeners
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
// First load settings to ensure we have the correct values
|
||||
await loadSettings();
|
||||
// Then load filter stats
|
||||
await loadFilterlistStats();
|
||||
// Explicitly update the update status to ensure it's not stuck
|
||||
await updateNextUpdateStatus();
|
||||
|
||||
// Log current settings for debugging
|
||||
console.log("Current update frequency:", updateFrequency.value);
|
||||
await window.checkCurrentSettings();
|
||||
});
|
||||
|
||||
saveButton.addEventListener("click", saveSettings);
|
||||
|
||||
if (themeSelect) {
|
||||
themeSelect.addEventListener("change", (e) => {
|
||||
applyTheme(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", (e) => {
|
||||
if (themeSelect && themeSelect.value === "system") {
|
||||
applyTheme("system");
|
||||
}
|
||||
});
|
||||
|
||||
browserAPI.runtime.onMessage.addListener((message) => {
|
||||
if (message.type === "filterlistUpdated") {
|
||||
loadFilterlistStats();
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(updateNextUpdateStatus, 60000);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue