Files
flxn-app/src/app/routes/api/spotify/playback.ts
2025-09-24 00:13:41 -05:00

204 lines
6.6 KiB
TypeScript

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" },
}
);
}
},
},
},
});