Skip to main content

dioxus_clerk/
hooks.rs

1//! Reactive accessors for descendants of `ClerkProvider`.
2
3use crate::context::{ClerkContext, use_clerk_context};
4use crate::core::{AuthRequirement, AuthState, AuthStatus, ClerkError, Session, User};
5use dioxus::prelude::*;
6
7fn auth_state_signal(
8    ctx: ClerkContext,
9    treat_pending_as_signed_out: bool,
10) -> ReadSignal<AuthState> {
11    // `treat_pending_as_signed_out` is a plain value, not a signal, so track it
12    // with `use_reactive`; otherwise a caller passing a changing flag would
13    // keep reading the first render's value.
14    use_memo(use_reactive(
15        &treat_pending_as_signed_out,
16        move |treat_pending_as_signed_out| {
17            ctx.auth
18                .read()
19                .resolve_pending(treat_pending_as_signed_out)
20                .to_state()
21        },
22    ))
23    .into()
24}
25
26/// Reactive auth hook result returned by [`use_auth`].
27#[derive(Clone, Copy)]
28pub struct UseAuth {
29    inner: ReadSignal<AuthState>,
30    ctx: ClerkContext,
31}
32
33impl std::fmt::Debug for UseAuth {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.debug_struct("UseAuth")
36            .field("state", &*self.inner.peek())
37            .finish_non_exhaustive()
38    }
39}
40
41impl UseAuth {
42    /// Return the current app-visible auth state.
43    pub fn state(&self) -> AuthState {
44        self.inner.read().clone()
45    }
46
47    /// Return the underlying read-only Dioxus signal for advanced composition.
48    pub fn signal(&self) -> ReadSignal<AuthState> {
49        self.inner
50    }
51
52    /// Return the explicit auth resolution status.
53    pub fn status(&self) -> AuthStatus {
54        self.inner.read().status
55    }
56
57    /// True while auth has not resolved to signed-in or signed-out yet.
58    pub fn is_loading(&self) -> bool {
59        self.status().is_loading()
60    }
61
62    /// True only when auth has resolved and no active session is known.
63    pub fn is_signed_out(&self) -> bool {
64        self.status().is_signed_out()
65    }
66
67    /// Whether clerk-js has finished loading on the client.
68    pub fn is_loaded(&self) -> bool {
69        self.inner.read().is_loaded
70    }
71
72    /// Whether a session is active.
73    pub fn is_signed_in(&self) -> bool {
74        self.inner.read().is_signed_in()
75    }
76
77    /// Clerk user id, if signed in.
78    pub fn user_id(&self) -> Option<String> {
79        self.inner.read().user_id.clone()
80    }
81
82    /// Return the user id, or an error when auth is not signed in.
83    ///
84    /// Returns [`ClerkError::NotLoaded`] while auth is still resolving and
85    /// [`ClerkError::Unauthenticated`] once it has resolved without a
86    /// signed-in user, so callers can wait out the former and redirect on the
87    /// latter without flashing sign-in UI at a signed-in user.
88    pub fn require_signed_in(&self) -> Result<String, ClerkError> {
89        self.inner.read().require_signed_in().map(ToOwned::to_owned)
90    }
91
92    /// Get the active session token from clerk-js.
93    ///
94    /// This mirrors Clerk React's `useAuth().getToken()` convenience. The call
95    /// waits for the browser Clerk lifecycle to load before reading clerk-js.
96    ///
97    /// Returns `Err(`[`ClerkError::Offline`]`)` when the browser is offline
98    /// (clerk-js 6 throws `ClerkOfflineError` here), a transient condition
99    /// callers can retry rather than treat as signed-out.
100    pub async fn get_token(&self) -> Result<Option<String>, ClerkError> {
101        self.get_token_with_options(serde_json::Value::Null).await
102    }
103
104    /// Get the active session token with Clerk `getToken(...)` options.
105    pub async fn get_token_with_options(
106        &self,
107        options: impl Into<serde_json::Value>,
108    ) -> Result<Option<String>, ClerkError> {
109        ClerkActions { ctx: self.ctx }
110            .get_token_with_options(options)
111            .await
112    }
113
114    /// Active session id, if signed in and available.
115    pub fn session_id(&self) -> Option<String> {
116        self.inner.read().session_id.clone()
117    }
118
119    /// Active organization id, if any.
120    pub fn org_id(&self) -> Option<String> {
121        self.inner.read().org_id.clone()
122    }
123
124    /// Active organization slug, if any.
125    pub fn org_slug(&self) -> Option<String> {
126        self.inner.read().org_slug.clone()
127    }
128
129    /// Organization role from verified server auth, if any.
130    pub fn org_role(&self) -> Option<String> {
131        self.inner.read().org_role.clone()
132    }
133
134    /// Organization permissions from verified server auth.
135    pub fn org_permissions(&self) -> Vec<String> {
136        self.inner.read().org_permissions.clone()
137    }
138
139    /// True if the auth state includes the given server-verified org role.
140    pub fn has_role(&self, role: &str) -> bool {
141        self.inner.read().has_role(role)
142    }
143
144    /// True if the auth state includes the given server-verified org permission.
145    pub fn has_permission(&self, permission: &str) -> bool {
146        self.inner.read().has_permission(permission)
147    }
148
149    /// True if the auth state satisfies a rendering auth requirement.
150    pub fn has(&self, requirement: &AuthRequirement) -> bool {
151        self.inner.read().has(requirement)
152    }
153
154    /// Sign out after Clerk lifecycle loadedness.
155    ///
156    /// This mirrors Clerk React's `useAuth().signOut()` convenience. Failures
157    /// from the scheduled browser action are surfaced through
158    /// [`use_clerk_error`]. Use [`UseAuth::try_sign_out`] when the caller needs
159    /// to await completion or handle errors locally.
160    pub fn sign_out(&self) {
161        self.sign_out_with_options(serde_json::Value::Null);
162    }
163
164    /// Sign out with Clerk `signOut(...)` options after Clerk lifecycle loadedness.
165    pub fn sign_out_with_options(&self, options: impl Into<serde_json::Value>) {
166        ClerkActions { ctx: self.ctx }.sign_out_with_options(options);
167    }
168
169    /// Sign out and return any lifecycle or clerk-js error.
170    pub async fn try_sign_out(&self) -> Result<(), ClerkError> {
171        self.try_sign_out_with_options(serde_json::Value::Null)
172            .await
173    }
174
175    /// Sign out with Clerk `signOut(...)` options and return any error.
176    pub async fn try_sign_out_with_options(
177        &self,
178        options: impl Into<serde_json::Value>,
179    ) -> Result<(), ClerkError> {
180        ClerkActions { ctx: self.ctx }
181            .try_sign_out_with_options(options)
182            .await
183    }
184}
185
186/// Options for [`use_auth_with_options`], mirroring the option object Clerk
187/// React's `useAuth(...)` accepts.
188///
189/// Construct with [`UseAuthOptions::new`] and chain setters; the struct is
190/// `#[non_exhaustive]` so new options can be added without a breaking release.
191#[derive(Clone, Debug, PartialEq, Eq)]
192#[non_exhaustive]
193pub struct UseAuthOptions {
194    treat_pending_as_signed_out: bool,
195}
196
197impl Default for UseAuthOptions {
198    fn default() -> Self {
199        // clerk-js treats pending sessions as signed out by default; matching
200        // that here keeps `use_auth()` consistent with the control components.
201        Self {
202            treat_pending_as_signed_out: true,
203        }
204    }
205}
206
207impl UseAuthOptions {
208    /// Options with clerk-js defaults (`treat_pending_as_signed_out = true`).
209    pub fn new() -> Self {
210        Self::default()
211    }
212
213    /// Set whether a session with pending after-auth tasks is treated as signed
214    /// out. `true` (the default) matches clerk-js; `false` reads a pending
215    /// session as signed in.
216    pub fn treat_pending_as_signed_out(mut self, value: bool) -> Self {
217        self.treat_pending_as_signed_out = value;
218        self
219    }
220}
221
222/// Read the current app-visible auth state.
223///
224/// # Example
225///
226/// ```no_run
227/// use dioxus::prelude::*;
228/// use dioxus_clerk::*;
229///
230/// #[component]
231/// fn AccountActions() -> Element {
232///     let auth = use_auth();
233///
234///     rsx! {
235///         if auth.is_signed_in() {
236///             button { onclick: move |_| auth.sign_out(), "Sign out" }
237///         }
238///     }
239/// }
240/// ```
241pub fn use_auth() -> UseAuth {
242    use_auth_with_options(UseAuthOptions::new())
243}
244
245/// Read the current app-visible auth state with explicit [`UseAuthOptions`].
246///
247/// Mirrors Clerk React's `useAuth({ treatPendingAsSignedOut })`: passing
248/// `UseAuthOptions::new().treat_pending_as_signed_out(false)` makes
249/// `is_signed_in()`, `status()`, and `has(...)` read a pending session as
250/// signed in.
251///
252/// # Example
253///
254/// ```no_run
255/// use dioxus::prelude::*;
256/// use dioxus_clerk::*;
257///
258/// #[component]
259/// fn PendingAware() -> Element {
260///     let auth = use_auth_with_options(
261///         UseAuthOptions::new().treat_pending_as_signed_out(false),
262///     );
263///
264///     rsx! {
265///         if auth.is_signed_in() {
266///             p { "Signed in (pending tasks count as signed in here)." }
267///         }
268///     }
269/// }
270/// ```
271pub fn use_auth_with_options(options: UseAuthOptions) -> UseAuth {
272    let ctx = use_clerk_context();
273    UseAuth {
274        inner: auth_state_signal(ctx, options.treat_pending_as_signed_out),
275        ctx,
276    }
277}
278
279/// Generate the auth-resolution predicate set for resource state structs, so
280/// the semantics of loading/signed-out/signed-in are stated once for every
281/// state shape that carries an [`AuthStatus`].
282macro_rules! status_predicates {
283    ($($ty:ident),* $(,)?) => {$(
284        impl $ty {
285            /// The explicit auth resolution status.
286            pub fn status(&self) -> AuthStatus {
287                self.status
288            }
289
290            /// Whether clerk-js has finished loading on the client.
291            pub fn is_loaded(&self) -> bool {
292                self.is_loaded
293            }
294
295            /// True while auth has not resolved to signed-in or signed-out yet.
296            pub fn is_loading(&self) -> bool {
297                self.status.is_loading()
298            }
299
300            /// True only when auth has resolved and no active session is known.
301            pub fn is_signed_out(&self) -> bool {
302                self.status.is_signed_out()
303            }
304
305            /// Whether a session is active.
306            pub fn is_signed_in(&self) -> bool {
307                self.status.is_signed_in()
308            }
309        }
310    )*};
311}
312
313/// Generate the shared accessor set for reactive hook results wrapping a
314/// resource state signal. Resource-specific accessors (`user()`, `session()`)
315/// stay hand-written next to each wrapper.
316macro_rules! hook_state_accessors {
317    ($($wrapper:ident => $state:ident, $doc:literal;)*) => {$(
318        impl $wrapper {
319            #[doc = concat!("Return the current ", $doc, " state.")]
320            pub fn state(&self) -> $state {
321                self.inner.read().clone()
322            }
323
324            /// Return the underlying read-only Dioxus signal for advanced composition.
325            pub fn signal(&self) -> ReadSignal<$state> {
326                self.inner
327            }
328
329            /// Return the explicit auth resolution status.
330            pub fn status(&self) -> AuthStatus {
331                self.inner.read().status
332            }
333
334            /// True while auth has not resolved to signed-in or signed-out yet.
335            pub fn is_loading(&self) -> bool {
336                self.status().is_loading()
337            }
338
339            /// True only when auth has resolved and no active session is known.
340            pub fn is_signed_out(&self) -> bool {
341                self.status().is_signed_out()
342            }
343
344            /// Whether clerk-js has finished loading on the client.
345            pub fn is_loaded(&self) -> bool {
346                self.inner.read().is_loaded
347            }
348
349            /// Whether a session is active.
350            pub fn is_signed_in(&self) -> bool {
351                self.inner.read().is_signed_in()
352            }
353        }
354
355        impl std::fmt::Debug for $wrapper {
356            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357                f.debug_struct(stringify!($wrapper))
358                    .field("state", &*self.inner.peek())
359                    .finish_non_exhaustive()
360            }
361        }
362    )*};
363}
364
365/// Generate a memoized resource state signal projecting Auth state plus one
366/// resource read out of the provider-owned runtime state.
367macro_rules! resource_state_signal {
368    ($($name:ident, $state:ident { $field:ident };)*) => {$(
369        fn $name() -> ReadSignal<$state> {
370            let ctx = use_clerk_context();
371            use_memo(move || {
372                let state = ctx.auth.read();
373                let auth = state.to_state();
374                $state {
375                    status: auth.status,
376                    is_loaded: state.is_loaded(),
377                    $field: state.$field().cloned(),
378                }
379            })
380            .into()
381        }
382    )*};
383}
384
385/// Current-user state that distinguishes loading, signed-out,
386/// and signed-in-without-full-browser-user states.
387///
388/// Fields are read through accessors ([`status`](UserState::status),
389/// [`is_loaded`](UserState::is_loaded), [`user`](UserState::user)), mirroring
390/// [`AuthState`] so every read-model snapshot in the
391/// crate shares one convention.
392#[derive(Debug, Clone, PartialEq)]
393#[non_exhaustive]
394pub struct UserState {
395    pub(crate) status: AuthStatus,
396    pub(crate) is_loaded: bool,
397    pub(crate) user: Option<User>,
398}
399
400impl UserState {
401    /// Full clerk-js user details, available after browser hydration.
402    pub fn user(&self) -> Option<&User> {
403        self.user.as_ref()
404    }
405}
406
407status_predicates!(UserState, SessionState);
408
409resource_state_signal! {
410    user_state_signal, UserState { user };
411    session_state_signal, SessionState { session };
412}
413
414hook_state_accessors! {
415    UseUser => UserState, "user";
416    UseSession => SessionState, "session";
417}
418
419/// Reactive user hook result returned by [`use_user`].
420#[derive(Clone, Copy)]
421pub struct UseUser {
422    inner: ReadSignal<UserState>,
423}
424
425impl UseUser {
426    /// Full clerk-js user details, available after browser hydration.
427    pub fn user(&self) -> Option<User> {
428        self.inner.read().user.clone()
429    }
430}
431
432/// Read the current user together with loadedness and signed-in status.
433///
434/// This mirrors Clerk React's `useUser()` shape more closely than a bare
435/// `Option<User>` and should be preferred in app code.
436///
437/// # Example
438///
439/// ```no_run
440/// use dioxus::prelude::*;
441/// use dioxus_clerk::*;
442///
443/// #[component]
444/// fn Greeting() -> Element {
445///     let user = use_user();
446///
447///     if !user.is_loaded() {
448///         return rsx! { "Loading..." };
449///     }
450///
451///     rsx! {
452///         if let Some(user) = user.user() {
453///             p { "Hello {user.id}" }
454///         }
455///     }
456/// }
457/// ```
458pub fn use_user() -> UseUser {
459    UseUser {
460        inner: user_state_signal(),
461    }
462}
463
464/// Current-session state that distinguishes loading,
465/// signed-out, and signed-in-without-full-browser-session states.
466///
467/// Fields are read through accessors ([`status`](SessionState::status),
468/// [`is_loaded`](SessionState::is_loaded), [`session`](SessionState::session)),
469/// mirroring [`AuthState`] so every read-model snapshot
470/// in the crate shares one convention.
471#[derive(Debug, Clone, PartialEq)]
472#[non_exhaustive]
473pub struct SessionState {
474    pub(crate) status: AuthStatus,
475    pub(crate) is_loaded: bool,
476    pub(crate) session: Option<Session>,
477}
478
479impl SessionState {
480    /// Full clerk-js session details, available after browser hydration.
481    pub fn session(&self) -> Option<&Session> {
482        self.session.as_ref()
483    }
484}
485
486/// Reactive session hook result returned by [`use_session`].
487#[derive(Clone, Copy)]
488pub struct UseSession {
489    inner: ReadSignal<SessionState>,
490}
491
492impl UseSession {
493    /// Full clerk-js session details, available after browser hydration.
494    pub fn session(&self) -> Option<Session> {
495        self.inner.read().session.clone()
496    }
497}
498
499/// Read the current session together with loadedness and signed-in status.
500///
501/// This mirrors Clerk React's `useSession()` shape more closely than a bare
502/// `Option<Session>` and should be preferred in app code.
503///
504/// # Example
505///
506/// ```no_run
507/// use dioxus::prelude::*;
508/// use dioxus_clerk::*;
509///
510/// #[component]
511/// fn SessionId() -> Element {
512///     let session = use_session();
513///
514///     rsx! {
515///         if let Some(session) = session.session() {
516///             code { "{session.id}" }
517///         }
518///     }
519/// }
520/// ```
521pub fn use_session() -> UseSession {
522    UseSession {
523        inner: session_state_signal(),
524    }
525}
526
527/// Read the latest Clerk error, if initialization failed, a scheduled
528/// browser action failed, or startup found a non-fatal configuration
529/// problem (e.g. an SSR seed publishable-key mismatch). A fatal
530/// initialization failure wins over the recoverable kinds.
531pub fn use_clerk_error() -> ReadSignal<Option<ClerkError>> {
532    let ctx = use_clerk_context();
533    use_memo(move || ctx.current_error()).into()
534}
535
536/// Return a callback that clears the latest recoverable error (a
537/// scheduled-action failure or a non-fatal startup configuration warning).
538///
539/// Fatal initialization failures are terminal for the provider instance;
540/// nothing retries the load, so clearing them would only convert a visible
541/// error into a silent forever-loading state. They are therefore not cleared
542/// by this callback.
543pub fn use_clear_clerk_error() -> Callback<()> {
544    let ctx = use_clerk_context();
545    use_callback(move |()| {
546        let mut action_error = ctx.action_error;
547        action_error.set(None);
548    })
549}
550
551/// Lifecycle-aware browser actions for the current clerk-js singleton.
552///
553/// Actions scheduled through this type wait for Clerk lifecycle loadedness
554/// before touching clerk-js.
555///
556/// Each operation comes in two forms. The plain method (e.g. `sign_out`) is
557/// fire-and-forget: it schedules the action and returns immediately, so it
558/// drops into an event handler, and any failure is surfaced through
559/// [`use_clerk_error`]. The `try_`-prefixed method (e.g. `try_sign_out`) is
560/// awaited and hands the [`ClerkError`] back to the caller, for when it needs
561/// to sequence work or handle the error locally. Each also has a
562/// `_with_options` variant taking Clerk options.
563#[derive(Clone, Copy)]
564pub struct ClerkActions {
565    ctx: ClerkContext,
566}
567
568impl std::fmt::Debug for ClerkActions {
569    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570        f.debug_struct("ClerkActions").finish_non_exhaustive()
571    }
572}
573
574/// Generate the four public entry points for one Clerk operation that takes
575/// options: fire-and-forget, fire-and-forget with options, awaited, and
576/// awaited with options. Every body lands on the same two Clerk action
577/// dispatch functions; the operation-to-JS mapping lives once in
578/// `ClerkBridge::run`.
579macro_rules! clerk_option_actions {
580    ($($doc:literal:
581        $name:ident, $with_options:ident,
582        $try_name:ident, $try_with_options:ident => $op:ident;)*) => {$(
583        #[doc = concat!($doc, " after Clerk lifecycle loadedness.")]
584        pub fn $name(&self) {
585            self.$with_options(serde_json::Value::Null);
586        }
587
588        #[doc = concat!($doc, " with options after Clerk lifecycle loadedness.")]
589        pub fn $with_options(&self, options: impl Into<serde_json::Value>) {
590            crate::actions::schedule(
591                self.ctx,
592                crate::actions::ClerkOperation::$op(options.into()),
593            );
594        }
595
596        #[doc = concat!($doc, " and return any lifecycle or clerk-js error.")]
597        pub async fn $try_name(&self) -> Result<(), ClerkError> {
598            self.$try_with_options(serde_json::Value::Null).await
599        }
600
601        #[doc = concat!($doc, " with options and return any error.")]
602        pub async fn $try_with_options(
603            &self,
604            options: impl Into<serde_json::Value>,
605        ) -> Result<(), ClerkError> {
606            crate::actions::try_run(
607                self.ctx,
608                crate::actions::ClerkOperation::$op(options.into()),
609            )
610            .await
611        }
612    )*};
613}
614
615/// Generate the two public entry points for one option-less Clerk operation.
616macro_rules! clerk_plain_actions {
617    ($($doc:literal: $name:ident, $try_name:ident => $op:ident;)*) => {$(
618        #[doc = concat!($doc, " after Clerk lifecycle loadedness.")]
619        pub fn $name(&self) {
620            crate::actions::schedule(self.ctx, crate::actions::ClerkOperation::$op);
621        }
622
623        #[doc = concat!($doc, " and return any lifecycle or clerk-js error.")]
624        pub async fn $try_name(&self) -> Result<(), ClerkError> {
625            crate::actions::try_run(self.ctx, crate::actions::ClerkOperation::$op).await
626        }
627    )*};
628}
629
630impl ClerkActions {
631    clerk_option_actions! {
632        "Open the Clerk sign-in modal":
633            open_sign_in, open_sign_in_with_options,
634            try_open_sign_in, try_open_sign_in_with_options => OpenSignIn;
635        "Open the Clerk sign-up modal":
636            open_sign_up, open_sign_up_with_options,
637            try_open_sign_up, try_open_sign_up_with_options => OpenSignUp;
638        "Open the Clerk user-profile modal":
639            open_user_profile, open_user_profile_with_options,
640            try_open_user_profile, try_open_user_profile_with_options => OpenUserProfile;
641        "Sign out":
642            sign_out, sign_out_with_options,
643            try_sign_out, try_sign_out_with_options => SignOut;
644        "Redirect to Clerk sign-in":
645            redirect_to_sign_in, redirect_to_sign_in_with_options,
646            try_redirect_to_sign_in, try_redirect_to_sign_in_with_options => RedirectToSignIn;
647        "Redirect to Clerk sign-up":
648            redirect_to_sign_up, redirect_to_sign_up_with_options,
649            try_redirect_to_sign_up, try_redirect_to_sign_up_with_options => RedirectToSignUp;
650    }
651
652    clerk_plain_actions! {
653        "Close the Clerk sign-in modal": close_sign_in, try_close_sign_in => CloseSignIn;
654        "Close the Clerk sign-up modal": close_sign_up, try_close_sign_up => CloseSignUp;
655        "Close the Clerk user-profile modal":
656            close_user_profile, try_close_user_profile => CloseUserProfile;
657    }
658
659    /// Get the active session token from clerk-js.
660    pub async fn get_token(&self) -> Result<Option<String>, ClerkError> {
661        self.get_token_with_options(serde_json::Value::Null).await
662    }
663
664    /// Get the active session token with Clerk `getToken(...)` options.
665    pub async fn get_token_with_options(
666        &self,
667        options: impl Into<serde_json::Value>,
668    ) -> Result<Option<String>, ClerkError> {
669        #[cfg(clerk_client)]
670        {
671            let options = options.into();
672            crate::lifecycle::run_async_bridge_action_after_loaded(
673                self.ctx,
674                move |bridge| async move { bridge.get_token(&options).await },
675            )
676            .await
677        }
678        #[cfg(not(clerk_client))]
679        {
680            let _ = options;
681            Err(ClerkError::UnsupportedTarget)
682        }
683    }
684}
685
686/// Library-level browser actions for descendants of `ClerkProvider`.
687///
688/// # Example
689///
690/// Use this for design-system buttons that own their own DOM element:
691///
692/// ```no_run
693/// use dioxus::prelude::*;
694/// use dioxus_clerk::*;
695///
696/// #[component]
697/// fn CustomSignInButton() -> Element {
698///     let clerk = use_clerk();
699///
700///     rsx! {
701///         button {
702///             class: "btn btn-primary",
703///             onclick: move |_| clerk.open_sign_in(),
704///             "Sign in"
705///         }
706///     }
707/// }
708/// ```
709pub fn use_clerk() -> ClerkActions {
710    ClerkActions {
711        ctx: use_clerk_context(),
712    }
713}
714
715#[cfg(test)]
716mod tests {
717    use super::UseAuthOptions;
718
719    #[test]
720    fn use_auth_options_default_treats_pending_as_signed_out() {
721        // clerk-js's default is `true`; Rust's `bool::default()` is `false`, so
722        // the default must be set explicitly or parity silently breaks.
723        assert_eq!(
724            UseAuthOptions::new(),
725            UseAuthOptions::new().treat_pending_as_signed_out(true)
726        );
727        assert_ne!(
728            UseAuthOptions::new(),
729            UseAuthOptions::new().treat_pending_as_signed_out(false)
730        );
731    }
732}