diff --git a/apps/browser-extension/src/OptionsPage.tsx b/apps/browser-extension/src/OptionsPage.tsx index 9670558e..24785857 100644 --- a/apps/browser-extension/src/OptionsPage.tsx +++ b/apps/browser-extension/src/OptionsPage.tsx @@ -17,6 +17,8 @@ export default function OptionsPage() { }, ); + const { mutate: deleteKey } = api.apiKeys.revoke.useMutation(); + const invalidateWhoami = api.useUtils().users.whoami.refetch; useEffect(() => { @@ -39,7 +41,10 @@ export default function OptionsPage() { } const onLogout = () => { - setSettings((s) => ({ ...s, apiKey: "" })); + if (settings.apiKeyId) { + deleteKey({ id: settings.apiKeyId }); + } + setSettings((s) => ({ ...s, apiKey: "", apiKeyId: undefined })); invalidateWhoami(); navigate("/notconfigured"); }; diff --git a/apps/browser-extension/src/SignInPage.tsx b/apps/browser-extension/src/SignInPage.tsx index a9b77e83..aa8699ae 100644 --- a/apps/browser-extension/src/SignInPage.tsx +++ b/apps/browser-extension/src/SignInPage.tsx @@ -15,7 +15,7 @@ export default function SignInPage() { isPending, } = api.apiKeys.exchange.useMutation({ onSuccess: (resp) => { - setSettings((s) => ({ ...s, apiKey: resp.key })); + setSettings((s) => ({ ...s, apiKey: resp.key, apiKeyId: resp.id })); navigate("/options"); }, }); diff --git a/apps/browser-extension/src/utils/providers.tsx b/apps/browser-extension/src/utils/providers.tsx index 7b14b22c..4ca17016 100644 --- a/apps/browser-extension/src/utils/providers.tsx +++ b/apps/browser-extension/src/utils/providers.tsx @@ -1,47 +1,9 @@ -import { useEffect, useState } from "react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { httpBatchLink } from "@trpc/client"; -import superjson from "superjson"; +import { TRPCProvider } from "@hoarder/shared-react/providers/trpc-provider"; -import usePluginSettings, { getPluginSettings } from "./settings"; -import { api } from "./trpc"; - -function getTRPCClient(address: string) { - return api.createClient({ - links: [ - httpBatchLink({ - url: `${address}/api/trpc`, - async headers() { - const settings = await getPluginSettings(); - return { - Authorization: `Bearer ${settings.apiKey}`, - }; - }, - transformer: superjson, - }), - ], - }); -} +import usePluginSettings from "./settings"; export function Providers({ children }: { children: React.ReactNode }) { const { settings } = usePluginSettings(); - const [queryClient] = useState(() => new QueryClient()); - - const [trpcClient, setTrpcClient] = useState< - ReturnType - >(getTRPCClient(settings.address)); - - useEffect(() => { - setTrpcClient(getTRPCClient(settings.address)); - }, [settings.address]); - return ( - - {children} - - ); + return {children}; } diff --git a/apps/browser-extension/src/utils/settings.ts b/apps/browser-extension/src/utils/settings.ts index f20e9827..ef290555 100644 --- a/apps/browser-extension/src/utils/settings.ts +++ b/apps/browser-extension/src/utils/settings.ts @@ -2,6 +2,7 @@ import { useChromeStorageSync } from "use-chrome-storage"; export interface Settings { apiKey: string; + apiKeyId?: string; address: string; } diff --git a/apps/mobile/app/signin.tsx b/apps/mobile/app/signin.tsx index 283528df..80a5f219 100644 --- a/apps/mobile/app/signin.tsx +++ b/apps/mobile/app/signin.tsx @@ -22,7 +22,7 @@ export default function Signin() { const { mutate: login, isPending } = api.apiKeys.exchange.useMutation({ onSuccess: (resp) => { - setSettings({ ...settings, apiKey: resp.key }); + setSettings({ ...settings, apiKey: resp.key, apiKeyId: resp.id }); }, onError: (e) => { if (e.data?.code === "UNAUTHORIZED") { diff --git a/apps/mobile/lib/providers.tsx b/apps/mobile/lib/providers.tsx index ed04b9bf..c2a573f5 100644 --- a/apps/mobile/lib/providers.tsx +++ b/apps/mobile/lib/providers.tsx @@ -1,52 +1,11 @@ -import { useEffect, useMemo } from "react"; +import { useEffect } from "react"; import { SafeAreaProvider } from "react-native-safe-area-context"; import FullPageSpinner from "@/components/ui/FullPageSpinner"; import { ToastProvider } from "@/components/ui/Toast"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { httpBatchLink } from "@trpc/client"; -import superjson from "superjson"; -import type { Settings } from "./settings"; -import useAppSettings from "./settings"; -import { api } from "./trpc"; - -function getTRPCClient(settings: Settings) { - return api.createClient({ - links: [ - httpBatchLink({ - url: `${settings.address}/api/trpc`, - headers() { - return { - Authorization: settings?.apiKey - ? `Bearer ${settings.apiKey}` - : undefined, - }; - }, - transformer: superjson, - }), - ], - }); -} - -function TrpcProvider({ - children, - settings, -}: { - settings: Settings; - children: React.ReactNode; -}) { - const queryClient = useMemo(() => new QueryClient(), [settings]); - - const trpcClient = useMemo(() => getTRPCClient(settings), [settings]); +import { TRPCProvider } from "@hoarder/shared-react/providers/trpc-provider"; - return ( - - - {children} - - - ); -} +import useAppSettings from "./settings"; export function Providers({ children }: { children: React.ReactNode }) { const { settings, isLoading, load } = useAppSettings(); @@ -62,9 +21,9 @@ export function Providers({ children }: { children: React.ReactNode }) { return ( - + {children} - + ); } diff --git a/apps/mobile/lib/session.ts b/apps/mobile/lib/session.ts index bafb3a09..8eb646cb 100644 --- a/apps/mobile/lib/session.ts +++ b/apps/mobile/lib/session.ts @@ -1,12 +1,18 @@ import { useCallback } from "react"; import useAppSettings from "./settings"; +import { api } from "./trpc"; export function useSession() { const { settings, setSettings } = useAppSettings(); + const { mutate: deleteKey } = api.apiKeys.revoke.useMutation(); + const logout = useCallback(() => { - setSettings({ ...settings, apiKey: undefined }); + if (settings.apiKeyId) { + deleteKey({ id: settings.apiKeyId }); + } + setSettings({ ...settings, apiKey: undefined, apiKeyId: undefined }); }, [settings, setSettings]); return { diff --git a/apps/mobile/lib/settings.ts b/apps/mobile/lib/settings.ts index 3ec53231..efb5593a 100644 --- a/apps/mobile/lib/settings.ts +++ b/apps/mobile/lib/settings.ts @@ -5,6 +5,7 @@ const SETTING_NAME = "settings"; export interface Settings { apiKey?: string; + apiKeyId?: string; address: string; } diff --git a/packages/shared-react/package.json b/packages/shared-react/package.json index 46a2adb5..5e0a1d23 100644 --- a/packages/shared-react/package.json +++ b/packages/shared-react/package.json @@ -5,7 +5,9 @@ "private": true, "dependencies": { "@hoarder/trpc": "workspace:^0.1.0", - "@tanstack/react-query": "^5.24.8" + "@tanstack/react-query": "^5.24.8", + "superjson": "^2.2.1", + "@trpc/client": "11.0.0-next-beta.308" }, "devDependencies": { "@hoarder/eslint-config": "workspace:^0.2.0", diff --git a/packages/shared-react/providers/trpc-provider.tsx b/packages/shared-react/providers/trpc-provider.tsx new file mode 100644 index 00000000..2cd8661b --- /dev/null +++ b/packages/shared-react/providers/trpc-provider.tsx @@ -0,0 +1,50 @@ +import { useMemo } from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { httpBatchLink } from "@trpc/client"; +import superjson from "superjson"; + +import { api } from "../trpc"; + +interface Settings { + apiKey?: string; + address: string; +} + +function getTRPCClient(settings: Settings) { + return api.createClient({ + links: [ + httpBatchLink({ + url: `${settings.address}/api/trpc`, + headers() { + return { + Authorization: settings.apiKey + ? `Bearer ${settings.apiKey}` + : undefined, + }; + }, + transformer: superjson, + }), + ], + }); +} + +export function TRPCProvider({ + settings, + children, +}: { + settings: Settings; + children: React.ReactNode; +}) { + const queryClient = useMemo(() => new QueryClient(), [settings]); + const trpcClient = useMemo(() => getTRPCClient(settings), [settings]); + + return ( + + {children} + + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00b895e6..caeed82f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -796,6 +796,9 @@ importers: '@tanstack/react-query': specifier: ^5.24.8 version: 5.24.8(react@18.2.0) + '@trpc/client': + specifier: 11.0.0-next-beta.308 + version: 11.0.0-next-beta.308(@trpc/server@11.0.0-next-beta.308) react: specifier: '*' version: 18.2.0 @@ -805,6 +808,9 @@ importers: react-native-web: specifier: '*' version: 0.19.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + superjson: + specifier: ^2.2.1 + version: 2.2.1 devDependencies: '@hoarder/eslint-config': specifier: workspace:^0.2.0