embermarks/background.js

238 lines
6 KiB
JavaScript
Raw Normal View History

2026-01-04 19:28:14 +00:00
// Embermarks - Background Script
// Handles bookmark analysis and forgotten bookmark detection
// Minimum valid timestamp (Jan 1, 2010) - older dates are likely corrupted imports
const MIN_VALID_TIMESTAMP = 1262304000000;
// Cache duration for "daily" refresh mode (24 hours in ms)
const DAILY_CACHE_DURATION = 24 * 60 * 60 * 1000;
2026-01-04 19:28:14 +00:00
const DEFAULT_SETTINGS = {
excludedFolders: [],
numBookmarks: 5,
maxVisitCount: 2, // Consider "forgotten" if visited 2 or fewer times
minAgeDays: 7, // Only show bookmarks older than 7 days
refreshBehavior: "always", // "always", "daily", or "manual"
2026-01-04 19:28:14 +00:00
};
// In-memory cache for bookmarks
let cachedBookmarks = null;
let cacheTimestamp = null;
2026-01-04 19:28:14 +00:00
// Get user settings
async function getSettings() {
const result = await browser.storage.local.get("settings");
return { ...DEFAULT_SETTINGS, ...result.settings };
}
// Save settings
async function saveSettings(settings) {
await browser.storage.local.set({
settings: { ...DEFAULT_SETTINGS, ...settings },
});
}
// Get cached bookmarks if valid
async function getCachedBookmarks() {
const result = await browser.storage.local.get([
"cachedBookmarks",
"cacheTimestamp",
]);
return {
bookmarks: result.cachedBookmarks || null,
timestamp: result.cacheTimestamp || null,
};
}
// Save bookmarks to cache
async function setCachedBookmarks(bookmarks) {
await browser.storage.local.set({
cachedBookmarks: bookmarks,
cacheTimestamp: Date.now(),
});
}
// Clear the cache
async function clearCache() {
await browser.storage.local.remove(["cachedBookmarks", "cacheTimestamp"]);
cachedBookmarks = null;
cacheTimestamp = null;
}
2026-01-04 19:28:14 +00:00
// Get all bookmarks recursively, respecting excluded folders
async function getAllBookmarks(excludedFolderIds = []) {
const tree = await browser.bookmarks.getTree();
const bookmarks = [];
function traverse(nodes, currentPath = "") {
for (const node of nodes) {
// Skip excluded folders and their children
if (excludedFolderIds.includes(node.id)) {
continue;
}
if (node.url) {
// It's a bookmark
bookmarks.push({
id: node.id,
title: node.title || "Untitled",
url: node.url,
dateAdded: node.dateAdded,
path: currentPath,
});
}
if (node.children) {
const folderName = node.title || "";
const newPath = currentPath
? `${currentPath} / ${folderName}`
: folderName;
traverse(node.children, newPath);
}
}
}
traverse(tree);
return bookmarks;
}
// Get visit count for a URL
async function getVisitCount(url) {
try {
const visits = await browser.history.getVisits({ url });
return visits.length;
} catch (e) {
return 0;
}
}
// Fetch fresh forgotten bookmarks
async function fetchFreshBookmarks() {
2026-01-04 19:28:14 +00:00
const settings = await getSettings();
const bookmarks = await getAllBookmarks(settings.excludedFolders);
const now = Date.now();
const minAgeMs = settings.minAgeDays * 24 * 60 * 60 * 1000;
// Filter by age and get visit counts
const candidates = [];
for (const bookmark of bookmarks) {
// Skip if too new
if (bookmark.dateAdded && now - bookmark.dateAdded < minAgeMs) {
continue;
}
// Skip non-http(s) URLs
if (
!bookmark.url.startsWith("http://") &&
!bookmark.url.startsWith("https://")
) {
continue;
}
const visitCount = await getVisitCount(bookmark.url);
if (visitCount <= settings.maxVisitCount) {
// Check if dateAdded is valid (not corrupted/too old)
const hasValidDate =
bookmark.dateAdded && bookmark.dateAdded >= MIN_VALID_TIMESTAMP;
candidates.push({
...bookmark,
visitCount,
daysSinceAdded: hasValidDate
? Math.floor((now - bookmark.dateAdded) / (24 * 60 * 60 * 1000))
: null,
});
}
}
// Shuffle and pick random ones
const shuffled = candidates.sort(() => Math.random() - 0.5);
return shuffled.slice(0, settings.numBookmarks);
}
// Get forgotten bookmarks (with caching logic)
async function getForgottenBookmarks(forceRefresh = false) {
const settings = await getSettings();
const { bookmarks: cached, timestamp } = await getCachedBookmarks();
// Determine if we should use cache
if (!forceRefresh && cached && timestamp) {
const now = Date.now();
const cacheAge = now - timestamp;
if (settings.refreshBehavior === "manual") {
// Always use cache in manual mode (until explicit refresh)
return cached;
}
if (
settings.refreshBehavior === "daily" &&
cacheAge < DAILY_CACHE_DURATION
) {
// Use cache if less than 24 hours old
return cached;
}
}
// Fetch fresh bookmarks
const freshBookmarks = await fetchFreshBookmarks();
// Cache the results (for daily and manual modes)
if (settings.refreshBehavior !== "always") {
await setCachedBookmarks(freshBookmarks);
}
return freshBookmarks;
}
2026-01-04 19:28:14 +00:00
// Get all bookmark folders for settings UI
async function getBookmarkFolders() {
const tree = await browser.bookmarks.getTree();
const folders = [];
function traverse(nodes, depth = 0) {
for (const node of nodes) {
if (node.children) {
folders.push({
id: node.id,
title: node.title || "Root",
depth,
});
traverse(node.children, depth + 1);
}
}
}
traverse(tree);
return folders;
}
// Handle messages from popup/options
2026-01-04 19:28:14 +00:00
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
switch (message.action) {
case "getForgottenBookmarks":
return getForgottenBookmarks(message.forceRefresh || false);
case "clearCache":
return clearCache();
2026-01-04 19:28:14 +00:00
case "getBookmarkFolders":
return getBookmarkFolders();
case "getSettings":
return getSettings();
case "saveSettings":
return saveSettings(message.settings);
case "deleteBookmark":
return browser.bookmarks.remove(message.bookmarkId);
default:
return Promise.resolve(null);
}
});