Like A Girl

Pushing the conversation on gender equality.

Code Like A Girl

How I wrote my first Google Chrome Extension using Spotify’s API

UPDATE 4/4/18: Download my chrome extension here! https://chrome.google.com/webstore/detail/spotify-key-bpm-finder/legnebocohbmkickdngjafeincjgnggp

In mid-December I finished the first half of Grace Hopper’s immersive coding bootcamp. With winter break stretching out in front of me, I was eager to flex my newly developed JavaScript skills and code a side project. I knew I wanted to make something using Spotify’s API to display the key signature and beats per minute (or BPM) of songs. One of my hobbies is making mixes of electronic music and I had been frustrated in the past at trying to figure out the key signatures and BPM of songs so I could mix them smoothly into my sets.

Sure, as a programmer I could make individual queries to Spotify’s API for the data but repeating the same task over and over again is something I outsourced to my computer about 10,000 for loops ago. So during an office hour before the break, my instructor gave me a great suggestion: do this project as a Google Chrome Extension. With a Chrome Extension I would be able to have the key signature and BPM of songs automatically displayed while casually browsing Spotify’s website.

While this idea was exactly what I wanted in theory, it was a bit more intimidating in practice. Nevertheless, I figured out how to break the project up into bite-sized pieces, coded out my extension, and this is the guide for anyone looking to make something similar.

From a technical standpoint, I’ll assume an intermediate knowledge of Javascript, some experience building web applications in Node, and an interest in learning how to leverage these skills to build something on a new platform. I’ll focus on three main things: getting acquainted with the layout of Chrome Extensions, the Chrome Platform API’s that govern when and how your code is run, and integrating a third-party API, in this case Spotify.

The Bones of a Chrome Extension

The first thing you need to know about in order to build a Chrome Extension is the manifest.json. If you’re familiar with Node, you can think about the manifest.json as the rough equivalent of your package.json. Oftentimes with Node projects, you can get away with editing the package.json from the command line with npm init and npm install and you only have to add a few scripts over the course of building your app. In a Chrome Extension you’ll be editing the manifest.json extensively so if you don’t have a linter installed to check your quotes, object nesting, and commas now is the time to do so.

The Chrome Developer website has the rundown on every possible field a manifest.json can have. I’m going to give you the bullet points on the important ones.

First off: “browser_action”

"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html",
"default_title": "Click here!"
}

The default icon is the little square that pops up to the right of your search bar and the default title is the text that pops up when you hover your cursor over it.

here’s my eclipse icon and title text

The default popup is where things start to get interesting. It’s the link to the html file that will appear when you click on the icon. In Node, the way code from different files know about each other is by specifying them as module.exports in the exporting file and requiring them in as variables in the receiving file. In Chrome Extensions your files get connected by the manifest.json. If your browser action designates popup.html, that’s the file that will appear when you click on the icon for your extension.

The next up in the manifest.json: “content_scripts”

"content_scripts": [
{
"matches": ["https://*.spotify.com/*"],
"js": ["content_script.js"],
"run_at": "document_end"
}
]

The content script is the file that can directly access the contents of a web page. That means it’s the file that actually writes stuff on Spotify’s website when the app is running. The matches field specifies which domains to run the content script on. In my case, the content script was only relevant when browsing through Spotify’s music, and the wildcards before Spotify and after the slash covered all the urls I needed.

The “run_at” key specifies when to inject the content script. I chose “document_end” so that my content_script would be injected after the DOM finished rendering, that way the content script could grab and add the key signature and BPM information to the correct element on the page. If I had picked “document_start” I might have been trying to grab a DOM element that didn’t exist yet.

Wait so what DOM elements was I grabbing? Well after I fetched the song data (more on that flow later), I grabbed each track’s title by class name, spread it into an array with the ES6 spread operator, and mapped the data from the API directly onto the song titles. The key signature data from Spotify came in numbers so I used an array of the equivalent pitches to display the data in a way musicians are familiar with.

function addSongInfoToTitle (songDataArr) {
const songTitlesArr = [...document.getElementsByClassName('tracklist-name')]
const pitchClass = [
'C',
'C♯/D♭',
'D',
'D♯/E♭',
'E',
'F',
'F♯/G♭',
'G',
'G♯/A♭',
'A',
'A♯/B♭',
'B'
]
let keyArr = songDataArr.map(songDatum => songDatum.track.key)
let bpmArr = songDataArr.map(songDatum => songDatum.track.tempo)
let mode = songDataArr.map(songDatum => songDatum.track.mode)
songTitlesArr.map((songTitle, index) => {
let keyMode = (mode[index] === 1) ? 'maj' : 'min'
return songTitle.append(` - ${pitchClass[keyArr[index]]} ${keyMode} & ${bpmArr[index].toFixed(0)} BPM`)
})
}

At this point you may be wondering what initiated the process of retrieving the song data. How exactly did I get it from Spotify in the first place? To explain that we’ll head back to the manifest.json and talk about event pages.

“background”:

Event pages are specified in the background section of the manifest.json. In the early days of Chrome Extensions, background pages were constantly running scripts to manage the state and tasks required of your extension. However, because they were always running, they took up significant memory and resources. Event pages to the rescue. Event pages are like background pages in that they can use the full range of Chrome Platform API’s to control the behavior of your app but they are unloaded when not needed freeing up memory and offering performance advantages. All you need to do to specify an event page is add “persistent”: false to your background object in your manifest.json.

Chrome Platform APIs

Chrome Platform APIs are a set of JavaScript APIs that communicate with the Chrome browser and depending on their flavor, can be used to implement a whole range of behaviors you need for your app. For example, chrome.runtime.onInstalled is fired when the extension is installed. From there you can chain an event listener and write a callback function to be fired once the event fires. The only difference between vanilla front end JavaScript and a Chrome Extension is that the extension uses the “addListener” function to do the job of the “addEventListener” function.

I used Chrome Platform APIs to send a message to launch my Oauth flow, listen for user authentication, keep track of the state of the app (whether a user had authenticated or not), and to send a message to my content script to get to work fetching data. As a first time user, I found the platform APIs surprisingly straightforward to get started with but a little bit tricky to implement them exactly the way I wanted. The most important thing that helped me get the hang of things was the difference between the content script and the event page. An event page can access ALL Chrome Platform APIs but it can’t access the content of web pages. A content script can directly read and modify web pages but it can only use a limited range of Chrome Platform APIs. Luckily chrome.extension.onMessage in the event page can be used to pass messages to chrome.runtime.onMessage in the content script to communicate about what your extension needs to do.

The other tricky part of getting used to the Chrome Platform APIs was figuring out where my console.logs were going. Here’s the cheat sheet:

  1. In a content script, the logs will appear on pages in your browser that are executing that content script. Which domains your content script executes on are specified in your manifest.json content script’s “matches” key
  2. In a background page or event page, go to chrome://extensions, find your extension, and click on Inspect views: background page. A window will pop up that shows all your console.log statements.
  3. If you have a JavaScript file associated with your popup.html (I have a popup.js file that’s linked in a script tag in my popup.html for initiating Oauth flow), control click on your extension’s icon and select “Inspect popup”
A developer window will pop up with your popup.js console.logs

Don’t blindly guess what your code is doing while debugging, use your console.logs!

Integrating with Spotify’s API

After acquainting myself with the layout of Google Chrome Extensions, the core technical challenge was figuring out how to make XMLHttpRequests to get the data I needed from Spotify’s API into my extension. Spotify’s developer site is well documented and easy to use so I’ll skip the part about setting up an account and what API endpoints are available. In order to use my extension, a user needs to be able to authenticate with Spotify, receive an access token, and pass that token to me. Then without storing sensitive information like their username and password, I use the token to make requests to Spotify’s API for song key signature and BPM data.

In making my XMLHttpRequests to Spotify, I wanted to control the asynchronous flow of data, write readable code, and avoid callback hell. I decided to write a promisified function to make xhr requests. That way, I would be able to chain multiple asynchronous requests to Spotify’s API together to find the right API endpoints based on the song data I grabbed from the DOM. Here’s the work-horse function I used to do the job:

function makeXhrRequest(method, url, token) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.setRequestHeader('Authorization', 'Bearer ' + token)
xhr.onload = function(){
if (xhr.status >= 200 && xhr.status < 300){
return resolve(xhr.response);
} else {
reject(Error({
status: xhr.status,
statusTextInElse: xhr.statusText
}))
}
}
xhr.onerror = function(){
reject(Error({
status: xhr.status,
statusText: xhr.statusText
}))
}
xhr.send()
})
}

My chained XMLHttpRequests eventually returned the data that I needed to display on the DOM, which I passed to a function that added the information to the correct element.

During development, I hard-coded the token for a while to get things going. Spotify allows you to generate tokens with curl requests, so that’s what I used to quickly grab a token to hard-code in for testing purposes. This allowed me to troubleshoot getting the data back from Spotify’s API, parsing the JSON, and finding the correct keys and values on the response object to pass to the next part of my promise chain.

After I set up the auth flow in my Event Page with Chrome’s chrome.identity API, I re-used my makeXhrRequest promisified function with a simple change to the request header to dynamically pass along a user’s token. The final step was integrating a click event in my popup.js to launch the authentication flow, which prompted the user to authenticate with Spotify and pass a token to my Event Page. The token gives a user access for an hour. With my extension, a user won’t always have to look at key signature data while browsing Spotify, but he or she can retrieve an hour’s worth of data with the click of a button. My extension loads key and bpm dynamically as a user browses Spotify for a seamless experience. In the mood to create a DJ mix or find the perfect speed song to work out to? Launch KEY and BPM Finder and get searching on Spotify!

When I started building my extension, I didn’t know anything about how Chrome Extensions were structured, how to manipulate the DOM with content scripts, or how to request data from Spotify’s API to use in my project. While it was frustrating at times, I eventually put the pieces together and made something I’m proud of. I read the docs, searched Stack Overflow for questions, and tweaked things over and over again until I got a product that worked. Consistency was key in making progress and once I got some momentum going, making my extension was a lot of fun. Building this project showed me what I was capable of after an intense learning phase at Grace Hopper. I was surprised by how quickly I could pick up new technologies and use them in a new paradigm.

Here’s a link to the source code on Github with my sensitive client data redacted. In the next few weeks, as I finish up at Grace Hopper, I will be releasing my extension on the Chrome Web Store. Check back for the link soon!