From 5767e222cbb0d7fab644edcbe586701d93d8135d Mon Sep 17 00:00:00 2001 From: 0x1eef <0x1eef@protonmail.com> Date: Tue, 26 Sep 2023 21:57:29 -0300 Subject: [PATCH] Add source --- .editorconfig | 7 ++++ .eslintrc.js | 28 +++++++++++++ .gitignore | 2 + .projectile | 2 + README.md | 46 ++++++++++++++++++++++ package-lock.json | 32 +++++++++++++++ package.json | 4 +- src/_locales/en/messages.json | 26 +++++++++++++ src/css/components/Response.css | 10 +++++ src/css/index.css | 52 +++++++++++++++++++++++++ src/css/label.css | 17 ++++++++ src/html/browseraction.html | 15 +++++++ src/images/icons/icon128.png | Bin 0 -> 1632 bytes src/images/icons/icon16.png | Bin 0 -> 321 bytes src/js/components/App.tsx | 15 +++++++ src/js/components/ResponseRenderer.tsx | 39 +++++++++++++++++++ src/js/hooks/useWebService.ts | 19 +++++++++ src/js/index.tsx | 8 ++++ src/js/lib/response.ts | 30 ++++++++++++++ src/manifest.json | 15 +++++++ tsconfig.json | 17 ++++++++ webpack.config.js | 42 ++++++++++++++++++++ 22 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .projectile create mode 100644 README.md create mode 100644 src/_locales/en/messages.json create mode 100644 src/css/components/Response.css create mode 100644 src/css/index.css create mode 100644 src/css/label.css create mode 100644 src/html/browseraction.html create mode 100644 src/images/icons/icon128.png create mode 100644 src/images/icons/icon16.png create mode 100644 src/js/components/App.tsx create mode 100644 src/js/components/ResponseRenderer.tsx create mode 100644 src/js/hooks/useWebService.ts create mode 100644 src/js/index.tsx create mode 100644 src/js/lib/response.ts create mode 100644 src/manifest.json create mode 100644 tsconfig.json create mode 100644 webpack.config.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8a42b78 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*] +end_of_line = lf +insert_final_newline = true + +[{*.js,*.ts,*.tsx,*.json,*.css}] +indent_style = space +indent_size = 2 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..121f232 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,28 @@ +module.exports = { + extends: ["standard-with-typescript", "standard-jsx", "prettier"], + parserOptions: { + project: "./tsconfig.json", + }, + rules: { + "@typescript-eslint/member-delimiter-style": 2, + "@typescript-eslint/semi": ["error", "always"], + "@typescript-eslint/no-extra-semi": "error", + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/strict-boolean-expressions": 0, + "@typescript-eslint/no-floating-promises": 0, + "@typescript-eslint/prefer-nullish-coalescing": 0, + "@typescript-eslint/restrict-template-expressions": 0, + "@typescript-eslint/promise-function-async": 0, + "@typescript-eslint/consistent-type-definitions": 0, + "@typescript-eslint/no-misused-promises": ["error", {"checksConditionals": false}], + "@typescript-eslint/no-redeclare": 0, + "@typescript-eslint/no-non-null-assertion": 0, + "@typescript-eslint/member-delimiter-style": 0, + "import/no-absolute-path": 0, + "no-return-assign": 0, + "no-useless-return": 0, + "quotes": 0, + "object-curly-spacing": 2, + "n/no-callback-literal": 0, + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e2e84b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +node_modules/ diff --git a/.projectile b/.projectile new file mode 100644 index 0000000..c9543be --- /dev/null +++ b/.projectile @@ -0,0 +1,2 @@ +-node_modules/ +-build/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bbc196 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +## About + +Photon is a Google Chrome extension that provides information about +the public IP address you are using to connect to the internet. When +the extension's icon is clicked, the extension makes a request to the +[clean.wtfismyip.com](https://clean.wtfismyip.com) +web service to discover information about your public IP address, and +then displays that information in a simple HTML view. + +## Features + +* Displays a public IP address. +* Displays the Internet Service Provider (ISP) associated with a public IP address. +* Displays the city associated with a public IP address. +* Displays the country associated with a public IP address. +* Displays whether or not a public IP address is a Tor exit node. + +## Install + +Produce the "build" directory: + + git clone https://github.com/0x1eef/photon.git + cd photon + npm i + npm run build + +Add the extension to Chrome: + +* Visit `chrome://extensions`. +* Check "Developer mode" (top right hand corner). +* Click "Load unpacked extension". +* Choose the "build/" directory from the file dialog. +* Done. + +## Sources + +* [Source code (GitHub)](https://github.com/0x1eef/photon#readme) +* [Source code (GitLab)](https://gitlab.com/0x1eef/photon#about) + +## License + +[BSD Zero Clause](https://choosealicense.com/licenses/0bsd/). +
+See [LICENSE](./LICENSE) + + diff --git a/package-lock.json b/package-lock.json index af744ef..fbaf28d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@types/chrome": "^0.0.246", "@types/react": "^18.0.18", "@types/react-dom": "^18.0.6", "clean-webpack-plugin": "^4.0.0", @@ -224,6 +225,16 @@ "node": ">= 8" } }, + "node_modules/@types/chrome": { + "version": "0.0.246", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.246.tgz", + "integrity": "sha512-MxGxEomGxsJiL9xe/7ZwVgwdn8XVKWbPvxpVQl3nWOjrS0Ce63JsfzxUc4aU3GvRcUPYsfufHmJ17BFyKxeA4g==", + "dev": true, + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, "node_modules/@types/eslint": { "version": "8.44.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", @@ -250,6 +261,21 @@ "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", "dev": true }, + "node_modules/@types/filesystem": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.33.tgz", + "integrity": "sha512-2KedRPzwu2K528vFkoXnnWdsG0MtUwPjuA7pRy4vKxlxHEe8qUDZibYHXJKZZr2Cl/ELdCWYqyb/MKwsUuzBWw==", + "dev": true, + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.30.tgz", + "integrity": "sha512-lB98tui0uxc7erbj0serZfJlHKLNJHwBltPnbmO1WRpL5T325GOHRiQfr2E29V2q+S1brDO63Fpdt6vb3bES9Q==", + "dev": true + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -260,6 +286,12 @@ "@types/node": "*" } }, + "node_modules/@types/har-format": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.13.tgz", + "integrity": "sha512-PwBsCBD3lDODn4xpje3Y1di0aDJp4Ww7aSfMRVw6ysnxD4I7Wmq2mBkSKaDtN403hqH5sp6c9xQUvFYY3+lkBg==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", diff --git a/package.json b/package.json index e8909e6..e9e717e 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { - "name": "whatismyip", + "name": "photon", + "version": "0.1.0", "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@types/chrome": "^0.0.246", "@types/react": "^18.0.18", "@types/react-dom": "^18.0.6", "clean-webpack-plugin": "^4.0.0", diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json new file mode 100644 index 0000000..78f6ed8 --- /dev/null +++ b/src/_locales/en/messages.json @@ -0,0 +1,26 @@ +{ + "ip_address": { + "message": "IP Address" + }, + "isp": { + "message": "ISP" + }, + "city": { + "message": "City" + }, + "country": { + "message": "Country" + }, + "tor_exit_node": { + "message": "Tor exit node" + }, + "loading": { + "message": "Loading..." + }, + "yes": { + "message": "Yes" + }, + "no": { + "message": "No" + } +} diff --git a/src/css/components/Response.css b/src/css/components/Response.css new file mode 100644 index 0000000..7e57e24 --- /dev/null +++ b/src/css/components/Response.css @@ -0,0 +1,10 @@ +body .response { + padding: 30px; +} + +body .response .row { + display: flex; + place-content: space-between; + font-size: var(--default-font-size); + margin: 0 0 10px 0; +} diff --git a/src/css/index.css b/src/css/index.css new file mode 100644 index 0000000..6450af4 --- /dev/null +++ b/src/css/index.css @@ -0,0 +1,52 @@ +:root { + /* Defaults */ + --default-font-size: 1.15em; + --default-font-family: "SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace; + + /* Colors */ + --primary-red: #d73e48; + --primary-green: #32b643; + --primary-blue: #302ecd; + --secondary-blue: #807fe2; + --primary-white: #FFF; + --secondary-white: #f1f1fc; +} + +body { + font-family: var(--default-font-family); + width: 368px; + height: 212px; + margin: 0; +} + +#root, .loader { + height: 100%; +} +a { + color: var(--primary-blue) !important; + text-decoration: none; +} +a:visited { + color: var(--primary-blue) !important; +} +a:active, a:focus, a:hover { + color: var(--primary-blue) !important; + text-decoration: underline; +} + +footer { + background: var(--secondary-white); + color: var(--primary-red); + position: fixed; + bottom: 0; + width: 100%; + padding: 10px; + text-align: center; +} + +.loader { + display: flex; + flex-direction: column; + align-items: center; + place-content: center; +} diff --git a/src/css/label.css b/src/css/label.css new file mode 100644 index 0000000..ae6c199 --- /dev/null +++ b/src/css/label.css @@ -0,0 +1,17 @@ +.label { + padding: 4px; +} + +.label-rounded { + border-radius: 5px; +} + +.label-error { + background: var(--primary-red); + color: var(--primary-white); +} + +.label-success { + background: var(--primary-green); + color: var(--primary-white); +} diff --git a/src/html/browseraction.html b/src/html/browseraction.html new file mode 100644 index 0000000..70c2e28 --- /dev/null +++ b/src/html/browseraction.html @@ -0,0 +1,15 @@ + + + + + + + + +
+ + + + diff --git a/src/images/icons/icon128.png b/src/images/icons/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0f978677d8515cef9009adffa89ed4c3186ffc GIT binary patch literal 1632 zcmZ{lSx{4V6vqEImvC(mC=w79P;g1D@m4043Lpf)aqfvseFteUlFZT=Mq^L8gP!NR6wJP1~ zwR%HA&6H)cl^}D|G9Ld)~bc~&(#)JVgUlNXMiE2-X`NOcZT z%&XjQnkMwc?|Cdws%YV^Lv1qqVwO7lUS8_FPkM7n*Xm=PZ1$E#$%1Hj6uG3G>1n*_ zQM-}JjTnuv*Ir^oh32*DceyFB#Sb*y;`SNjF~~R?Y8R8ddAPuxYYJ)eO819Oagg8E z@m>`a8(IDE=jdVMGf%1-3}|ar+A0@gJ(uxAvS#`Cbwt|r+P&`cm+!*Qb(Mx4^m_Ij zGj9Kjm1%gbX|)})bcQ>m~x7TL6F>?BKH zWdMXdGc5f;u<9qkET+qB2e*U)*Phj6ca^iAs0q*OOUH4{HS$L|Bi=1rAPjl`xG@0o zJ&QTvj9s6GBR|3acVO?F!d(3=c!EoFvDeXHIl6C5qg(O3^nI8GNEEy6EvNUuQX51? z&R+bvH5t!~BOn%C+(!e7`f`hjva}o@N)H(FN5Ua7$dU-{TO1_#J>i z`*tR@-ETYir&>qgmk;@-m6r!6Y;0VMk(dxSWmz#TE9%x_REDgt)WUu~v*zFmnaMq3 zH2F&`8TSG6|Iqt@)uz^(2{qb)?hc_p#15PCxDyQW%lQ85989jBX#PO@P*A|Lsd7x< zLe><N9P|k5e%>f*F)E%8q^$P-hZKl_{oXeg8hxEc{oPd_3 zMI_xV2X^|NUFq6O1s#IysvG#hd`vZL|Mw&cvKRI}s-UjH?o5|Y1NPcFB7TXZh!@uDl)#bZw7Al1Vl ziQF$N@-M&Jy*eY;9gb+-KR^K_`1uctPu!y!llCmKVy5{@hMvwaZsb&TJKOBaVGM`w z%NPnDP;A&+8LzMUVu%rcdg#?jz7h_HJ6+@>)aHBUXpuQFHWP*M<9!1%gcyqV(DCY@~pS&Uv~xhFJ6-y=3U+>?pwYpr2)r zbH%ccT$)ZSxxveX`X_SVSjJ(d_&`xdhedya-UC*aKpmH+2_c*=x^Z3G8=ne(7UTYG zZoK*bzyIfq4lfMk|0o%I=3~&p%*599KMaoZ*mL%;+jwL{PJwiiXpv14<5re~dVBIF zi!=-Od~Ecac_(#3VoTWy_8l(F$2HlOzew)VKDtD0|JLpAxxZ(;d#KI3Z}FcO`fSmA z7KfK=#x3byv5)gl); + } else if (error) { + /* FIXME: Add ErrorRenderer */ + } else { + return (
{t("loading")}
); + } +} diff --git a/src/js/components/ResponseRenderer.tsx b/src/js/components/ResponseRenderer.tsx new file mode 100644 index 0000000..17fae8a --- /dev/null +++ b/src/js/components/ResponseRenderer.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { TResponse } from "/lib/response"; + +export function ResponseRenderer({ response }: {response: TResponse}) { + const t = chrome.i18n.getMessage; + + function YesOrNoLabel({yes}: {yes: boolean}) { + if (yes) { + return (); + } else { + return (); + } + } + + return ( +
+
+
{t("ip_address")}
+
{response.IPAddress}
+
+
+
{t("isp")}
+
{response.ISP}
+
+
+
{t("city")}
+
{response.City}
+
+
+
{t("country")}
+
{response.Country}
+
+
+
{t("tor_exit_node")}
+
{}
+
+
+ ); +} diff --git a/src/js/hooks/useWebService.ts b/src/js/hooks/useWebService.ts new file mode 100644 index 0000000..048440d --- /dev/null +++ b/src/js/hooks/useWebService.ts @@ -0,0 +1,19 @@ +import { TResponse, Response } from "/lib/response"; +import { useEffect, useState } from "react"; + +type Maybe = T | null; + +export function useWebService(): [Maybe, Maybe] { + const endpoint = "https://wtfismyip.com/json"; + const [response, setResponse] = useState>(null); + const [error, setError] = useState>(null); + + useEffect(() => { + fetch(endpoint) + .then((res) => res.json()) + .then((json) => setResponse(Response(json))) + .catch((err) => setError(err)); + }); + + return [response, error]; +} diff --git a/src/js/index.tsx b/src/js/index.tsx new file mode 100644 index 0000000..5ba1a1f --- /dev/null +++ b/src/js/index.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { App } from "/components/App"; + +document.addEventListener("DOMContentLoaded", () => { + const el: HTMLElement = document.getElementById("root")!; + ReactDOM.createRoot(el).render(); +}); diff --git a/src/js/lib/response.ts b/src/js/lib/response.ts new file mode 100644 index 0000000..a493f1e --- /dev/null +++ b/src/js/lib/response.ts @@ -0,0 +1,30 @@ +export type TResponse = { + IPAddress: string; + Location: string; + ISP: string; + City: string; + Country: string; + isTorExitNode: boolean; +}; + +export type TServerResponse = { + YourFuckingIPAddress: string; + YourFuckingLocation: string; + YourFuckingISP: string; + YourFuckingCity: string; + YourFuckingCountry: string; + YourFuckingTorExit: boolean; +}; + +export function Response(res: TServerResponse): TResponse { + const self = Object.create(null); + + self.IPAddress = res.YourFuckingIPAddress; + self.Location = res.YourFuckingLocation; + self.ISP = res.YourFuckingISP; + self.City = res.YourFuckingCity; + self.Country = res.YourFuckingCountry; + self.isTorExitNode = res.YourFuckingTorExit; + + return self; +} diff --git a/src/manifest.json b/src/manifest.json new file mode 100644 index 0000000..ddeabc1 --- /dev/null +++ b/src/manifest.json @@ -0,0 +1,15 @@ +{ + "manifest_version": 3, + "name": "Photon", + "version": "0.1.0", + "description": "Provides various information about your public IP address.", + "action": { + "default_popup": "/html/browseraction.html" + }, + "permissions": [], + "icons": { + "16": "images/icons/icon16.png", + "128": "images/icons/icon128.png" + }, + "default_locale": "en" +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..15a5eaf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": true, + "strictNullChecks": false, + "module": "commonjs", + "target": "ES2020", + "noImplicitAny": true, + "moduleResolution": "node", + "esModuleInterop": true, + "jsx": "react", + "allowJs": true, + "lib": [ "ES2020", "DOM" ], + + "baseUrl": "src/", + "paths": { "*": ["js/*"] }, + } +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..0682059 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,42 @@ +const path = require('path'); +const process = require('process'); +const CopyPlugin = require("copy-webpack-plugin"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); + +module.exports = { + mode: process.env.NODE_ENV || "development", + devtool: "inline-source-map", + entry: { + index: './src/js/index.tsx', + }, + output: { + filename: 'js/[name].js', + path: path.resolve(__dirname, 'build') + }, + resolve: { + roots: [path.resolve('src/js'), path.resolve('node_modules')], + modules: [path.resolve('src/js'), path.resolve('node_modules')], + extensions: ['.ts', '.tsx'] + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + plugins: [ + new CopyPlugin({ + patterns: [ + { from: "src/html/", to: "html" }, + { from: "src/css/", to: "css" }, + { from: "src/manifest.json", to: "manifest.json" }, + { from: "src/images", to: "images/" }, + { from: "src/_locales", to: "_locales/" } + ], + }), + new CleanWebpackPlugin(), + ], +}