mirror of
https://github.com/skidoodle/hostinfo
synced 2026-04-28 17:47:36 +02:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
283ebe4dcb
|
|||
|
b488b7576a
|
|||
|
3fcbab5d30
|
|||
|
a755d3f1c2
|
|||
|
9db73ee29c
|
|||
|
76758c4572
|
|||
|
55e97a68f6
|
|||
|
25d0865f89
|
|||
|
85942e5827
|
|||
|
7048baebc2
|
|||
|
b3b76366bf
|
|||
|
c564ac8b69
|
|||
|
2f16721a93
|
|||
|
56ed5909c7
|
|||
|
55b3b61bbf
|
|||
|
5c107c2f0d
|
|||
|
fea3cf5a62
|
@@ -5,21 +5,19 @@
|
||||
"": {
|
||||
"name": "hostinfo",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"clsx": "^2.1.1",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"@heroicons/react": "latest",
|
||||
"@tailwindcss/vite": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"tailwindcss": "latest",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.1.37",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@wxt-dev/module-react": "^1.1.5",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.18",
|
||||
"@types/chrome": "latest",
|
||||
"@types/react": "latest",
|
||||
"@types/react-dom": "latest",
|
||||
"@wxt-dev/module-react": "latest",
|
||||
"typescript": "latest",
|
||||
"wxt": "latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -268,7 +266,7 @@
|
||||
|
||||
"@wxt-dev/browser": ["@wxt-dev/browser@0.1.37", "", { "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, "sha512-I32XWCNRy2W6UgbaVXz8BHGBGtm8urGRRBrcNLagUBXTrBi7wCE6zWePUvvK+nUl7qUCZ7iQ1ufdP0c1DEWisw=="],
|
||||
|
||||
"@wxt-dev/module-react": ["@wxt-dev/module-react@1.1.5", "", { "dependencies": { "@vitejs/plugin-react": "^4.4.1 || ^5.0.0" }, "peerDependencies": { "wxt": ">=0.19.16" } }, "sha512-KgsUrsgH5rBT8MwiipnDEOHBXmLvTIdFICrI7KjngqSf9DpVRn92HsKmToxY0AYpkP19hHWta2oNYFTzmmm++g=="],
|
||||
"@wxt-dev/module-react": ["@wxt-dev/module-react@1.2.1", "", { "dependencies": { "@vitejs/plugin-react": "^4.4.1 || ^5.0.0" }, "peerDependencies": { "vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0", "wxt": ">=0.19.16" } }, "sha512-NkXhXP1KqbTmKuQ7LwESFUnQDxQRiHw98ZQ6cXKuulRvyxtyCfc6gOqbKMHPP9bp497UVHaozr3ZQj1lvcPPTQ=="],
|
||||
|
||||
"@wxt-dev/storage": ["@wxt-dev/storage@1.2.6", "", { "dependencies": { "@wxt-dev/browser": "^0.1.4", "async-mutex": "^0.5.0", "dequal": "^2.0.3" } }, "sha512-f6AknnpJvhNHW4s0WqwSGCuZAj0fjP3EVNPBO5kB30pY+3Zt/nqZGqJN6FgBLCSkYjPJ8VL1hNX5LMVmvxQoDw=="],
|
||||
|
||||
@@ -344,8 +342,6 @@
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
@@ -684,7 +680,7 @@
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pino": ["pino@9.7.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg=="],
|
||||
|
||||
@@ -810,8 +806,6 @@
|
||||
|
||||
"stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
@@ -906,6 +900,8 @@
|
||||
|
||||
"@aklinker1/rollup-plugin-visualizer/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
|
||||
|
||||
"@aklinker1/rollup-plugin-visualizer/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="],
|
||||
@@ -966,6 +962,8 @@
|
||||
|
||||
"log-update/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||
|
||||
"multimatch/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
@@ -990,18 +988,8 @@
|
||||
|
||||
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"unimport/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"unplugin/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"unplugin-utils/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"update-notifier/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"vite/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
@@ -3,12 +3,19 @@ import { CheckIcon, ClipboardDocumentIcon } from '@heroicons/react/24/outline';
|
||||
export const CopyButton = ({ text }: { text: string }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
if (copied) {
|
||||
timeout = setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
return () => clearTimeout(timeout);
|
||||
}, [copied]);
|
||||
|
||||
const handleCopy = async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -23,14 +23,14 @@ export const PublicNetworkView = ({ data, domain }: { data: GeoData, domain: str
|
||||
<InfoRow
|
||||
icon={GlobeAltIcon}
|
||||
label="Hostname"
|
||||
value={domain}
|
||||
value={data.hostname || 'N/A'}
|
||||
canCopy
|
||||
iconColor="text-indigo-500"
|
||||
/>
|
||||
<InfoRow
|
||||
icon={MapPinIcon}
|
||||
label="Location"
|
||||
value={data.countryName || 'Unknown Location'}
|
||||
value={data.countryName || 'N/A'}
|
||||
iconColor="text-emerald-500"
|
||||
/>
|
||||
<InfoRow
|
||||
|
||||
+81
-17
@@ -3,11 +3,6 @@ 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) {
|
||||
@@ -19,8 +14,8 @@ function getDomain(url: string) {
|
||||
}
|
||||
|
||||
function applyIconForState(tabId: number, state: TabState) {
|
||||
const isSystem = SYSTEM_PROTOCOLS.some(p => state.url.startsWith(p));
|
||||
if (isSystem) {
|
||||
const isWebProtocol = state.url.startsWith('http://') || state.url.startsWith('https://');
|
||||
if (!isWebProtocol) {
|
||||
IconService.updateIcon(tabId, null, true);
|
||||
} else if (state.status === 'success' && state.data) {
|
||||
let code = state.data.countryCode;
|
||||
@@ -33,7 +28,7 @@ function applyIconForState(tabId: number, state: TabState) {
|
||||
|
||||
async function initTab(tabId: number, url: string, resolveDns = false) {
|
||||
if (!url) return;
|
||||
const isSystem = SYSTEM_PROTOCOLS.some(p => url.startsWith(p));
|
||||
const isWebProtocol = url.startsWith('http://') || url.startsWith('https://');
|
||||
const domain = getDomain(url);
|
||||
|
||||
let currentState = tabStates.get(tabId);
|
||||
@@ -63,26 +58,41 @@ async function initTab(tabId: number, url: string, resolveDns = false) {
|
||||
const newState: TabState = {
|
||||
url,
|
||||
domain,
|
||||
status: isSystem || hasExistingData ? 'success' : 'loading',
|
||||
status: !isWebProtocol || hasExistingData ? 'success' : 'loading',
|
||||
data: hasExistingData ? currentState!.data : null,
|
||||
errorMessage: null,
|
||||
lastUpdated: Date.now()
|
||||
};
|
||||
|
||||
tabStates.set(tabId, newState);
|
||||
StorageService.setTabState(tabId, newState).catch(() => { });
|
||||
await StorageService.setTabState(tabId, newState).catch(() => { });
|
||||
|
||||
applyIconForState(tabId, newState);
|
||||
|
||||
if (!isSystem && !hasExistingData && resolveDns) {
|
||||
if (!isWebProtocol && !hasExistingData) {
|
||||
const performDnsFallback = async () => {
|
||||
const state = tabStates.get(tabId);
|
||||
if (state?.status !== 'loading' || state.url !== url) return;
|
||||
|
||||
const ip = await DnsService.resolve(domain);
|
||||
|
||||
const stateAfterDns = tabStates.get(tabId);
|
||||
if (stateAfterDns?.status !== 'loading' || stateAfterDns.url !== url) return;
|
||||
|
||||
if (ip) {
|
||||
await processIp(tabId, url, ip);
|
||||
} else {
|
||||
await updateState(tabId, {
|
||||
status: 'error',
|
||||
errorMessage: 'Could not resolve host'
|
||||
});
|
||||
}, url);
|
||||
}
|
||||
};
|
||||
|
||||
if (resolveDns) {
|
||||
await performDnsFallback();
|
||||
} else {
|
||||
setTimeout(performDnsFallback, 1500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,18 +137,19 @@ async function processIp(tabId: number, url: string, ip: string) {
|
||||
};
|
||||
|
||||
tabStates.set(tabId, newState);
|
||||
await StorageService.setTabState(tabId, newState);
|
||||
await StorageService.setTabState(tabId, newState).catch(() => { });
|
||||
|
||||
applyIconForState(tabId, newState);
|
||||
}
|
||||
|
||||
async function updateState(tabId: number, updates: Partial<TabState>) {
|
||||
async function updateState(tabId: number, updates: Partial<TabState>, expectedUrl?: string) {
|
||||
let current = tabStates.get(tabId);
|
||||
if (!current) {
|
||||
current = await StorageService.getTabState(tabId) || undefined;
|
||||
}
|
||||
|
||||
if (current) {
|
||||
if (expectedUrl && current.url !== expectedUrl) return;
|
||||
const newState = { ...current, ...updates };
|
||||
tabStates.set(tabId, newState);
|
||||
await StorageService.setTabState(tabId, newState);
|
||||
@@ -148,6 +159,32 @@ async function updateState(tabId: number, updates: Partial<TabState>) {
|
||||
|
||||
export default defineBackground({
|
||||
main() {
|
||||
browser.runtime.onStartup.addListener(() => {
|
||||
StorageService.cleanExpiredGeoCache().catch(console.error);
|
||||
});
|
||||
|
||||
browser.runtime.onInstalled.addListener(() => {
|
||||
StorageService.cleanExpiredGeoCache().catch(console.error);
|
||||
});
|
||||
|
||||
browser.alarms.create('cleanup-geo-cache', { periodInMinutes: 1440 });
|
||||
browser.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === 'cleanup-geo-cache') {
|
||||
StorageService.cleanExpiredGeoCache().catch(console.error);
|
||||
}
|
||||
});
|
||||
|
||||
browser.tabs.onReplaced.addListener((addedTabId, removedTabId) => {
|
||||
tabStates.delete(removedTabId);
|
||||
StorageService.removeTabState(removedTabId);
|
||||
|
||||
browser.tabs.get(addedTabId).then((tab) => {
|
||||
if (tab.url) {
|
||||
initTab(tab.id!, tab.url, true)
|
||||
}
|
||||
}).catch(() => { })
|
||||
})
|
||||
|
||||
browser.webNavigation.onBeforeNavigate.addListener((details) => {
|
||||
if (details.frameId !== 0) return;
|
||||
initTab(details.tabId, details.url, false);
|
||||
@@ -194,13 +231,16 @@ export default defineBackground({
|
||||
}
|
||||
const ip = await DnsService.resolve(hostname);
|
||||
|
||||
const currentState = tabStates.get(details.tabId);
|
||||
if (currentState?.status !== 'loading' || currentState.url !== details.url) return;
|
||||
|
||||
if (ip) {
|
||||
await processIp(details.tabId, details.url, ip);
|
||||
} else {
|
||||
await updateState(details.tabId, {
|
||||
status: 'error',
|
||||
errorMessage: 'Could not resolve host'
|
||||
});
|
||||
}, details.url);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -208,12 +248,23 @@ export default defineBackground({
|
||||
browser.webRequest.onErrorOccurred.addListener(
|
||||
async (details) => {
|
||||
if (details.type !== 'main_frame') return;
|
||||
if (details.error === 'net::ERR_ABORTED') return;
|
||||
if (details.error === 'net::ERR_ABORTED') {
|
||||
try {
|
||||
const tab = await browser.tabs.get(details.tabId);
|
||||
if (tab.url) {
|
||||
const currentState = tabStates.get(details.tabId);
|
||||
if (currentState && currentState.url !== tab.url) {
|
||||
initTab(tab.id!, tab.url, true);
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
return;
|
||||
}
|
||||
|
||||
await updateState(details.tabId, {
|
||||
status: 'error',
|
||||
errorMessage: details.error
|
||||
});
|
||||
}, details.url);
|
||||
},
|
||||
{ urls: ['<all_urls>'] }
|
||||
);
|
||||
@@ -229,6 +280,9 @@ export default defineBackground({
|
||||
const state = tabStates.get(tab.id!);
|
||||
if (state) {
|
||||
applyIconForState(tab.id!, state);
|
||||
if (state.status === 'loading' && Date.now() - state.lastUpdated > 2000) {
|
||||
initTab(tab.id!, tab.url, true)
|
||||
}
|
||||
} else {
|
||||
initTab(tab.id!, tab.url, true);
|
||||
}
|
||||
@@ -237,9 +291,19 @@ export default defineBackground({
|
||||
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (changeInfo.status && tab.url) {
|
||||
if (tab.url) {
|
||||
const state = tabStates.get(tabId);
|
||||
if (state) {
|
||||
applyIconForState(tabId, state);
|
||||
if (!state || state.url !== tab.url) {
|
||||
initTab(tabId, tab.url, true)
|
||||
} else {
|
||||
applyIconForState(tabId, state);
|
||||
if (changeInfo.status === 'complete' && state.status === 'loading') {
|
||||
initTab(tabId, tab.url, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+15
-1
@@ -8,7 +8,10 @@ export function useHostInfo() {
|
||||
const fetchInfo = async () => {
|
||||
try {
|
||||
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
if (!tab?.id) return;
|
||||
if (!tab?.id) {
|
||||
if (isMounted) setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await StorageService.getTabState(tab.id);
|
||||
if (data) {
|
||||
@@ -16,6 +19,9 @@ export function useHostInfo() {
|
||||
setInfo(data);
|
||||
setLoading(false);
|
||||
}
|
||||
if (data.status === 'loading' && Date.now() - data.lastUpdated > 2000) {
|
||||
browser.runtime.sendMessage({ type: 'INIT_TAB', tabId: tab.id, url: tab.url })
|
||||
}
|
||||
} else {
|
||||
if (tab.url) {
|
||||
await browser.runtime.sendMessage({ type: 'INIT_TAB', tabId: tab.id, url: tab.url });
|
||||
@@ -32,8 +38,16 @@ export function useHostInfo() {
|
||||
|
||||
const listener = (changes: any, areaName: string) => {
|
||||
if (areaName === 'session' || areaName === 'local') {
|
||||
browser.tabs.query({ active: true, currentWindow: true }).then(([tab]) => {
|
||||
if (tab?.id) {
|
||||
const sessionKey = `tab_${tab.id}`;
|
||||
const localKey = `session_tab_${tab.id}`;
|
||||
if (changes[sessionKey] || changes[localKey]) {
|
||||
fetchInfo();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
browser.storage.onChanged.addListener(listener);
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"name": "hostinfo",
|
||||
"description": "Receive information of a domain directly in the browser when browsing a website",
|
||||
"private": true,
|
||||
"version": "2.0.0",
|
||||
"version": "2.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
@@ -25,7 +25,7 @@
|
||||
"@types/chrome": "^0.1.37",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@wxt-dev/module-react": "^1.1.5",
|
||||
"@wxt-dev/module-react": "^1.2.1",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.18"
|
||||
}
|
||||
|
||||
+8
-1
@@ -1,13 +1,20 @@
|
||||
export const DnsService = {
|
||||
async resolve(hostname: string): Promise<string | null> {
|
||||
if (hostname === 'localhost' || hostname.endsWith('.local')) {
|
||||
return '127.0.0.1';
|
||||
}
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
const response = await fetch(
|
||||
`https://cloudflare-dns.com/dns-query?name=${hostname}&type=A`,
|
||||
{
|
||||
headers: { accept: 'application/dns-json' },
|
||||
credentials: 'omit'
|
||||
credentials: 'omit',
|
||||
signal: controller.signal,
|
||||
}
|
||||
);
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
|
||||
+16
-4
@@ -13,11 +13,15 @@ export const GeoService = {
|
||||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
const res = await fetch(`https://ip.albert.lol/${ip}`, {
|
||||
method: 'GET',
|
||||
cache: 'force-cache',
|
||||
credentials: 'omit'
|
||||
credentials: 'omit',
|
||||
signal: controller.signal,
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (!res.ok) throw new Error(`API Error ${res.status}`);
|
||||
|
||||
@@ -28,8 +32,9 @@ export const GeoService = {
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.warn('Geo lookup failed for', ip, error);
|
||||
return {
|
||||
const failedData: GeoData = {
|
||||
ip,
|
||||
hostname: null,
|
||||
countryCode: null,
|
||||
countryName: 'Unknown',
|
||||
city: null,
|
||||
@@ -40,12 +45,16 @@ export const GeoService = {
|
||||
isLocal: false,
|
||||
isBogon: false
|
||||
};
|
||||
|
||||
await StorageService.setGeoCache(ip, failedData);
|
||||
return failedData;
|
||||
}
|
||||
},
|
||||
|
||||
getLocalData(ip: string): GeoData {
|
||||
return {
|
||||
ip,
|
||||
hostname: null,
|
||||
countryCode: null,
|
||||
countryName: 'Local Network',
|
||||
city: null,
|
||||
@@ -59,17 +68,20 @@ export const GeoService = {
|
||||
},
|
||||
|
||||
transform(ip: string, apiData: any): GeoData {
|
||||
const asnMatch = apiData.org?.match(/^AS\d+/i);
|
||||
|
||||
return {
|
||||
ip,
|
||||
hostname: apiData.hostname || null,
|
||||
countryCode: apiData.country || null,
|
||||
countryName: apiData.country ? (codes[apiData.country.toLowerCase()] || apiData.country) : null,
|
||||
city: apiData.city || null,
|
||||
region: apiData.region || null,
|
||||
org: apiData.org || null,
|
||||
asn: apiData.org?.split(' ')[0] || null,
|
||||
asn: asnMatch ? asnMatch[0].toUpperCase() : null,
|
||||
timezone: apiData.timezone || null,
|
||||
isLocal: false,
|
||||
isBogon: apiData.bogon || false
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
|
||||
const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,3}(:[0-9a-fA-F]{1,4}){1,4})|([0-9a-fA-F]{1,2}(:[0-9a-fA-F]{1,4}){1,5})|([0-9a-fA-F]{1,4}:)((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
||||
|
||||
export const IpUtils = {
|
||||
@@ -13,8 +13,10 @@ export const IpUtils = {
|
||||
const clean = ip.replace(/^\[|\]$/g, '');
|
||||
|
||||
if (clean === '::1' || clean === '::') return true;
|
||||
if (clean.toLowerCase().startsWith('fe80:')) return true;
|
||||
if (clean.toLowerCase().startsWith('fc00:')) return true;
|
||||
|
||||
const lowerClean = clean.toLowerCase();
|
||||
if (lowerClean.startsWith('fe80:')) return true;
|
||||
if (lowerClean.startsWith('fc') || lowerClean.startsWith('fd')) return true;
|
||||
|
||||
if (clean.includes('.')) {
|
||||
const parts = clean.split('.').map(Number);
|
||||
|
||||
@@ -7,6 +7,7 @@ export const StorageService = {
|
||||
try {
|
||||
const res = await browser.storage.session.get(key);
|
||||
if (res[key]) return res[key] as TabState;
|
||||
return null;
|
||||
} catch { }
|
||||
}
|
||||
const res = await browser.storage.local.get(`session_${key}`);
|
||||
@@ -53,5 +54,23 @@ export const StorageService = {
|
||||
const key = `geo_${ip.replace(/:/g, '_')}`;
|
||||
const entry: CacheEntry = { data, timestamp: Date.now() };
|
||||
await browser.storage.local.set({ [key]: entry });
|
||||
},
|
||||
|
||||
async cleanExpiredGeoCache(): Promise<void> {
|
||||
const res = await browser.storage.local.get(null);
|
||||
const keysToRemove: string[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(res)) {
|
||||
if (key.startsWith('geo_')) {
|
||||
const entry = value as CacheEntry;
|
||||
if (Date.now() - entry.timestamp > CACHE_TTL) {
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (keysToRemove.length > 0) {
|
||||
await browser.storage.local.remove(keysToRemove);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface GeoData {
|
||||
ip: string;
|
||||
hostname: string | null;
|
||||
countryCode: string | null;
|
||||
countryName: string | null;
|
||||
city: string | null;
|
||||
|
||||
+3
-2
@@ -7,12 +7,13 @@ export default defineConfig({
|
||||
manifest: {
|
||||
name: 'Host Info',
|
||||
description: 'Get host information',
|
||||
version: '2.0.0',
|
||||
version: '2.2',
|
||||
permissions: [
|
||||
'tabs',
|
||||
'webRequest',
|
||||
'webNavigation',
|
||||
'storage'
|
||||
'storage',
|
||||
'alarms'
|
||||
],
|
||||
host_permissions: [
|
||||
'<all_urls>',
|
||||
|
||||
Reference in New Issue
Block a user