import { computed, onMounted, ref, watch } from "vue"; import { invoke } from "@tauri-apps/api/core"; import { open } from "@tauri-apps/plugin-dialog"; import { sortBookmarks, sortExtensions, sortPasswordSites, sortProfiles } from "../utils/sort"; import type { ActiveSection, AppPage, AssociatedProfileSummary, BookmarkAssociatedProfileSummary, BookmarkSortKey, BrowserConfigEntry, BrowserConfigListResponse, BrowserView, CleanupHistoryInput, CleanupHistoryResponse, CreateCustomBrowserConfigInput, ExtensionSortKey, PasswordSiteSortKey, ProfileSortKey, ScanResponse, } from "../types/browser"; export function useBrowserManager() { const page = ref("browserData"); const loading = ref(true); const error = ref(""); const openProfileError = ref(""); const openingProfileKey = ref(""); const response = ref({ browsers: [] }); const browserConfigs = ref([]); const configsLoading = ref(true); const configError = ref(""); const savingConfig = ref(false); const deletingConfigId = ref(""); const createConfigForm = ref({ name: "", iconKey: "chrome", executablePath: "", userDataPath: "", }); const selectedBrowserId = ref(""); const activeSection = ref("profiles"); const associatedProfilesModal = ref<{ title: string; browserId: string; profiles: (AssociatedProfileSummary | BookmarkAssociatedProfileSummary)[]; isBookmark: boolean; } | null>(null); const profileSortKey = ref("name"); const extensionSortKey = ref("name"); const bookmarkSortKey = ref("title"); const passwordSiteSortKey = ref("domain"); const cleanupHistorySelectedProfiles = ref([]); const historyCleanupBusy = ref(false); const cleanupHistoryError = ref(""); const cleanupHistoryResults = ref([]); const historyCleanupConfirmProfileIds = ref([]); const historyCleanupResultOpen = ref(false); const browsers = computed(() => response.value.browsers); const currentBrowser = computed( () => browsers.value.find((browser) => browser.browserId === selectedBrowserId.value) ?? browsers.value[0] ?? null, ); const sortedProfiles = computed(() => sortProfiles(currentBrowser.value?.profiles ?? [], profileSortKey.value), ); const sortedExtensions = computed(() => sortExtensions(currentBrowser.value?.extensions ?? [], extensionSortKey.value), ); const sortedBookmarks = computed(() => sortBookmarks(currentBrowser.value?.bookmarks ?? [], bookmarkSortKey.value), ); const sortedPasswordSites = computed(() => sortPasswordSites(currentBrowser.value?.passwordSites ?? [], passwordSiteSortKey.value), ); watch( browsers, (items) => { if (!items.length) { selectedBrowserId.value = ""; return; } const hasSelected = items.some( (browser) => browser.browserId === selectedBrowserId.value, ); if (!hasSelected) { selectedBrowserId.value = items[0].browserId; } }, { immediate: true }, ); watch(selectedBrowserId, () => { openProfileError.value = ""; associatedProfilesModal.value = null; cleanupHistorySelectedProfiles.value = []; cleanupHistoryResults.value = []; cleanupHistoryError.value = ""; historyCleanupConfirmProfileIds.value = []; historyCleanupResultOpen.value = false; }); async function loadBrowserConfigs() { configsLoading.value = true; configError.value = ""; try { const result = await invoke("list_browser_configs"); browserConfigs.value = result.configs; } catch (loadError) { configError.value = loadError instanceof Error ? loadError.message : "Failed to load browser configs."; } finally { configsLoading.value = false; } } async function scanBrowsers() { loading.value = true; error.value = ""; try { response.value = await invoke("scan_browsers"); } catch (scanError) { error.value = scanError instanceof Error ? scanError.message : "Failed to scan browser data."; } finally { loading.value = false; } } async function refreshAll() { await Promise.all([loadBrowserConfigs(), scanBrowsers()]); } async function openBrowserProfile(browserId: string, profileId: string) { const profileKey = `${browserId}:${profileId}`; openingProfileKey.value = profileKey; openProfileError.value = ""; try { await invoke("open_browser_profile", { browserId, profileId, }); } catch (openError) { openProfileError.value = openError instanceof Error ? openError.message : "Failed to open the selected browser profile."; } finally { openingProfileKey.value = ""; } } async function createCustomBrowserConfig() { savingConfig.value = true; configError.value = ""; try { const result = await invoke("create_custom_browser_config", { input: createConfigForm.value, }); browserConfigs.value = result.configs; createConfigForm.value = { name: "", iconKey: "chrome", executablePath: "", userDataPath: "", }; await scanBrowsers(); } catch (saveError) { configError.value = saveError instanceof Error ? saveError.message : "Failed to create browser config."; } finally { savingConfig.value = false; } } async function deleteCustomBrowserConfig(configId: string) { deletingConfigId.value = configId; configError.value = ""; try { const result = await invoke("delete_custom_browser_config", { configId, }); browserConfigs.value = result.configs; await scanBrowsers(); } catch (deleteError) { configError.value = deleteError instanceof Error ? deleteError.message : "Failed to delete browser config."; } finally { deletingConfigId.value = ""; } } async function pickExecutablePath() { const selected = await open({ multiple: false, directory: false, filters: [ { name: "Executable", extensions: ["exe"], }, ], }); if (typeof selected === "string") { createConfigForm.value.executablePath = selected; } } async function pickUserDataPath() { const selected = await open({ multiple: false, directory: true, }); if (typeof selected === "string") { createConfigForm.value.userDataPath = selected; } } function isDeletingConfig(configId: string) { return deletingConfigId.value === configId; } function isOpeningProfile(browserId: string, profileId: string) { return openingProfileKey.value === `${browserId}:${profileId}`; } function browserMonogram(browserId: string) { const current = browsers.value.find((browser) => browser.browserId === browserId); const iconKey = current?.iconKey ?? current?.browserFamilyId; if (iconKey === "chrome") return "CH"; if (iconKey === "edge") return "ED"; if (iconKey === "brave") return "BR"; if (iconKey === "vivaldi") return "VI"; if (iconKey === "yandex") return "YA"; if (iconKey === "chromium") return "CR"; const name = current?.browserName?.trim() ?? ""; if (name) { const letters = name .split(/\s+/) .filter(Boolean) .slice(0, 2) .map((part) => part[0]); if (letters.length) return letters.join("").toUpperCase(); } return browserId.slice(0, 2).toUpperCase(); } function configMonogram(config: BrowserConfigEntry) { const iconKey = config.iconKey ?? config.browserFamilyId; if (iconKey === "chrome") return "CH"; if (iconKey === "edge") return "ED"; if (iconKey === "brave") return "BR"; if (iconKey === "vivaldi") return "VI"; if (iconKey === "yandex") return "YA"; if (iconKey === "chromium") return "CR"; const letters = config.name .trim() .split(/\s+/) .filter(Boolean) .slice(0, 2) .map((part) => part[0]); return (letters.join("") || config.id.slice(0, 2)).toUpperCase(); } function extensionMonogram(name: string) { return name.trim().slice(0, 1).toUpperCase() || "?"; } function sectionCount(section: ActiveSection) { if (!currentBrowser.value) return 0; if (section === "profiles") return currentBrowser.value.profiles.length; if (section === "extensions") return currentBrowser.value.extensions.length; if (section === "bookmarks") return currentBrowser.value.bookmarks.length; if (section === "passwords") return currentBrowser.value.passwordSites.length; return currentBrowser.value.stats.historyCleanupProfileCount; } function showExtensionProfilesModal(extensionId: string) { const extension = currentBrowser.value?.extensions.find((item) => item.id === extensionId); if (!extension || !currentBrowser.value) return; associatedProfilesModal.value = { title: `${extension.name} Profiles`, browserId: currentBrowser.value.browserId, profiles: extension.profiles, isBookmark: false, }; } function showBookmarkProfilesModal(url: string) { const bookmark = currentBrowser.value?.bookmarks.find((item) => item.url === url); if (!bookmark || !currentBrowser.value) return; associatedProfilesModal.value = { title: `${bookmark.title} Profiles`, browserId: currentBrowser.value.browserId, profiles: bookmark.profiles, isBookmark: true, }; } function showPasswordSiteProfilesModal(url: string) { const passwordSite = currentBrowser.value?.passwordSites.find((item) => item.url === url); if (!passwordSite || !currentBrowser.value) return; associatedProfilesModal.value = { title: `${passwordSite.domain} Profiles`, browserId: currentBrowser.value.browserId, profiles: passwordSite.profiles, isBookmark: false, }; } function toggleHistoryProfile(profileId: string) { if (cleanupHistorySelectedProfiles.value.includes(profileId)) { cleanupHistorySelectedProfiles.value = cleanupHistorySelectedProfiles.value.filter( (selectedId) => selectedId !== profileId, ); return; } cleanupHistorySelectedProfiles.value = [ ...cleanupHistorySelectedProfiles.value, profileId, ]; } function toggleAllHistoryProfiles() { const current = currentBrowser.value; if (!current) return; const selectableIds = current.profiles .filter((profile) => { const cleanup = profile.historyCleanup; return ( cleanup.history === "found" || cleanup.topSites === "found" || cleanup.visitedLinks === "found" ); }) .map((profile) => profile.id); const allSelected = selectableIds.length > 0 && selectableIds.every((profileId) => cleanupHistorySelectedProfiles.value.includes(profileId), ); cleanupHistorySelectedProfiles.value = allSelected ? [] : selectableIds; } function cleanupProfileIdsWithHistory(browser: BrowserView) { return browser.profiles .filter((profile) => { const cleanup = profile.historyCleanup; return ( cleanup.history === "found" || cleanup.topSites === "found" || cleanup.visitedLinks === "found" ); }) .map((profile) => profile.id); } function historyCleanupConfirmProfiles() { const browser = currentBrowser.value; if (!browser) return []; return browser.profiles.filter((profile) => historyCleanupConfirmProfileIds.value.includes(profile.id), ); } function cleanupSelectedHistoryProfiles() { if (!cleanupHistorySelectedProfiles.value.length) return; historyCleanupConfirmProfileIds.value = [...cleanupHistorySelectedProfiles.value]; } function cleanupHistoryForProfile(profileId: string) { historyCleanupConfirmProfileIds.value = [profileId]; } function closeHistoryCleanupConfirm() { if (historyCleanupBusy.value) return; historyCleanupConfirmProfileIds.value = []; } function closeHistoryCleanupResult() { historyCleanupResultOpen.value = false; cleanupHistoryResults.value = []; cleanupHistoryError.value = ""; } function applyCleanupHistoryResults(results: CleanupHistoryResponse["results"]) { const browser = currentBrowser.value; if (!browser) return; const succeededProfileIds = results .filter((result) => !result.error) .map((result) => result.profileId); if (!succeededProfileIds.length) return; for (const profile of browser.profiles) { if (!succeededProfileIds.includes(profile.id)) continue; const deletedFiles = results.find((result) => result.profileId === profile.id)?.deletedFiles ?? []; if (deletedFiles.includes("History")) { profile.historyCleanup.history = "missing"; } if (deletedFiles.includes("Top Sites")) { profile.historyCleanup.topSites = "missing"; } if (deletedFiles.includes("Visited Links")) { profile.historyCleanup.visitedLinks = "missing"; } } browser.stats.historyCleanupProfileCount = cleanupProfileIdsWithHistory(browser).length; } async function confirmHistoryCleanup() { const browser = currentBrowser.value; const profileIds = [...historyCleanupConfirmProfileIds.value]; if (!browser || profileIds.length === 0) return; if (!currentBrowser.value || profileIds.length === 0) return; historyCleanupBusy.value = true; cleanupHistoryError.value = ""; cleanupHistoryResults.value = []; historyCleanupResultOpen.value = false; try { const input: CleanupHistoryInput = { browserId: browser.browserId, profileIds, }; const result = await invoke("cleanup_history_files", { input }); applyCleanupHistoryResults(result.results); cleanupHistoryResults.value = result.results; cleanupHistorySelectedProfiles.value = cleanupHistorySelectedProfiles.value.filter( (profileId) => !profileIds.includes(profileId), ); historyCleanupConfirmProfileIds.value = []; historyCleanupResultOpen.value = true; } catch (cleanupErrorValue) { historyCleanupConfirmProfileIds.value = []; cleanupHistoryError.value = cleanupErrorValue instanceof Error ? cleanupErrorValue.message : "Failed to clean history files."; historyCleanupResultOpen.value = true; } finally { historyCleanupBusy.value = false; } } function closeAssociatedProfilesModal() { associatedProfilesModal.value = null; } onMounted(() => { void refreshAll(); }); return { activeSection, associatedProfilesModal, bookmarkSortKey, browserConfigs, browserMonogram, browsers, configError, configMonogram, configsLoading, createConfigForm, createCustomBrowserConfig, currentBrowser, deleteCustomBrowserConfig, error, extensionMonogram, extensionSortKey, cleanupHistoryError, cleanupHistoryResults, cleanupHistorySelectedProfiles, cleanupSelectedHistoryProfiles, closeHistoryCleanupConfirm, closeHistoryCleanupResult, confirmHistoryCleanup, historyCleanupBusy, historyCleanupConfirmProfiles: computed(historyCleanupConfirmProfiles), historyCleanupResultOpen, isDeletingConfig, isOpeningProfile, loading, openProfileError, openBrowserProfile, page, pickExecutablePath, pickUserDataPath, passwordSiteSortKey, profileSortKey, refreshAll, savingConfig, sectionCount, selectedBrowserId, showBookmarkProfilesModal, showExtensionProfilesModal, showPasswordSiteProfilesModal, sortedBookmarks, sortedExtensions, sortedPasswordSites, sortedProfiles, cleanupHistoryForProfile, toggleAllHistoryProfiles, toggleHistoryProfile, closeAssociatedProfilesModal, }; }