half way there

This commit is contained in:
2026-02-03 04:24:11 +01:00
parent 991cd10f40
commit 2973e038d6
274 changed files with 992 additions and 964 deletions
+1 -1
View File
@@ -305,4 +305,4 @@ export const codes: Record<string, string> = {
za: 'South Africa',
zm: 'Zambia',
zw: 'Zimbabwe',
}
};
-72
View File
@@ -1,72 +0,0 @@
import { browser } from 'webextension-polyfill-ts'
export async function updateIcon(countryCode: string | null) {
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 = browser.runtime.getURL('/')
try {
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)
} catch (error) {
throw new Error(
`Failed to load flag: ${code} - ${
error instanceof Error ? error.message : String(error)
}`
)
}
}
const processImage = async (bitmap: ImageBitmap) => {
const canvas = new OffscreenCanvas(128, 128)
const ctx = canvas.getContext('2d')!
const ratio = Math.min(
canvas.width / bitmap.width,
canvas.height / bitmap.height
)
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(
bitmap,
0,
0,
bitmap.width,
bitmap.height,
(canvas.width - bitmap.width * ratio) / 2,
(canvas.height - bitmap.height * ratio) / 2,
bitmap.width * ratio,
bitmap.height * ratio
)
const sizes = [16, 32, 48, 128]
return Object.fromEntries(
sizes.map(size => {
const resizedCanvas = new OffscreenCanvas(size, size)
const ctx = resizedCanvas.getContext('2d')!
ctx.drawImage(canvas, 0, 0, size, size)
return [size, ctx.getImageData(0, 0, size, size)]
})
)
}
try {
const bitmap = await loadImageBitmap(validCode)
browser.action.setIcon({ imageData: await processImage(bitmap) })
} catch (error) {
console.error('Primary flag failed, trying unknown:', error)
try {
const unknownBitmap = await loadImageBitmap('unknown')
browser.action.setIcon({ imageData: await processImage(unknownBitmap) })
} catch (fallbackError) {
console.error('Both flag assets failed:', fallbackError)
}
}
}
+33
View File
@@ -0,0 +1,33 @@
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 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,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 = {
isValidIP(ip: string): boolean {
const clean = ip.replace(/^\[|\]$/g, '');
return ipv4Regex.test(clean) || ipv6Regex.test(clean);
},
isLocalOrBogon(ip: string): boolean {
const clean = ip.replace(/^\[|\]$/g, '');
// IPv6 Local/Bogon
if (clean === '::1' || clean === '::') return true;
if (clean.toLowerCase().startsWith('fe80:')) return true;
if (clean.toLowerCase().startsWith('fc00:')) return true;
if (clean.toLowerCase().startsWith('fd00:')) return true;
if (!clean.includes('.')) return false;
// IPv4 Local/Bogon
const parts = clean.split('.').map(Number);
if (parts.length !== 4) return false;
if (parts[0] === 10) return true;
if (parts[0] === 192 && parts[1] === 168) return true;
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
if (parts[0] === 127) return true;
if (parts[0] === 169 && parts[1] === 254) return true;
return false;
}
};
-25
View File
@@ -1,25 +0,0 @@
export interface ServerData {
origin: string
ip: string
hostname: string | null
country: string | null
city: string | null
org: string
isLocal?: boolean
isBrowserResource?: boolean
}
export interface DNSEntry {
type: number
data: string
}
export interface FetchServerInfoRequest {
type: 'FETCH_SERVER_INFO'
hostname: string
}
export interface FetchServerInfoResponse {
error?: string
data?: ServerData
}
+31
View File
@@ -0,0 +1,31 @@
import type { HostInfo } from './types';
export const StorageService = {
/**
* Returns the storage key for a specific tab with the required type prefix
*/
getKey(tabId: number): `session:${string}` {
return `session:host-info-${tabId}`;
},
/**
* Save host info for a tab
*/
async set(tabId: number, data: HostInfo) {
await storage.setItem<HostInfo>(this.getKey(tabId), data);
},
/**
* Get host info for a tab
*/
async get(tabId: number): Promise<HostInfo | null> {
return await storage.getItem<HostInfo>(this.getKey(tabId));
},
/**
* Clear data when a tab is closed
*/
async remove(tabId: number) {
await storage.removeItem(this.getKey(tabId));
}
};
+38
View File
@@ -0,0 +1,38 @@
export interface HostLocation {
countryCode: string | null;
countryName: string | null;
city: string | null;
region: string | null;
timezone: string | null;
}
export interface HostNetwork {
ip: string;
asn: string | null;
org: string | null;
hostname: string | null;
isLocal: boolean;
isBogon: boolean;
}
export interface HostInfo {
url: string;
domain: string;
network: HostNetwork | null;
location: HostLocation | null;
error: string | null;
loading: boolean;
isBrowserResource: boolean;
}
export interface GeoApiResponse {
ip: string;
hostname?: string;
city?: string;
region?: string;
country?: string;
loc?: string;
org?: string;
timezone?: string;
bogon?: boolean;
}