diff --git a/src/js/background.js b/src/js/background.js new file mode 100644 index 0000000..31421f2 --- /dev/null +++ b/src/js/background.js @@ -0,0 +1,401 @@ +// Cross-browser compatibility shim +const browserAPI = typeof browser !== "undefined" ? browser : chrome; + +// URLs and Constants +const filterListURLUnsafe = + "https://raw.githubusercontent.com/fmhy/FMHYFilterlist/refs/heads/main/sitelist.txt"; +const filterListURLPotentiallyUnsafe = + "https://raw.githubusercontent.com/fmhy/FMHYFilterlist/refs/heads/main/sitelist-plus.txt"; +const safeListURL = "https://api.fmhy.net/single-page"; +const starredListURL = + "https://raw.githubusercontent.com/fmhy/bookmarks/refs/heads/main/fmhy_in_bookmarks_starred_only.html"; +const fmhyFilterListURL = + "https://raw.githubusercontent.com/fmhy/FMHY-SafeGuard/refs/heads/main/fmhy-filterlist.txt"; +const DEFAULT_CHECK_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds + +// State Variables +let unsafeSitesRegex = null; +let potentiallyUnsafeSitesRegex = null; +let fmhySitesRegex = null; +let safeSites = []; +let starredSites = []; +const approvedUrls = new Map(); // Map to store approved URLs per tab + +// Helper Functions +function extractUrlsFromMarkdown(markdown) { + const urlRegex = /https?:\/\/[^\s)]+/g; + return markdown.match(urlRegex) || []; +} + +function extractUrlsFromBookmarks(html) { + const urlRegex = / + domain.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + ); + return new RegExp(`(${escapedList.join("|")})`, "i"); +} + +function extractUrlsFromFilterList(text) { + return text + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("!")) + .map((line) => normalizeUrl(line)) + .filter((url) => url !== null); +} + +// Fetch and Update Functions +async function fetchFilterLists() { + console.log("Fetching filter lists..."); + try { + const [unsafeResponse, potentiallyUnsafeResponse, fmhyResponse] = + await Promise.all([ + fetch(filterListURLUnsafe), + fetch(filterListURLPotentiallyUnsafe), + fetch(fmhyFilterListURL), + ]); + + let unsafeSites = []; + let potentiallyUnsafeSites = []; + let fmhySites = []; + + if (unsafeResponse.ok) { + const unsafeText = await unsafeResponse.text(); + unsafeSites = extractUrlsFromFilterList(unsafeText); + unsafeSitesRegex = generateRegexFromList(unsafeSites); + } + + if (potentiallyUnsafeResponse.ok) { + const potentiallyUnsafeText = await potentiallyUnsafeResponse.text(); + potentiallyUnsafeSites = extractUrlsFromFilterList(potentiallyUnsafeText); + potentiallyUnsafeSitesRegex = generateRegexFromList( + potentiallyUnsafeSites + ); + } + + if (fmhyResponse.ok) { + const fmhyText = await fmhyResponse.text(); + fmhySites = extractUrlsFromFilterList(fmhyText); + fmhySitesRegex = generateRegexFromList(fmhySites); + } + + await browserAPI.storage.local.set({ + unsafeFilterCount: unsafeSites.length, + potentiallyUnsafeFilterCount: potentiallyUnsafeSites.length, + fmhyFilterCount: fmhySites.length, + lastUpdated: new Date().toISOString(), + }); + + console.log("Stored FMHY filter count:", fmhySites.length); + + notifySettingsPage(); + } catch (error) { + console.error("Error fetching filter lists:", error); + } +} + +async function fetchSafeSites() { + console.log("Fetching safe sites..."); + try { + const response = await fetch(safeListURL); + if (response.ok) { + const markdown = await response.text(); + const urls = extractUrlsFromMarkdown(markdown); + safeSites = [...new Set(urls.map((url) => normalizeUrl(url.trim())))]; + + await browserAPI.storage.local.set({ + safeSiteCount: safeSites.length, + }); + + console.log("Stored safe site count:", safeSites.length); + } + } catch (error) { + console.error("Error fetching safe sites:", error); + } +} + +async function fetchStarredSites() { + console.log("Fetching starred sites..."); + try { + const response = await fetch(starredListURL); + if (response.ok) { + const html = await response.text(); + const urls = extractUrlsFromBookmarks(html); + starredSites = [...new Set([...urls.map(normalizeUrl), ...starredSites])]; + } + } catch (error) { + console.error("Error fetching starred sites:", error); + } +} + +// UI Update Functions +function updatePageAction(status, tabId) { + const icons = { + safe: { + 19: "../res/icons/safe_19.png", + 38: "../res/icons/safe_38.png", + }, + unsafe: { + 19: "../res/icons/unsafe_19.png", + 38: "../res/icons/unsafe_38.png", + }, + potentially_unsafe: { + 19: "../res/icons/potentially_unsafe_19.png", + 38: "../res/icons/potentially_unsafe_38.png", + }, + starred: { + 19: "../res/icons/starred_19.png", + 38: "../res/icons/starred_38.png", + }, + fmhy: { + 19: "../res/icons/fmhy_19.png", + 38: "../res/icons/fmhy_38.png", + }, + default: { + 19: "../res/icons/default_19.png", + 38: "../res/icons/default_38.png", + }, + }; + + const icon = icons[status] || icons["default"]; + + browserAPI.action.setIcon({ + tabId: tabId, + path: icon, + }); +} + +async function notifySettingsPage() { + const tabs = await browserAPI.tabs.query({}); + for (const tab of tabs) { + try { + await browserAPI.tabs.sendMessage(tab.id, { type: "filterlistUpdated" }); + } catch (e) { + // Ignore errors for tabs that can't receive messages + } + } +} + +// Site Status Checking +function checkSiteAndUpdatePageAction(tabId, url) { + if (!url) { + updatePageAction("default", tabId); + return; + } + + const normalizedUrl = normalizeUrl(url.trim()); + const rootUrl = extractRootUrl(normalizedUrl); + + const isUnsafe = + unsafeSitesRegex?.test(rootUrl) || unsafeSitesRegex?.test(normalizedUrl); + const isPotentiallyUnsafe = + potentiallyUnsafeSitesRegex?.test(rootUrl) || + potentiallyUnsafeSitesRegex?.test(normalizedUrl); + const isFMHY = + fmhySitesRegex?.test(rootUrl) || fmhySitesRegex?.test(normalizedUrl); + const isStarred = + starredSites.includes(rootUrl) || starredSites.includes(normalizedUrl); + const isSafe = + safeSites.includes(rootUrl) || safeSites.includes(normalizedUrl); + + const tabApprovedUrls = approvedUrls.get(tabId) || []; + const isApproved = tabApprovedUrls.includes(normalizedUrl); + + if (isUnsafe && !isApproved) { + updatePageAction("unsafe", tabId); + openWarningPage(tabId, url); + } else if (isPotentiallyUnsafe) { + updatePageAction("potentially_unsafe", tabId); + } else if (isFMHY) { + updatePageAction("fmhy", tabId); + } else if (isStarred) { + updatePageAction("starred", tabId); + } else if (isSafe) { + updatePageAction("safe", tabId); + } else { + updatePageAction("default", tabId); + } +} + +// Update Schedule Management +async function shouldUpdate() { + try { + const { lastUpdated } = await browserAPI.storage.local.get("lastUpdated"); + const { updateFrequency } = await browserAPI.storage.sync.get({ + updateFrequency: "daily", + }); + + if (!lastUpdated) return true; + + const lastUpdate = new Date(lastUpdated); + const now = new Date(); + const diffHours = (now - lastUpdate) / (1000 * 60 * 60); + + switch (updateFrequency) { + case "daily": + return diffHours >= 24; + case "weekly": + return diffHours >= 168; + case "monthly": + return diffHours >= 720; + default: + return false; + } + } catch (error) { + console.error("Error checking update schedule:", error); + return false; + } +} + +async function setupUpdateSchedule() { + await browserAPI.alarms.clearAll(); + browserAPI.alarms.create("checkUpdate", { + periodInMinutes: 60, + }); +} + +// Event Listeners +browserAPI.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === "checkSiteStatus") { + const normalizedUrl = normalizeUrl(message.url.trim()); + const rootUrl = extractRootUrl(normalizedUrl); + + let isUnsafe = + unsafeSitesRegex?.test(rootUrl) || unsafeSitesRegex?.test(normalizedUrl); + let isPotentiallyUnsafe = + potentiallyUnsafeSitesRegex?.test(rootUrl) || + potentiallyUnsafeSitesRegex?.test(normalizedUrl); + let isFMHY = + fmhySitesRegex?.test(rootUrl) || fmhySitesRegex?.test(normalizedUrl); + let isStarred = + starredSites.includes(rootUrl) || starredSites.includes(normalizedUrl); + let isSafe = + safeSites.includes(rootUrl) || safeSites.includes(normalizedUrl); + + let status = "no_data"; + if (isFMHY) { + status = "fmhy"; + } else if (isStarred) { + status = "starred"; + } else if (isUnsafe) { + status = "unsafe"; + } else if (isPotentiallyUnsafe) { + status = "potentially_unsafe"; + } else if (isSafe) { + status = "safe"; + } + + sendResponse({ status: status }); + return true; // Indicates asynchronous response handling + } +}); + +async function openWarningPage(tabId, unsafeUrl) { + const tabApprovedUrls = approvedUrls.get(tabId) || []; + if (tabApprovedUrls.includes(normalizeUrl(unsafeUrl))) { + console.log(`URL ${unsafeUrl} was already approved for tab ${tabId}`); + return; + } + + const { warningPage } = await browserAPI.storage.sync.get({ + warningPage: true, + }); + + if (!warningPage) { + console.log("Warning page is disabled by the user settings."); + return; + } + + const warningPageUrl = browserAPI.runtime.getURL( + `../pub/warning-page.html?url=${encodeURIComponent(unsafeUrl)}` + ); + browserAPI.tabs.update(tabId, { url: warningPageUrl }); +} + +browserAPI.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if (changeInfo.status === "complete" && tab.url) { + checkSiteAndUpdatePageAction(tabId, tab.url); + } +}); + +browserAPI.tabs.onActivated.addListener(async (activeInfo) => { + const tab = await browserAPI.tabs.get(activeInfo.tabId); + if (tab.url) { + checkSiteAndUpdatePageAction(tab.id, tab.url); + } +}); + +browserAPI.alarms.onAlarm.addListener(async (alarm) => { + if (alarm.name === "checkUpdate") { + const needsUpdate = await shouldUpdate(); + if (needsUpdate) { + await fetchFilterLists(); + } + } +}); + +browserAPI.tabs.onRemoved.addListener((tabId) => { + approvedUrls.delete(tabId); + browserAPI.storage.local.remove(`proceedTab_${tabId}`); +}); + +// Initialize extension +async function initializeExtension() { + try { + await Promise.all([ + fetchFilterLists(), + fetchSafeSites(), + fetchStarredSites(), + setupUpdateSchedule(), + ]); + console.log("Extension initialized successfully."); + } catch (error) { + console.error("Error during extension initialization:", error); + } +} + +initializeExtension(); diff --git a/src/pub/index.html b/src/pub/index.html new file mode 100644 index 0000000..ec25225 --- /dev/null +++ b/src/pub/index.html @@ -0,0 +1,214 @@ + + + + + + FMHY SafeGuard + + + +
+
+

FMHY SafeGuard

+
+ +
+
+ Status Icon +

Checking site status...

+
+

+
+

+ Think this is a mistake? + Let us know. +

+
+ + + + + diff --git a/src/pub/index.js b/src/pub/index.js new file mode 100644 index 0000000..9cce8d7 --- /dev/null +++ b/src/pub/index.js @@ -0,0 +1,182 @@ +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"); + + // Cross-browser compatibility shim + const browserAPI = typeof browser !== "undefined" ? browser : chrome; + + // Helper function to normalize URLs (removes trailing slashes, query parameters, and fragments) + const normalizeUrl = (url) => { + const urlObj = new URL(url); + urlObj.search = ""; // Remove query parameters + urlObj.hash = ""; // Remove fragments + return urlObj.href.replace(/\/+$/, ""); // Remove trailing slash only + }; + + // Helper function to extract the root domain from a URL + function extractRootDomain(url) { + let urlObj = new URL(url); + return `${urlObj.protocol}//${urlObj.hostname}`; + } + + // Function to apply theme based on settings + async function applyTheme() { + try { + const { theme } = await browserAPI.storage.sync.get("theme"); + + if (theme === "dark") { + document.body.setAttribute("data-theme", "dark"); + } else if (theme === "light") { + document.body.setAttribute("data-theme", "light"); + } else { + const prefersDark = window.matchMedia( + "(prefers-color-scheme: dark)" + ).matches; + document.body.setAttribute( + "data-theme", + prefersDark ? "dark" : "light" + ); + } + } catch (error) { + console.error("Error applying theme:", error); + } + } + + // Apply theme on load + await applyTheme(); + + try { + // Get the active tab's URL + 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 = normalizeUrl(activeTab.url); + const rootDomain = extractRootDomain(currentUrl); + console.log(`Active tab URL: ${currentUrl}, Root domain: ${rootDomain}`); + + // Send a message to the background script to check the site's status + const response = await browserAPI.runtime.sendMessage({ + action: "checkSiteStatus", + url: currentUrl, + }); + + if (!response || !response.status) { + throw new Error( + "Failed to retrieve site status from the background script." + ); + } + + // Determine if the root domain or the full URL is marked as safe/starred + const isRootDomainMarked = await browserAPI.runtime.sendMessage({ + action: "checkSiteStatus", + url: rootDomain, + }); + + // Handle different site statuses and update the UI accordingly + if ( + isRootDomainMarked.status === response.status && + response.status !== "no_data" + ) { + // If both the root domain and the current URL share the same status, show the root domain in the message + handleStatusUpdate(response.status, rootDomain); + } else { + // Otherwise, show the current URL in the message + handleStatusUpdate(response.status, currentUrl); + } + } 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."); + } + + /** + * Updates the UI based on the site status + * @param {string} status - The status of the site (e.g., "safe", "unsafe") + * @param {string} displayUrl - The URL or root domain to display in the message + */ + function handleStatusUpdate(status, displayUrl) { + switch (status) { + case "unsafe": + updateUI( + "unsafe", + `${displayUrl} is flagged as unsafe. Be cautious when interacting with this site.` + ); + break; + case "potentially_unsafe": + updateUI( + "potentially_unsafe", + `${displayUrl} is potentially unsafe. Proceed with caution.` + ); + break; + case "fmhy": + updateUI( + "fmhy", + `${displayUrl} is an FMHY related site. Proceed confidently.` + ); + break; + case "safe": + updateUI("safe", `${displayUrl} is safe to browse.`); + break; + case "starred": + updateUI( + "starred", + `${displayUrl} is a starred site.` + ); + break; + case "no_data": + updateUI( + "no_data", + `No data available for ${displayUrl}.` + ); + break; + default: + updateUI( + "unknown", + `An unknown status was received for ${displayUrl}.` + ); + } + } + + /** + * Updates the UI with the appropriate icon, message, and effects. + * @param {string} status - The status of the site (e.g., "safe", "unsafe"). + * @param {string} message - The message to display to the user. + */ + 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", + no_data: "../res/ext_icon_144.png", + error: "../res/icons/error.png", + unknown: "../res/ext_icon_144.png", + }; + + // Update the icon and message + statusIcon.src = icons[status] || icons["unknown"]; + statusMessage.innerHTML = message || "An unknown error occurred."; + + // Add a small animation when the status changes + statusIcon.classList.add("active"); + setTimeout(() => statusIcon.classList.remove("active"), 300); + + console.log(`UI updated: ${message}`); + } + + // Add settings button functionality + document.getElementById("settingsButton").addEventListener("click", () => { + // Open the settings page in a new tab + browserAPI.runtime.openOptionsPage(); + }); +}); diff --git a/src/pub/settings-page.html b/src/pub/settings-page.html new file mode 100644 index 0000000..b58fcc0 --- /dev/null +++ b/src/pub/settings-page.html @@ -0,0 +1,605 @@ + + + + + + FMHY SafeGuard - Settings + + + + +
+

FMHY SafeGuard Settings

+ +
+ +
+
+
Filterlist Statistics
+
+ Last Updated: Today at 2:56:48 PM +
+
+ +
+
+
Unsafe Sites
+
266
+
+
+
Potentially Unsafe Sites
+
46
+
+
+
Safe Sites
+
21901
+
+
+ +
+ Next update scheduled for: Checking... +
+
+ + +
+
+
+

Theme

+

+ Choose your preferred appearance mode +

+
+
+ +
+
+
+ + +
+
+
+

Warning Page

+

+ Show warning page for unsafe sites +

+
+ +
+
+ + +
+
+
+

Auto-Update Filterlist

+

+ Automatically update security filters +

+
+
+ +
+
+
+
+ + +
+ +
+ + +
+ + +
+ Settings saved successfully! +
+ + + + diff --git a/src/pub/settings-page.js b/src/pub/settings-page.js new file mode 100644 index 0000000..978f4ff --- /dev/null +++ b/src/pub/settings-page.js @@ -0,0 +1,261 @@ +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 = ` + + + + 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, + }; + + await browserAPI.storage.sync.set(settings); + showNotification("Settings saved successfully!"); + applyTheme(settings.theme); + await updateNextUpdateStatus(); + + await browserAPI.runtime.sendMessage({ + type: "settingsUpdated", + settings: settings, + }); + } catch (error) { + console.error("Error saving settings:", 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(); +}); diff --git a/src/pub/warning-page.html b/src/pub/warning-page.html new file mode 100644 index 0000000..5cf1733 --- /dev/null +++ b/src/pub/warning-page.html @@ -0,0 +1,212 @@ + + + + + + FMHY SafeGuard - Warning + + + + + +
+

FMHY SafeGuard

+
+ Warning +

+

+ This site has been flagged as unsafe.
+ Be cautious when interacting with this site. +

+
+ + +
+
+ +
+ + diff --git a/src/pub/warning-page.js b/src/pub/warning-page.js new file mode 100644 index 0000000..413d7f9 --- /dev/null +++ b/src/pub/warning-page.js @@ -0,0 +1,33 @@ +document.addEventListener("DOMContentLoaded", () => { + // Cross-browser compatibility shim + const browserAPI = typeof browser !== "undefined" ? browser : chrome; + + const urlParams = new URLSearchParams(window.location.search); + const unsafeUrl = urlParams.get("url") || "unknown site"; + document.getElementById("unsafeUrl").textContent = unsafeUrl; + console.log(`Warning page loaded for URL: ${unsafeUrl}`); + + document.getElementById("goBack").addEventListener("click", () => { + console.log("User clicked Go Back."); + // Go back twice to skip over the warning page + window.history.go(-2); + }); + + document.getElementById("proceed").addEventListener("click", async () => { + if (confirm("Are you sure you want to proceed? This site may be unsafe.")) { + const [tab] = await browserAPI.tabs.query({ + active: true, + currentWindow: true, + }); + + console.log(`Sending proceedAnyway message for tab ${tab.id}`); + await browserAPI.runtime.sendMessage({ + action: "proceedAnyway", + tabId: tab.id, + }); + + console.log("Proceed flag set, navigating to unsafe URL..."); + await browserAPI.tabs.update(tab.id, { url: unsafeUrl }); + } + }); +}); diff --git a/src/pub/welcome-page.html b/src/pub/welcome-page.html new file mode 100644 index 0000000..19c42ed --- /dev/null +++ b/src/pub/welcome-page.html @@ -0,0 +1,355 @@ + + + + + + Welcome to FMHY SafeGuard + + + + +
+
+

Welcome to FMHY SafeGuard

+

Let's get you set up to browse safely

+
+ +
+
+
1
+
+

Pin Extension to Toolbar

+

+ For quick access, pin FMHY SafeGuard to your browser toolbar: +

+
    +
  1. + Click the extensions menu (puzzle piece icon) in your toolbar +
  2. +
  3. Find "FMHY SafeGuard" in the list
  4. +
  5. + Click the pin icon next to it or right-click it and select "Pin + to Toolbar" +
  6. +
+
+
+ +
+
2
+
+

How It Works

+

+ FMHY SafeGuard automatically checks websites against our security + database: +

+
    +
  • 🛡️ Blocks access to known unsafe sites
  • +
  • ⚠️ Shows warnings for potentially unsafe sites
  • +
  • ✅ Identifies trusted safe sites
  • +
+

+ The extension icon color indicates the current site's status: +

+
+
+ Not in Wiki + Not in Wiki +
+
+ Starred + Starred +
+
+ Safe + Safe +
+
+ Potentially Unsafe + Potentially Unsafe +
+
+ Unsafe + Unsafe +
+
+
+
+ +
+
3
+
+

Customize Your Settings

+

+ Configure the extension to work best for you: +

+
    +
  • Choose light or dark theme
  • +
  • Enable/disable warning pages
  • +
  • Set automatic update frequency
  • +
+
+
+
+ +
+ Open Settings +
+ + +
+ + diff --git a/src/res/ext_icon_144.png b/src/res/ext_icon_144.png new file mode 100644 index 0000000..099c350 Binary files /dev/null and b/src/res/ext_icon_144.png differ diff --git a/src/res/fonts/inter.woff2 b/src/res/fonts/inter.woff2 new file mode 100644 index 0000000..07d3c53 Binary files /dev/null and b/src/res/fonts/inter.woff2 differ diff --git a/src/res/icons/default.png b/src/res/icons/default.png new file mode 100644 index 0000000..8140bbb Binary files /dev/null and b/src/res/icons/default.png differ diff --git a/src/res/icons/default_19.png b/src/res/icons/default_19.png new file mode 100644 index 0000000..fffc683 Binary files /dev/null and b/src/res/icons/default_19.png differ diff --git a/src/res/icons/default_38.png b/src/res/icons/default_38.png new file mode 100644 index 0000000..2e9331a Binary files /dev/null and b/src/res/icons/default_38.png differ diff --git a/src/res/icons/error.png b/src/res/icons/error.png new file mode 100644 index 0000000..38a27e8 Binary files /dev/null and b/src/res/icons/error.png differ diff --git a/src/res/icons/fmhy.png b/src/res/icons/fmhy.png new file mode 100644 index 0000000..c1e00c1 Binary files /dev/null and b/src/res/icons/fmhy.png differ diff --git a/src/res/icons/fmhy_19.png b/src/res/icons/fmhy_19.png new file mode 100644 index 0000000..c32638f Binary files /dev/null and b/src/res/icons/fmhy_19.png differ diff --git a/src/res/icons/fmhy_38.png b/src/res/icons/fmhy_38.png new file mode 100644 index 0000000..cb45215 Binary files /dev/null and b/src/res/icons/fmhy_38.png differ diff --git a/src/res/icons/potentially_unsafe.png b/src/res/icons/potentially_unsafe.png new file mode 100644 index 0000000..3c88816 Binary files /dev/null and b/src/res/icons/potentially_unsafe.png differ diff --git a/src/res/icons/potentially_unsafe_19.png b/src/res/icons/potentially_unsafe_19.png new file mode 100644 index 0000000..a6138ee Binary files /dev/null and b/src/res/icons/potentially_unsafe_19.png differ diff --git a/src/res/icons/potentially_unsafe_38.png b/src/res/icons/potentially_unsafe_38.png new file mode 100644 index 0000000..7859131 Binary files /dev/null and b/src/res/icons/potentially_unsafe_38.png differ diff --git a/src/res/icons/safe.png b/src/res/icons/safe.png new file mode 100644 index 0000000..2e91dbc Binary files /dev/null and b/src/res/icons/safe.png differ diff --git a/src/res/icons/safe_19.png b/src/res/icons/safe_19.png new file mode 100644 index 0000000..3895060 Binary files /dev/null and b/src/res/icons/safe_19.png differ diff --git a/src/res/icons/safe_38.png b/src/res/icons/safe_38.png new file mode 100644 index 0000000..1369f83 Binary files /dev/null and b/src/res/icons/safe_38.png differ diff --git a/src/res/icons/starred.png b/src/res/icons/starred.png new file mode 100644 index 0000000..fd68271 Binary files /dev/null and b/src/res/icons/starred.png differ diff --git a/src/res/icons/starred_19.png b/src/res/icons/starred_19.png new file mode 100644 index 0000000..cb1bd78 Binary files /dev/null and b/src/res/icons/starred_19.png differ diff --git a/src/res/icons/starred_38.png b/src/res/icons/starred_38.png new file mode 100644 index 0000000..ad17975 Binary files /dev/null and b/src/res/icons/starred_38.png differ diff --git a/src/res/icons/unsafe.png b/src/res/icons/unsafe.png new file mode 100644 index 0000000..1abdf95 Binary files /dev/null and b/src/res/icons/unsafe.png differ diff --git a/src/res/icons/unsafe_19.png b/src/res/icons/unsafe_19.png new file mode 100644 index 0000000..db2a533 Binary files /dev/null and b/src/res/icons/unsafe_19.png differ diff --git a/src/res/icons/unsafe_38.png b/src/res/icons/unsafe_38.png new file mode 100644 index 0000000..1aeed93 Binary files /dev/null and b/src/res/icons/unsafe_38.png differ