• Starts the OAuth authentication flow

    Initiates the complete Spotify OAuth 2.0 authorization code flow by:

    1. Setting API credentials in the Spotify service
    2. Optionally clearing existing session data for account selection
    3. Extracting the callback port from the redirect URI
    4. Configuring authorization scopes for required permissions
    5. Generating a secure state parameter to prevent CSRF attacks
    6. Creating a local callback server to capture the authorization code
    7. Opening the authentication browser window for user login
    8. Managing promise resolution/rejection based on outcome
    9. Providing comprehensive error handling throughout the flow
    10. Ensuring proper cleanup of resources in all scenarios

    The flow handles both successful authentication (resolving with tokens) and failure scenarios (rejecting with detailed error information). The implementation maintains state across multiple async operations, coordinating the window, server, and token exchange phases.

    Parameters

    • parentWindow: BrowserWindow

      Parent window that owns the authentication dialog

    • clientId: string

      Spotify API client ID

    • clientSecret: string

      Spotify API client secret

    • redirectUri: string

      OAuth redirect URI for callback (must include protocol and port)

    • forceAccountSelection: boolean = false

      Whether to force account selection screen (default: false)

    Returns Promise<AuthTokens>

    Promise resolving to authentication tokens or rejecting with error

    // Start authentication flow from main application window
    try {
    const tokens = await startAuthFlow(
    mainWindow,
    "spotify-client-id",
    "spotify-client-secret",
    "http://localhost:8888/callback",
    true // Force account selection
    );
    console.log("Authentication successful!", tokens.accessToken);
    } catch (error) {
    console.error("Authentication failed:", error.message);
    }

    Error if authentication fails or is canceled

    export function startAuthFlow(
    parentWindow: BrowserWindow,
    clientId: string,
    clientSecret: string,
    redirectUri: string,
    forceAccountSelection: boolean = false,
    ): Promise<AuthTokens> {
    // Set credentials in the spotify API module
    spotifyApi.setCredentials(clientId, clientSecret);

    // Clear cookies if forcing account selection
    if (forceAccountSelection) {
    clearSpotifyAuthData();
    }

    return new Promise((resolve, reject) => {
    // Save reject function for external cancellation
    authPromiseReject = reject;

    // Track processing state
    isProcessingTokens = false;

    try {
    // Extract port from redirect URI for callback server
    const redirectUrl = new URL(redirectUri);
    const port = parseInt(redirectUrl.port) || 8888;

    // Build authorization URL with proper scopes
    const authScopes = [
    "user-read-playback-state",
    "user-modify-playback-state",
    "user-library-modify",
    "user-library-read",
    "user-read-recently-played",
    ];

    // Generate random state parameter
    const stateParam = `${Math.random().toString(36).substring(2, 15)}_${Date.now()}`;

    // Get authorization URL
    const authUrl = spotifyApi.getAuthorizationUrl(
    redirectUri,
    authScopes,
    stateParam,
    );

    // Set up the callback server to capture the OAuth redirect
    const serverOptions: CallbackHandlerOptions = {
    port,
    parentWindow,
    redirectUri,
    };

    // Create server and handle success/error
    createCallbackServer(
    serverOptions,
    (tokens) => {
    // Success handler
    if (!isProcessingTokens) {
    isProcessingTokens = true;

    // Clean up window
    closeAuthWindow();

    resolve(tokens);
    }
    },
    (error) => {
    // Error handler
    closeAuthWindow();
    authPromiseReject = null;
    reject(error);
    },
    );

    // Create and show the authentication window
    createAuthWindow(parentWindow, authUrl, () => {
    // Window closed handler
    if (!isProcessingTokens) {
    shutdownServer();
    authPromiseReject = null;
    reject(new Error("Authentication canceled by user"));
    }
    });
    } catch (error) {
    // Clean up on any initialization errors
    shutdownServer();
    closeAuthWindow();
    authPromiseReject = null;

    saveLog(`Error starting authentication flow: ${error}`, "ERROR");
    reject(new Error(`Failed to start authentication flow: ${error}`));
    }
    });
    }