import { createFileRoute } from "@tanstack/react-router"; import { SpotifyWebApiClient } from "@/lib/spotify/client"; function getAccessTokenFromCookies(request: Request): string | null { const cookieHeader = request.headers.get("cookie"); if (!cookieHeader) return null; const cookies = Object.fromEntries( cookieHeader.split("; ").map((c) => c.split("=")) ); return cookies.spotify_access_token || null; } export const Route = createFileRoute("/api/spotify/playback")({ server: { handlers: { POST: async ({ request }: { request: Request }) => { try { const accessToken = getAccessTokenFromCookies(request); if (!accessToken) { return new Response( JSON.stringify({ error: "No access token found" }), { status: 401, headers: { "Content-Type": "application/json" }, } ); } const body = await request.json(); const { action, deviceId, volumePercent, trackId, positionMs } = body; const spotifyClient = new SpotifyWebApiClient(accessToken); switch (action) { case "play": await spotifyClient.play(deviceId); break; case "playTrack": if (!trackId) { return new Response( JSON.stringify({ error: "trackId is required for playTrack action", }), { status: 400, headers: { "Content-Type": "application/json" }, } ); } await spotifyClient.playTrack(trackId, deviceId, positionMs); break; case "pause": await spotifyClient.pause(); break; case "next": await spotifyClient.skipToNext(); break; case "previous": await spotifyClient.skipToPrevious(); break; case "volume": if (typeof volumePercent !== "number") { return new Response( JSON.stringify({ error: "volumePercent must be a number" }), { status: 400, headers: { "Content-Type": "application/json" }, } ); } await spotifyClient.setVolume(volumePercent); break; case "transfer": if (!deviceId) { return new Response( JSON.stringify({ error: "deviceId is required for transfer action", }), { status: 400, headers: { "Content-Type": "application/json" }, } ); } await spotifyClient.transferPlayback(deviceId); break; default: return new Response(JSON.stringify({ error: "Invalid action" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } return new Response(JSON.stringify({ success: true }), { status: 200, headers: { "Content-Type": "application/json" }, }); } catch (error) { console.error("Playback control error:", error); if (error instanceof Error) { if (error.message.includes("NO_ACTIVE_DEVICE")) { return new Response( JSON.stringify({ error: "No active device found. Please select a device first.", }), { status: 400, headers: { "Content-Type": "application/json" }, } ); } if (error.message.includes("PREMIUM_REQUIRED")) { return new Response( JSON.stringify({ error: "Spotify Premium is required for playback control.", }), { status: 403, headers: { "Content-Type": "application/json" }, } ); } console.error("Full error details:", { message: error.message, stack: error.stack, name: error.name, }); } return new Response( JSON.stringify({ error: "Playback control failed", details: error instanceof Error ? error.message : "Unknown error", }), { status: 500, headers: { "Content-Type": "application/json" }, } ); } }, GET: async ({ request }: { request: Request }) => { try { const accessToken = getAccessTokenFromCookies(request); if (!accessToken) { return new Response( JSON.stringify({ error: "No access token found" }), { status: 401, headers: { "Content-Type": "application/json" }, } ); } const url = new URL(request.url); const type = url.searchParams.get("type"); const spotifyClient = new SpotifyWebApiClient(accessToken); if (type === "devices") { const devices = await spotifyClient.getDevices(); return new Response(JSON.stringify({ devices }), { status: 200, headers: { "Content-Type": "application/json" }, }); } else if (type === "state") { const playbackState = await spotifyClient.getPlaybackState(); return new Response(JSON.stringify({ playbackState }), { status: 200, headers: { "Content-Type": "application/json" }, }); } else { const [devices, playbackState] = await Promise.all([ spotifyClient.getDevices(), spotifyClient.getPlaybackState(), ]); return new Response(JSON.stringify({ devices, playbackState }), { status: 200, headers: { "Content-Type": "application/json" }, }); } } catch (error) { console.error("Get playback data error:", error); return new Response( JSON.stringify({ error: "Failed to get playback data" }), { status: 500, headers: { "Content-Type": "application/json" }, } ); } }, }, }, });