#![cfg(feature = "cloudflare")]
use std::sync::Arc;
use std::time::Duration;
use rmcp::ErrorData;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use zendriver::{ClearanceOutcome, ZendriverError};
use crate::errors::{McpServerError, map_error};
use crate::state::SessionState;
use crate::tools::common::current_tab;
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SolveInput {
#[serde(default = "default_timeout")]
pub timeout_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub poll_interval_ms: Option<u64>,
}
fn default_timeout() -> u64 {
30_000
}
#[derive(Debug, Serialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Outcome {
Solved,
ChallengeGone,
Timeout,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct SolveOutput {
pub outcome: Outcome,
#[serde(skip_serializing_if = "Option::is_none")]
pub token: Option<String>,
}
pub async fn solve_turnstile(
state: Arc<Mutex<SessionState>>,
input: SolveInput,
) -> Result<SolveOutput, ErrorData> {
let s = state.lock().await;
let tab = current_tab(&s).await?;
let mut bypass = tab.cloudflare();
if let Some(p) = input.poll_interval_ms {
bypass = bypass.poll_interval(Duration::from_millis(p));
}
match bypass
.wait_for_clearance(Duration::from_millis(input.timeout_ms))
.await
{
Ok(ClearanceOutcome::TokenAcquired(t)) => Ok(SolveOutput {
outcome: Outcome::Solved,
token: Some(t),
}),
Ok(ClearanceOutcome::ChallengeGone) => Ok(SolveOutput {
outcome: Outcome::ChallengeGone,
token: None,
}),
Ok(ClearanceOutcome::TimedOut { .. }) => Ok(SolveOutput {
outcome: Outcome::Timeout,
token: None,
}),
Err(other) => Err(map_error(McpServerError::from(ZendriverError::from(other)))),
}
}
#[cfg(test)]
#[allow(clippy::panic, clippy::unwrap_used)]
mod tests {
use super::*;
#[tokio::test]
async fn solve_with_no_browser_errors() {
let state = Arc::new(Mutex::new(SessionState::new()));
let err = solve_turnstile(
state,
SolveInput {
timeout_ms: 100,
poll_interval_ms: None,
},
)
.await
.expect_err("expected BrowserNotOpen");
assert!(err.message.contains("Browser not open"));
}
}