@@ -15,49 +15,146 @@ use crate::core::{
1515 }
1616} ;
1717
18- /// Initializes Telegram WebApp SDK by extracting and validating context.
18+ /// Typed initialization errors for better error handling and debugging.
19+ #[ derive( Debug , Clone , PartialEq ) ]
20+ pub enum InitError {
21+ /// Browser `window` object is not available
22+ WindowUnavailable ,
23+ /// `window.Telegram` is undefined
24+ TelegramUnavailable ,
25+ /// `Telegram.WebApp` is undefined
26+ WebAppUnavailable ,
27+ /// Failed to parse `WebApp.initData`
28+ InitDataParseFailed ( String ) ,
29+ /// Failed to parse theme parameters
30+ ThemeParamsParseFailed ( String ) ,
31+ /// Failed to initialize global context
32+ ContextInitFailed ( String )
33+ }
34+
35+ impl std:: fmt:: Display for InitError {
36+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
37+ match self {
38+ Self :: WindowUnavailable => write ! ( f, "Browser window object is not available" ) ,
39+ Self :: TelegramUnavailable => write ! ( f, "window.Telegram is undefined" ) ,
40+ Self :: WebAppUnavailable => write ! ( f, "Telegram.WebApp is undefined" ) ,
41+ Self :: InitDataParseFailed ( msg) => write ! ( f, "Failed to parse initData: {msg}" ) ,
42+ Self :: ThemeParamsParseFailed ( msg) => {
43+ write ! ( f, "Failed to parse theme parameters: {msg}" )
44+ }
45+ Self :: ContextInitFailed ( msg) => write ! ( f, "Failed to initialize context: {msg}" )
46+ }
47+ }
48+ }
49+
50+ impl std:: error:: Error for InitError { }
51+
52+ impl From < InitError > for JsValue {
53+ fn from ( err : InitError ) -> Self {
54+ JsValue :: from_str ( & err. to_string ( ) )
55+ }
56+ }
57+
58+ /// Check if Telegram WebApp environment is available.
1959///
20- /// - Parses `initData` (urlencoded) with embedded JSON.
21- /// - Parses `themeParams` (object).
22- /// - Initializes global context.
60+ /// Returns `true` if `window.Telegram.WebApp` exists and is defined.
61+ ///
62+ /// # Examples
63+ /// ```no_run
64+ /// use telegram_webapp_sdk::core::init::is_telegram_available;
65+ ///
66+ /// if is_telegram_available() {
67+ /// println!("Running inside Telegram Mini App");
68+ /// } else {
69+ /// println!("Running in regular browser");
70+ /// }
71+ /// ```
72+ pub fn is_telegram_available ( ) -> bool {
73+ window ( )
74+ . and_then ( |w| Reflect :: get ( & w, & "Telegram" . into ( ) ) . ok ( ) )
75+ . filter ( |tg| !tg. is_undefined ( ) )
76+ . and_then ( |tg| Reflect :: get ( & tg, & "WebApp" . into ( ) ) . ok ( ) )
77+ . filter ( |webapp| !webapp. is_undefined ( ) )
78+ . is_some ( )
79+ }
80+
81+ /// Attempt to initialize SDK without panicking if Telegram environment is
82+ /// unavailable.
83+ ///
84+ /// Returns:
85+ /// - `Ok(true)` if SDK was successfully initialized
86+ /// - `Ok(false)` if Telegram environment is not available (graceful
87+ /// degradation)
88+ /// - `Err(InitError)` for actual initialization failures
89+ ///
90+ /// # Examples
91+ /// ```no_run
92+ /// use telegram_webapp_sdk::core::init::try_init_sdk;
93+ ///
94+ /// match try_init_sdk() {
95+ /// Ok(true) => println!("SDK initialized successfully"),
96+ /// Ok(false) => println!("Not running in Telegram, using fallback"),
97+ /// Err(e) => eprintln!("Initialization error: {}", e)
98+ /// }
99+ /// ```
23100///
24101/// # Errors
25- /// Returns `Err(JsValue)` on failure to access JS globals, parse, or init
26- /// context.
27- pub fn init_sdk ( ) -> Result < ( ) , JsValue > {
28- let win = window ( ) . ok_or_else ( || JsValue :: from_str ( "window is not available" ) ) ?;
29- let telegram = Reflect :: get ( & win, & "Telegram" . into ( ) ) ?;
30- let webapp = Reflect :: get ( & telegram, & "WebApp" . into ( ) ) ?;
102+ /// Returns typed `InitError` for parsing failures or context initialization
103+ /// issues.
104+ pub fn try_init_sdk ( ) -> Result < bool , InitError > {
105+ if !is_telegram_available ( ) {
106+ return Ok ( false ) ;
107+ }
108+ init_sdk_typed ( ) . map ( |_| true )
109+ }
110+
111+ /// Internal typed version of init_sdk for use by try_init_sdk.
112+ fn init_sdk_typed ( ) -> Result < ( ) , InitError > {
113+ let win = window ( ) . ok_or ( InitError :: WindowUnavailable ) ?;
114+ let telegram =
115+ Reflect :: get ( & win, & "Telegram" . into ( ) ) . map_err ( |_| InitError :: TelegramUnavailable ) ?;
116+
117+ if telegram. is_undefined ( ) {
118+ return Err ( InitError :: TelegramUnavailable ) ;
119+ }
120+
121+ let webapp =
122+ Reflect :: get ( & telegram, & "WebApp" . into ( ) ) . map_err ( |_| InitError :: WebAppUnavailable ) ?;
123+
124+ if webapp. is_undefined ( ) {
125+ return Err ( InitError :: WebAppUnavailable ) ;
126+ }
31127
32128 // === 1. Parse initData string ===
33- let init_data_str = Reflect :: get ( & webapp, & "initData" . into ( ) ) ?
34- . as_string ( )
35- . ok_or_else ( || JsValue :: from_str ( "Telegram.WebApp.initData is not a string" ) ) ?;
129+ let init_data_str = Reflect :: get ( & webapp, & "initData" . into ( ) )
130+ . ok ( )
131+ . and_then ( |v| v. as_string ( ) )
132+ . ok_or_else ( || InitError :: InitDataParseFailed ( "initData is not a string" . to_string ( ) ) ) ?;
36133
37134 let raw: TelegramInitDataInternal = serde_urlencoded:: from_str ( & init_data_str)
38- . map_err ( |e| JsValue :: from_str ( & format ! ( "Failed to parse initData: {e}" ) ) ) ?;
135+ . map_err ( |e| InitError :: InitDataParseFailed ( e . to_string ( ) ) ) ?;
39136
40137 // === 2. Parse embedded JSON fields ===
41138 let user: Option < TelegramUser > = raw
42139 . user
43140 . as_deref ( )
44141 . map ( serde_json:: from_str)
45142 . transpose ( )
46- . map_err ( |e| JsValue :: from_str ( & format ! ( "Failed to parse user: {e}" ) ) ) ?;
143+ . map_err ( |e| InitError :: InitDataParseFailed ( format ! ( "Failed to parse user: {e}" ) ) ) ?;
47144
48145 let receiver: Option < TelegramUser > = raw
49146 . receiver
50147 . as_deref ( )
51148 . map ( serde_json:: from_str)
52149 . transpose ( )
53- . map_err ( |e| JsValue :: from_str ( & format ! ( "Failed to parse receiver: {e}" ) ) ) ?;
150+ . map_err ( |e| InitError :: InitDataParseFailed ( format ! ( "Failed to parse receiver: {e}" ) ) ) ?;
54151
55152 let chat: Option < TelegramChat > = raw
56153 . chat
57154 . as_deref ( )
58155 . map ( serde_json:: from_str)
59156 . transpose ( )
60- . map_err ( |e| JsValue :: from_str ( & format ! ( "Failed to parse chat: {e}" ) ) ) ?;
157+ . map_err ( |e| InitError :: InitDataParseFailed ( format ! ( "Failed to parse chat: {e}" ) ) ) ?;
61158
62159 // === 3. Construct final typed initData ===
63160 let init_data = TelegramInitData {
@@ -75,13 +172,47 @@ pub fn init_sdk() -> Result<(), JsValue> {
75172 } ;
76173
77174 // === 4. Parse themeParams ===
78- let theme_val = Reflect :: get ( & webapp, & "themeParams" . into ( ) ) ? ;
79- let theme_params : TelegramThemeParams = from_value ( theme_val ) ?;
80-
81- // theme_params.clone().apply_to_root() ;
175+ let theme_val = Reflect :: get ( & webapp, & "themeParams" . into ( ) )
176+ . map_err ( |e| InitError :: ThemeParamsParseFailed ( format ! ( "{e:?}" ) ) ) ?;
177+ let theme_params : TelegramThemeParams =
178+ from_value ( theme_val ) . map_err ( |e| InitError :: ThemeParamsParseFailed ( format ! ( "{e:?}" ) ) ) ? ;
82179
83180 // === 5. Init global context ===
84- TelegramContext :: init ( init_data, theme_params, init_data_str) ?;
181+ TelegramContext :: init ( init_data, theme_params, init_data_str)
182+ . map_err ( |e| InitError :: ContextInitFailed ( format ! ( "{e:?}" ) ) ) ?;
85183
86184 Ok ( ( ) )
87185}
186+
187+ /// Initializes Telegram WebApp SDK by extracting and validating context.
188+ ///
189+ /// - Parses `initData` (urlencoded) with embedded JSON.
190+ /// - Parses `themeParams` (object).
191+ /// - Initializes global context.
192+ ///
193+ /// # Errors
194+ ///
195+ /// Returns `Err(JsValue)` in the following cases:
196+ ///
197+ /// - `WindowUnavailable`: No browser `window` object found
198+ /// - `TelegramUnavailable`: `window.Telegram` is undefined
199+ /// - `WebAppUnavailable`: `Telegram.WebApp` is undefined
200+ /// - `InitDataParseFailed`: Failed to parse `WebApp.initData`
201+ /// - `ThemeParamsParseFailed`: Failed to parse theme parameters
202+ /// - `ContextInitFailed`: Failed to initialize global context
203+ ///
204+ /// # Examples
205+ /// ```no_run
206+ /// use telegram_webapp_sdk::core::init::init_sdk;
207+ ///
208+ /// match init_sdk() {
209+ /// Ok(_) => println!("SDK initialized successfully"),
210+ /// Err(e) => eprintln!("Initialization failed: {:?}", e)
211+ /// }
212+ /// ```
213+ ///
214+ /// For better error handling, consider using [`try_init_sdk`] which returns
215+ /// typed [`InitError`].
216+ pub fn init_sdk ( ) -> Result < ( ) , JsValue > {
217+ init_sdk_typed ( ) . map_err ( Into :: into)
218+ }
0 commit comments