mirror of
https://github.com/skidoodle/hostinfo
synced 2026-04-28 01:27:36 +02:00
half way there
This commit is contained in:
+28
-170
@@ -1,177 +1,35 @@
|
||||
import psl from 'psl'
|
||||
|
||||
let currentTabUrl: string | null = null
|
||||
|
||||
async function resolveARecord(hostname: string): Promise<string | null> {
|
||||
try {
|
||||
let dnsResponse = await fetch(
|
||||
`https://cloudflare-dns.com/dns-query?name=${hostname}&type=A`,
|
||||
{ headers: { Accept: 'application/dns-json' } }
|
||||
)
|
||||
if (dnsResponse.ok) {
|
||||
const dnsData = await dnsResponse.json()
|
||||
const aRecord = dnsData.Answer?.find(
|
||||
(entry: DNSEntry) => entry.type === 1
|
||||
)?.data
|
||||
if (aRecord) return aRecord
|
||||
}
|
||||
|
||||
dnsResponse = await fetch(
|
||||
`https://cloudflare-dns.com/dns-query?name=${hostname}&type=AAAA`,
|
||||
{ headers: { Accept: 'application/dns-json' } }
|
||||
)
|
||||
if (dnsResponse.ok) {
|
||||
const dnsData = await dnsResponse.json()
|
||||
const aaaaRecord = dnsData.Answer?.find(
|
||||
(entry: DNSEntry) => entry.type === 28
|
||||
)?.data
|
||||
if (aaaaRecord) return aaaaRecord
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch DNS data:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function isIP(host: string): boolean {
|
||||
const cleanedHost = host.replace(/^\[|\]$/g, '')
|
||||
|
||||
const ipv4Regex =
|
||||
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
||||
|
||||
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,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){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]))/
|
||||
|
||||
return ipv4Regex.test(cleanedHost) || ipv6Regex.test(cleanedHost)
|
||||
}
|
||||
|
||||
async function getIPInfo(host: string): Promise<any | null> {
|
||||
const cleanedHost = host.replace(/^\[|\]$/g, '')
|
||||
|
||||
if (isIP(cleanedHost)) {
|
||||
const response = await fetch(`https://ip.albert.lol/${cleanedHost}`)
|
||||
const data = await response.json()
|
||||
return data.ip ? data : null
|
||||
}
|
||||
|
||||
const resolvedIp = await resolveARecord(cleanedHost)
|
||||
if (!resolvedIp) return null
|
||||
|
||||
const response = await fetch(`https://ip.albert.lol/${resolvedIp}`)
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async function handleTabUpdate(url: string) {
|
||||
if (url === currentTabUrl) return
|
||||
currentTabUrl = url
|
||||
|
||||
try {
|
||||
const hostname = new URL(url).hostname
|
||||
const apiData = await getIPInfo(hostname)
|
||||
if (!apiData || !apiData.ip) {
|
||||
await updateIcon(null)
|
||||
return
|
||||
}
|
||||
|
||||
if (apiData.bogon === true) {
|
||||
await updateIcon(null)
|
||||
return
|
||||
}
|
||||
const country = apiData.country || null
|
||||
const asn = apiData.org?.split(' ')[0]
|
||||
let iconCode = country
|
||||
if (!iconCode && asn === 'AS13335') {
|
||||
iconCode = 'cloudflare'
|
||||
}
|
||||
await updateIcon(iconCode)
|
||||
} catch (error) {
|
||||
console.error('Failed to handle tab update:', error)
|
||||
await updateIcon(null)
|
||||
}
|
||||
}
|
||||
|
||||
browser.tabs.onActivated.addListener(async activeInfo => {
|
||||
const tab = await browser.tabs.get(activeInfo.tabId)
|
||||
if (tab.url) await handleTabUpdate(tab.url)
|
||||
})
|
||||
|
||||
browser.tabs.onUpdated.addListener(async (_tabId, changeInfo) => {
|
||||
if (changeInfo.url) await handleTabUpdate(changeInfo.url)
|
||||
})
|
||||
import { browser } from 'wxt/browser';
|
||||
import { Tab } from '@/services/tab';
|
||||
import { StorageService } from '@/utils/storage';
|
||||
|
||||
export default defineBackground({
|
||||
main() {
|
||||
browser.runtime.onMessage.addListener(
|
||||
(request: any, _sender, sendResponse) => {
|
||||
if (request.type === 'FETCH_SERVER_INFO') {
|
||||
;(async () => {
|
||||
try {
|
||||
const cleanHostname =
|
||||
request.hostname.startsWith('[') &&
|
||||
request.hostname.endsWith(']')
|
||||
? request.hostname.slice(1, -1)
|
||||
: request.hostname
|
||||
// Listen for Network Responses (Source of Truth for IPs)
|
||||
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>'] }
|
||||
);
|
||||
|
||||
const apiData = await getIPInfo(cleanHostname)
|
||||
if (!apiData || !apiData.ip) {
|
||||
sendResponse({ error: 'DNS resolution failed', data: null })
|
||||
return
|
||||
}
|
||||
if (apiData.bogon === true) {
|
||||
await updateIcon(null)
|
||||
sendResponse({
|
||||
error: null,
|
||||
data: {
|
||||
origin: '',
|
||||
ip: cleanHostname,
|
||||
hostname: '',
|
||||
country: '',
|
||||
city: '',
|
||||
org: '',
|
||||
isLocal: true,
|
||||
isBrowserResource: false,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
// Listen for Navigation
|
||||
browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
||||
if (changeInfo.status === 'complete' && tab.url && 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 parsed = psl.parse(cleanHostname)
|
||||
const origin = 'domain' in parsed ? parsed.domain : null
|
||||
const country = apiData.country || null
|
||||
const asn = apiData.org?.split(' ')[0]
|
||||
let iconCode = country
|
||||
if (!iconCode && asn === 'AS13335') {
|
||||
iconCode = 'cloudflare'
|
||||
}
|
||||
await updateIcon(iconCode)
|
||||
sendResponse({
|
||||
error: null,
|
||||
data: {
|
||||
origin,
|
||||
ip: apiData.ip,
|
||||
hostname: apiData.hostname || 'N/A',
|
||||
country: apiData.country || null,
|
||||
city: apiData.city || null,
|
||||
org: apiData.org,
|
||||
isLocal: false,
|
||||
isBrowserResource: false,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
await updateIcon(null)
|
||||
sendResponse({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
data: null,
|
||||
})
|
||||
}
|
||||
})()
|
||||
return true
|
||||
}
|
||||
sendResponse({ error: 'Unknown request type', data: null })
|
||||
return true
|
||||
// Force connection to ensure webRequest fires if cached
|
||||
try {
|
||||
await fetch(tab.url, { method: 'HEAD', cache: 'no-store', mode: 'no-cors' });
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
// 3. Cleanup
|
||||
browser.tabs.onRemoved.addListener(async (tabId) => {
|
||||
await StorageService.remove(tabId);
|
||||
});
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
import ServerInfo from '@/components/ServerInfo';
|
||||
import Error from '@/components/Error';
|
||||
import { useHostInfo } from '@/hooks/useHostInfo';
|
||||
|
||||
export default function Popup() {
|
||||
const { data, error } = useTabData();
|
||||
const { info, loading } = useHostInfo();
|
||||
|
||||
if (error) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Error error={error} />
|
||||
<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-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>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<Error error="No data found" />
|
||||
);
|
||||
if (!info) {
|
||||
return <Error error="No active page found" />;
|
||||
}
|
||||
|
||||
return <ServerInfo data={data} />;
|
||||
if (info.error) {
|
||||
return <Error error={info.error} />;
|
||||
}
|
||||
|
||||
return <ServerInfo data={info} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user