From ffab780f34e7c23c389b67f6dd035f6bed51d2f5 Mon Sep 17 00:00:00 2001 From: busybox11 Date: Sun, 17 Nov 2024 22:01:10 +0100 Subject: [PATCH] refactor(providers): Use classes instead of fns, new PlayerProviders --- app/components/contexts/PlayerProviders.tsx | 42 ++++++++++++++++++ app/providers/index.ts | 28 +++++++++--- app/providers/spotify/client.ts | 49 +++++++++++++++------ app/providers/spotify/index.ts | 8 ++-- app/routes/__root.tsx | 5 ++- app/routes/auth/callback.$provider.tsx | 3 ++ app/routes/index.tsx | 25 ++++++----- app/types/providers/client.ts | 14 ++++++ app/types/providers/meta.ts | 2 - 9 files changed, 137 insertions(+), 39 deletions(-) create mode 100644 app/components/contexts/PlayerProviders.tsx create mode 100644 app/types/providers/client.ts diff --git a/app/components/contexts/PlayerProviders.tsx b/app/components/contexts/PlayerProviders.tsx new file mode 100644 index 0000000..170c520 --- /dev/null +++ b/app/components/contexts/PlayerProviders.tsx @@ -0,0 +1,42 @@ +import { createContext, useContext, useCallback, useMemo } from "react"; +import type { ReactNode } from "@tanstack/react-router"; + +import providers from "@/providers"; +import { IProviderClient } from "@/types/providers/client"; + +const PlayerProvidersContext = createContext<{ + [key: string]: IProviderClient; +}>({}); + +export function PlayerProvidersProvider({ + children, +}: Readonly<{ + children: ReactNode; +}>) { + const handleAuth = useCallback((provider: string) => { + console.log("auth", provider); + }, []); + + const value = useMemo(() => { + return Object.fromEntries( + providers.map(([id, provider]) => { + return [ + id, + new provider({ + onAuth: () => handleAuth(id), + }), + ]; + }) + ); + }, []); + + return ( + + {children} + + ); +} + +export function usePlayerProviders() { + return useContext(PlayerProvidersContext); +} diff --git a/app/providers/index.ts b/app/providers/index.ts index 9023625..0fb5006 100644 --- a/app/providers/index.ts +++ b/app/providers/index.ts @@ -1,12 +1,26 @@ -import { ProviderMeta } from "@/types/providers/meta"; +import type { + IProviderClient, + IProviderClientConstructor, +} from "@/types/providers/client"; -const providersGlob = import.meta.glob("./*/index.ts", { - eager: true, - import: "default", -}); +type NewProviderClient = new ( + args: IProviderClientConstructor +) => IProviderClient; -const providers = Object.fromEntries( - Object.values(providersGlob).map((value) => [value.id, value]) +const providersClientGlob = import.meta.glob( + "./*/client.ts", + { + eager: true, + import: "default", + } ); +const providers: [string, NewProviderClient][] = Object.entries( + providersClientGlob +).map(([importPath, client]) => { + const [, id] = importPath.split("/"); + + return [id, client]; +}); + export default providers; diff --git a/app/providers/spotify/client.ts b/app/providers/spotify/client.ts index cb0abe2..56ddcef 100644 --- a/app/providers/spotify/client.ts +++ b/app/providers/spotify/client.ts @@ -1,29 +1,50 @@ import { SPOTIFY_OAUTH_SCOPES } from "./constants"; import { - AccessToken, SpotifyApi, AuthorizationCodeWithPKCEStrategy, } from "@spotify/web-api-ts-sdk"; import { providerConfig } from "./config"; +import { + IProviderClient, + IProviderClientConstructor, +} from "@/types/providers/client"; +import spotifyProviderMeta from "@/providers/spotify"; const { VITE_SPOTIFY_CLIENT_ID, VITE_SPOTIFY_REDIRECT_URI } = providerConfig; -export async function authenticate(callback: (token: AccessToken) => void) { - const auth = new AuthorizationCodeWithPKCEStrategy( - VITE_SPOTIFY_CLIENT_ID, - VITE_SPOTIFY_REDIRECT_URI, - [...SPOTIFY_OAUTH_SCOPES] - ); - const client = new SpotifyApi(auth); +export default class SpotifyProvider implements IProviderClient { + readonly meta = spotifyProviderMeta; + isAuthenticated = false; + + // API event handlers + private onAuth: () => void; + + constructor({ onAuth }: IProviderClientConstructor) { + this.onAuth = onAuth; + } - try { - const { authenticated } = await client.authenticate(); + async authenticate() { + const auth = new AuthorizationCodeWithPKCEStrategy( + VITE_SPOTIFY_CLIENT_ID, + VITE_SPOTIFY_REDIRECT_URI, + [...SPOTIFY_OAUTH_SCOPES] + ); + const client = new SpotifyApi(auth); - if (authenticated) { - console.log("Authenticated"); + try { + const { authenticated } = await client.authenticate(); + + if (authenticated) { + this.isAuthenticated = true; + this.onAuth(); + } + } catch (e) { + console.error(e); } - } catch (e) { - console.error(e); + } + + async callback() { + await this.authenticate(); } } diff --git a/app/providers/spotify/index.ts b/app/providers/spotify/index.ts index 0ea7296..3920028 100644 --- a/app/providers/spotify/index.ts +++ b/app/providers/spotify/index.ts @@ -1,8 +1,8 @@ import type { ProviderMeta } from "@/types/providers/meta"; -export default { +const spotifyProviderMeta: ProviderMeta = { name: "Spotify", id: "spotify", - auth: (await import("./client")).authenticate, - callback: (await import("./client")).authenticate, -} as ProviderMeta; +}; + +export default spotifyProviderMeta; diff --git a/app/routes/__root.tsx b/app/routes/__root.tsx index 857a0b3..08e6f2f 100644 --- a/app/routes/__root.tsx +++ b/app/routes/__root.tsx @@ -7,6 +7,7 @@ import { Body, Head, Html, Meta, Scripts } from "@tanstack/start"; import type { ReactNode } from "react"; import appCss from "../styles/app.css?url"; +import { PlayerProvidersProvider } from "@/components/contexts/PlayerProviders"; export const Route = createRootRoute({ meta: () => [ @@ -34,7 +35,9 @@ export const Route = createRootRoute({ function RootComponent() { return ( - + + + ); } diff --git a/app/routes/auth/callback.$provider.tsx b/app/routes/auth/callback.$provider.tsx index 18936ed..65726f4 100644 --- a/app/routes/auth/callback.$provider.tsx +++ b/app/routes/auth/callback.$provider.tsx @@ -2,6 +2,7 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; import providers from "@/providers"; import { useEffect } from "react"; +import { usePlayerProviders } from "@/components/contexts/PlayerProviders"; export const Route = createFileRoute("/auth/callback/$provider")({ component: RouteComponent, @@ -11,6 +12,8 @@ function RouteComponent() { const { provider } = Route.useParams(); const navigate = useNavigate(); + const providers = usePlayerProviders(); + if (!providers[provider]) { return

Provider not found

; } diff --git a/app/routes/index.tsx b/app/routes/index.tsx index e2f3401..50afc8b 100644 --- a/app/routes/index.tsx +++ b/app/routes/index.tsx @@ -1,13 +1,14 @@ import { createFileRoute } from "@tanstack/react-router"; import { MiscLinks } from "@/components/MiscLinks"; - -import providers from "@/providers"; +import { usePlayerProviders } from "@/components/contexts/PlayerProviders"; export const Route = createFileRoute("/")({ component: Home, }); function Home() { + const providers = usePlayerProviders(); + return (
@@ -25,15 +26,17 @@ function Home() {
- {Object.values(providers).map((provider) => ( - - ))} + {Object.entries(providers).map(([id, provider]) => { + return ( + + ); + })}
); diff --git a/app/types/providers/client.ts b/app/types/providers/client.ts new file mode 100644 index 0000000..b33ed95 --- /dev/null +++ b/app/types/providers/client.ts @@ -0,0 +1,14 @@ +import { ProviderMeta } from "@/types/providers/meta"; + +export interface IProviderClientConstructor { + onAuth: () => void; +} + +export interface IProviderClient { + readonly meta: ProviderMeta; + + isAuthenticated: boolean; + + authenticate(): Promise; + callback(): Promise; +} diff --git a/app/types/providers/meta.ts b/app/types/providers/meta.ts index e441be9..2cbc396 100644 --- a/app/types/providers/meta.ts +++ b/app/types/providers/meta.ts @@ -1,6 +1,4 @@ export interface ProviderMeta { name: string; id: string; - auth: (callback?: (token: any) => void) => any; - callback: (callback?: (token: any) => void) => any; }