use crate::{
auth_urls,
clients::{BaseClient, OAuthClient},
http::{Form, HttpClient},
join_scopes, params,
sync::Mutex,
ClientError, ClientResult, Config, Credentials, OAuth, Token,
};
use std::collections::HashMap;
use std::sync::Arc;
use maybe_async::maybe_async;
use url::Url;
#[derive(Clone, Debug, Default)]
pub struct AuthCodeSpotify {
pub creds: Credentials,
pub oauth: OAuth,
pub config: Config,
pub token: Arc<Mutex<Option<Token>>>,
pub(crate) http: HttpClient,
}
#[cfg_attr(target_arch = "wasm32", maybe_async(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), maybe_async)]
impl BaseClient for AuthCodeSpotify {
fn get_http(&self) -> &HttpClient {
&self.http
}
fn get_token(&self) -> Arc<Mutex<Option<Token>>> {
Arc::clone(&self.token)
}
fn get_creds(&self) -> &Credentials {
&self.creds
}
fn get_config(&self) -> &Config {
&self.config
}
async fn refetch_token(&self) -> ClientResult<Option<Token>> {
match self.token.lock().await.unwrap().as_ref() {
Some(Token {
refresh_token: Some(refresh_token),
..
}) => {
let mut data = Form::new();
data.insert(params::REFRESH_TOKEN, refresh_token);
data.insert(params::GRANT_TYPE, params::REFRESH_TOKEN);
let headers = self
.creds
.auth_headers()
.expect("No client secret set in the credentials.");
let mut token = self.fetch_access_token(&data, Some(&headers)).await?;
token.refresh_token = Some(refresh_token.to_string());
if let Some(callback_fn) = &*self.get_config().token_callback_fn.clone() {
callback_fn.0(token.clone())?;
}
Ok(Some(token))
}
_ => {
log::warn!("Can not refresh token! Token missing!");
Err(ClientError::InvalidToken)
}
}
}
}
#[cfg_attr(target_arch = "wasm32", maybe_async(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), maybe_async)]
impl OAuthClient for AuthCodeSpotify {
fn get_oauth(&self) -> &OAuth {
&self.oauth
}
async fn request_token(&self, code: &str) -> ClientResult<()> {
log::info!("Requesting Auth Code token");
let scopes = join_scopes(&self.oauth.scopes);
let mut data = Form::new();
data.insert(params::GRANT_TYPE, params::GRANT_TYPE_AUTH_CODE);
data.insert(params::REDIRECT_URI, &self.oauth.redirect_uri);
data.insert(params::CODE, code);
data.insert(params::SCOPE, &scopes);
data.insert(params::STATE, &self.oauth.state);
let headers = self
.creds
.auth_headers()
.expect("No client secret set in the credentials.");
let token = self.fetch_access_token(&data, Some(&headers)).await?;
if let Some(callback_fn) = &*self.get_config().token_callback_fn.clone() {
callback_fn.0(token.clone())?;
}
*self.token.lock().await.unwrap() = Some(token);
self.write_token_cache().await
}
}
impl AuthCodeSpotify {
#[must_use]
pub fn new(creds: Credentials, oauth: OAuth) -> Self {
Self {
creds,
oauth,
..Default::default()
}
}
#[must_use]
pub fn from_token(token: Token) -> Self {
Self {
token: Arc::new(Mutex::new(Some(token))),
..Default::default()
}
}
#[must_use]
pub fn with_config(creds: Credentials, oauth: OAuth, config: Config) -> Self {
Self {
creds,
oauth,
config,
..Default::default()
}
}
#[must_use]
pub fn from_token_with_config(
token: Token,
creds: Credentials,
oauth: OAuth,
config: Config,
) -> Self {
Self {
token: Arc::new(Mutex::new(Some(token))),
creds,
oauth,
config,
..Default::default()
}
}
#[cfg(feature = "reqwest-middleware")]
pub fn with_middleware<M: rspotify_http::Middleware>(self, middleware: M) -> Self {
use rspotify_http::HttpClientBuilder;
let http = HttpClientBuilder::default().with(middleware).build();
Self { http, ..self }
}
#[cfg(feature = "reqwest-middleware")]
pub fn with_middleware_arc(self, middleware: Arc<dyn rspotify_http::Middleware>) -> Self {
use rspotify_http::HttpClientBuilder;
let http = HttpClientBuilder::default().with_arc(middleware).build();
Self { http, ..self }
}
pub fn get_authorize_url(&self, show_dialog: bool) -> ClientResult<String> {
log::info!("Building auth URL");
let scopes = join_scopes(&self.oauth.scopes);
let mut payload: HashMap<&str, &str> = HashMap::new();
payload.insert(params::CLIENT_ID, &self.creds.id);
payload.insert(params::RESPONSE_TYPE, params::RESPONSE_TYPE_CODE);
payload.insert(params::REDIRECT_URI, &self.oauth.redirect_uri);
payload.insert(params::SCOPE, &scopes);
payload.insert(params::STATE, &self.oauth.state);
if show_dialog {
payload.insert(params::SHOW_DIALOG, "true");
}
let request_url = self.auth_url(auth_urls::AUTHORIZE);
let parsed = Url::parse_with_params(&request_url, payload)?;
Ok(parsed.into())
}
}