use jacquard_common::deps::smol_str::{SmolStr, ToSmolStr}; use jacquard_common::types::string::{Did, Handle, UriValue}; use polymodel_api::space_polymodel::actor::ProfileView; use polymodel_api::space_polymodel::actor::get_session::{GetSessionOutput, SessionView}; #[derive(Clone, Debug, Default, PartialEq, Eq)] pub enum SessionIdentity { /// Session not yet determined — the initial state before `getSession` /// resolves. Distinct from `Anonymous` so the UI can show a loading state /// instead of flashing signed-out chrome at users who are actually signed in. #[default] Unknown, Anonymous, Authenticated(AuthenticatedIdentity), } #[derive(Clone, Debug, PartialEq, Eq)] pub struct AuthenticatedIdentity { pub avatar: Option, pub did: Did, pub handle: Handle, pub display_name: Option, } impl SessionIdentity { pub fn from_get_session(output: GetSessionOutput) -> Self { match output.value { SessionView::SessionUnauthenticated(_) => Self::Anonymous, SessionView::SessionAuthenticated(session) => { let avatar = session.profile.avatar.clone(); Self::Authenticated(AuthenticatedIdentity { avatar, did: session.did, handle: session.handle, display_name: profile_display_name(&session.profile), }) } } } } impl AuthenticatedIdentity { pub fn label(&self) -> SmolStr { self.display_name .clone() .unwrap_or_else(|| self.handle.as_str().to_smolstr()) } } fn profile_display_name(profile: &ProfileView) -> Option { profile .display_name .as_ref() .map(|value| value.to_smolstr()) .filter(|value| !value.trim().is_empty()) } #[cfg(test)] mod tests { use super::*; use jacquard_common::types::did::Did; use polymodel_api::space_polymodel::actor::ProfileView; use polymodel_api::space_polymodel::actor::get_session::{ SessionAuthenticated, SessionUnauthenticated, }; #[test] fn unauthenticated_get_session_maps_to_anonymous() { let output = GetSessionOutput { value: SessionView::SessionUnauthenticated(Box::new(SessionUnauthenticated { authenticated: false, extra_data: None, })), extra_data: None, }; assert_eq!( SessionIdentity::from_get_session(output), SessionIdentity::Anonymous ); } #[test] fn authenticated_get_session_maps_identity_label() { let output = GetSessionOutput { value: SessionView::SessionAuthenticated(Box::new(SessionAuthenticated { did: Did::new_owned("did:plc:alice").unwrap(), handle: Handle::new_owned("alice.test").unwrap(), profile: ProfileView { avatar: None, default_license: None, indexed_at: None, description: None, did: Did::new_owned("did:plc:alice").unwrap(), display_name: Some("Alice Maker".into()), handle: "alice.test".into(), pronouns: None, thing_count: None, extra_data: None, links: None, lists: None, printers: None, record: jacquard::Data::Null, follower_count: None, following_count: None, total_likes: None, viewer: None, }, extra_data: None, authenticated: true, })), extra_data: None, }; let identity = SessionIdentity::from_get_session(output); match identity { SessionIdentity::Authenticated(auth) => assert_eq!(auth.label(), "Alice Maker"), _ => panic!("expected authenticated identity"), } } #[test] fn authenticated_get_session_retains_avatar() { let output = GetSessionOutput { value: SessionView::SessionAuthenticated(Box::new(SessionAuthenticated { did: Did::new_owned("did:plc:bob").unwrap(), handle: Handle::new_owned("bob.test").unwrap(), profile: ProfileView { avatar: Some(UriValue::new_owned("https://cdn.example/bob.jpg").unwrap()), default_license: None, indexed_at: None, description: None, did: Did::new_owned("did:plc:bob").unwrap(), display_name: None, handle: "bob.test".into(), pronouns: None, thing_count: None, extra_data: None, links: None, lists: None, printers: None, record: jacquard::Data::Null, follower_count: None, following_count: None, total_likes: None, viewer: None, }, extra_data: None, authenticated: true, })), extra_data: None, }; match SessionIdentity::from_get_session(output) { SessionIdentity::Authenticated(identity) => { assert_eq!( identity.avatar.as_ref().map(AsRef::as_ref), Some("https://cdn.example/bob.jpg") ); } _ => panic!("expected authenticated identity"), } } #[test] fn get_session_json_decodes_unauthenticated_union_arm() { let output: GetSessionOutput = serde_json::from_str(r#"{"$type":"space.polymodel.actor.getSession#sessionUnauthenticated","authenticated":false}"#).unwrap(); assert_eq!( SessionIdentity::from_get_session(output), SessionIdentity::Anonymous ); } #[test] fn get_session_json_decodes_authenticated_union_arm() { let output: GetSessionOutput = serde_json::from_str(r#"{"$type":"space.polymodel.actor.getSession#sessionAuthenticated","authenticated":true,"did":"did:plc:alice","handle":"alice.test","profile":{"avatar":null,"description":null,"did":"did:plc:alice","displayName":"Alice Maker","handle":"alice.test","record":null}}"#).unwrap(); match SessionIdentity::from_get_session(output) { SessionIdentity::Authenticated(auth) => assert_eq!(auth.label(), "Alice Maker"), _ => panic!("expected authenticated identity"), } } }