use serde::{Deserialize, Serialize};
use crate::bidi::BiDi;
use crate::bidi::command::{BidiCommand, BidiEvent, Empty};
use crate::bidi::error::BidiError;
use crate::bidi::ids::{BrowsingContextId, CollectorId, InterceptId, RequestId, UserContextId};
use crate::common::protocol::string_enum;
string_enum! {
pub enum InterceptPhase {
BeforeRequestSent = "beforeRequestSent",
ResponseStarted = "responseStarted",
AuthRequired = "authRequired",
}
}
string_enum! {
pub enum CacheBehavior {
Default = "default",
Bypass = "bypass",
}
}
string_enum! {
pub enum DataType {
Request = "request",
Response = "response",
}
}
string_enum! {
pub enum CollectorType {
Blob = "blob",
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddIntercept {
pub phases: Vec<InterceptPhase>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub contexts: Vec<BrowsingContextId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url_patterns: Option<Vec<serde_json::Value>>,
}
impl BidiCommand for AddIntercept {
const METHOD: &'static str = "network.addIntercept";
type Returns = AddInterceptResult;
}
#[derive(Debug, Clone, Deserialize)]
pub struct AddInterceptResult {
pub intercept: InterceptId,
}
#[derive(Debug, Clone, Serialize)]
pub struct RemoveIntercept {
pub intercept: InterceptId,
}
impl BidiCommand for RemoveIntercept {
const METHOD: &'static str = "network.removeIntercept";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ContinueRequest {
pub request: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookies: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
impl BidiCommand for ContinueRequest {
const METHOD: &'static str = "network.continueRequest";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ContinueResponse {
pub request: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookies: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub credentials: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason_phrase: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status_code: Option<u16>,
}
impl BidiCommand for ContinueResponse {
const METHOD: &'static str = "network.continueResponse";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ContinueWithAuth {
pub request: RequestId,
#[serde(flatten)]
pub action: AuthAction,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "action", rename_all = "lowercase")]
pub enum AuthAction {
ProvideCredentials {
credentials: AuthCredentials,
},
Cancel,
Default,
}
#[derive(Debug, Clone, Serialize)]
pub struct AuthCredentials {
pub r#type: String,
pub username: String,
pub password: String,
}
impl AuthCredentials {
pub fn basic(username: impl Into<String>, password: impl Into<String>) -> Self {
Self {
r#type: "password".to_string(),
username: username.into(),
password: password.into(),
}
}
}
impl BidiCommand for ContinueWithAuth {
const METHOD: &'static str = "network.continueWithAuth";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
pub struct FailRequest {
pub request: RequestId,
}
impl BidiCommand for FailRequest {
const METHOD: &'static str = "network.failRequest";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProvideResponse {
pub request: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookies: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason_phrase: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status_code: Option<u16>,
}
impl BidiCommand for ProvideResponse {
const METHOD: &'static str = "network.provideResponse";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetCacheBehavior {
pub cache_behavior: CacheBehavior,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub contexts: Vec<BrowsingContextId>,
#[serde(rename = "userContexts", skip_serializing_if = "Vec::is_empty")]
pub user_contexts: Vec<UserContextId>,
}
impl BidiCommand for SetCacheBehavior {
const METHOD: &'static str = "network.setCacheBehavior";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddDataCollector {
pub data_types: Vec<DataType>,
pub max_encoded_data_size: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub collector_type: Option<CollectorType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contexts: Option<Vec<BrowsingContextId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_contexts: Option<Vec<UserContextId>>,
}
impl BidiCommand for AddDataCollector {
const METHOD: &'static str = "network.addDataCollector";
type Returns = AddDataCollectorResult;
}
#[derive(Debug, Clone, Deserialize)]
pub struct AddDataCollectorResult {
pub collector: CollectorId,
}
#[derive(Debug, Clone, Serialize)]
pub struct RemoveDataCollector {
pub collector: CollectorId,
}
impl BidiCommand for RemoveDataCollector {
const METHOD: &'static str = "network.removeDataCollector";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GetData {
pub data_type: DataType,
pub request: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub collector: Option<CollectorId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disown: Option<bool>,
}
impl BidiCommand for GetData {
const METHOD: &'static str = "network.getData";
type Returns = GetDataResult;
}
#[derive(Debug, Clone, Deserialize)]
pub struct GetDataResult {
pub bytes: serde_json::Value,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DisownData {
pub data_type: DataType,
pub collector: CollectorId,
pub request: RequestId,
}
impl BidiCommand for DisownData {
const METHOD: &'static str = "network.disownData";
type Returns = Empty;
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetExtraHeaders {
pub headers: Vec<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contexts: Option<Vec<BrowsingContextId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_contexts: Option<Vec<UserContextId>>,
}
impl BidiCommand for SetExtraHeaders {
const METHOD: &'static str = "network.setExtraHeaders";
type Returns = Empty;
}
pub(crate) mod events {
use super::*;
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BeforeRequestSent {
pub context: Option<BrowsingContextId>,
pub request: RequestData,
pub timestamp: u64,
#[serde(default)]
pub initiator: Option<serde_json::Value>,
#[serde(default)]
pub is_blocked: bool,
#[serde(default)]
pub intercepts: Vec<InterceptId>,
}
impl BidiEvent for BeforeRequestSent {
const METHOD: &'static str = "network.beforeRequestSent";
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseStarted {
pub context: Option<BrowsingContextId>,
pub request: RequestData,
pub response: ResponseData,
pub timestamp: u64,
#[serde(default)]
pub is_blocked: bool,
#[serde(default)]
pub intercepts: Vec<InterceptId>,
}
impl BidiEvent for ResponseStarted {
const METHOD: &'static str = "network.responseStarted";
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseCompleted {
pub context: Option<BrowsingContextId>,
pub request: RequestData,
pub response: ResponseData,
pub timestamp: u64,
}
impl BidiEvent for ResponseCompleted {
const METHOD: &'static str = "network.responseCompleted";
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchError {
pub context: Option<BrowsingContextId>,
pub request: RequestData,
pub timestamp: u64,
pub error_text: String,
}
impl BidiEvent for FetchError {
const METHOD: &'static str = "network.fetchError";
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthRequired {
pub context: Option<BrowsingContextId>,
pub request: RequestData,
pub response: ResponseData,
pub timestamp: u64,
#[serde(default)]
pub is_blocked: bool,
}
impl BidiEvent for AuthRequired {
const METHOD: &'static str = "network.authRequired";
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RequestData {
#[serde(rename = "request")]
pub id: RequestId,
pub method: String,
pub url: String,
#[serde(default)]
pub headers: Vec<serde_json::Value>,
#[serde(default)]
pub cookies: Vec<serde_json::Value>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseData {
pub url: String,
#[serde(default)]
pub protocol: Option<String>,
pub status: u16,
pub status_text: String,
#[serde(default)]
pub headers: Vec<serde_json::Value>,
#[serde(flatten)]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug)]
pub struct InterceptGuard {
bidi: BiDi,
intercept: Option<InterceptId>,
}
impl InterceptGuard {
pub(crate) fn new(bidi: BiDi, intercept: InterceptId) -> Self {
Self {
bidi,
intercept: Some(intercept),
}
}
pub fn id(&self) -> &InterceptId {
self.intercept.as_ref().expect("InterceptGuard already removed")
}
pub async fn remove(mut self) -> Result<(), BidiError> {
if let Some(intercept) = self.intercept.take() {
self.bidi
.send(RemoveIntercept {
intercept,
})
.await?;
}
Ok(())
}
}
impl Drop for InterceptGuard {
fn drop(&mut self) {
let Some(intercept) = self.intercept.take() else {
return;
};
if let Ok(handle) = tokio::runtime::Handle::try_current() {
let bidi = self.bidi.clone();
handle.spawn(async move {
let _ = bidi
.send(RemoveIntercept {
intercept,
})
.await;
});
}
}
}
#[derive(Debug)]
pub struct NetworkModule<'a> {
bidi: &'a BiDi,
}
impl<'a> NetworkModule<'a> {
pub(crate) fn new(bidi: &'a BiDi) -> Self {
Self {
bidi,
}
}
pub async fn add_intercept(
&self,
phases: Vec<InterceptPhase>,
url_patterns: Option<Vec<serde_json::Value>>,
) -> Result<InterceptGuard, BidiError> {
let result = self
.bidi
.send(AddIntercept {
phases,
contexts: vec![],
url_patterns,
})
.await?;
Ok(InterceptGuard::new(self.bidi.clone(), result.intercept))
}
pub async fn remove_intercept(&self, intercept: InterceptId) -> Result<(), BidiError> {
self.bidi
.send(RemoveIntercept {
intercept,
})
.await?;
Ok(())
}
pub async fn continue_request(&self, request: RequestId) -> Result<(), BidiError> {
self.bidi
.send(ContinueRequest {
request,
body: None,
cookies: None,
headers: None,
method: None,
url: None,
})
.await?;
Ok(())
}
pub async fn continue_response(&self, request: RequestId) -> Result<(), BidiError> {
self.bidi
.send(ContinueResponse {
request,
cookies: None,
credentials: None,
headers: None,
reason_phrase: None,
status_code: None,
})
.await?;
Ok(())
}
pub async fn fail_request(&self, request: RequestId) -> Result<(), BidiError> {
self.bidi
.send(FailRequest {
request,
})
.await?;
Ok(())
}
pub async fn set_cache_behavior(&self, mode: CacheBehavior) -> Result<(), BidiError> {
self.bidi
.send(SetCacheBehavior {
cache_behavior: mode,
contexts: vec![],
user_contexts: vec![],
})
.await?;
Ok(())
}
pub async fn add_data_collector(
&self,
data_types: Vec<DataType>,
max_encoded_data_size: u64,
) -> Result<AddDataCollectorResult, BidiError> {
self.bidi
.send(AddDataCollector {
data_types,
max_encoded_data_size,
collector_type: None,
contexts: None,
user_contexts: None,
})
.await
}
pub async fn remove_data_collector(&self, collector: CollectorId) -> Result<(), BidiError> {
self.bidi
.send(RemoveDataCollector {
collector,
})
.await?;
Ok(())
}
pub async fn get_data(
&self,
data_type: DataType,
request: RequestId,
) -> Result<GetDataResult, BidiError> {
self.bidi
.send(GetData {
data_type,
request,
collector: None,
disown: None,
})
.await
}
pub async fn disown_data(
&self,
data_type: DataType,
collector: CollectorId,
request: RequestId,
) -> Result<(), BidiError> {
self.bidi
.send(DisownData {
data_type,
collector,
request,
})
.await?;
Ok(())
}
pub async fn set_extra_headers(
&self,
headers: Vec<serde_json::Value>,
) -> Result<(), BidiError> {
self.bidi
.send(SetExtraHeaders {
headers,
contexts: None,
user_contexts: None,
})
.await?;
Ok(())
}
}