Note: This site is currently "Under construction". I'm migrating to a new version of my site building software. Lots of things are in a state of disrepair as a result (for example, footnote links aren't working). It's all part of the process of building in public. Most things should still be readable though.

Access The Spotify API From A Tauri App

I'm

FILE: src/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <script type="module">
    import {SpotifyControl} from "./main.js"
    document.addEventListener('DOMContentLoaded', async () => {
      const sc = new SpotifyControl()
      sc.init()
    })
  </script>
</head>

<body>
  <div id="auth_button_holder"></div>
</body>

</html>

FILE: src/main.js

const { invoke } = window.__TAURI__.tauri;

class SpotifyControl {
  constructor() { }

  async init() {
    let user_id = await invoke('user_id')
    if (user_id) {
      this.make_logout_button(user_id)
    }
    else {
      const urlParams = new URLSearchParams(window.location.search)
      let code = urlParams.get('code')
      if (code) {
        await invoke("process_auth_code", { url: window.location.href })
        window.location.href = '/'
      }
      this.make_login_button()
    }
  }

  make_login_button() {
    while (auth_button_holder.children.length > 0) {
      auth_button_holder.children[0].remove()
    }
    const login_button = document.createElement('button')
    login_button.innerText = 'login'
    login_button.addEventListener('click', async () => {
      window.location.href = await invoke('start_login')
    })
    auth_button_holder.appendChild(login_button)
  }

  make_logout_button(user_id) {
    while (auth_button_holder.children.length > 0) {
      auth_button_holder.children[0].remove()
    }
    const name_badge = document.createElement('div')
    name_badge.innerHTML = user_id
    auth_button_holder.appendChild(name_badge)
    const logout_button = document.createElement('button')
    logout_button.innerText = 'logout'
    logout_button.addEventListener('click', async () => {
      await invoke("logout")
      window.location.href = '/'
    })
    auth_button_holder.appendChild(logout_button)
  }
}

export { SpotifyControl }

FILE: src-tuari/Cargo.toml

[package]
name = "spotify_test"
version = "0.0.1"
description = "Spotify API Test App"
authors = ["Alan W. Smith"]
license = "MIT"
edition = "2021"

[build-dependencies]
tauri-build = { version = "1.5", features = [] }

[dependencies]
tauri = { version = "1.5", features = ["shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rspotify = { version = "0.12.0", default-features = false, features = ["client-ureq", "ureq-rustls-tls"] }

[features]
custom-protocol = ["tauri/custom-protocol"]

FILE: src-tauri/src/main.rs

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use rspotify::{prelude::*, scopes, AuthCodePkceSpotify, Credentials, OAuth};
use std::sync::Mutex;
use tauri::Manager;
use tauri::State;

struct Storage {
    spotify: Mutex<AuthCodePkceSpotify>,
    user_id: Mutex<Option<String>>,
}

#[tauri::command]
fn logout(store: State<'_, Storage>) {
    let mut uid = store.user_id.lock().unwrap();
    *uid = None
}

#[tauri::command]
fn process_auth_code(url: String, store: State<'_, Storage>) -> Result<String, ()> {
    let s = store.spotify.lock().unwrap();
    let _ = s.request_token(s.parse_response_code(&url).unwrap().as_ref());
    let user = s.me();
    match user {
        Ok(data) => {
            let mut uid = store.user_id.lock().unwrap();
            *uid = Some(data.id.id().to_string());
        }
        Err(_) => {}
    }
    Ok(format!("{}", &url))
}

#[tauri::command]
fn start_login(store: State<Storage>) -> String {
    let mut s = store.spotify.lock().unwrap();
    let url = s.get_authorize_url(None).unwrap();
    format!("{}", &url)
}

#[tauri::command]
fn user_id(store: State<'_, Storage>) -> Option<String> {
    let uid = store.user_id.lock().unwrap();
    uid.as_ref().cloned()
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            logout,
            process_auth_code,
            start_login,
            user_id,
        ])
        .setup(|app| {
            {
                let window = app.get_window("main").unwrap();
                window.open_devtools();
            }
            Ok(())
        })
        .manage(Storage {
            spotify: Mutex::new(AuthCodePkceSpotify::new(
                Credentials::new_pkce("YOUR_CLIENT_ID"),
                OAuth {
                    redirect_uri: "http://127.0.0.1:1430/".to_string(),
                    scopes: scopes!("user-read-private user-read-email"),
                    ..Default::default()
                },
            )),
            user_id: Mutex::new(None),
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}