Bug fixes and performance improvements

This commit is contained in:
2026-02-24 16:05:02 +01:00
parent da23868817
commit 7d614a2a7e
532 changed files with 639 additions and 523 deletions
+234 -32
View File
@@ -1,51 +1,253 @@
import { browser } from 'wxt/browser';
import { Tab } from '@/services/tab';
import { StorageService } from '@/utils/storage';
import { GeoService } from '@/services/geo';
import { IconService } from '@/services/icon';
import { DnsService } from '@/services/dns';
const SYSTEM_PROTOCOLS = [
'chrome:', 'about:', 'edge:', 'moz-extension:',
'chrome-extension:', 'file:', 'view-source:', 'data:', 'devtools:'
];
const tabStates = new Map<number, TabState>();
function getDomain(url: string) {
try {
return new URL(url).hostname;
} catch {
return url;
}
}
function applyIconForState(tabId: number, state: TabState) {
const isSystem = SYSTEM_PROTOCOLS.some(p => state.url.startsWith(p));
if (isSystem) {
IconService.updateIcon(tabId, null, true);
} else if (state.status === 'success' && state.data) {
let code = state.data.countryCode;
if (state.data.asn === 'AS13335') code = 'cloudflare';
IconService.updateIcon(tabId, code, state.data.isLocal);
} else {
IconService.updateIcon(tabId, null, false);
}
}
async function initTab(tabId: number, url: string, resolveDns = false) {
if (!url) return;
const isSystem = SYSTEM_PROTOCOLS.some(p => url.startsWith(p));
const domain = getDomain(url);
let currentState = tabStates.get(tabId);
if (!currentState || currentState.url !== url) {
tabStates.set(tabId, {
url,
domain,
status: 'loading',
data: null,
errorMessage: null,
lastUpdated: Date.now()
});
}
if (!currentState) {
currentState = await StorageService.getTabState(tabId) || undefined;
}
const latestState = tabStates.get(tabId);
if (latestState && latestState.url !== url) return;
if (latestState && latestState.status === 'success' && latestState.data) return;
const isSameDomain = currentState?.domain === domain;
const hasExistingData = isSameDomain && !!currentState?.data;
const newState: TabState = {
url,
domain,
status: isSystem || hasExistingData ? 'success' : 'loading',
data: hasExistingData ? currentState!.data : null,
errorMessage: null,
lastUpdated: Date.now()
};
tabStates.set(tabId, newState);
StorageService.setTabState(tabId, newState).catch(() => { });
applyIconForState(tabId, newState);
if (!isSystem && !hasExistingData && resolveDns) {
const ip = await DnsService.resolve(domain);
if (ip) {
await processIp(tabId, url, ip);
} else {
await updateState(tabId, {
status: 'error',
errorMessage: 'Could not resolve host'
});
}
}
}
async function processIp(tabId: number, url: string, ip: string) {
let current = tabStates.get(tabId);
if (!current) {
current = await StorageService.getTabState(tabId) || undefined;
}
const latestState1 = tabStates.get(tabId);
if (latestState1) {
try {
if (new URL(latestState1.url).hostname !== new URL(url).hostname) return;
} catch {
return;
}
}
if (current?.status === 'success' && current.data?.ip === ip) {
return;
}
const geoData = await GeoService.getGeoData(ip);
const stateAfterFetch = tabStates.get(tabId);
if (stateAfterFetch) {
try {
if (new URL(stateAfterFetch.url).hostname !== new URL(url).hostname) return;
} catch {
return;
}
}
const newState: TabState = {
url: stateAfterFetch?.url || url,
domain: stateAfterFetch?.domain || getDomain(url),
status: 'success',
data: geoData,
errorMessage: null,
lastUpdated: Date.now()
};
tabStates.set(tabId, newState);
await StorageService.setTabState(tabId, newState);
applyIconForState(tabId, newState);
}
async function updateState(tabId: number, updates: Partial<TabState>) {
let current = tabStates.get(tabId);
if (!current) {
current = await StorageService.getTabState(tabId) || undefined;
}
if (current) {
const newState = { ...current, ...updates };
tabStates.set(tabId, newState);
await StorageService.setTabState(tabId, newState);
applyIconForState(tabId, newState);
}
}
export default defineBackground({
main() {
// Listen for Network Responses
browser.webNavigation.onBeforeNavigate.addListener((details) => {
if (details.frameId !== 0) return;
initTab(details.tabId, details.url, false);
});
browser.webNavigation.onHistoryStateUpdated.addListener((details) => {
if (details.frameId !== 0) return;
initTab(details.tabId, details.url, true);
});
browser.webNavigation.onCommitted.addListener((details) => {
if (details.frameId !== 0) return;
const state = tabStates.get(details.tabId);
if (state) {
applyIconForState(details.tabId, state);
}
});
browser.webRequest.onResponseStarted.addListener(
async (details) => {
if (details.tabId === -1 || details.type !== 'main_frame' || !details.ip) return;
await Tab.process(details.tabId, details.url, details.ip);
},
{ urls: ['<all_urls>'] }
);
// Listen for Network Errors (DNS, Connection Refused, etc.)
browser.webRequest.onErrorOccurred.addListener(
async (details) => {
(details) => {
if (details.tabId === -1 || details.type !== 'main_frame') return;
await Tab.handleError(details.tabId, details.url, details.error);
if (details.ip) {
processIp(details.tabId, details.url, details.ip);
}
},
{ urls: ['<all_urls>'] }
);
// Listen for Navigation
browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete' && tab.url) {
const urlObj = new URL(tab.url);
const isSystemPage = ['chrome:', 'about:', 'edge:', 'moz-extension:', 'chrome-extension:', 'file:'].includes(urlObj.protocol);
browser.webNavigation.onCompleted.addListener(async (details) => {
if (details.frameId !== 0) return;
if (isSystemPage) {
await Tab.processSystemPage(tabId);
} else if (tab.url.startsWith('http')) {
// We might not have the IP yet if it was cached, so we trigger a process
// If IP is missing, Tab waits or we can force a HEAD request here
await Tab.process(tabId, tab.url);
const state = tabStates.get(details.tabId);
// Force connection to ensure webRequest fires if cached
try {
await fetch(tab.url, { method: 'HEAD', cache: 'no-store', mode: 'no-cors' });
} catch { /* ignore */ }
if (state) {
applyIconForState(details.tabId, state);
}
if (state && state.status === 'loading' && !state.data) {
let hostname = '';
try {
hostname = new URL(details.url).hostname;
} catch {
return;
}
const ip = await DnsService.resolve(hostname);
if (ip) {
await processIp(details.tabId, details.url, ip);
} else {
await updateState(details.tabId, {
status: 'error',
errorMessage: 'Could not resolve host'
});
}
}
});
// Cleanup
browser.tabs.onRemoved.addListener(async (tabId) => {
await StorageService.remove(tabId);
browser.webRequest.onErrorOccurred.addListener(
async (details) => {
if (details.type !== 'main_frame') return;
if (details.error === 'net::ERR_ABORTED') return;
await updateState(details.tabId, {
status: 'error',
errorMessage: details.error
});
},
{ urls: ['<all_urls>'] }
);
browser.tabs.onRemoved.addListener((tabId) => {
tabStates.delete(tabId);
StorageService.removeTabState(tabId);
});
browser.tabs.onActivated.addListener(async (activeInfo) => {
const tab = await browser.tabs.get(activeInfo.tabId);
if (tab.url) {
const state = tabStates.get(tab.id!);
if (state) {
applyIconForState(tab.id!, state);
} else {
initTab(tab.id!, tab.url, true);
}
}
});
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status && tab.url) {
const state = tabStates.get(tabId);
if (state) {
applyIconForState(tabId, state);
}
}
});
browser.runtime.onMessage.addListener((message) => {
if (message.type === 'INIT_TAB' && message.tabId && message.url) {
initTab(message.tabId, message.url, true);
}
});
},
});
+2 -7
View File
@@ -7,9 +7,8 @@ export default function Popup() {
if (loading) {
return (
<div className="w-80 h-75 bg-white dark:bg-gray-950 flex flex-col items-center justify-center space-y-4 font-sans">
<div className="w-80 h-64 bg-white dark:bg-gray-950 flex flex-col items-center justify-center font-sans">
<div className="w-6 h-6 border-2 border-gray-200 dark:border-gray-700 border-t-blue-600 rounded-full animate-spin"></div>
<p className="text-gray-400 text-xs font-medium">Loading host info...</p>
</div>
);
}
@@ -18,9 +17,5 @@ export default function Popup() {
return <Error error="No active page found" />;
}
if (info.error) {
return <Error error={info.error} />;
}
return <ServerInfo data={info} />;
return <ServerInfo state={info} />;
}
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Host Info</title>
<meta name="manifest.type" content="browser_action" />
<meta name="manifest.type" content="action" />
</head>
<body>
<div id="root"></div>