use crate::browser::views::{ServiceWorkerRegistration, ServiceWorkerVersion};
use crate::error::Result;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Debug, Clone)]
pub struct ServiceWorkerManager {
client: Arc<crate::browser::cdp::CdpClient>,
registrations: Arc<Mutex<HashMap<String, ServiceWorkerRegistration>>>,
versions: Arc<Mutex<HashMap<String, ServiceWorkerVersion>>>,
}
impl ServiceWorkerManager {
pub fn new(client: Arc<crate::browser::cdp::CdpClient>) -> Self {
Self {
client,
registrations: Arc::new(Mutex::new(HashMap::new())),
versions: Arc::new(Mutex::new(HashMap::new())),
}
}
pub async fn enable(&self) -> Result<()> {
self.client
.send_command("ServiceWorker.enable", serde_json::json!({}))
.await?;
let registrations = Arc::clone(&self.registrations);
let versions = Arc::clone(&self.versions);
let mut rx = self.client.subscribe_events().await?;
tokio::spawn(async move {
while let Ok(event) = rx.recv().await {
match event.method.as_str() {
"ServiceWorker.workerRegistrationUpdated" => {
if let Some(regs) = event.params.get("registrations").and_then(|v| v.as_array()) {
for reg in regs {
let reg_id = reg.get("registrationId").and_then(|v| v.as_str()).unwrap_or("").to_string();
let scope_url = reg.get("scopeURL").and_then(|v| v.as_str()).unwrap_or("").to_string();
let is_deleted = reg.get("isDeleted").and_then(|v| v.as_bool()).unwrap_or(false);
let info = ServiceWorkerRegistration {
registration_id: reg_id.clone(),
scope_url,
is_deleted,
};
registrations.lock().await.insert(reg_id, info);
}
}
}
"ServiceWorker.workerVersionUpdated" => {
if let Some(vers) = event.params.get("versions").and_then(|v| v.as_array()) {
for ver in vers {
let version_id = ver.get("versionId").and_then(|v| v.as_str()).unwrap_or("").to_string();
let registration_id = ver.get("registrationId").and_then(|v| v.as_str()).unwrap_or("").to_string();
let script_url = ver.get("scriptURL").and_then(|v| v.as_str()).unwrap_or("").to_string();
let status = ver.get("status").and_then(|v| v.as_str()).unwrap_or("").to_string();
let running_status = ver.get("runningStatus").and_then(|v| v.as_str()).map(|s| s.to_string());
let info = ServiceWorkerVersion {
version_id: version_id.clone(),
registration_id,
script_url,
status,
running_status,
};
versions.lock().await.insert(version_id, info);
}
}
}
_ => {}
}
}
});
Ok(())
}
pub async fn disable(&self) -> Result<()> {
self.client
.send_command("ServiceWorker.disable", serde_json::json!({}))
.await?;
self.registrations.lock().await.clear();
self.versions.lock().await.clear();
Ok(())
}
pub async fn stop_all(&self) -> Result<()> {
self.client
.send_command("ServiceWorker.stopAllWorkers", serde_json::json!({}))
.await?;
Ok(())
}
pub async fn start(&self, scope_url: &str) -> Result<()> {
self.client
.send_command(
"ServiceWorker.startWorker",
serde_json::json!({ "scopeURL": scope_url }),
)
.await?;
Ok(())
}
pub async fn unregister(&self, scope_url: &str) -> Result<()> {
self.client
.send_command(
"ServiceWorker.unregister",
serde_json::json!({ "scopeURL": scope_url }),
)
.await?;
Ok(())
}
pub async fn update(&self, scope_url: &str) -> Result<()> {
self.client
.send_command(
"ServiceWorker.updateRegistration",
serde_json::json!({ "scopeURL": scope_url }),
)
.await?;
Ok(())
}
pub async fn get_registrations(&self) -> Vec<ServiceWorkerRegistration> {
self.registrations.lock().await.values().cloned().collect()
}
pub async fn get_versions(&self) -> Vec<ServiceWorkerVersion> {
self.versions.lock().await.values().cloned().collect()
}
pub async fn registration_count(&self) -> usize {
self.registrations.lock().await.len()
}
pub async fn version_count(&self) -> usize {
self.versions.lock().await.len()
}
pub async fn clear(&self) {
self.registrations.lock().await.clear();
self.versions.lock().await.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_service_worker_registration() {
let reg = ServiceWorkerRegistration {
registration_id: "reg-1".to_string(),
scope_url: "https://example.com/app/".to_string(),
is_deleted: false,
};
assert_eq!(reg.registration_id, "reg-1");
assert_eq!(reg.scope_url, "https://example.com/app/");
}
#[test]
fn test_service_worker_version() {
let ver = ServiceWorkerVersion {
version_id: "v-1".to_string(),
registration_id: "reg-1".to_string(),
script_url: "https://example.com/sw.js".to_string(),
status: "activated".to_string(),
running_status: Some("running".to_string()),
};
assert_eq!(ver.status, "activated");
}
#[test]
fn test_service_worker_serialization() {
let reg = ServiceWorkerRegistration {
registration_id: "r1".to_string(),
scope_url: "/".to_string(),
is_deleted: false,
};
let json = serde_json::to_string(®).unwrap();
assert!(json.contains("r1"));
}
}