use js_sys::Reflect;
use serde_wasm_bindgen::from_value;
use wasm_bindgen::JsValue;
use web_sys::window;
use crate::core::{
context::TelegramContext,
types::{
chat::TelegramChat, init_data::TelegramInitData,
init_data_internal::TelegramInitDataInternal, theme_params::TelegramThemeParams,
user::TelegramUser
}
};
#[derive(Debug, Clone, PartialEq)]
pub enum InitError {
WindowUnavailable,
TelegramUnavailable,
WebAppUnavailable,
InitDataParseFailed(String),
ThemeParamsParseFailed(String),
ContextInitFailed(String)
}
impl std::fmt::Display for InitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::WindowUnavailable => write!(f, "Browser window object is not available"),
Self::TelegramUnavailable => write!(f, "window.Telegram is undefined"),
Self::WebAppUnavailable => write!(f, "Telegram.WebApp is undefined"),
Self::InitDataParseFailed(msg) => write!(f, "Failed to parse initData: {msg}"),
Self::ThemeParamsParseFailed(msg) => {
write!(f, "Failed to parse theme parameters: {msg}")
}
Self::ContextInitFailed(msg) => write!(f, "Failed to initialize context: {msg}")
}
}
}
impl std::error::Error for InitError {}
impl From<InitError> for JsValue {
fn from(err: InitError) -> Self {
JsValue::from_str(&err.to_string())
}
}
pub fn is_telegram_available() -> bool {
window()
.and_then(|w| Reflect::get(&w, &"Telegram".into()).ok())
.filter(|tg| !tg.is_undefined())
.and_then(|tg| Reflect::get(&tg, &"WebApp".into()).ok())
.filter(|webapp| !webapp.is_undefined())
.is_some()
}
pub fn try_init_sdk() -> Result<bool, InitError> {
if !is_telegram_available() {
return Ok(false);
}
init_sdk_typed().map(|_| true)
}
fn init_sdk_typed() -> Result<(), InitError> {
let win = window().ok_or(InitError::WindowUnavailable)?;
let telegram =
Reflect::get(&win, &"Telegram".into()).map_err(|_| InitError::TelegramUnavailable)?;
if telegram.is_undefined() {
return Err(InitError::TelegramUnavailable);
}
let webapp =
Reflect::get(&telegram, &"WebApp".into()).map_err(|_| InitError::WebAppUnavailable)?;
if webapp.is_undefined() {
return Err(InitError::WebAppUnavailable);
}
let init_data_str = Reflect::get(&webapp, &"initData".into())
.ok()
.and_then(|v| v.as_string())
.ok_or_else(|| InitError::InitDataParseFailed("initData is not a string".to_string()))?;
let raw: TelegramInitDataInternal = serde_urlencoded::from_str(&init_data_str)
.map_err(|e| InitError::InitDataParseFailed(e.to_string()))?;
let user: Option<TelegramUser> = raw
.user
.as_deref()
.map(serde_json::from_str)
.transpose()
.map_err(|e| InitError::InitDataParseFailed(format!("Failed to parse user: {e}")))?;
let receiver: Option<TelegramUser> = raw
.receiver
.as_deref()
.map(serde_json::from_str)
.transpose()
.map_err(|e| InitError::InitDataParseFailed(format!("Failed to parse receiver: {e}")))?;
let chat: Option<TelegramChat> = raw
.chat
.as_deref()
.map(serde_json::from_str)
.transpose()
.map_err(|e| InitError::InitDataParseFailed(format!("Failed to parse chat: {e}")))?;
let init_data = TelegramInitData {
query_id: raw.query_id,
user,
receiver,
chat,
chat_type: raw.chat_type,
chat_instance: raw.chat_instance,
start_param: raw.start_param,
can_send_after: raw.can_send_after,
auth_date: raw.auth_date,
hash: raw.hash,
signature: raw.signature
};
let theme_val = Reflect::get(&webapp, &"themeParams".into())
.map_err(|e| InitError::ThemeParamsParseFailed(format!("{e:?}")))?;
let theme_params: TelegramThemeParams =
from_value(theme_val).map_err(|e| InitError::ThemeParamsParseFailed(format!("{e:?}")))?;
TelegramContext::init(init_data, theme_params, init_data_str)
.map_err(|e| InitError::ContextInitFailed(format!("{e:?}")))?;
Ok(())
}
pub fn init_sdk() -> Result<(), JsValue> {
init_sdk_typed().map_err(Into::into)
}