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",
|
"name": "hostinfo",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "latest",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "latest",
|
||||||
"clsx": "^2.1.1",
|
"react": "latest",
|
||||||
"react": "^19.2.4",
|
"react-dom": "latest",
|
||||||
"react-dom": "^19.2.4",
|
"tailwindcss": "latest",
|
||||||
"tailwind-merge": "^3.5.0",
|
|
||||||
"tailwindcss": "^4.2.1",
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chrome": "^0.1.37",
|
"@types/chrome": "latest",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "latest",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "latest",
|
||||||
"@wxt-dev/module-react": "^1.1.5",
|
"@wxt-dev/module-react": "latest",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "latest",
|
||||||
"wxt": "^0.20.18",
|
"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/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=="],
|
"@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=="],
|
"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-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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
|
||||||
|
|
||||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
"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/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=="],
|
"@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=="],
|
"@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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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 }) => {
|
export const CopyButton = ({ text }: { text: string }) => {
|
||||||
const [copied, setCopied] = useState(false);
|
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) => {
|
const handleCopy = async (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ export const PublicNetworkView = ({ data, domain }: { data: GeoData, domain: str
|
|||||||
<InfoRow
|
<InfoRow
|
||||||
icon={GlobeAltIcon}
|
icon={GlobeAltIcon}
|
||||||
label="Hostname"
|
label="Hostname"
|
||||||
value={domain}
|
value={data.hostname || 'N/A'}
|
||||||
canCopy
|
canCopy
|
||||||
iconColor="text-indigo-500"
|
iconColor="text-indigo-500"
|
||||||
/>
|
/>
|
||||||
<InfoRow
|
<InfoRow
|
||||||
icon={MapPinIcon}
|
icon={MapPinIcon}
|
||||||
label="Location"
|
label="Location"
|
||||||
value={data.countryName || 'Unknown Location'}
|
value={data.countryName || 'N/A'}
|
||||||
iconColor="text-emerald-500"
|
iconColor="text-emerald-500"
|
||||||
/>
|
/>
|
||||||
<InfoRow
|
<InfoRow
|
||||||
|
|||||||
+90
-26
@@ -3,11 +3,6 @@ import { GeoService } from '@/services/geo';
|
|||||||
import { IconService } from '@/services/icon';
|
import { IconService } from '@/services/icon';
|
||||||
import { DnsService } from '@/services/dns';
|
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>();
|
const tabStates = new Map<number, TabState>();
|
||||||
|
|
||||||
function getDomain(url: string) {
|
function getDomain(url: string) {
|
||||||
@@ -19,8 +14,8 @@ function getDomain(url: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyIconForState(tabId: number, state: TabState) {
|
function applyIconForState(tabId: number, state: TabState) {
|
||||||
const isSystem = SYSTEM_PROTOCOLS.some(p => state.url.startsWith(p));
|
const isWebProtocol = state.url.startsWith('http://') || state.url.startsWith('https://');
|
||||||
if (isSystem) {
|
if (!isWebProtocol) {
|
||||||
IconService.updateIcon(tabId, null, true);
|
IconService.updateIcon(tabId, null, true);
|
||||||
} else if (state.status === 'success' && state.data) {
|
} else if (state.status === 'success' && state.data) {
|
||||||
let code = state.data.countryCode;
|
let code = state.data.countryCode;
|
||||||
@@ -33,7 +28,7 @@ function applyIconForState(tabId: number, state: TabState) {
|
|||||||
|
|
||||||
async function initTab(tabId: number, url: string, resolveDns = false) {
|
async function initTab(tabId: number, url: string, resolveDns = false) {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
const isSystem = SYSTEM_PROTOCOLS.some(p => url.startsWith(p));
|
const isWebProtocol = url.startsWith('http://') || url.startsWith('https://');
|
||||||
const domain = getDomain(url);
|
const domain = getDomain(url);
|
||||||
|
|
||||||
let currentState = tabStates.get(tabId);
|
let currentState = tabStates.get(tabId);
|
||||||
@@ -63,26 +58,41 @@ async function initTab(tabId: number, url: string, resolveDns = false) {
|
|||||||
const newState: TabState = {
|
const newState: TabState = {
|
||||||
url,
|
url,
|
||||||
domain,
|
domain,
|
||||||
status: isSystem || hasExistingData ? 'success' : 'loading',
|
status: !isWebProtocol || hasExistingData ? 'success' : 'loading',
|
||||||
data: hasExistingData ? currentState!.data : null,
|
data: hasExistingData ? currentState!.data : null,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
lastUpdated: Date.now()
|
lastUpdated: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
tabStates.set(tabId, newState);
|
tabStates.set(tabId, newState);
|
||||||
StorageService.setTabState(tabId, newState).catch(() => { });
|
await StorageService.setTabState(tabId, newState).catch(() => { });
|
||||||
|
|
||||||
applyIconForState(tabId, newState);
|
applyIconForState(tabId, newState);
|
||||||
|
|
||||||
if (!isSystem && !hasExistingData && resolveDns) {
|
if (!isWebProtocol && !hasExistingData) {
|
||||||
const ip = await DnsService.resolve(domain);
|
const performDnsFallback = async () => {
|
||||||
if (ip) {
|
const state = tabStates.get(tabId);
|
||||||
await processIp(tabId, url, ip);
|
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 {
|
} else {
|
||||||
await updateState(tabId, {
|
setTimeout(performDnsFallback, 1500);
|
||||||
status: 'error',
|
|
||||||
errorMessage: 'Could not resolve host'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,18 +137,19 @@ async function processIp(tabId: number, url: string, ip: string) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
tabStates.set(tabId, newState);
|
tabStates.set(tabId, newState);
|
||||||
await StorageService.setTabState(tabId, newState);
|
await StorageService.setTabState(tabId, newState).catch(() => { });
|
||||||
|
|
||||||
applyIconForState(tabId, newState);
|
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);
|
let current = tabStates.get(tabId);
|
||||||
if (!current) {
|
if (!current) {
|
||||||
current = await StorageService.getTabState(tabId) || undefined;
|
current = await StorageService.getTabState(tabId) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
|
if (expectedUrl && current.url !== expectedUrl) return;
|
||||||
const newState = { ...current, ...updates };
|
const newState = { ...current, ...updates };
|
||||||
tabStates.set(tabId, newState);
|
tabStates.set(tabId, newState);
|
||||||
await StorageService.setTabState(tabId, newState);
|
await StorageService.setTabState(tabId, newState);
|
||||||
@@ -148,6 +159,32 @@ async function updateState(tabId: number, updates: Partial<TabState>) {
|
|||||||
|
|
||||||
export default defineBackground({
|
export default defineBackground({
|
||||||
main() {
|
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) => {
|
browser.webNavigation.onBeforeNavigate.addListener((details) => {
|
||||||
if (details.frameId !== 0) return;
|
if (details.frameId !== 0) return;
|
||||||
initTab(details.tabId, details.url, false);
|
initTab(details.tabId, details.url, false);
|
||||||
@@ -194,13 +231,16 @@ export default defineBackground({
|
|||||||
}
|
}
|
||||||
const ip = await DnsService.resolve(hostname);
|
const ip = await DnsService.resolve(hostname);
|
||||||
|
|
||||||
|
const currentState = tabStates.get(details.tabId);
|
||||||
|
if (currentState?.status !== 'loading' || currentState.url !== details.url) return;
|
||||||
|
|
||||||
if (ip) {
|
if (ip) {
|
||||||
await processIp(details.tabId, details.url, ip);
|
await processIp(details.tabId, details.url, ip);
|
||||||
} else {
|
} else {
|
||||||
await updateState(details.tabId, {
|
await updateState(details.tabId, {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
errorMessage: 'Could not resolve host'
|
errorMessage: 'Could not resolve host'
|
||||||
});
|
}, details.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -208,12 +248,23 @@ export default defineBackground({
|
|||||||
browser.webRequest.onErrorOccurred.addListener(
|
browser.webRequest.onErrorOccurred.addListener(
|
||||||
async (details) => {
|
async (details) => {
|
||||||
if (details.type !== 'main_frame') return;
|
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, {
|
await updateState(details.tabId, {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
errorMessage: details.error
|
errorMessage: details.error
|
||||||
});
|
}, details.url);
|
||||||
},
|
},
|
||||||
{ urls: ['<all_urls>'] }
|
{ urls: ['<all_urls>'] }
|
||||||
);
|
);
|
||||||
@@ -229,6 +280,9 @@ export default defineBackground({
|
|||||||
const state = tabStates.get(tab.id!);
|
const state = tabStates.get(tab.id!);
|
||||||
if (state) {
|
if (state) {
|
||||||
applyIconForState(tab.id!, state);
|
applyIconForState(tab.id!, state);
|
||||||
|
if (state.status === 'loading' && Date.now() - state.lastUpdated > 2000) {
|
||||||
|
initTab(tab.id!, tab.url, true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
initTab(tab.id!, tab.url, true);
|
initTab(tab.id!, tab.url, true);
|
||||||
}
|
}
|
||||||
@@ -237,9 +291,19 @@ export default defineBackground({
|
|||||||
|
|
||||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||||
if (changeInfo.status && tab.url) {
|
if (changeInfo.status && tab.url) {
|
||||||
const state = tabStates.get(tabId);
|
if (tab.url) {
|
||||||
if (state) {
|
const state = tabStates.get(tabId);
|
||||||
applyIconForState(tabId, state);
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+16
-2
@@ -8,7 +8,10 @@ export function useHostInfo() {
|
|||||||
const fetchInfo = async () => {
|
const fetchInfo = async () => {
|
||||||
try {
|
try {
|
||||||
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
|
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);
|
const data = await StorageService.getTabState(tab.id);
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -16,6 +19,9 @@ export function useHostInfo() {
|
|||||||
setInfo(data);
|
setInfo(data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
if (data.status === 'loading' && Date.now() - data.lastUpdated > 2000) {
|
||||||
|
browser.runtime.sendMessage({ type: 'INIT_TAB', tabId: tab.id, url: tab.url })
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tab.url) {
|
if (tab.url) {
|
||||||
await browser.runtime.sendMessage({ type: 'INIT_TAB', tabId: tab.id, url: tab.url });
|
await browser.runtime.sendMessage({ type: 'INIT_TAB', tabId: tab.id, url: tab.url });
|
||||||
@@ -32,7 +38,15 @@ export function useHostInfo() {
|
|||||||
|
|
||||||
const listener = (changes: any, areaName: string) => {
|
const listener = (changes: any, areaName: string) => {
|
||||||
if (areaName === 'session' || areaName === 'local') {
|
if (areaName === 'session' || areaName === 'local') {
|
||||||
fetchInfo();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -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": "2.0.0",
|
"version": "2.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "wxt",
|
"dev": "wxt",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"@types/chrome": "^0.1.37",
|
"@types/chrome": "^0.1.37",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@wxt-dev/module-react": "^1.1.5",
|
"@wxt-dev/module-react": "^1.2.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"wxt": "^0.20.18"
|
"wxt": "^0.20.18"
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-1
@@ -1,13 +1,20 @@
|
|||||||
export const DnsService = {
|
export const DnsService = {
|
||||||
async resolve(hostname: string): Promise<string | null> {
|
async resolve(hostname: string): Promise<string | null> {
|
||||||
|
if (hostname === 'localhost' || hostname.endsWith('.local')) {
|
||||||
|
return '127.0.0.1';
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://cloudflare-dns.com/dns-query?name=${hostname}&type=A`,
|
`https://cloudflare-dns.com/dns-query?name=${hostname}&type=A`,
|
||||||
{
|
{
|
||||||
headers: { accept: 'application/dns-json' },
|
headers: { accept: 'application/dns-json' },
|
||||||
credentials: 'omit'
|
credentials: 'omit',
|
||||||
|
signal: controller.signal,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
if (!response.ok) return null;
|
if (!response.ok) return null;
|
||||||
|
|
||||||
|
|||||||
+16
-4
@@ -13,11 +13,15 @@ export const GeoService = {
|
|||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||||
const res = await fetch(`https://ip.albert.lol/${ip}`, {
|
const res = await fetch(`https://ip.albert.lol/${ip}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
cache: 'force-cache',
|
cache: 'force-cache',
|
||||||
credentials: 'omit'
|
credentials: 'omit',
|
||||||
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`API Error ${res.status}`);
|
if (!res.ok) throw new Error(`API Error ${res.status}`);
|
||||||
|
|
||||||
@@ -28,8 +32,9 @@ export const GeoService = {
|
|||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Geo lookup failed for', ip, error);
|
console.warn('Geo lookup failed for', ip, error);
|
||||||
return {
|
const failedData: GeoData = {
|
||||||
ip,
|
ip,
|
||||||
|
hostname: null,
|
||||||
countryCode: null,
|
countryCode: null,
|
||||||
countryName: 'Unknown',
|
countryName: 'Unknown',
|
||||||
city: null,
|
city: null,
|
||||||
@@ -40,12 +45,16 @@ export const GeoService = {
|
|||||||
isLocal: false,
|
isLocal: false,
|
||||||
isBogon: false
|
isBogon: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await StorageService.setGeoCache(ip, failedData);
|
||||||
|
return failedData;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getLocalData(ip: string): GeoData {
|
getLocalData(ip: string): GeoData {
|
||||||
return {
|
return {
|
||||||
ip,
|
ip,
|
||||||
|
hostname: null,
|
||||||
countryCode: null,
|
countryCode: null,
|
||||||
countryName: 'Local Network',
|
countryName: 'Local Network',
|
||||||
city: null,
|
city: null,
|
||||||
@@ -59,17 +68,20 @@ export const GeoService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
transform(ip: string, apiData: any): GeoData {
|
transform(ip: string, apiData: any): GeoData {
|
||||||
|
const asnMatch = apiData.org?.match(/^AS\d+/i);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ip,
|
ip,
|
||||||
|
hostname: apiData.hostname || null,
|
||||||
countryCode: apiData.country || null,
|
countryCode: apiData.country || null,
|
||||||
countryName: apiData.country ? (codes[apiData.country.toLowerCase()] || apiData.country) : null,
|
countryName: apiData.country ? (codes[apiData.country.toLowerCase()] || apiData.country) : null,
|
||||||
city: apiData.city || null,
|
city: apiData.city || null,
|
||||||
region: apiData.region || null,
|
region: apiData.region || null,
|
||||||
org: apiData.org || null,
|
org: apiData.org || null,
|
||||||
asn: apiData.org?.split(' ')[0] || null,
|
asn: asnMatch ? asnMatch[0].toUpperCase() : null,
|
||||||
timezone: apiData.timezone || null,
|
timezone: apiData.timezone || null,
|
||||||
isLocal: false,
|
isLocal: false,
|
||||||
isBogon: apiData.bogon || 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]))$/;
|
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 = {
|
export const IpUtils = {
|
||||||
@@ -13,8 +13,10 @@ export const IpUtils = {
|
|||||||
const clean = ip.replace(/^\[|\]$/g, '');
|
const clean = ip.replace(/^\[|\]$/g, '');
|
||||||
|
|
||||||
if (clean === '::1' || clean === '::') return true;
|
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('.')) {
|
if (clean.includes('.')) {
|
||||||
const parts = clean.split('.').map(Number);
|
const parts = clean.split('.').map(Number);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const StorageService = {
|
|||||||
try {
|
try {
|
||||||
const res = await browser.storage.session.get(key);
|
const res = await browser.storage.session.get(key);
|
||||||
if (res[key]) return res[key] as TabState;
|
if (res[key]) return res[key] as TabState;
|
||||||
|
return null;
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
const res = await browser.storage.local.get(`session_${key}`);
|
const res = await browser.storage.local.get(`session_${key}`);
|
||||||
@@ -53,5 +54,23 @@ export const StorageService = {
|
|||||||
const key = `geo_${ip.replace(/:/g, '_')}`;
|
const key = `geo_${ip.replace(/:/g, '_')}`;
|
||||||
const entry: CacheEntry = { data, timestamp: Date.now() };
|
const entry: CacheEntry = { data, timestamp: Date.now() };
|
||||||
await browser.storage.local.set({ [key]: entry });
|
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 {
|
export interface GeoData {
|
||||||
ip: string;
|
ip: string;
|
||||||
|
hostname: string | null;
|
||||||
countryCode: string | null;
|
countryCode: string | null;
|
||||||
countryName: string | null;
|
countryName: string | null;
|
||||||
city: string | null;
|
city: string | null;
|
||||||
|
|||||||
+3
-2
@@ -7,12 +7,13 @@ export default defineConfig({
|
|||||||
manifest: {
|
manifest: {
|
||||||
name: 'Host Info',
|
name: 'Host Info',
|
||||||
description: 'Get host information',
|
description: 'Get host information',
|
||||||
version: '2.0.0',
|
version: '2.2',
|
||||||
permissions: [
|
permissions: [
|
||||||
'tabs',
|
'tabs',
|
||||||
'webRequest',
|
'webRequest',
|
||||||
'webNavigation',
|
'webNavigation',
|
||||||
'storage'
|
'storage',
|
||||||
|
'alarms'
|
||||||
],
|
],
|
||||||
host_permissions: [
|
host_permissions: [
|
||||||
'<all_urls>',
|
'<all_urls>',
|
||||||
|
|||||||
Reference in New Issue
Block a user