Access The Spotify API From A Tauri App
December 2023
I'm >dissatisfied with the recomendations for new music>/pages/2a038rtz/> from the various streaming services. So, I'm bulding my own robot DJ on top of the Spotify Web API. I've decided to build it as a Tuari app. This is the first set of work getting a connection to the API working.
Notes
This is how I'm connecting to the >Spotify Web API>https://developer.spotify.com/documentation/web-api> from a >Tauri>https://tauri.app> app
It's bare bones prototype code
The code produces an app with a working login/logout button that also shows the user id when logged in
The files are based of a standard `cargo create-tauri-app` run with JavaScript for the front end language
More error handling is a good idea
The >rspotify crate>https://docs.rs/rspotify/latest/rspotify/> I'm using has async capabilities. I went with synchronous. It was easier to figure out
The only thing I did outside this code was create an app in the >Spotify developer dashboard>https://developer.spotify.com/dashboard> and get the Client ID for it to use in the code
The code uses the >PKCE Auth Code flow>https://developer.spotify.com/documentation/web-api/tutorials/code-pkce-flow> approach which means the Client Secret is not required
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>
ToggleWrap
Copy
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 }
ToggleWrap
Copy
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"]
ToggleWrap
Copy
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" ) ;
}
ToggleWrap
Copy