eoka-server 0.4.0

Shared browser runtime and sidecar process for eoka browser automation
Documentation
use crate::{protocol::ServerError, state::AppState};
use eoka::{Action, ActionOptions, LocatorSpec};
use serde_json::{json, Value};

pub async fn dispatch(
    state: &mut AppState,
    method: &str,
    params: &Value,
) -> Option<Result<Value, ServerError>> {
    let mapped = method.replace("operations.", "operation_");
    if mapped.starts_with("operation_") {
        let response = state.operations.query(&mapped, params).await?;
        return Some(if response.ok {
            Ok(response.data.unwrap_or(Value::Null))
        } else {
            Err(ServerError::internal(response.error.unwrap_or_default()))
        });
    }
    let supported = matches!(
        method,
        "actions.perform"
            | "page.goto"
            | "page.click"
            | "page.click_text"
            | "page.human_click"
            | "page.human_click_text"
            | "page.fill"
            | "page.human_fill"
            | "page.type_into"
            | "page.select"
            | "page.hover"
            | "page.press_key"
    );
    if !supported {
        return None;
    }
    Some(
        async {
            let page = state
                .page(
                    params["pageId"]
                        .as_str()
                        .ok_or_else(|| ServerError::invalid_params("pageId required"))?,
                )?
                .clone();
            let id = params["operation_id"]
                .as_str()
                .ok_or_else(|| {
                    ServerError::invalid_params("operation_id is required; upgrade the client")
                })?
                .to_owned();
            let options: ActionOptions =
                serde_json::from_value(params.get("options").cloned().unwrap_or(json!({})))
                    .map_err(|e| ServerError::invalid_params(e.to_string()))?;
            let string = |key: &str| {
                params[key]
                    .as_str()
                    .map(str::to_owned)
                    .ok_or_else(|| ServerError::invalid_params(format!("{key} required")))
            };
            let target = || string("selector").map(LocatorSpec::css);
            let action = match method {
                "actions.perform" => serde_json::from_value(params["action"].clone())
                    .map_err(|e| ServerError::invalid_params(e.to_string()))?,
                "page.goto" => Action::Navigate {
                    url: string("url")?,
                    referrer: None,
                },
                "page.click" | "page.human_click" => Action::Click {
                    target: target()?,
                    human: method == "page.human_click",
                    double: false,
                },
                "page.click_text" | "page.human_click_text" => Action::Click {
                    target: LocatorSpec::text(string("text")?),
                    human: method == "page.human_click_text",
                    double: false,
                },
                "page.fill" | "page.human_fill" => Action::Fill {
                    target: target()?,
                    value: string("value")?,
                    human: method == "page.human_fill",
                },
                "page.type_into" => Action::Type {
                    target: target()?,
                    text: string("text")?,
                    human: false,
                },
                "page.select" => Action::SelectOption {
                    target: target()?,
                    value: string("value")?,
                },
                "page.hover" => Action::Hover {
                    target: target()?,
                    human: false,
                },
                "page.press_key" => Action::PressKey {
                    key: string("key")?,
                },
                _ => return Err(ServerError::unknown_method(method)),
            };
            let record = state
                .operations
                .submit(page, id, action, options)
                .await
                .map_err(|e| ServerError::internal(e.to_string()))?;
            serde_json::to_value(record).map_err(|e| ServerError::internal(e.to_string()))
        }
        .await,
    )
}