mirror of
https://github.com/skidoodle/hostinfo
synced 2026-04-28 17:47:36 +02:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
d5a4fcbe5a
|
|||
|
c3be23d369
|
|||
|
90bb276622
|
|||
|
806072fbf1
|
|||
|
baa277fbd9
|
|||
|
3e9fe9a199
|
|||
|
a540f1eb0a
|
|||
|
940b8bbec4
|
|||
| 3419544ba4 | |||
| 87b4c93268 | |||
| feaaa65960 | |||
|
971a980def
|
+36
@@ -0,0 +1,36 @@
|
||||
# Privacy Policy
|
||||
|
||||
**Last Updated:** 2025-03-16
|
||||
|
||||
Thank you for using HostInfo (the "Extension"). This Privacy Policy explains how we handle your information when you use our Extension. Please read this policy carefully to understand our practices regarding your data.
|
||||
|
||||
## 1. **Information We Do Not Collect**
|
||||
|
||||
HostInfo does not collect, store, or transmit any personal or sensitive information from its users. This includes, but is not limited to:
|
||||
|
||||
- Personal identification information (e.g., name, email address, phone number).
|
||||
- Browsing history or activity.
|
||||
- IP addresses or location data (except as described below for GeoIP lookups).
|
||||
- Any other data that could be used to identify you.
|
||||
|
||||
## 2. **Third-Party Services**
|
||||
|
||||
The Extension uses the following third-party services to provide functionality:
|
||||
|
||||
- **Cloudflare DNS (`cloudflare-dns.com`)**: The Extension queries hostnames using Cloudflare's DNS service to resolve website IP addresses. This is done to provide accurate and fast DNS resolution. Cloudflare's privacy policy can be found [here](https://www.cloudflare.com/privacypolicy/).
|
||||
|
||||
- **GeoIP Lookups (`ip.albert.lol`)**: The Extension uses `ip.albert.lol` to perform GeoIP lookups. This service may receive your IP address to determine your approximate geographic location (e.g., country or region). No other personal information is shared with this service.
|
||||
|
||||
Neither of these services is used to collect, store, or track your personal information. The data sent to these services is used solely for the purpose of providing the Extension's functionality.
|
||||
|
||||
## 3. **How We Use Information**
|
||||
|
||||
Since we do not collect any personal information, there is no data to use, share, or sell. The Extension operates locally on your device and only communicates with the aforementioned third-party services for DNS resolution and GeoIP lookups.
|
||||
|
||||
## 4. **Changes to This Privacy Policy**
|
||||
|
||||
We may update this Privacy Policy from time to time. If we make any changes, we will update the "Last Updated" date at the top of this policy. We encourage you to review this Privacy Policy periodically to stay informed about how we are protecting your information.
|
||||
|
||||
## 5. **Contact Us**
|
||||
|
||||
If you have any questions or concerns about this Privacy Policy, please feel free to contact us at `contact@albert.lol`.
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
A browser extension built with [WXT.dev](https://wxt.dev) and React that lets you discover the origin of the website you're visiting. With a single click, you can view detailed information such as the **country of origin**, **IP address**, **ASN (Autonomous System Number)**, and more. You can also quickly search for the website's details on [Censys](https://censys.io) for deeper insights.
|
||||
|
||||
<img src="https://github.com/user-attachments/assets/83a6316c-54b8-41a8-8d43-c794a5f62696" alt="Showcase" width="200"/>
|
||||
|
||||
<a href="https://addons.mozilla.org/addon/hostinfo/"><img src="https://github.com/user-attachments/assets/4e69214c-c11a-4202-919a-fac7d58dbb55" alt="Get hostinfo for Firefox"></a>
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
@@ -4,8 +4,8 @@ import { codes } from '@/utils/codes';
|
||||
export default function ServerInfo({ data }: { data: ServerData }) {
|
||||
|
||||
const countryName = data.country
|
||||
? codes[data.country.toLowerCase()] || "N/A"
|
||||
: "N/A";
|
||||
? codes[data.country.toLowerCase()] || "N/A"
|
||||
: "N/A";
|
||||
|
||||
if (data.isBrowserResource) {
|
||||
return (
|
||||
@@ -32,13 +32,26 @@ export default function ServerInfo({ data }: { data: ServerData }) {
|
||||
Internal Network
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<ServerIcon className="w-6 h-6 text-yellow-400 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-gray-400">IP Address</p>
|
||||
<p className="font-medium">{data.ip}</p>
|
||||
|
||||
{data.ip && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<ServerIcon className="w-6 h-6 text-yellow-400 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-gray-400">IP Address</p>
|
||||
<p className="font-medium">{data.ip}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.hostname && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<LinkIcon className="w-6 h-6 text-green-400 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-gray-400">Hostname</p>
|
||||
<p className="font-medium break-all">{data.hostname}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+58
-40
@@ -42,63 +42,81 @@ async function handleTabUpdate(url: string) {
|
||||
const apiResponse = await fetch(`https://ip.albert.lol/${ip}`)
|
||||
const apiData = await apiResponse.json()
|
||||
|
||||
await updateIcon(apiData.country || null)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
chrome.tabs.onActivated.addListener(async activeInfo => {
|
||||
const tab = await chrome.tabs.get(activeInfo.tabId)
|
||||
browser.tabs.onActivated.addListener(async activeInfo => {
|
||||
const tab = await browser.tabs.get(activeInfo.tabId)
|
||||
if (tab.url) await handleTabUpdate(tab.url)
|
||||
})
|
||||
|
||||
chrome.tabs.onUpdated.addListener(async (_tabId, changeInfo) => {
|
||||
browser.tabs.onUpdated.addListener(async (_tabId, changeInfo) => {
|
||||
if (changeInfo.url) await handleTabUpdate(changeInfo.url)
|
||||
})
|
||||
|
||||
export default defineBackground({
|
||||
main() {
|
||||
chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
|
||||
if (request.type === 'FETCH_SERVER_INFO') {
|
||||
;(async () => {
|
||||
try {
|
||||
const ip = await resolveARecord(request.hostname)
|
||||
if (!ip) {
|
||||
sendResponse({ error: 'DNS resolution failed', data: null })
|
||||
return
|
||||
browser.runtime.onMessage.addListener(
|
||||
(request: any, _sender, sendResponse) => {
|
||||
if (request.type === 'FETCH_SERVER_INFO') {
|
||||
;(async () => {
|
||||
try {
|
||||
const ip = await resolveARecord(request.hostname)
|
||||
if (!ip) {
|
||||
sendResponse({ error: 'DNS resolution failed', data: null })
|
||||
return
|
||||
}
|
||||
|
||||
const apiResponse = await fetch(`https://ip.albert.lol/${ip}`)
|
||||
const apiData = await apiResponse.json()
|
||||
|
||||
const parsed = psl.parse(request.hostname)
|
||||
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,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
await updateIcon(null)
|
||||
sendResponse({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
data: null,
|
||||
})
|
||||
}
|
||||
})()
|
||||
return true
|
||||
}
|
||||
|
||||
const apiResponse = await fetch(`https://ip.albert.lol/${ip}`)
|
||||
const apiData = await apiResponse.json()
|
||||
|
||||
const parsed = psl.parse(request.hostname)
|
||||
const origin = 'domain' in parsed ? parsed.domain : null
|
||||
|
||||
await updateIcon(apiData.country)
|
||||
|
||||
sendResponse({
|
||||
error: null,
|
||||
data: {
|
||||
origin,
|
||||
ip: apiData.ip,
|
||||
hostname: apiData.hostname || 'N/A',
|
||||
country: apiData.country || null,
|
||||
city: apiData.city || null,
|
||||
org: apiData.org,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
await updateIcon(null)
|
||||
sendResponse({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
data: null,
|
||||
})
|
||||
}
|
||||
})()
|
||||
sendResponse({ error: 'Unknown request type', data: null })
|
||||
return true
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
+26
-5
@@ -1,3 +1,12 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { isPrivateIP } from '@/utils'
|
||||
import {
|
||||
FetchServerInfoRequest,
|
||||
FetchServerInfoResponse,
|
||||
ServerData,
|
||||
} from '@/utils/model'
|
||||
import browser from 'webextension-polyfill'
|
||||
|
||||
export function useTabData() {
|
||||
const [data, setData] = useState<ServerData | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -6,7 +15,7 @@ export function useTabData() {
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({
|
||||
const [tab] = await browser.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
})
|
||||
@@ -34,7 +43,7 @@ export function useTabData() {
|
||||
return setData({
|
||||
origin: '',
|
||||
ip: hostname,
|
||||
hostname: url.href,
|
||||
hostname: '',
|
||||
country: '',
|
||||
city: '',
|
||||
org: '',
|
||||
@@ -43,7 +52,10 @@ export function useTabData() {
|
||||
})
|
||||
}
|
||||
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
const response = await browser.runtime.sendMessage<
|
||||
FetchServerInfoRequest,
|
||||
FetchServerInfoResponse
|
||||
>({
|
||||
type: 'FETCH_SERVER_INFO',
|
||||
hostname: hostname,
|
||||
})
|
||||
@@ -53,7 +65,16 @@ export function useTabData() {
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error)
|
||||
return setData({
|
||||
origin: '',
|
||||
ip: '',
|
||||
hostname: hostname,
|
||||
country: '',
|
||||
city: '',
|
||||
org: '',
|
||||
isLocal: true,
|
||||
isBrowserResource: false,
|
||||
})
|
||||
}
|
||||
|
||||
if (!response.data?.ip) {
|
||||
@@ -63,7 +84,7 @@ export function useTabData() {
|
||||
setData(response.data)
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch data')
|
||||
setError(err instanceof Error ? err.message : 'No data found')
|
||||
setData(null)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "hostinfo",
|
||||
"description": "Receive information of a domain directly in the browser when browsing a website",
|
||||
"private": true,
|
||||
"version": "1.1",
|
||||
"version": "1.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
+9
-4
@@ -1,11 +1,16 @@
|
||||
export async function updateIcon(countryCode: string | null) {
|
||||
const validCode =
|
||||
countryCode?.match(/^[A-Z]{2}$/i)?.[0]?.toLowerCase() || 'unknown'
|
||||
let validCode
|
||||
if (countryCode === 'cloudflare') {
|
||||
validCode = 'cloudflare'
|
||||
} else {
|
||||
validCode =
|
||||
countryCode?.match(/^[A-Z]{2}$/i)?.[0]?.toLowerCase() || 'unknown'
|
||||
}
|
||||
|
||||
const loadImageBitmap = async (code: string): Promise<ImageBitmap> => {
|
||||
const url = chrome.runtime.getURL(`${code}.webp`)
|
||||
const url = browser.runtime.getURL('/')
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
const response = await fetch(url + `${code}.webp`)
|
||||
if (!response.ok) throw new Error('Flag not found')
|
||||
const blob = await response.blob()
|
||||
return await createImageBitmap(blob)
|
||||
|
||||
@@ -13,3 +13,13 @@ export interface DNSEntry {
|
||||
type: number
|
||||
data: string
|
||||
}
|
||||
|
||||
export interface FetchServerInfoRequest {
|
||||
type: 'FETCH_SERVER_INFO';
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
export interface FetchServerInfoResponse {
|
||||
error?: string;
|
||||
data?: ServerData;
|
||||
}
|
||||
|
||||
+9
-7
@@ -1,17 +1,19 @@
|
||||
import { defineConfig } from 'wxt';
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from 'wxt'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
export default defineConfig({
|
||||
extensionApi: 'chrome',
|
||||
modules: ['@wxt-dev/module-react'],
|
||||
manifest: {
|
||||
permissions: ['tabs', 'activeTab', 'webRequest', 'file://*', 'debugger'],
|
||||
host_permissions: ['https://ip.albert.lol/*', 'https://dns.google/*', 'https://flagcdn.com/*'],
|
||||
permissions: ['tabs', 'activeTab'],
|
||||
host_permissions: [
|
||||
'https://ip.albert.lol/*',
|
||||
'https://cloudflare-dns.com/*',
|
||||
],
|
||||
action: {
|
||||
default_title: 'Host Info',
|
||||
},
|
||||
},
|
||||
vite: () => ({
|
||||
plugins: [tailwindcss()],
|
||||
})
|
||||
});
|
||||
}),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user