From 33c9e8bca54d753c7ea976dd178db0cd5408c218 Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Tue, 13 Feb 2024 00:29:25 +0000 Subject: feature: A usable, yet ugly browser extension --- .github/workflows/main.yml | 3 ++ packages/browser-extension/.eslintrc.cjs | 3 ++ packages/browser-extension/manifest.json | 8 +-- packages/browser-extension/postcss.config.js | 2 +- packages/browser-extension/src/App.tsx | 5 +- .../browser-extension/src/NotConfiguredPage.tsx | 16 ++++++ packages/browser-extension/src/OptionsPage.tsx | 42 +++++++++++++++ packages/browser-extension/src/SavePage.tsx | 61 +++++++++++++++------- packages/browser-extension/src/SettingsPage.tsx | 53 ------------------- packages/browser-extension/src/main.tsx | 21 ++++---- packages/browser-extension/tailwind.config.js | 10 ++-- packages/web/app/api/trpc/[trpc]/route.ts | 7 +++ packages/web/next.config.mjs | 29 +++++++++- 13 files changed, 163 insertions(+), 97 deletions(-) create mode 100644 packages/browser-extension/.eslintrc.cjs create mode 100644 packages/browser-extension/src/NotConfiguredPage.tsx create mode 100644 packages/browser-extension/src/OptionsPage.tsx delete mode 100644 packages/browser-extension/src/SettingsPage.tsx diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cc8a45d0..68fe9fb3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,3 +32,6 @@ jobs: - name: Build web app working-directory: packages/web run: yarn run build + - name: Build the browser extension + working-directory: packages/browser-extension + run: yarn build diff --git a/packages/browser-extension/.eslintrc.cjs b/packages/browser-extension/.eslintrc.cjs new file mode 100644 index 00000000..450106a4 --- /dev/null +++ b/packages/browser-extension/.eslintrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + ignorePatterns: ["dist/"], +}; diff --git a/packages/browser-extension/manifest.json b/packages/browser-extension/manifest.json index bf3293ca..d7930ee8 100644 --- a/packages/browser-extension/manifest.json +++ b/packages/browser-extension/manifest.json @@ -6,7 +6,9 @@ "action": { "default_popup": "index.html" }, - "permissions": [ - "storage" - ] + "options_ui": { + "page": "index.html#options", + "open_in_tab": false + }, + "permissions": ["storage", "activeTab", "tabs"] } diff --git a/packages/browser-extension/postcss.config.js b/packages/browser-extension/postcss.config.js index 2e7af2b7..2aa7205d 100644 --- a/packages/browser-extension/postcss.config.js +++ b/packages/browser-extension/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/packages/browser-extension/src/App.tsx b/packages/browser-extension/src/App.tsx index 21fec92f..8a2c4920 100644 --- a/packages/browser-extension/src/App.tsx +++ b/packages/browser-extension/src/App.tsx @@ -1,4 +1,3 @@ -import { redirect } from "react-router-dom"; import SavePage from "./SavePage"; import usePluginSettings from "./settings"; import { useNavigate } from "react-router-dom"; @@ -12,7 +11,7 @@ function App() { } if (!settings.apiKey || !settings.address) { - navigate("/settings"); + navigate("/notconfigured"); return; } @@ -21,7 +20,7 @@ function App() {
-
diff --git a/packages/browser-extension/src/NotConfiguredPage.tsx b/packages/browser-extension/src/NotConfiguredPage.tsx new file mode 100644 index 00000000..ef73f149 --- /dev/null +++ b/packages/browser-extension/src/NotConfiguredPage.tsx @@ -0,0 +1,16 @@ +import { useNavigate } from "react-router-dom"; + +export default function NotConfiguredPage() { + const navigate = useNavigate(); + return ( +
+ To use the plugin, you need to configure it first. + +
+ ); +} diff --git a/packages/browser-extension/src/OptionsPage.tsx b/packages/browser-extension/src/OptionsPage.tsx new file mode 100644 index 00000000..e3a34bd9 --- /dev/null +++ b/packages/browser-extension/src/OptionsPage.tsx @@ -0,0 +1,42 @@ +import { useRef } from "react"; +import usePluginSettings from "./settings"; + +export default function OptionsPage() { + const [settings, setSettings, _1, _2, _3] = usePluginSettings(); + + const apiKeyRef = useRef(null); + const addressRef = useRef(null); + + const onSave = () => { + setSettings({ + apiKey: apiKeyRef.current?.value || "", + address: addressRef.current?.value || "", + }); + }; + + return ( +
+ Settings +
+
+ + +
+
+ + +
+ +
+ ); +} diff --git a/packages/browser-extension/src/SavePage.tsx b/packages/browser-extension/src/SavePage.tsx index c66cc0ad..e202aea5 100644 --- a/packages/browser-extension/src/SavePage.tsx +++ b/packages/browser-extension/src/SavePage.tsx @@ -5,23 +5,49 @@ export default function SavePage({ settings }: { settings: Settings }) { const [loading, setLoading] = useState(true); const [error, setError] = useState(undefined); - async function runFetch() { - const resp = await fetch( - `${settings.address}/api/trpc/bookmarks.bookmarkLink`, - { - method: "POST", - }, - ); + useEffect(() => { + async function runFetch() { + let currentUrl; + const [currentTab] = await chrome.tabs.query({ + active: true, + lastFocusedWindow: true, + }); + if (currentTab?.url) { + currentUrl = currentTab.url; + } else { + setError("Couldn't find the URL of the current tab"); + setLoading(false); + return; + } - if (!resp.ok) { - setError("Something went wrong: " + (await resp.json())); - } - setLoading(false); - } + try { + const resp = await fetch( + `${settings.address}/api/trpc/bookmarks.bookmarkLink`, + { + method: "POST", + headers: { + Authorization: `Bearer ${settings.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + type: "link", + url: currentUrl, + }), + }, + ); + if (!resp.ok) { + setError( + "Something went wrong: " + JSON.stringify(await resp.json()), + ); + } + } catch (e) { + setError("Message: " + (e as Error).message); + } - useEffect(() => { + setLoading(false); + } runFetch(); - }, []); + }, [settings]); if (loading) { return
Loading ...
; @@ -31,10 +57,5 @@ export default function SavePage({ settings }: { settings: Settings }) { return
{error} ...
; } - return ( -
- SAVED! - -
- ); + return
SAVED!
; } diff --git a/packages/browser-extension/src/SettingsPage.tsx b/packages/browser-extension/src/SettingsPage.tsx deleted file mode 100644 index bae870ac..00000000 --- a/packages/browser-extension/src/SettingsPage.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useRef } from "react"; -import usePluginSettings from "./settings"; -import { useNavigate } from "react-router-dom"; - -export default function SettingsPage() { - const navigate = useNavigate(); - const [settings, setSettings, _1, _2, _3] = usePluginSettings(); - - const apiKeyRef = useRef(null); - const addressRef = useRef(null); - - const onSave = () => { - setSettings({ - apiKey: apiKeyRef.current?.value || "", - address: addressRef.current?.value || "", - }); - }; - - return ( -
- Settings -
-
- - -
-
- - -
- - -
- ); -} diff --git a/packages/browser-extension/src/main.tsx b/packages/browser-extension/src/main.tsx index 298ab388..c4f5a2d9 100644 --- a/packages/browser-extension/src/main.tsx +++ b/packages/browser-extension/src/main.tsx @@ -2,27 +2,28 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; import "./index.css"; -import { createBrowserRouter, Navigate, RouterProvider } from "react-router-dom"; -import SettingsPage from "./SettingsPage.tsx"; +import { createHashRouter, RouterProvider } from "react-router-dom"; +import OptionsPage from "./OptionsPage.tsx"; +import NotConfiguredPage from "./NotConfiguredPage.tsx"; -const router = createBrowserRouter([ - { - path: "/index.html", - element: , - }, +const router = createHashRouter([ { path: "/", element: , }, { - path: "/settings", - element: , + path: "/notconfigured", + element: , + }, + { + path: "/options", + element: , }, ]); ReactDOM.createRoot(document.getElementById("root")!).render( -
+
, diff --git a/packages/browser-extension/tailwind.config.js b/packages/browser-extension/tailwind.config.js index d37737fc..1c0c7c87 100644 --- a/packages/browser-extension/tailwind.config.js +++ b/packages/browser-extension/tailwind.config.js @@ -1,12 +1,10 @@ /** @type {import('tailwindcss').Config} */ -export default { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], +const tailwindConfig = { + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], -} +}; +export default tailwindConfig; diff --git a/packages/web/app/api/trpc/[trpc]/route.ts b/packages/web/app/api/trpc/[trpc]/route.ts index e04539a9..aea9bc70 100644 --- a/packages/web/app/api/trpc/[trpc]/route.ts +++ b/packages/web/app/api/trpc/[trpc]/route.ts @@ -8,6 +8,13 @@ const handler = (req: Request) => endpoint: "/api/trpc", req, router: appRouter, + onError: ({ path, error }) => { + if (process.env.NODE_ENV === "development") { + console.error(`❌ tRPC failed on ${path}`); + } + console.error(error); + }, + createContext: async (opts) => { // TODO: This is a hack until we offer a proper REST API instead of the trpc based one. // Check if the request has an Authorization token, if it does, assume that API key authentication is requested. diff --git a/packages/web/next.config.mjs b/packages/web/next.config.mjs index 4678774e..f2ed7754 100644 --- a/packages/web/next.config.mjs +++ b/packages/web/next.config.mjs @@ -1,4 +1,31 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + async headers() { + return [ + { + // Routes this applies to + source: "/api/(.*)", + // Headers + headers: [ + // Allow for specific domains to have access or * for all + { + key: "Access-Control-Allow-Origin", + value: "*", + }, + // Allows for specific methods accepted + { + key: "Access-Control-Allow-Methods", + value: "GET, POST, PUT, DELETE, OPTIONS", + }, + // Allows for specific headers accepted (These are a few standard ones) + { + key: "Access-Control-Allow-Headers", + value: "Content-Type, Authorization", + }, + ], + }, + ]; + }, +}; export default nextConfig; -- cgit v1.2.3-70-g09d2