Skip to content

Commit

Permalink
feat(providers): Base (very) WIP Spotify provider
Browse files Browse the repository at this point in the history
  • Loading branch information
busybox11 committed Nov 15, 2024
1 parent 9350086 commit c8f7cee
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 14 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package-lock.json
yarn.lock

# builds
types
build
*/build
dist
Expand Down
25 changes: 25 additions & 0 deletions app/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import z from "zod";

const providerConfigs: z.ZodObject<any>[] = [];

// Import all detected provider config files
const modules: Record<string, z.ZodObject<any>> = import.meta.glob(
"./providers/**/config.ts",
{
eager: true,
import: "default",
}
);
for (const path in modules) {
const module = modules[path];

providerConfigs.push(module);
}

const envSchema = z.object({}).merge(
providerConfigs.reduce((acc, config) => {
return acc.merge(config);
}, z.object({}))
);

export default envSchema.parse(import.meta.env);
12 changes: 12 additions & 0 deletions app/providers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ProviderMeta } from "@/types/providers/meta";

const providersGlob = import.meta.glob<ProviderMeta>("./*/index.ts", {
eager: true,
import: "default",
});

const providers = Object.fromEntries(
Object.values(providersGlob).map((value) => [value.id, value])
);

export default providers;
29 changes: 29 additions & 0 deletions app/providers/spotify/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { SPOTIFY_OAUTH_SCOPES } from "./constants";
import {
AccessToken,
SpotifyApi,
AuthorizationCodeWithPKCEStrategy,
} from "@spotify/web-api-ts-sdk";

import { providerConfig } from "./config";

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);

try {
const { authenticated } = await client.authenticate();

if (authenticated) {
console.log("Authenticated");
}
} catch (e) {
console.error(e);
}
}
10 changes: 10 additions & 0 deletions app/providers/spotify/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import z from "zod";

const envSchema = z.object({
VITE_SPOTIFY_CLIENT_ID: z.string(),
SPOTIFY_CLIENT_SECRET: z.string().optional(),
VITE_SPOTIFY_REDIRECT_URI: z.string(),
});
export default envSchema;

export const providerConfig = envSchema.parse(import.meta.env || process.env);
5 changes: 5 additions & 0 deletions app/providers/spotify/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const SPOTIFY_OAUTH_SCOPES = [
"user-read-currently-playing",
"user-read-playback-state",
"streaming",
] as const;
8 changes: 8 additions & 0 deletions app/providers/spotify/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ProviderMeta } from "@/types/providers/meta";

export default {
name: "Spotify",
id: "spotify",
auth: (await import("./client")).authenticate,
callback: (await import("./client")).authenticate,
} as ProviderMeta;
31 changes: 27 additions & 4 deletions app/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import { Route as rootRoute } from './routes/__root'
import { Route as IndexImport } from './routes/index'
import { Route as AuthCallbackProviderImport } from './routes/auth/callback.$provider'

// Create/Update Routes

Expand All @@ -21,6 +22,12 @@ const IndexRoute = IndexImport.update({
getParentRoute: () => rootRoute,
} as any)

const AuthCallbackProviderRoute = AuthCallbackProviderImport.update({
id: '/auth/callback/$provider',
path: '/auth/callback/$provider',
getParentRoute: () => rootRoute,
} as any)

// Populate the FileRoutesByPath interface

declare module '@tanstack/react-router' {
Expand All @@ -32,39 +39,51 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/auth/callback/$provider': {
id: '/auth/callback/$provider'
path: '/auth/callback/$provider'
fullPath: '/auth/callback/$provider'
preLoaderRoute: typeof AuthCallbackProviderImport
parentRoute: typeof rootRoute
}
}
}

// Create and export the route tree

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/auth/callback/$provider': typeof AuthCallbackProviderRoute
}

export interface FileRoutesByTo {
'/': typeof IndexRoute
'/auth/callback/$provider': typeof AuthCallbackProviderRoute
}

export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/auth/callback/$provider': typeof AuthCallbackProviderRoute
}

export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/'
fullPaths: '/' | '/auth/callback/$provider'
fileRoutesByTo: FileRoutesByTo
to: '/'
id: '__root__' | '/'
to: '/' | '/auth/callback/$provider'
id: '__root__' | '/' | '/auth/callback/$provider'
fileRoutesById: FileRoutesById
}

export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AuthCallbackProviderRoute: typeof AuthCallbackProviderRoute
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AuthCallbackProviderRoute: AuthCallbackProviderRoute,
}

export const routeTree = rootRoute
Expand All @@ -77,11 +96,15 @@ export const routeTree = rootRoute
"__root__": {
"filePath": "__root.tsx",
"children": [
"/"
"/",
"/auth/callback/$provider"
]
},
"/": {
"filePath": "index.tsx"
},
"/auth/callback/$provider": {
"filePath": "auth/callback.$provider.tsx"
}
}
}
Expand Down
1 change: 1 addition & 0 deletions app/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const Route = createRootRoute({
},
],
component: RootComponent,
notFoundComponent: () => <h1>404 - Not Found</h1>,
});

function RootComponent() {
Expand Down
30 changes: 30 additions & 0 deletions app/routes/auth/callback.$provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router";

import providers from "@/providers";
import { useEffect } from "react";

export const Route = createFileRoute("/auth/callback/$provider")({
component: RouteComponent,
});

function RouteComponent() {
const { provider } = Route.useParams();
const navigate = useNavigate();

if (!providers[provider]) {
return <h1>Provider not found</h1>;
}

useEffect(() => {
providers[provider].callback().then(() => {
console.log("Authenticated");

navigate({
to: "/",
replace: true,
});
});
}, []);

return <h1>Authenticating...</h1>;
}
20 changes: 16 additions & 4 deletions app/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as fs from "node:fs";
import { createFileRoute, useRouter } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/start";
import { createFileRoute } from "@tanstack/react-router";
import { MiscLinks } from "@/components/MiscLinks";

import providers from "@/providers";

export const Route = createFileRoute("/")({
component: Home,
});

function Home() {
return (
<main className="flex flex-col items-center justify-center h-screen">
<main className="flex flex-col items-center justify-center h-screen gap-12">
<section className="flex flex-row gap-6 items-center">
<img
src="/images/favicon.png"
Expand All @@ -23,6 +23,18 @@ function Home() {
<MiscLinks />
</div>
</section>

<div className="flex flex-col gap-4">
{Object.values(providers).map((provider) => (
<button
key={provider.id}
onClick={() => provider.auth()}
className="border-b-2 text-white/70 hover:text-white border-white/50 hover:border-white/70 text-lg tracking-wide active:scale-95 transition mx-auto"
>
{provider.name}
</button>
))}
</div>
</main>
);
}
10 changes: 6 additions & 4 deletions app/ssr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/start/server'
import { getRouterManifest } from '@tanstack/start/router-manifest'
} from "@tanstack/start/server";
import { getRouterManifest } from "@tanstack/start/router-manifest";

import { createRouter } from './router'
import { createRouter } from "./router";

import "./config";

export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler)
})(defaultStreamHandler);
6 changes: 6 additions & 0 deletions app/types/providers/meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface ProviderMeta {
name: string;
id: string;
auth: (callback?: (token: any) => void) => any;
callback: (callback?: (token: any) => void) => any;
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
"author": "busybox11",
"license": "GPL-3.0",
"dependencies": {
"@spotify/web-api-ts-sdk": "^1.2.0",
"@tanstack/react-router": "^1.81.5",
"@tanstack/start": "^1.81.5",
"dotenv": "^16.4.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"vinxi": "^0.4.3"
"vinxi": "^0.4.3",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/react": "^18.3.12",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c8f7cee

Please sign in to comment.