use std::collections::HashMap;
use std::sync::Arc;
use log::info;
use openlark_core::api::{ApiRequest, ApiResponseTrait};
use openlark_core::constants::AccessTokenType;
use openlark_core::http::Transport;
use serde::Deserialize;
use serde_json::json;
use tokio_tungstenite::connect_async_with_config;
use url::Url;
use super::dispatcher::EventDispatcherHandler;
use super::session::{Session, SessionOptions, WsClientError, WsClientResult};
const END_POINT_URL: &str = "/callback/ws/endpoint";
pub struct LarkWsClient;
impl LarkWsClient {
pub async fn open(
config: Arc<openlark_core::config::Config>,
event_handler: EventDispatcherHandler,
) -> WsClientResult<()> {
Self::open_with(config, event_handler, SessionOptions::default()).await
}
pub(crate) async fn open_with(
config: Arc<openlark_core::config::Config>,
event_handler: EventDispatcherHandler,
options: SessionOptions,
) -> WsClientResult<()> {
let end_point = Self::get_conn_url(&config).await?;
let conn_url = end_point.url.ok_or(WsClientError::UnexpectedResponse)?;
let client_config = end_point
.client_config
.ok_or(WsClientError::UnexpectedResponse)?;
let url = Url::parse(&conn_url)?;
let query_pairs: HashMap<_, _> = url.query_pairs().into_iter().collect();
let service_id = query_pairs
.get("service_id")
.ok_or(WsClientError::UnexpectedResponse)?
.parse()
.map_err(|_| WsClientError::UnexpectedResponse)?;
let ws_config = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default()
.max_message_size(Some(config.max_response_size() as usize))
.max_frame_size(Some(config.max_response_size() as usize));
let (conn, _response) = connect_async_with_config(conn_url, Some(ws_config), false).await?;
info!("connected to {url}");
Session::new(service_id, client_config, conn, event_handler, options)
.run()
.await
}
async fn get_conn_url(
config: &Arc<openlark_core::config::Config>,
) -> WsClientResult<EndPointResponse> {
let req = ApiRequest::<()>::post(END_POINT_URL)
.with_supported_access_token_types(vec![AccessTokenType::None])
.header("locale", "zh")
.body(json!({
"AppID": config.app_id(),
"AppSecret": config.app_secret(),
}));
let end_point =
Transport::<EndPointResponse>::request_typed(req, config, None, "ws endpoint").await?;
Ok(end_point)
}
}
#[derive(Debug, Deserialize)]
pub(crate) struct EndPointResponse {
#[serde(rename = "URL")]
pub url: Option<String>,
#[serde(rename = "ClientConfig")]
pub client_config: Option<ClientConfig>,
}
impl ApiResponseTrait for EndPointResponse {}
#[derive(Debug, Deserialize, Clone)]
pub(crate) struct ClientConfig {
#[serde(rename = "PingInterval")]
pub(crate) ping_interval: i32,
}