rust_fm 0.1.4

last.fm OAUTH and scrobbling
Documentation
use axum::{extract::Query, response::Html, routing::get, Router};
use serde::Deserialize;

use crate::auth::WebOAuth;

pub const CALLBACK_URL: &str = "http://localhost:8080/callback";

#[derive(Clone, Debug)]
pub enum WebCallbackError {
    UnableToBind,
    ChannelClosed
}

/// Struct to handle the callback system for authentication.
pub struct WebCallback {
    receiver: crossbeam::channel::Receiver<Result<String, WebCallbackError>>,
    _handle: tokio::task::JoinHandle<()>
}

impl WebCallback {
    /// This function will spawn the browser and manager to listen for a redirect authentication.
    pub async fn oauth(mut auth: WebOAuth) -> (WebOAuth, Result<(), WebCallbackError>) {

        let manager = Self::spawn();
        let _ = auth.browser_auth().await;
        
        match manager.callback().await {
            Ok(token) => {
                auth.set_token(token);
                (auth, Ok(()))
            },
            Err(error) => (auth, Err(error))
        }
    }

    fn spawn() -> Self {
        let (sender, receiver) = crossbeam::channel::unbounded();
        let _handle = tokio::spawn(start_server(sender));
        Self { receiver, _handle }
    }

    async fn callback(&self) -> Result<String, WebCallbackError> {
        let recv = self.receiver.clone();
        let res = tokio::task::spawn_blocking(move || recv.recv()).await;

        match res {
            Ok(channel_result) => match channel_result {
                Ok(token) => token,
                Err(_) => Err(WebCallbackError::ChannelClosed)
            }
            Err(_) => Err(WebCallbackError::ChannelClosed)
        }
    }
}

#[derive(Debug, Deserialize)]
struct CallbackQuery {
    token: String,
}

async fn start_server(token_sender: crossbeam::channel::Sender<Result<String, WebCallbackError>>) {
    let reporter = token_sender.clone();
    let app = Router::new().route(
        "/callback",
        get(move |Query(params): Query<CallbackQuery>| {
            async move {
                if token_sender.send(Ok(params.token.clone())).is_ok() {
                    Html(format!("
                        <h1>AUTHENTICATION SUCCESSFUL!</h1>
                        <p>Token: {}</p>
                        <p>Close this tab.</p>
                    ", params.token))
                } else {
                    Html("<h1>AUTHENTICATION FAILED.</h1>".to_string())
                }
            }
        }),
    );

    let listener = match tokio::net::TcpListener::bind("127.0.0.1:8080").await {
        Ok(listener) => listener,
        Err(_) => {
            let _ = reporter.send(Err(WebCallbackError::UnableToBind));
            return;
        }
    };

    axum::serve(listener, app).await.unwrap();
}