Rewrite project (and rename as wimi)
12
README.md
|
@ -1,4 +1,8 @@
|
||||||
## About
|
<p align="center">
|
||||||
|
<img src="src/images/icon128x128.png"></img>
|
||||||
|
<br>
|
||||||
|
<strong>wimi</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
wimi relays information about your public IP address
|
wimi relays information about your public IP address
|
||||||
from
|
from
|
||||||
|
@ -13,12 +17,6 @@ The extension can be built locally and installed as a
|
||||||
developer extension / addon on both Chromium and FireFox.
|
developer extension / addon on both Chromium and FireFox.
|
||||||
There are XPI files provided for FireFox users as well.
|
There are XPI files provided for FireFox users as well.
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img src="https://raw.githubusercontent.com/0x1eef/wimi/main/share/wimi/wimi.png">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
**Chrome**
|
**Chrome**
|
||||||
|
|
15
bin/iconify
|
@ -1,15 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
sizes="16x16 48x48 64x64 128x128 256x256"
|
|
||||||
dir="src/images/icons"
|
|
||||||
for size in ${sizes}; do
|
|
||||||
convert ${dir}/icon.svg \
|
|
||||||
-resize ${size} \
|
|
||||||
-background none \
|
|
||||||
-trim +repage \
|
|
||||||
-fuzz 5% \
|
|
||||||
-transparent white \
|
|
||||||
-gravity center \
|
|
||||||
-extent $size \
|
|
||||||
${dir}/wtf${size}.png
|
|
||||||
done
|
|
22
bin/mkicons
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
##
|
||||||
|
# variables
|
||||||
|
sizes="16x16 48x48 64x64 128x128 256x256"
|
||||||
|
dir="src/images/"
|
||||||
|
|
||||||
|
##
|
||||||
|
# main
|
||||||
|
for size in ${sizes}; do
|
||||||
|
gm convert \
|
||||||
|
"${dir}"/icon.svg \
|
||||||
|
-resize "${size}" \
|
||||||
|
-background none \
|
||||||
|
-trim +repage \
|
||||||
|
-fuzz 5% \
|
||||||
|
-transparent white \
|
||||||
|
-gravity center \
|
||||||
|
-extent "${size}" \
|
||||||
|
"${dir}"/icon"${size}".png
|
||||||
|
done
|
|
@ -1,5 +1,17 @@
|
||||||
# -*- mode: org -*-
|
# -*- mode: org -*-
|
||||||
|
|
||||||
|
** vNEXT
|
||||||
|
|
||||||
|
**** Add bin/mkicons
|
||||||
|
Replace the previous script
|
||||||
|
|
||||||
|
**** Add new icons
|
||||||
|
Replace the previous set of icons with wimi icons
|
||||||
|
|
||||||
|
**** Hello wimi
|
||||||
|
The project is now known as wimi - short for:
|
||||||
|
what is my ip address
|
||||||
|
|
||||||
** v0.5.1
|
** v0.5.1
|
||||||
|
|
||||||
**** Shorten description in manifest
|
**** Shorten description in manifest
|
||||||
|
|
Before Width: | Height: | Size: 11 KiB |
|
@ -1,45 +1,11 @@
|
||||||
:root {
|
:root {
|
||||||
/* Defaults */
|
|
||||||
--default-font-size: 1.15em;
|
|
||||||
|
|
||||||
/* Colors */
|
/* Colors */
|
||||||
--primary-red: #d73e48;
|
--primary-color: #E3F2FD;
|
||||||
--primary-green: #32b643;
|
--secondary-color: #333333;
|
||||||
--primary-blue: #302ecd;
|
--accent-color: #007BFF;
|
||||||
--secondary-blue: #807fe2;
|
|
||||||
--primary-white: #FFF;
|
|
||||||
--secondary-white: #f1f1fc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
#root {
|
||||||
font-size: small;
|
background: var(--primary-color);
|
||||||
width: 400px;
|
color: var(--secondary-color);
|
||||||
}
|
|
||||||
|
|
||||||
#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);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
place-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<link rel="stylesheet" href="/css/index.css"/>
|
<link rel="stylesheet" href="/css/index.css"/>
|
||||||
<link rel="stylesheet" href="/css/components/BooleanLabel.css"/>
|
<link rel="stylesheet" href="/css/components/BooleanLabel.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body class="h-60">
|
<body class="h-12 w-full">
|
||||||
<div id="root" class="h-full font-sans"></div>
|
<div id="root"></div>
|
||||||
<script src="/js/index.js"></script>
|
<script src="/js/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
1
src/images/icon.svg
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
src/images/icon128x128.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
src/images/icon16x16.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/images/icon256x256.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
src/images/icon48x48.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
src/images/icon64x64.png
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 15 KiB |
|
@ -1,15 +1,29 @@
|
||||||
import { ErrorRenderer } from "~/components/ErrorRenderer";
|
|
||||||
import { ResponseRenderer } from "~/components/ResponseRenderer";
|
|
||||||
import { Loader } from "~/components/Loader";
|
|
||||||
import { useWebService } from "~/hooks/useWebService";
|
import { useWebService } from "~/hooks/useWebService";
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [response, error] = useWebService();
|
const [response, error] = useWebService();
|
||||||
if (response) {
|
if (response) {
|
||||||
return <ResponseRenderer response={response} />;
|
return (
|
||||||
|
<div data-testid="response" className="font-sans p-2 flex">
|
||||||
|
<img className="w-8 h-8" src={`/images/flags/${response.countryCode}.svg`} />
|
||||||
|
<div className="flex flex-col ml-3">
|
||||||
|
<span className="text-xs">{response.IPAddress}</span>
|
||||||
|
<span className="text-xs">{response.ISP}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
return <ErrorRenderer error={error} />;
|
return (
|
||||||
|
<div data-testid="error" className="font-sans p-2 flex items-center w-full h-full">
|
||||||
|
<img className="w-8 h-8" src={`/images/icon.svg`} />
|
||||||
|
<span className="ml-3 text-xs">Error</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <Loader />;
|
return (
|
||||||
|
<div data-testid="loading" className="font-sans p-2 flex items-center w-full h-full">
|
||||||
|
<img className="w-8 h-8" src={`/images/icon.svg`} />
|
||||||
|
<span className="ml-3 text-xs">Loading</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { Footer } from "~/components/Footer";
|
|
||||||
|
|
||||||
export function ErrorRenderer({ error }: { error: Error }) {
|
|
||||||
const t = chrome.i18n.getMessage;
|
|
||||||
return (
|
|
||||||
<div data-testid="error" className="w-full h-full">
|
|
||||||
<div className="flex flex-col h-5/6 justify-center w-3/4 m-auto"></div>
|
|
||||||
<Footer>
|
|
||||||
<span className="font-bold">{t("error")}</span>:
|
|
||||||
<span data-testid="error-message">{error.message}</span>
|
|
||||||
</Footer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import type { ReactNode } from "react";
|
|
||||||
|
|
||||||
export function Footer({ children }: { children: ReactNode }) {
|
|
||||||
return (
|
|
||||||
<footer className="flex h-10">
|
|
||||||
<div className="w-3/4 m-auto">{children}</div>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { Footer } from "~/components/Footer";
|
|
||||||
|
|
||||||
export function Loader() {
|
|
||||||
const t = chrome.i18n.getMessage;
|
|
||||||
return (
|
|
||||||
<div className="w-full h-full" data-testid="loader">
|
|
||||||
<div className="flex flex-col h-5/6 justify-center w-3/4 m-auto"></div>
|
|
||||||
<Footer>{t("loading")}</Footer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
import { useEffect, ReactNode } from "react";
|
|
||||||
import { TResponse } from "~/lib/response";
|
|
||||||
import { Footer } from "~/components/Footer";
|
|
||||||
type TFunction = typeof chrome.i18n.getMessage;
|
|
||||||
|
|
||||||
export function ResponseRenderer({ response }: { response: TResponse }) {
|
|
||||||
const t = chrome.i18n.getMessage;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const { body } = document;
|
|
||||||
const width = (() => {
|
|
||||||
const addr = response.IPAddress;
|
|
||||||
if (addr.length >= 30) {
|
|
||||||
return 525;
|
|
||||||
} else if (addr.length >= 20) {
|
|
||||||
return 450;
|
|
||||||
} else {
|
|
||||||
return 400;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
body.style.width = `${width}px`;
|
|
||||||
}, [response]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<div className="flex flex-col h-5/6 justify-center w-3/4 m-auto" data-testid="response">
|
|
||||||
<Row name={t("ip_address")} value={response.IPAddress} />
|
|
||||||
<Row name={t("location")} value={`${response.City}, ${response.Country}`} />
|
|
||||||
<Row name={t("isp")} value={response.ISP} />
|
|
||||||
<Row name={t("tor_exit_node")} value={<BooleanLabel on={response.isTorExitNode} t={t} />} />
|
|
||||||
</div>
|
|
||||||
<Footer>
|
|
||||||
<a target="_blank" href={`https://${t("clean.myip.wtf")}`}>
|
|
||||||
{t("clean.myip.wtf")}
|
|
||||||
</a>
|
|
||||||
</Footer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BooleanLabel({ on, t }: { on: boolean; t: TFunction }) {
|
|
||||||
if (on) {
|
|
||||||
return <label className="label label-rounded label-success">{t("yes")}</label>;
|
|
||||||
} else {
|
|
||||||
return <label className="label label-rounded label-error">{t("no")}</label>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Row({ name, value }: { name: string; value: ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-8">
|
|
||||||
<div className="flex w-2/4 items-center font-semibold">{name}</div>
|
|
||||||
<div className="flex w-3/4 items-center justify-end">{value}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ export type TResponse = {
|
||||||
ISP: string;
|
ISP: string;
|
||||||
City: string;
|
City: string;
|
||||||
Country: string;
|
Country: string;
|
||||||
|
countryCode: string;
|
||||||
isTorExitNode: boolean;
|
isTorExitNode: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ export type TServerResponse = {
|
||||||
YourISP: string;
|
YourISP: string;
|
||||||
YourCity: string;
|
YourCity: string;
|
||||||
YourCountry: string;
|
YourCountry: string;
|
||||||
|
YourCountryCode: string;
|
||||||
YourTorExit: boolean;
|
YourTorExit: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +23,7 @@ export function Response(res: TServerResponse): TResponse {
|
||||||
self.ISP = res.YourISP;
|
self.ISP = res.YourISP;
|
||||||
self.City = res.YourCity;
|
self.City = res.YourCity;
|
||||||
self.Country = res.YourCountry;
|
self.Country = res.YourCountry;
|
||||||
|
self.countryCode = res.YourCountryCode;
|
||||||
self.isTorExitNode = res.YourTorExit;
|
self.isTorExitNode = res.YourTorExit;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
"default_popup": "/html/browseraction.html"
|
"default_popup": "/html/browseraction.html"
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "images/icons/wtf16x16.png",
|
"16": "images/icon16x16.png",
|
||||||
"48": "images/icons/wtf48x48.png",
|
"48": "images/icon48x48.png",
|
||||||
"64": "images/icons/wtf64x64.png",
|
"64": "images/icon64x64.png",
|
||||||
"128": "images/icons/wtf128x128.png",
|
"128": "images/icon128x128.png",
|
||||||
"256": "images/icons/wtf256x256.png"
|
"256": "images/icon256x256.png"
|
||||||
},
|
},
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
|
|
|
@ -24,7 +24,7 @@ describe("App.tsx", () => {
|
||||||
|
|
||||||
test("loading text is rendered", () => {
|
test("loading text is rendered", () => {
|
||||||
render(<App/>);
|
render(<App/>);
|
||||||
expect(screen.getByText("Loading...")).toBeInTheDocument();
|
expect(screen.getByTestId("loading")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import '@testing-library/jest-dom';
|
|
||||||
import { render, screen } from "@testing-library/react";
|
|
||||||
import { ErrorRenderer } from "~/components/ErrorRenderer";
|
|
||||||
import { getMessage } from "./mocks/chrome.i18n";
|
|
||||||
|
|
||||||
describe("ErrorRenderer.tsx", () => {
|
|
||||||
const globalChrome = global.chrome;
|
|
||||||
const error = new Error("This is an example error message");
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const chrome: any = { i18n: { getMessage } };
|
|
||||||
global.chrome = chrome;
|
|
||||||
render(<ErrorRenderer error={error}/>);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
global.chrome = globalChrome;
|
|
||||||
});
|
|
||||||
|
|
||||||
test("an error is rendered", () => {
|
|
||||||
const { getByTestId, getByText } = screen;
|
|
||||||
const span = getByTestId("error-message");
|
|
||||||
expect(getByText("Error")).toBeInTheDocument();
|
|
||||||
expect(span.textContent).toEqual(error.message);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,67 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import '@testing-library/jest-dom';
|
|
||||||
import { render, screen } from "@testing-library/react";
|
|
||||||
import { ResponseRenderer } from "~/components/ResponseRenderer";
|
|
||||||
import { getMessage } from "./mocks/chrome.i18n";
|
|
||||||
|
|
||||||
describe("ResponseRenderer.tsx", () => {
|
|
||||||
const globalChrome = global.chrome;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const chrome: any = { i18n: { getMessage } };
|
|
||||||
global.chrome = chrome;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
global.chrome = globalChrome;
|
|
||||||
});
|
|
||||||
|
|
||||||
const defaultResponse = {
|
|
||||||
IPAddress: "89.222.123.45",
|
|
||||||
ISP: "FooBar Ltd",
|
|
||||||
City: "FooBar City",
|
|
||||||
Country: "United States of FooBar",
|
|
||||||
isTorExitNode: false
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("when isTorExitNode is false", () => {
|
|
||||||
const response = { ...defaultResponse };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
render(<ResponseRenderer response={response}/>);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("an IP address is rendered", () => {
|
|
||||||
expect(screen.getByText("IP Address")).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(response.IPAddress)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("an ISP is rendered", () => {
|
|
||||||
expect(screen.getByText("ISP")).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(response.ISP)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("a location is rendered", () => {
|
|
||||||
expect(screen.getByText("Location")).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(`${response.City}, ${response.Country}`)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("isTorExitNode is rendered as No", () => {
|
|
||||||
expect(screen.getByText("Tor exit node")).toBeInTheDocument();
|
|
||||||
expect(screen.getByText("No")).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when isTorExitNode is true", () => {
|
|
||||||
const response = { ...defaultResponse, isTorExitNode: true };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
render(<ResponseRenderer response={response}/>);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("isTorExitNode is rendered as Yes", () => {
|
|
||||||
expect(screen.getByText("Tor exit node")).toBeInTheDocument();
|
|
||||||
expect(screen.getByText("Yes")).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -8,6 +8,7 @@ export function success(_path: RequestInfo | URL, _options?: RequestInit) {
|
||||||
YourISP: "FooBar Ltd",
|
YourISP: "FooBar Ltd",
|
||||||
YourCity: "Foo City",
|
YourCity: "Foo City",
|
||||||
YourCountry: "United States of FooBar",
|
YourCountry: "United States of FooBar",
|
||||||
|
YourCountryCode: "BR",
|
||||||
YourTorExit: false
|
YourTorExit: false
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|