12 Commits

Author SHA1 Message Date
x d5a4fcbe5a bump version to 1.5 2025-03-19 22:08:35 +01:00
x c3be23d369 handle local domains 2025-03-19 22:08:30 +01:00
x 90bb276622 fix permissions (remove webrequest) 2025-03-19 22:08:20 +01:00
x 806072fbf1 bump version to 1.4 2025-03-18 15:56:41 +01:00
x baa277fbd9 fix manifest (dns server) 2025-03-18 15:56:20 +01:00
x 3e9fe9a199 bump version to 1.3 2025-03-18 12:55:38 +01:00
x a540f1eb0a add cloudflare 2025-03-18 12:55:32 +01:00
x 940b8bbec4 fix manifest 2025-03-18 12:55:17 +01:00
x 3419544ba4 firefox approved 2025-03-17 19:49:27 +01:00
x 87b4c93268 Create PRIVACY.md 2025-03-16 11:32:29 +01:00
x feaaa65960 add showcase 2025-03-15 22:05:41 +01:00
x 971a980def firefox support beta 2025-03-15 19:41:30 +01:00
10 changed files with 174 additions and 65 deletions
+36
View File
@@ -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`.
+4
View File
@@ -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. 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 ## ✨ Features
+13
View File
@@ -32,6 +32,8 @@ export default function ServerInfo({ data }: { data: ServerData }) {
Internal Network Internal Network
</h2> </h2>
</div> </div>
{data.ip && (
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<ServerIcon className="w-6 h-6 text-yellow-400 flex-shrink-0" /> <ServerIcon className="w-6 h-6 text-yellow-400 flex-shrink-0" />
<div> <div>
@@ -39,6 +41,17 @@ export default function ServerInfo({ data }: { data: ServerData }) {
<p className="font-medium">{data.ip}</p> <p className="font-medium">{data.ip}</p>
</div> </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> </div>
); );
} }
+25 -7
View File
@@ -42,25 +42,33 @@ async function handleTabUpdate(url: string) {
const apiResponse = await fetch(`https://ip.albert.lol/${ip}`) const apiResponse = await fetch(`https://ip.albert.lol/${ip}`)
const apiData = await apiResponse.json() 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) { } catch (error) {
console.error('Failed to handle tab update:', error) console.error('Failed to handle tab update:', error)
await updateIcon(null) await updateIcon(null)
} }
} }
chrome.tabs.onActivated.addListener(async activeInfo => { browser.tabs.onActivated.addListener(async activeInfo => {
const tab = await chrome.tabs.get(activeInfo.tabId) const tab = await browser.tabs.get(activeInfo.tabId)
if (tab.url) await handleTabUpdate(tab.url) 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) if (changeInfo.url) await handleTabUpdate(changeInfo.url)
}) })
export default defineBackground({ export default defineBackground({
main() { main() {
chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { browser.runtime.onMessage.addListener(
(request: any, _sender, sendResponse) => {
if (request.type === 'FETCH_SERVER_INFO') { if (request.type === 'FETCH_SERVER_INFO') {
;(async () => { ;(async () => {
try { try {
@@ -76,7 +84,13 @@ export default defineBackground({
const parsed = psl.parse(request.hostname) const parsed = psl.parse(request.hostname)
const origin = 'domain' in parsed ? parsed.domain : null const origin = 'domain' in parsed ? parsed.domain : null
await updateIcon(apiData.country) const country = apiData.country || null
const asn = apiData.org?.split(' ')[0]
let iconCode = country
if (!iconCode && asn === 'AS13335') {
iconCode = 'cloudflare'
}
await updateIcon(iconCode)
sendResponse({ sendResponse({
error: null, error: null,
@@ -99,6 +113,10 @@ export default defineBackground({
})() })()
return true return true
} }
})
sendResponse({ error: 'Unknown request type', data: null })
return true
}
)
}, },
}) })
+26 -5
View File
@@ -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() { export function useTabData() {
const [data, setData] = useState<ServerData | null>(null) const [data, setData] = useState<ServerData | null>(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
@@ -6,7 +15,7 @@ export function useTabData() {
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
const [tab] = await chrome.tabs.query({ const [tab] = await browser.tabs.query({
active: true, active: true,
currentWindow: true, currentWindow: true,
}) })
@@ -34,7 +43,7 @@ export function useTabData() {
return setData({ return setData({
origin: '', origin: '',
ip: hostname, ip: hostname,
hostname: url.href, hostname: '',
country: '', country: '',
city: '', city: '',
org: '', 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', type: 'FETCH_SERVER_INFO',
hostname: hostname, hostname: hostname,
}) })
@@ -53,7 +65,16 @@ export function useTabData() {
} }
if (response.error) { 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) { if (!response.data?.ip) {
@@ -63,7 +84,7 @@ export function useTabData() {
setData(response.data) setData(response.data)
setError(null) setError(null)
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch data') setError(err instanceof Error ? err.message : 'No data found')
setData(null) setData(null)
} finally { } finally {
setLoading(false) setLoading(false)
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "hostinfo", "name": "hostinfo",
"description": "Receive information of a domain directly in the browser when browsing a website", "description": "Receive information of a domain directly in the browser when browsing a website",
"private": true, "private": true,
"version": "1.1", "version": "1.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "wxt", "dev": "wxt",
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

+8 -3
View File
@@ -1,11 +1,16 @@
export async function updateIcon(countryCode: string | null) { export async function updateIcon(countryCode: string | null) {
const validCode = let validCode
if (countryCode === 'cloudflare') {
validCode = 'cloudflare'
} else {
validCode =
countryCode?.match(/^[A-Z]{2}$/i)?.[0]?.toLowerCase() || 'unknown' countryCode?.match(/^[A-Z]{2}$/i)?.[0]?.toLowerCase() || 'unknown'
}
const loadImageBitmap = async (code: string): Promise<ImageBitmap> => { const loadImageBitmap = async (code: string): Promise<ImageBitmap> => {
const url = chrome.runtime.getURL(`${code}.webp`) const url = browser.runtime.getURL('/')
try { try {
const response = await fetch(url) const response = await fetch(url + `${code}.webp`)
if (!response.ok) throw new Error('Flag not found') if (!response.ok) throw new Error('Flag not found')
const blob = await response.blob() const blob = await response.blob()
return await createImageBitmap(blob) return await createImageBitmap(blob)
+10
View File
@@ -13,3 +13,13 @@ export interface DNSEntry {
type: number type: number
data: string data: string
} }
export interface FetchServerInfoRequest {
type: 'FETCH_SERVER_INFO';
hostname: string;
}
export interface FetchServerInfoResponse {
error?: string;
data?: ServerData;
}
+8 -6
View File
@@ -1,17 +1,19 @@
import { defineConfig } from 'wxt'; import { defineConfig } from 'wxt'
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from '@tailwindcss/vite'
export default defineConfig({ export default defineConfig({
extensionApi: 'chrome',
modules: ['@wxt-dev/module-react'], modules: ['@wxt-dev/module-react'],
manifest: { manifest: {
permissions: ['tabs', 'activeTab', 'webRequest', 'file://*', 'debugger'], permissions: ['tabs', 'activeTab'],
host_permissions: ['https://ip.albert.lol/*', 'https://dns.google/*', 'https://flagcdn.com/*'], host_permissions: [
'https://ip.albert.lol/*',
'https://cloudflare-dns.com/*',
],
action: { action: {
default_title: 'Host Info', default_title: 'Host Info',
}, },
}, },
vite: () => ({ vite: () => ({
plugins: [tailwindcss()], plugins: [tailwindcss()],
}),
}) })
});