Jammming - Spotify keeps asking me to accept conditions after each search

Hi everyone!

I have just finished the Jammming project, but I’m facing a very annoying issue:

Every time I search for something and click on ‘Search’, I’m automatically shown a Spotify screen asking me to accept their conditions, I accept them, and then I’m brought back again to the Jammming screen with an empty searchbar. I’m stucked in some kind of loop.

Any help will be very appreciated

You can access to it here: HTTP://marinabir94.surge.sh

Spotify.js :

let token;
let expiresIn;
const clientID = "{my_private_clientID}";
const redirectURI = "http://marinabir94.surge.sh";

const Spotify = {
  getAccessToken() {
    if (token) {
      return token;
    }

    //check access token match.
    const tokenMatch = window.location.href.match(/access_token=([^&]*)/);
    const expirationMatch = window.location.href.match(/expires_in=([^&]*)/);

    if (tokenMatch && expirationMatch) {
      token = tokenMatch[1];
      expiresIn = Number(expirationMatch[1]);
      //This clears the parameters, allowing us to grab a new access token when it expires
      window.setTimeout(() => token = '', expiresIn * 1000);
      window.history.pushState('Access Token', null, '/');
      return token;
    } else {
      window.location = `https://accounts.spotify.com/authorize?client_id=${clientID}&response_type=code&redirect_uri=${redirectURI}`;
    }
  },

  search(term) {
    const accessToken = Spotify.getAccessToken();
    return fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, {
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
      }).then((response) => {
        return response.json();
      }).then((jsonResponse) => {
        if (!jsonResponse.tracks) {
          return [];
        } 
          return jsonResponse.tracks.items.map((track) => ({
            id: track.id,
            name: track.name,
            artists: track.artists[0].name,
            album: track.album.name,
          }));
      });
  },

  savePlaylist(playlistName, arrURIs) {
    if (!playlistName || !arrURIs.length) {
      return;
    }
    const accessToken = Spotify.getAccessToken();
    const headers = {Authorization: `Bearer ${accessToken}`};
    let userID;
    let playlistID;

    //GET - Return user's ID
    return fetch(`https://api.spotify.com/v1/me`, {headers : headers}
    ).then(response => response.json()
    ).then(jsonResponse => {
        userID = jsonResponse.id;
        //POST - We use the userID to create a Playlist ID
        return fetch(`https://api.spotify.com/v1/users/${userID}/playlists`,
        {
            headers : headers,
            method : 'POST',
            body : JSON.stringify({
                name : playlistName,
                description: 'New playlist description',
                public: false})
        }
        ).then(response => response.json()
        ).then(jsonResponse => {
            playlistID = jsonResponse.id;
            //POST - We use the Playlist ID to upload tracks to that playlist
            return fetch(`https://api.spotify.com/v1/users/${userID}/playlists/${playlistID}/tracks`,
            {
                header : headers,
                method : 'POST',
                body : JSON.stringify({
                    uris : arrURIs
                })
            });
        })
    });



  },

  componentDidMount() {
    window.addEventListener('load', () => {Spotify.getAccessToken()});
  }


};

export default Spotify;

App.js :

import React from "react";
import "./App.css";
import SearchBar from "../SearchBar/SearchBar";
import SearchResults from "../SearchResults/SearchResults";
import Playlist from "../Playlist/Playlist";
import Spotify from "../../utils/Spotify";
//import { render } from "@testing-library/react";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      searchResults: [],
      playlistName: "New Playlist",
      playlistTracks: []
    };

    this.addTrack = this.addTrack.bind(this);
    this.removeTrack = this.removeTrack.bind(this);
    this.updatePlaylistName = this.updatePlaylistName.bind(this);
    this.savePlaylist = this.savePlaylist.bind(this);
    this.search = this.search.bind(this);
  }

  //Click handler
  addTrack(track) {
    let currentTracks = this.state.playlistTracks;
    if (currentTracks.includes(track.id)) {
      return;
    } else {
      currentTracks.push(track);
      this.setState({ playlistTracks: currentTracks });
    }
  }

  //Click handler
  removeTrack(track) {
    let currentTracks = this.state.playlistTracks;
    currentTracks = currentTracks.filter((item) => item.id !== track.id);
    this.setState({ playlistTracks: currentTracks });
  }

  updatePlaylistName(name) {
    this.setState({ playlistName: name });
  }

  //Needs an array of URIs to be sent to the API GET method.
  savePlaylist() {
    const trackURIs = this.state.playlistTracks.map((track) => track.uri);
    Spotify.savePlaylist(this.state.playlistName, trackURIs).then(() => {
      //After sending the POST, we restore the default values
      this.setState({
        playlistName: "New Playlist",
        playlistTracks: [],
      });
    });
  }

  search(term) {
    Spotify.search(term).then(results => {
      this.setState({ searchResults: results });
    })
  }

  render() {
    return (
      <div>
        <h1>
          Ja<span className="highlight">mmm</span>ing
        </h1>
        <div className="App">
          <SearchBar onSearch={this.search} />
          <div className="App-playlist">
            <SearchResults
              searchResults={this.state.searchResults}
              onAdd={this.addTrack}
            />
            <Playlist
              playlistName={this.state.playlistName}
              playlistTracks={this.state.playlistTracks}
              onRemove={this.removeTrack}
              onNameChange={this.updatePlaylistName}
            />
          </div>
        </div>
      </div>
    );
  }

  // componentDidMount() {
  //   window.addEventListener('load', () => {Spotify.getAccessToken()});
  // }
}

export default App;

According to the response_type you’re using in the URL above, you intend to use Spotify’s Code Authorization method rather than the Implicit Grant Flow that the project instructions were based on. Spotify Authorization Guide

For this reason, when Spotify redirects back to your application the URL looks something like:

http://marinabir94.surge.sh/?code=AQAagy10j...

rather than something like

http://marinabir94.surge.sh/access_token=BQA8JqObyKKB32...&expires_in=3600

which means these matches can never be made since they’re looking for access_token and expires_in in the URL:

    //check access token match.
    const tokenMatch = window.location.href.match(/access_token=([^&]*)/);
    const expirationMatch = window.location.href.match(/expires_in=([^&]*)/);

That causes your getAccessToken() method to always go to the block of code that redirects back to Spotify to get the authorization.

It’s possible to adapt your application to use Code Authorization, but it would be easiest to change the response_type query parameter of the Spotify authorization URL so that it uses Implicit Grant Flow (token) rather than Code Authorization (code)

Ok… that was an absolute dumb error!!! Thanks a lot for the fast response and for your time.
I was trying to extract these endpoints directly from the Spotify API documentation and I guess I confused these terms.

THANK YOU! :smiley: