atproto blogging
1use crate::auth::AuthState;
2use crate::fetch::Fetcher;
3use dioxus::prelude::*;
4use jacquard::{
5 IntoStatic,
6 cowstr::ToCowStr,
7 oauth::{error::OAuthError, types::CallbackParams},
8 smol_str::SmolStr,
9};
10use tracing::{error, info};
11use weaver_api::sh_weaver::actor::profile::Profile as WeaverProfile;
12
13#[component]
14pub fn Callback(
15 state: ReadSignal<SmolStr>,
16 iss: ReadSignal<SmolStr>,
17 code: ReadSignal<SmolStr>,
18) -> Element {
19 let fetcher = use_context::<Fetcher>();
20 let mut auth = use_context::<Signal<AuthState>>();
21 #[cfg(feature = "web")]
22 let result = {
23 use_resource(move || {
24 let fetcher = fetcher.clone();
25 let callback_params = CallbackParams {
26 code: code().to_cowstr(),
27 state: Some(state().to_cowstr()),
28 iss: Some(iss().to_cowstr()),
29 }
30 .into_static();
31 info!("Auth Callback: {:?}", callback_params);
32 async move {
33 let session = fetcher
34 .client
35 .oauth_client
36 .callback(callback_params)
37 .await?;
38 let (did, session_id) = session.session_info().await;
39 let did_owned = did.into_static();
40 auth.write()
41 .set_authenticated(did_owned.clone(), session_id);
42 fetcher.upgrade_to_authenticated(session).await;
43
44 // Create weaver profile if it doesn't exist
45 if let Err(e) = ensure_weaver_profile(&fetcher, &did_owned).await {
46 error!("Failed to ensure weaver profile: {:?}", e);
47 }
48
49 Ok::<(), OAuthError>(())
50 }
51 })
52 };
53 #[cfg(not(feature = "web"))]
54 let result = { use_resource(move || async { Ok::<(), OAuthError>(()) }) };
55 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
56 let nav = use_navigator();
57
58 match &*result.read_unchecked() {
59 Some(Ok(())) => {
60 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
61 {
62 use gloo_storage::Storage;
63 let mut prev = gloo_storage::LocalStorage::get::<String>("cached_route").ok();
64 if let Some(prev) = prev.take() {
65 tracing::info!("Navigating to previous page");
66
67 gloo_storage::LocalStorage::delete("cached_route");
68 nav.replace(prev);
69 }
70 }
71 rsx! {
72 div {
73 h1 { "Success" }
74 p { "You have successfully authenticated. You can close this browser window." }
75 }
76 }
77 }
78 Some(Err(err)) => {
79 error!("Auth Error: {}", err);
80 rsx! {
81
82 div {
83 h1 { "Error" }
84 p { "{err}" }
85 }
86 }
87 }
88 None => rsx! {
89 div {
90 h1 { "Loading..." }
91 }
92 },
93 }
94}
95
96/// Ensures a weaver profile exists for the authenticated user.
97/// If no weaver profile exists, creates one by mirroring the bsky profile.
98#[cfg(feature = "web")]
99async fn ensure_weaver_profile(
100 fetcher: &Fetcher,
101 did: &jacquard::types::string::Did<'_>,
102) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
103 use jacquard::{
104 client::AgentSessionExt,
105 types::string::{Datetime, RecordKey},
106 };
107 use weaver_api::app_bsky::actor::profile::Profile as BskyProfile;
108
109 let weaver_uri_str = format!("at://{}/sh.weaver.actor.profile/self", did);
110 let weaver_uri = WeaverProfile::uri(&weaver_uri_str)?;
111
112 // Check if weaver profile already exists
113 if fetcher.fetch_record(&weaver_uri).await.is_ok() {
114 info!("Weaver profile already exists for {}", did);
115 return Ok(());
116 }
117
118 info!(
119 "No weaver profile found for {}, creating from bsky profile",
120 did
121 );
122
123 // Fetch bsky profile
124 let bsky_uri_str = format!("at://{}/app.bsky.actor.profile/self", did);
125 let bsky_uri = BskyProfile::uri(&bsky_uri_str)?;
126 let bsky_record = fetcher.fetch_record(&bsky_uri).await?;
127
128 // Create weaver profile mirroring bsky
129 let weaver_profile = WeaverProfile::new()
130 .maybe_display_name(bsky_record.value.display_name.clone())
131 .maybe_description(bsky_record.value.description.clone())
132 .maybe_avatar(bsky_record.value.avatar.clone())
133 .maybe_banner(bsky_record.value.banner.clone())
134 .bluesky(true)
135 .created_at(Datetime::now())
136 .build();
137
138 let self_rkey = RecordKey::any("self").expect("self is valid record key");
139
140 fetcher.put_record(self_rkey, weaver_profile).await?;
141 info!("Created weaver profile for {}", did);
142
143 Ok(())
144}