//! Background CORS preflight check task. use tokio::task::JoinHandle; use tracing::info; use pom::alerts::Alerter; use pom::checks::cors; use pom::config::Config; use pom::db; use super::{CheckInterval, configured_targets}; pub(crate) fn spawn_cors_tasks( config: &Config, pool: &sqlx::SqlitePool, cancel: &tokio_util::sync::CancellationToken, alerter: Option<&Alerter>, ) -> Vec> { let interval_secs = config.serve.cors_check_interval_secs; let mut handles = Vec::new(); for (name, target_config) in configured_targets(config) { if target_config.cors.is_empty() { continue; } let cors_checks = target_config.cors.clone(); let label = target_config.label.clone(); let pool = pool.clone(); let alerter = alerter.cloned(); let cancel = cancel.clone(); let n = cors_checks.len(); info!("{name}: CORS check every {interval_secs}s ({n} endpoints)"); handles.push(tokio::spawn(async move { let mut ticks = CheckInterval::new(interval_secs, cancel); let mut prev_failed: std::collections::HashSet = std::collections::HashSet::new(); while ticks.next().await { let results = cors::check_cors(&name, &cors_checks).await; for result in &results { if let Err(e) = db::insert_cors_check(&pool, result).await { tracing::error!( "{}: failed to store CORS check for {}: {e}", name, result.url ); } } let current_failed: std::collections::HashSet = results .iter() .filter(|r| !r.passes) .map(|r| r.url.clone()) .collect(); let ok_count = results.iter().filter(|r| r.passes).count(); info!("{name}: CORS {ok_count}/{n} pass"); if let Some(ref alerter) = alerter { // New failures let new_failures: Vec<&pom::types::CorsCheckResult> = results .iter() .filter(|r| !r.passes && !prev_failed.contains(&r.url)) .collect(); if !new_failures.is_empty() { let owned: Vec = new_failures.into_iter().cloned().collect(); alerter.send_cors_failure_alert(&name, &label, &owned).await; } // All recovered if !prev_failed.is_empty() && current_failed.is_empty() { alerter.send_cors_recovery_alert(&name, &label).await; } } prev_failed = current_failed; } })); } handles }