use crate::context::{ClerkContext, use_clerk_context};
use crate::core::{AuthRequirement, AuthState, AuthStatus, ClerkError, Session, User};
use dioxus::prelude::*;
fn auth_state_signal(
ctx: ClerkContext,
treat_pending_as_signed_out: bool,
) -> ReadSignal<AuthState> {
use_memo(use_reactive(
&treat_pending_as_signed_out,
move |treat_pending_as_signed_out| {
ctx.auth
.read()
.resolve_pending(treat_pending_as_signed_out)
.to_state()
},
))
.into()
}
#[derive(Clone, Copy)]
pub struct UseAuth {
inner: ReadSignal<AuthState>,
ctx: ClerkContext,
}
impl std::fmt::Debug for UseAuth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UseAuth")
.field("state", &*self.inner.peek())
.finish_non_exhaustive()
}
}
impl UseAuth {
pub fn state(&self) -> AuthState {
self.inner.read().clone()
}
pub fn signal(&self) -> ReadSignal<AuthState> {
self.inner
}
pub fn status(&self) -> AuthStatus {
self.inner.read().status
}
pub fn is_loading(&self) -> bool {
self.status().is_loading()
}
pub fn is_signed_out(&self) -> bool {
self.status().is_signed_out()
}
pub fn is_loaded(&self) -> bool {
self.inner.read().is_loaded
}
pub fn is_signed_in(&self) -> bool {
self.inner.read().is_signed_in()
}
pub fn user_id(&self) -> Option<String> {
self.inner.read().user_id.clone()
}
pub fn require_signed_in(&self) -> Result<String, ClerkError> {
self.inner.read().require_signed_in().map(ToOwned::to_owned)
}
pub async fn get_token(&self) -> Result<Option<String>, ClerkError> {
self.get_token_with_options(serde_json::Value::Null).await
}
pub async fn get_token_with_options(
&self,
options: impl Into<serde_json::Value>,
) -> Result<Option<String>, ClerkError> {
ClerkActions { ctx: self.ctx }
.get_token_with_options(options)
.await
}
pub fn session_id(&self) -> Option<String> {
self.inner.read().session_id.clone()
}
pub fn org_id(&self) -> Option<String> {
self.inner.read().org_id.clone()
}
pub fn org_slug(&self) -> Option<String> {
self.inner.read().org_slug.clone()
}
pub fn org_role(&self) -> Option<String> {
self.inner.read().org_role.clone()
}
pub fn org_permissions(&self) -> Vec<String> {
self.inner.read().org_permissions.clone()
}
pub fn has_role(&self, role: &str) -> bool {
self.inner.read().has_role(role)
}
pub fn has_permission(&self, permission: &str) -> bool {
self.inner.read().has_permission(permission)
}
pub fn has(&self, requirement: &AuthRequirement) -> bool {
self.inner.read().has(requirement)
}
pub fn sign_out(&self) {
self.sign_out_with_options(serde_json::Value::Null);
}
pub fn sign_out_with_options(&self, options: impl Into<serde_json::Value>) {
ClerkActions { ctx: self.ctx }.sign_out_with_options(options);
}
pub async fn try_sign_out(&self) -> Result<(), ClerkError> {
self.try_sign_out_with_options(serde_json::Value::Null)
.await
}
pub async fn try_sign_out_with_options(
&self,
options: impl Into<serde_json::Value>,
) -> Result<(), ClerkError> {
ClerkActions { ctx: self.ctx }
.try_sign_out_with_options(options)
.await
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct UseAuthOptions {
treat_pending_as_signed_out: bool,
}
impl Default for UseAuthOptions {
fn default() -> Self {
Self {
treat_pending_as_signed_out: true,
}
}
}
impl UseAuthOptions {
pub fn new() -> Self {
Self::default()
}
pub fn treat_pending_as_signed_out(mut self, value: bool) -> Self {
self.treat_pending_as_signed_out = value;
self
}
}
pub fn use_auth() -> UseAuth {
use_auth_with_options(UseAuthOptions::new())
}
pub fn use_auth_with_options(options: UseAuthOptions) -> UseAuth {
let ctx = use_clerk_context();
UseAuth {
inner: auth_state_signal(ctx, options.treat_pending_as_signed_out),
ctx,
}
}
macro_rules! status_predicates {
($($ty:ident),* $(,)?) => {$(
impl $ty {
pub fn status(&self) -> AuthStatus {
self.status
}
pub fn is_loaded(&self) -> bool {
self.is_loaded
}
pub fn is_loading(&self) -> bool {
self.status.is_loading()
}
pub fn is_signed_out(&self) -> bool {
self.status.is_signed_out()
}
pub fn is_signed_in(&self) -> bool {
self.status.is_signed_in()
}
}
)*};
}
macro_rules! hook_state_accessors {
($($wrapper:ident => $state:ident, $doc:literal;)*) => {$(
impl $wrapper {
#[doc = concat!("Return the current ", $doc, " state.")]
pub fn state(&self) -> $state {
self.inner.read().clone()
}
pub fn signal(&self) -> ReadSignal<$state> {
self.inner
}
pub fn status(&self) -> AuthStatus {
self.inner.read().status
}
pub fn is_loading(&self) -> bool {
self.status().is_loading()
}
pub fn is_signed_out(&self) -> bool {
self.status().is_signed_out()
}
pub fn is_loaded(&self) -> bool {
self.inner.read().is_loaded
}
pub fn is_signed_in(&self) -> bool {
self.inner.read().is_signed_in()
}
}
impl std::fmt::Debug for $wrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($wrapper))
.field("state", &*self.inner.peek())
.finish_non_exhaustive()
}
}
)*};
}
macro_rules! resource_state_signal {
($($name:ident, $state:ident { $field:ident };)*) => {$(
fn $name() -> ReadSignal<$state> {
let ctx = use_clerk_context();
use_memo(move || {
let state = ctx.auth.read();
let auth = state.to_state();
$state {
status: auth.status,
is_loaded: state.is_loaded(),
$field: state.$field().cloned(),
}
})
.into()
}
)*};
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct UserState {
pub(crate) status: AuthStatus,
pub(crate) is_loaded: bool,
pub(crate) user: Option<User>,
}
impl UserState {
pub fn user(&self) -> Option<&User> {
self.user.as_ref()
}
}
status_predicates!(UserState, SessionState);
resource_state_signal! {
user_state_signal, UserState { user };
session_state_signal, SessionState { session };
}
hook_state_accessors! {
UseUser => UserState, "user";
UseSession => SessionState, "session";
}
#[derive(Clone, Copy)]
pub struct UseUser {
inner: ReadSignal<UserState>,
}
impl UseUser {
pub fn user(&self) -> Option<User> {
self.inner.read().user.clone()
}
}
pub fn use_user() -> UseUser {
UseUser {
inner: user_state_signal(),
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct SessionState {
pub(crate) status: AuthStatus,
pub(crate) is_loaded: bool,
pub(crate) session: Option<Session>,
}
impl SessionState {
pub fn session(&self) -> Option<&Session> {
self.session.as_ref()
}
}
#[derive(Clone, Copy)]
pub struct UseSession {
inner: ReadSignal<SessionState>,
}
impl UseSession {
pub fn session(&self) -> Option<Session> {
self.inner.read().session.clone()
}
}
pub fn use_session() -> UseSession {
UseSession {
inner: session_state_signal(),
}
}
pub fn use_clerk_error() -> ReadSignal<Option<ClerkError>> {
let ctx = use_clerk_context();
use_memo(move || ctx.current_error()).into()
}
pub fn use_clear_clerk_error() -> Callback<()> {
let ctx = use_clerk_context();
use_callback(move |()| {
let mut action_error = ctx.action_error;
action_error.set(None);
})
}
#[derive(Clone, Copy)]
pub struct ClerkActions {
ctx: ClerkContext,
}
impl std::fmt::Debug for ClerkActions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClerkActions").finish_non_exhaustive()
}
}
macro_rules! clerk_option_actions {
($($doc:literal:
$name:ident, $with_options:ident,
$try_name:ident, $try_with_options:ident => $op:ident;)*) => {$(
#[doc = concat!($doc, " after Clerk lifecycle loadedness.")]
pub fn $name(&self) {
self.$with_options(serde_json::Value::Null);
}
#[doc = concat!($doc, " with options after Clerk lifecycle loadedness.")]
pub fn $with_options(&self, options: impl Into<serde_json::Value>) {
crate::actions::schedule(
self.ctx,
crate::actions::ClerkOperation::$op(options.into()),
);
}
#[doc = concat!($doc, " and return any lifecycle or clerk-js error.")]
pub async fn $try_name(&self) -> Result<(), ClerkError> {
self.$try_with_options(serde_json::Value::Null).await
}
#[doc = concat!($doc, " with options and return any error.")]
pub async fn $try_with_options(
&self,
options: impl Into<serde_json::Value>,
) -> Result<(), ClerkError> {
crate::actions::try_run(
self.ctx,
crate::actions::ClerkOperation::$op(options.into()),
)
.await
}
)*};
}
macro_rules! clerk_plain_actions {
($($doc:literal: $name:ident, $try_name:ident => $op:ident;)*) => {$(
#[doc = concat!($doc, " after Clerk lifecycle loadedness.")]
pub fn $name(&self) {
crate::actions::schedule(self.ctx, crate::actions::ClerkOperation::$op);
}
#[doc = concat!($doc, " and return any lifecycle or clerk-js error.")]
pub async fn $try_name(&self) -> Result<(), ClerkError> {
crate::actions::try_run(self.ctx, crate::actions::ClerkOperation::$op).await
}
)*};
}
impl ClerkActions {
clerk_option_actions! {
"Open the Clerk sign-in modal":
open_sign_in, open_sign_in_with_options,
try_open_sign_in, try_open_sign_in_with_options => OpenSignIn;
"Open the Clerk sign-up modal":
open_sign_up, open_sign_up_with_options,
try_open_sign_up, try_open_sign_up_with_options => OpenSignUp;
"Open the Clerk user-profile modal":
open_user_profile, open_user_profile_with_options,
try_open_user_profile, try_open_user_profile_with_options => OpenUserProfile;
"Sign out":
sign_out, sign_out_with_options,
try_sign_out, try_sign_out_with_options => SignOut;
"Redirect to Clerk sign-in":
redirect_to_sign_in, redirect_to_sign_in_with_options,
try_redirect_to_sign_in, try_redirect_to_sign_in_with_options => RedirectToSignIn;
"Redirect to Clerk sign-up":
redirect_to_sign_up, redirect_to_sign_up_with_options,
try_redirect_to_sign_up, try_redirect_to_sign_up_with_options => RedirectToSignUp;
}
clerk_plain_actions! {
"Close the Clerk sign-in modal": close_sign_in, try_close_sign_in => CloseSignIn;
"Close the Clerk sign-up modal": close_sign_up, try_close_sign_up => CloseSignUp;
"Close the Clerk user-profile modal":
close_user_profile, try_close_user_profile => CloseUserProfile;
}
pub async fn get_token(&self) -> Result<Option<String>, ClerkError> {
self.get_token_with_options(serde_json::Value::Null).await
}
pub async fn get_token_with_options(
&self,
options: impl Into<serde_json::Value>,
) -> Result<Option<String>, ClerkError> {
#[cfg(clerk_client)]
{
let options = options.into();
crate::lifecycle::run_async_bridge_action_after_loaded(
self.ctx,
move |bridge| async move { bridge.get_token(&options).await },
)
.await
}
#[cfg(not(clerk_client))]
{
let _ = options;
Err(ClerkError::UnsupportedTarget)
}
}
}
pub fn use_clerk() -> ClerkActions {
ClerkActions {
ctx: use_clerk_context(),
}
}
#[cfg(test)]
mod tests {
use super::UseAuthOptions;
#[test]
fn use_auth_options_default_treats_pending_as_signed_out() {
assert_eq!(
UseAuthOptions::new(),
UseAuthOptions::new().treat_pending_as_signed_out(true)
);
assert_ne!(
UseAuthOptions::new(),
UseAuthOptions::new().treat_pending_as_signed_out(false)
);
}
}