11// SPDX-FileCopyrightText: 2025 RAprogramm <[email protected] > 22// SPDX-License-Identifier: MIT
33
4- use wasm_bindgen:: JsValue ;
5- use yew:: prelude:: { hook, use_memo} ;
4+ use std:: { cell:: RefCell , rc:: Rc } ;
5+
6+ use wasm_bindgen:: { JsCast , JsValue , closure:: Closure } ;
7+ use yew:: prelude:: { hook, use_effect, use_state} ;
68
79use crate :: core:: { context:: TelegramContext , safe_context:: get_context} ;
810
911pub mod bottom_button;
1012pub use bottom_button:: BottomButton ;
1113
12- /// Yew hook that exposes the global [`TelegramContext`].
14+ type ClosureCell = Rc < RefCell < Option < Closure < dyn FnMut ( ) > > > > ;
15+
16+ /// Yew hook that reactively exposes the global [`TelegramContext`].
17+ ///
18+ /// This hook checks for context availability at mount time and reactively
19+ /// updates when the context becomes available. It uses `requestAnimationFrame`
20+ /// for efficient polling until the context is initialized.
1321///
1422/// # Errors
1523///
1624/// Returns an error if the context has not been initialized with
17- /// [`TelegramContext::init`].
25+ /// [`TelegramContext::init`]. The error state is reactive and will update
26+ /// to `Ok` once initialization completes.
1827///
1928/// # Examples
2029///
@@ -24,12 +33,167 @@ pub use bottom_button::BottomButton;
2433///
2534/// #[function_component(App)]
2635/// fn app() -> Html {
27- /// let ctx = use_telegram_context().expect("context");
28- /// html! { <span>{ ctx.init_data.auth_date }</span> }
36+ /// let ctx_result = use_telegram_context();
37+ ///
38+ /// match ctx_result.as_ref() {
39+ /// Ok(ctx) => html! { <span>{ ctx.init_data.auth_date }</span> },
40+ /// Err(_) => html! { <div>{"Loading Telegram context..."}</div> }
41+ /// }
2942/// }
3043/// ```
3144#[ hook]
3245pub fn use_telegram_context ( ) -> Result < TelegramContext , JsValue > {
33- let ctx = use_memo ( ( ) , |_| get_context ( |c| c. clone ( ) ) ) ;
34- ( * ctx) . clone ( )
46+ let context_state = use_state ( || get_context ( |c| c. clone ( ) ) ) ;
47+
48+ {
49+ let context_state = context_state. clone ( ) ;
50+ use_effect ( move || {
51+ let handle: Rc < RefCell < Option < i32 > > > = Rc :: new ( RefCell :: new ( None ) ) ;
52+ let closure: ClosureCell = Rc :: new ( RefCell :: new ( None ) ) ;
53+
54+ if context_state. is_err ( )
55+ && let Some ( win) = web_sys:: window ( )
56+ {
57+ let handle_clone = handle. clone ( ) ;
58+ let closure_clone = closure. clone ( ) ;
59+ let ctx_state = context_state. clone ( ) ;
60+
61+ let check_fn = Closure :: wrap ( Box :: new ( move || {
62+ if let Ok ( ctx) = get_context ( |c| c. clone ( ) ) {
63+ ctx_state. set ( Ok ( ctx) ) ;
64+ if let Some ( id) = handle_clone. borrow_mut ( ) . take ( )
65+ && let Some ( w) = web_sys:: window ( )
66+ {
67+ let _ = w. cancel_animation_frame ( id) ;
68+ }
69+ closure_clone. borrow_mut ( ) . take ( ) ;
70+ } else if let Some ( w) = web_sys:: window ( )
71+ && let Some ( cb) = closure_clone. borrow ( ) . as_ref ( )
72+ && let Ok ( id) = w. request_animation_frame ( cb. as_ref ( ) . unchecked_ref ( ) )
73+ {
74+ * handle_clone. borrow_mut ( ) = Some ( id) ;
75+ }
76+ } ) as Box < dyn FnMut ( ) > ) ;
77+
78+ if let Ok ( id) = win. request_animation_frame ( check_fn. as_ref ( ) . unchecked_ref ( ) ) {
79+ * handle. borrow_mut ( ) = Some ( id) ;
80+ }
81+
82+ * closure. borrow_mut ( ) = Some ( check_fn) ;
83+ }
84+
85+ let cleanup_handle = handle;
86+ let cleanup_closure = closure;
87+ move || {
88+ if let Some ( id) = cleanup_handle. borrow_mut ( ) . take ( )
89+ && let Some ( w) = web_sys:: window ( )
90+ {
91+ let _ = w. cancel_animation_frame ( id) ;
92+ }
93+ cleanup_closure. borrow_mut ( ) . take ( ) ;
94+ }
95+ } ) ;
96+ }
97+
98+ ( * context_state) . clone ( )
99+ }
100+
101+ #[ cfg( test) ]
102+ mod tests {
103+ #[ cfg( target_arch = "wasm32" ) ]
104+ mod wasm {
105+ use wasm_bindgen_test:: { wasm_bindgen_test, wasm_bindgen_test_configure} ;
106+ use yew:: prelude:: * ;
107+
108+ use super :: super :: use_telegram_context;
109+ use crate :: core:: {
110+ context:: TelegramContext ,
111+ types:: {
112+ init_data:: TelegramInitData , theme_params:: TelegramThemeParams , user:: TelegramUser
113+ }
114+ } ;
115+
116+ wasm_bindgen_test_configure ! ( run_in_browser) ;
117+
118+ #[ function_component( TestComponent ) ]
119+ fn test_component ( ) -> Html {
120+ let ctx_result = use_telegram_context ( ) ;
121+
122+ match ctx_result. as_ref ( ) {
123+ Ok ( ctx) => html ! {
124+ <div id="success" >{ format!( "auth_date: {}" , ctx. init_data. auth_date) } </div>
125+ } ,
126+ Err ( e) => html ! {
127+ <div id="error" >{ format!( "Error: {:?}" , e) } </div>
128+ }
129+ }
130+ }
131+
132+ #[ wasm_bindgen_test]
133+ fn hook_renders_component_with_context_result ( ) {
134+ if let Some ( window) = web_sys:: window ( ) {
135+ if let Some ( document) = window. document ( ) {
136+ if let Ok ( container) = document. create_element ( "div" ) {
137+ yew:: Renderer :: < TestComponent > :: with_root ( container) . render ( ) ;
138+ }
139+ }
140+ }
141+ }
142+
143+ #[ wasm_bindgen_test]
144+ fn hook_works_with_initialized_context ( ) {
145+ let init_data = TelegramInitData {
146+ query_id : Some ( String :: from ( "test_query_2" ) ) ,
147+ user : Some ( TelegramUser {
148+ id : 987654321 ,
149+ is_bot : Some ( false ) ,
150+ first_name : String :: from ( "Test2" ) ,
151+ last_name : Some ( String :: from ( "User2" ) ) ,
152+ username : Some ( String :: from ( "testuser2" ) ) ,
153+ language_code : Some ( String :: from ( "en" ) ) ,
154+ is_premium : Some ( false ) ,
155+ added_to_attachment_menu : Some ( false ) ,
156+ allows_write_to_pm : Some ( true ) ,
157+ photo_url : None
158+ } ) ,
159+ receiver : None ,
160+ chat : None ,
161+ chat_type : None ,
162+ chat_instance : None ,
163+ start_param : None ,
164+ can_send_after : None ,
165+ auth_date : 9876543210 ,
166+ hash : String :: from ( "test_hash_2" ) ,
167+ signature : None
168+ } ;
169+
170+ let theme_params = TelegramThemeParams {
171+ bg_color : Some ( String :: from ( "#000000" ) ) ,
172+ text_color : Some ( String :: from ( "#ffffff" ) ) ,
173+ hint_color : Some ( String :: from ( "#666666" ) ) ,
174+ link_color : Some ( String :: from ( "#00aaff" ) ) ,
175+ button_color : Some ( String :: from ( "#00aaff" ) ) ,
176+ button_text_color : Some ( String :: from ( "#000000" ) ) ,
177+ secondary_bg_color : Some ( String :: from ( "#1a1a1a" ) ) ,
178+ header_bg_color : None ,
179+ bottom_bar_bg_color : None ,
180+ accent_text_color : None ,
181+ section_bg_color : None ,
182+ section_header_text_color : None ,
183+ section_separator_color : None ,
184+ subtitle_text_color : None ,
185+ destructive_text_color : None
186+ } ;
187+
188+ let _ = TelegramContext :: init ( init_data, theme_params) ;
189+
190+ if let Some ( window) = web_sys:: window ( ) {
191+ if let Some ( document) = window. document ( ) {
192+ if let Ok ( container) = document. create_element ( "div" ) {
193+ yew:: Renderer :: < TestComponent > :: with_root ( container) . render ( ) ;
194+ }
195+ }
196+ }
197+ }
198+ }
35199}
0 commit comments