1mod browser;
7mod config;
8mod solver;
9
10pub use browser::BrowserManager;
11pub use config::ChaserConfig;
12
13use crate::error::{ChaserError, ChaserResult};
14use crate::models::{ProxyConfig, WafSession};
15
16use std::sync::Arc;
17use tokio::sync::RwLock;
18
19pub struct ChaserCF {
38 config: ChaserConfig,
39 browser: Arc<RwLock<Option<BrowserManager>>>,
40 initialized: Arc<RwLock<bool>>,
41}
42
43impl ChaserCF {
44 pub async fn new(config: ChaserConfig) -> ChaserResult<Self> {
49 let suite = Self {
50 config: config.clone(),
51 browser: Arc::new(RwLock::new(None)),
52 initialized: Arc::new(RwLock::new(false)),
53 };
54
55 if !config.lazy_init {
56 suite.init().await?;
57 }
58
59 Ok(suite)
60 }
61
62 pub async fn init(&self) -> ChaserResult<()> {
67 let mut initialized = self.initialized.write().await;
68 if *initialized {
69 return Ok(());
70 }
71
72 tracing::info!("Initializing chaser-cf browser...");
73
74 let manager = BrowserManager::new(&self.config).await?;
75
76 let mut browser = self.browser.write().await;
77 *browser = Some(manager);
78 *initialized = true;
79
80 tracing::info!("chaser-cf browser initialized");
81 Ok(())
82 }
83
84 async fn ensure_init(&self) -> ChaserResult<()> {
86 if !*self.initialized.read().await {
87 self.init().await?;
88 }
89 Ok(())
90 }
91
92 async fn browser(
94 &self,
95 ) -> ChaserResult<tokio::sync::RwLockReadGuard<'_, Option<BrowserManager>>> {
96 self.ensure_init().await?;
97 let guard = self.browser.read().await;
98 if guard.is_none() {
99 return Err(ChaserError::NotInitialized);
100 }
101 Ok(guard)
102 }
103
104 pub async fn shutdown(&self) {
106 let mut browser = self.browser.write().await;
107 if let Some(manager) = browser.take() {
108 manager.shutdown().await;
109 }
110 *self.initialized.write().await = false;
111 tracing::info!("chaser-cf shutdown complete");
112 }
113
114 pub async fn is_ready(&self) -> bool {
116 let initialized = *self.initialized.read().await;
117 if !initialized {
118 return false;
119 }
120
121 let browser = self.browser.read().await;
122 browser.as_ref().map(|b| b.is_healthy()).unwrap_or(false)
123 }
124
125 pub async fn get_source(&self, url: &str, proxy: Option<ProxyConfig>) -> ChaserResult<String> {
136 let browser = self.browser().await?;
137 let manager = browser.as_ref().ok_or(ChaserError::NotInitialized)?;
138
139 tokio::time::timeout(
140 self.config.timeout(),
141 solver::get_source(manager, url, proxy),
142 )
143 .await
144 .map_err(|_| ChaserError::Timeout(self.config.timeout_ms))?
145 }
146
147 pub async fn solve_waf_session(
159 &self,
160 url: &str,
161 proxy: Option<ProxyConfig>,
162 ) -> ChaserResult<WafSession> {
163 let browser = self.browser().await?;
164 let manager = browser.as_ref().ok_or(ChaserError::NotInitialized)?;
165
166 tokio::time::timeout(
167 self.config.timeout(),
168 solver::solve_waf_session(manager, url, proxy),
169 )
170 .await
171 .map_err(|_| ChaserError::Timeout(self.config.timeout_ms))?
172 }
173
174 pub async fn solve_turnstile(
185 &self,
186 url: &str,
187 proxy: Option<ProxyConfig>,
188 ) -> ChaserResult<String> {
189 let browser = self.browser().await?;
190 let manager = browser.as_ref().ok_or(ChaserError::NotInitialized)?;
191
192 tokio::time::timeout(
193 self.config.timeout(),
194 solver::solve_turnstile_max(manager, url, proxy),
195 )
196 .await
197 .map_err(|_| ChaserError::Timeout(self.config.timeout_ms))?
198 }
199
200 pub async fn solve_turnstile_min(
215 &self,
216 url: &str,
217 site_key: &str,
218 proxy: Option<ProxyConfig>,
219 ) -> ChaserResult<String> {
220 let browser = self.browser().await?;
221 let manager = browser.as_ref().ok_or(ChaserError::NotInitialized)?;
222
223 tokio::time::timeout(
224 self.config.timeout(),
225 solver::solve_turnstile_min(manager, url, site_key, proxy),
226 )
227 .await
228 .map_err(|_| ChaserError::Timeout(self.config.timeout_ms))?
229 }
230
231 pub fn config(&self) -> &ChaserConfig {
233 &self.config
234 }
235}
236
237impl Drop for ChaserCF {
238 fn drop(&mut self) {
239 if let Ok(guard) = self.browser.try_read() {
242 if guard.is_some() {
243 tracing::warn!(
244 "ChaserCF dropped without explicit shutdown(). \
245 Call shutdown() for clean resource release."
246 );
247 }
248 }
249 }
250}