wallet_adapter/
events.rs

1use std::rc::Rc;
2
3use async_channel::{Receiver, Sender};
4use wallet_adapter_common::standardized_events::{
5    WINDOW_APP_READY_EVENT_TYPE, WINDOW_REGISTER_WALLET_EVENT_TYPE,
6};
7use web_sys::{
8    js_sys::{Object, Reflect},
9    wasm_bindgen::{prelude::Closure, JsValue},
10    CustomEvent, CustomEventInit, Window,
11};
12
13use crate::{
14    InnerUtils, Reflection, StorageType, Wallet, WalletAccount, WalletAdapter, WalletError,
15    WalletResult,
16};
17
18/// The `Sender` part of an [async_channel::bounded] channel
19pub type WalletEventSender = Sender<WalletEvent>;
20
21/// The `Receiver` part of an [async_channel::bounded] channel
22pub type WalletEventReceiver = Receiver<WalletEvent>;
23
24/// Used to initialize the `Register` and `AppReady` events to the browser window
25#[derive(Debug, PartialEq, Eq)]
26pub struct InitEvents<'a> {
27    window: &'a Window,
28}
29
30impl<'a> InitEvents<'a> {
31    /// Instantiate [InitEvents]
32    pub fn new(window: &'a Window) -> Self {
33        Self { window }
34    }
35
36    /// Register events by providing a [crate::WalletStorage] that is used to store
37    /// all registered wallets
38    pub fn init(&self, adapter: &mut WalletAdapter) -> WalletResult<()> {
39        let storage = adapter.storage();
40        self.register_wallet_event(storage.clone_inner())?;
41        self.dispatch_app_event(storage.clone_inner());
42
43        Ok(())
44    }
45
46    /// An App Ready event registered to the browser window
47    pub fn dispatch_app_event(&self, storage: StorageType) {
48        let app_ready_init = CustomEventInit::new();
49        app_ready_init.set_bubbles(false);
50        app_ready_init.set_cancelable(false);
51        app_ready_init.set_composed(false);
52        app_ready_init.set_detail(&Self::register_object(storage));
53
54        let app_ready_ev =
55            CustomEvent::new_with_event_init_dict(WINDOW_APP_READY_EVENT_TYPE, &app_ready_init)
56                .unwrap();
57
58        self.window.dispatch_event(&app_ready_ev).unwrap();
59    }
60
61    /// The register wallet event registered to the browser window
62    pub fn register_wallet_event(&self, storage: StorageType) -> WalletResult<()> {
63        let inner_storage = Rc::clone(&storage);
64
65        let listener_closure = Closure::wrap(Box::new(move |custom_event: CustomEvent| {
66            let detail = Reflection::new(custom_event
67                .detail()).unwrap().into_function()
68                .expect("Unable to get the `detail` function from the `Event` object. This is a fatal error as the register handler won't execute.");
69
70            InnerUtils::jsvalue_to_error(detail.call1(
71                &JsValue::null(),
72                &Self::register_object(inner_storage.clone()),
73            ))
74            .unwrap()
75        }) as Box<dyn Fn(_)>);
76
77        let listener_fn = Reflection::new(listener_closure.into_js_value())
78            .unwrap()
79            .into_function()
80            .unwrap();
81
82        self.window
83            .add_event_listener_with_callback(WINDOW_REGISTER_WALLET_EVENT_TYPE, &listener_fn)?;
84
85        Ok(())
86    }
87
88    /// Sets the object to be passed to the register function
89    pub fn register_object(storage: StorageType) -> Object {
90        // The `register` function that logs and returns a closure like in your JS code
91        let register =
92            Closure::wrap(
93                Box::new(move |value: JsValue| match Wallet::from_jsvalue(value) {
94                    Ok(wallet) => {
95                        let inner_outcome = storage.clone();
96
97                        inner_outcome.borrow_mut().insert(
98                            blake3::hash(wallet.name().to_lowercase().as_bytes()),
99                            wallet,
100                        );
101                    }
102                    Err(error) => {
103                        let error = error.to_string();
104                        if error.contains("is not supported") {
105                        } else {
106                            web_sys::console::error_2(
107                                &"REGISTER EVENT ERROR".into(),
108                                &error.into(),
109                            );
110                        }
111                    }
112                }) as Box<dyn Fn(_)>,
113            );
114
115        // Create an object and set the `register` property
116        let register_object = Object::new();
117
118        if let Err(error) = Reflect::set(
119            &register_object,
120            &JsValue::from("register"),
121            &register.into_js_value(),
122        ) {
123            web_sys::console::error_2(&"REGISTER EVENT ERROR".into(), &error);
124        }
125
126        register_object
127    }
128}
129
130/// Events emitted by connected browser extensions
131/// when an account is connected, disconnected or changed.
132/// Wallets implementing the wallet standard emit these events
133/// from the `standard:events` events namespace specifically,
134/// `wallet.features[standard:events].on`
135#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Clone)]
136pub enum WalletEvent {
137    /// An account has been connected and an event `change` emitted.
138    Connected(WalletAccount),
139    /// An account has been reconnected and an event `change` emitted.
140    Reconnected(WalletAccount),
141    /// An account has been disconnected and an event `change` emitted.
142    Disconnected,
143    /// An account has been connected and an event `change` emitted.
144    /// The wallet adapter then updates the connected [WalletAccount].
145    AccountChanged(WalletAccount),
146    /// An error occurred when a background task was executed.
147    /// This type of event is encountered mostly from the
148    /// `on` method from the `[standard:events]` namespace
149    /// (when an account is connected, changed or disconnected)
150    BackgroundTaskError(WalletError),
151    /// An event was emitted by a wallet that is not connected.
152    #[default]
153    Skip,
154}
155
156impl core::fmt::Display for WalletEvent {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        let as_str = match self {
159            Self::Connected(_) => "Connected",
160            Self::Reconnected(_) => "Reconnected",
161            Self::Disconnected => "Disconnected",
162            Self::AccountChanged(_) => "Account Changed",
163            Self::BackgroundTaskError(error) => &format!("Task error: {error:?}"),
164            Self::Skip => "Skipped",
165        };
166        write!(f, "{as_str}")
167    }
168}