import type { PKCEState, SpotifyTokenResponse } from './types'; const SPOTIFY_AUTH_BASE = 'https://accounts.spotify.com'; const SPOTIFY_SCOPES = [ 'user-read-playback-state', 'user-modify-playback-state', 'user-read-currently-playing', 'streaming', ].join(' '); function generateRandomString(length: number): string { const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const values = crypto.getRandomValues(new Uint8Array(length)); return values.reduce((acc, x) => acc + possible[x % possible.length], ''); } export class SpotifyAuth { private clientId: string; private redirectUri: string; constructor(clientId: string, redirectUri: string) { this.clientId = clientId; this.redirectUri = redirectUri; } async startAuthFlow(returnPath: string = window.location.pathname): Promise { const randomState = generateRandomString(16); const stateWithPath = btoa(JSON.stringify({ state: randomState, returnPath: returnPath })); sessionStorage.setItem('spotify_state', randomState); const params = new URLSearchParams({ response_type: 'code', client_id: this.clientId, scope: SPOTIFY_SCOPES, redirect_uri: this.redirectUri, state: stateWithPath, }); const authUrl = `${SPOTIFY_AUTH_BASE}/authorize?${params.toString()}`; window.location.href = authUrl; } async refreshAccessToken(refreshToken: string): Promise { const response = await fetch('/api/spotify/token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh_token: refreshToken, }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Token refresh failed: ${error.error || 'Unknown error'}`); } return response.json(); } getReturnPath(): string { return '/'; } clearStoredData(): void { sessionStorage.removeItem('spotify_state'); } }