mqttmarionette/src/session.rs

142 lines
4.4 KiB
Rust

use std::time::Duration;
use tracing::{debug, error, info, warn};
use crate::browser::{Browser, BrowserError};
use crate::config::Configuration;
use crate::marionette::error::ConnectionError;
use crate::marionette::Marionette;
use crate::mqtt::{Message, MqttClient, MqttPublisher};
#[derive(Debug)]
pub enum SessionError {
Browser(BrowserError),
Io(std::io::Error),
Connection(ConnectionError),
InvalidState(String),
}
impl From<BrowserError> for SessionError {
fn from(e: BrowserError) -> Self {
Self::Browser(e)
}
}
impl From<std::io::Error> for SessionError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
impl From<ConnectionError> for SessionError {
fn from(e: ConnectionError) -> Self {
Self::Connection(e)
}
}
impl std::fmt::Display for SessionError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Browser(e) => write!(f, "Error launching browser: {}", e),
Self::Io(e) => write!(f, "I/O error: {}", e),
Self::Connection(e) => write!(f, "Connection error: {}", e),
Self::InvalidState(e) => write!(f, "Invalid state: {}", e),
}
}
}
impl std::error::Error for SessionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Browser(e) => Some(e),
Self::Io(e) => Some(e),
Self::Connection(e) => Some(e),
Self::InvalidState(_) => None,
}
}
}
pub struct Session {
config: Configuration,
browser: Browser,
marionette: Marionette,
}
impl Session {
pub async fn begin(config: Configuration) -> Result<Self, SessionError> {
debug!("Launching Firefox");
let browser = Browser::launch()?;
browser.wait_ready().await?;
debug!("Firefox Marionette is now ready");
let Some(port) = browser.marionette_port() else {
return Err(SessionError::InvalidState("No active Marionette port".into()));
};
debug!("Connecting to Firefox Marionette on port {}", port);
let mut marionette = Marionette::connect(("127.0.0.1", port)).await?;
info!("Successfully connected to Firefox Marionette");
let ses = marionette.new_session().await?;
debug!("Started Marionette session {}", ses.session_id);
Ok(Self {
config,
browser,
marionette,
})
}
pub async fn run(mut self) {
let mut client = MqttClient::new(&self.config).unwrap();
loop {
if let Err(e) = client.connect().await {
warn!("Failed to connect to MQTT server: {}", e);
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
if let Err(e) = client.subscribe().await {
warn!("Error subscribing to MQTT topics: {}", e);
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
break;
}
let handler = MessageHandler {
marionette: &mut self.marionette,
};
client.run(handler).await;
}
}
pub struct MessageHandler<'a> {
marionette: &'a mut Marionette,
}
#[async_trait::async_trait]
impl<'a> crate::mqtt::MessageHandler for MessageHandler<'a> {
async fn navigate(&mut self, publisher: &MqttPublisher, msg: &Message) {
let url = msg.payload_str();
let parts: Vec<&str> = msg.topic().split('/').rev().collect();
let screen = match parts.get(1) {
Some(&"") | None => {
warn!("Invalid navigate request: no screen");
return;
}
Some(s) => s,
};
debug!("Handling navigate request: {}", url);
info!("Navigate screen {} to {}", screen, url);
if let Err(e) = self.marionette.navigate(url.to_string()).await {
error!("Failed to navigate: {}", e);
}
if let Err(e) = publisher.publish_url(screen, &url).await {
error!("Failed to publish title: {}", e);
}
match self.marionette.get_title().await {
Ok(t) => {
if let Err(e) = publisher.publish_title(screen, &t).await {
error!("Failed to publish title: {}", e);
}
}
Err(e) => error!("Error getting title: {}", e),
}
}
}