commit c68f93ef61bea6916b2cbb836ace420430f25183
parent e0abab8c39b467921653d72f5d0e7e80369a93f0
Author: triesap <tyson@radroots.org>
Date: Mon, 2 Feb 2026 03:18:48 +0000
app: finalize setup on eula accept
- add setup finalize helper for explicit keys
- persist setup state on eula agreement
- handle add-existing keys and clear draft
- extend setup tests for finalize path
Diffstat:
3 files changed, 112 insertions(+), 2 deletions(-)
diff --git a/app/src/app.rs b/app/src/app.rs
@@ -9,6 +9,11 @@ use web_sys::HtmlElement;
use radroots_app_core::datastore::RadrootsClientDatastore;
use radroots_app_core::idb::IDB_CONFIG_LOGS;
+use radroots_app_core::keystore::{
+ RadrootsClientKeystoreError,
+ RadrootsClientKeystoreNostr,
+ RadrootsClientWebKeystoreNostr,
+};
use radroots_app_ui_components::{
RadrootsAppUiButtonLayoutAction,
RadrootsAppUiButtonLayoutBackAction,
@@ -33,11 +38,15 @@ use crate::{
app_log_error_emit,
app_log_error_store,
app_config_default,
+ app_datastore_clear_setup_draft,
app_datastore_read_state,
app_datastore_read_setup_draft,
app_datastore_write_setup_draft,
+ app_keystore_nostr_ensure_key,
app_state_notifications_permission_value,
app_state_set_notifications_permission_value,
+ app_setup_eula_date,
+ app_setup_finalize_with_key,
app_setup_step_default,
app_health_check_all,
RadrootsAppBackends,
@@ -49,6 +58,7 @@ use crate::{
RadrootsAppInitStage,
RadrootsAppNotifications,
RadrootsAppLogsPage,
+ RadrootsAppKeystoreError,
RadrootsAppRole,
RadrootsAppSettingsPage,
RadrootsAppSetupDraft,
@@ -320,14 +330,82 @@ fn SetupPage() -> impl IntoView {
}
});
let advance_step: Callback<()> = {
+ let backends = backends.clone();
let setup_step = setup_step.clone();
let setup_key_choice = setup_key_choice.clone();
+ let nostr_key_add = nostr_key_add.clone();
let profile_name = profile_name.clone();
let setup_required = setup_required.clone();
Callback::new(move |_| {
let current_step = setup_step.get();
if matches!(current_step, RadrootsAppSetupStep::Eula) {
- setup_required.set(Some(false));
+ let key_choice = setup_key_choice.get();
+ let nostr_key_add = nostr_key_add.get();
+ let eula_date = app_setup_eula_date();
+ let setup_required = setup_required.clone();
+ let backends = backends.clone();
+ spawn_local(async move {
+ let Some((datastore, key_maps, keystore_config)) = backends.with_untracked(|value| {
+ value.as_ref().map(|backends| {
+ (
+ backends.datastore.clone(),
+ backends.config.datastore.key_maps.clone(),
+ backends.nostr_keystore.get_config(),
+ )
+ })
+ }) else {
+ return;
+ };
+ let keystore = RadrootsClientWebKeystoreNostr::new(Some(keystore_config));
+ let active_key = match key_choice {
+ Some(RadrootsAppSetupKeyChoice::AddExisting) => {
+ let secret_key = nostr_key_add.trim();
+ if secret_key.is_empty() {
+ let err = RadrootsAppInitError::Keystore(
+ RadrootsClientKeystoreError::NostrInvalidSecretKey,
+ );
+ let _ = app_log_error_emit(&err);
+ return;
+ }
+ match keystore.add(secret_key).await {
+ Ok(value) => value,
+ Err(err) => {
+ let init_err = RadrootsAppInitError::Keystore(err);
+ let _ = app_log_error_emit(&init_err);
+ return;
+ }
+ }
+ }
+ _ => match app_keystore_nostr_ensure_key(&keystore).await {
+ Ok(value) => value,
+ Err(err) => {
+ let init_err = match err {
+ RadrootsAppKeystoreError::Keystore(inner) => {
+ RadrootsAppInitError::Keystore(inner)
+ }
+ RadrootsAppKeystoreError::KeyMismatch => RadrootsAppInitError::Keystore(
+ RadrootsClientKeystoreError::NostrInvalidSecretKey,
+ ),
+ };
+ let _ = app_log_error_emit(&init_err);
+ return;
+ }
+ },
+ };
+ if let Err(err) = app_setup_finalize_with_key(
+ datastore.as_ref(),
+ &key_maps,
+ active_key,
+ eula_date,
+ )
+ .await
+ {
+ let _ = app_log_error_emit(&err);
+ return;
+ }
+ let _ = app_datastore_clear_setup_draft(datastore.as_ref(), &key_maps).await;
+ setup_required.set(Some(false));
+ });
return;
}
if matches!(current_step, RadrootsAppSetupStep::Profile) {
diff --git a/app/src/lib.rs b/app/src/lib.rs
@@ -125,6 +125,7 @@ pub use logging::{
pub use notifications::{RadrootsAppNotifications, RadrootsAppNotificationsError, RadrootsAppNotificationsResult};
pub use setup::{
app_setup_eula_date,
+ app_setup_finalize_with_key,
app_setup_initialize,
app_setup_state_new,
app_setup_step_default,
diff --git a/app/src/setup.rs b/app/src/setup.rs
@@ -108,7 +108,16 @@ pub async fn app_setup_initialize<T: RadrootsClientDatastore, K: RadrootsClientK
RadrootsAppInitError::Keystore(RadrootsClientKeystoreError::NostrInvalidSecretKey)
}
})?;
- let state = app_setup_state_new(active_key.clone(), app_setup_eula_date());
+ app_setup_finalize_with_key(datastore, key_maps, active_key, app_setup_eula_date()).await
+}
+
+pub async fn app_setup_finalize_with_key<T: RadrootsClientDatastore>(
+ datastore: &T,
+ key_maps: &RadrootsAppKeyMapConfig,
+ active_key: String,
+ eula_date: String,
+) -> RadrootsAppInitResult<RadrootsAppState> {
+ let state = app_setup_state_new(active_key.clone(), eula_date);
let stored_state = app_datastore_create_state(datastore, key_maps, &state).await?;
let key_name = app_datastore_key_nostr_key(key_maps).map_err(RadrootsAppInitError::Config)?;
datastore
@@ -123,6 +132,7 @@ pub async fn app_setup_initialize<T: RadrootsClientDatastore, K: RadrootsClientK
mod tests {
use super::{
app_setup_eula_date,
+ app_setup_finalize_with_key,
app_setup_initialize,
app_setup_state_new,
app_setup_step_default,
@@ -433,4 +443,25 @@ mod tests {
assert_eq!(stored, public_key);
assert!(datastore.record.borrow().is_some());
}
+
+ #[test]
+ fn setup_finalize_with_key_writes_state() {
+ let datastore = TestDatastore {
+ record: RefCell::new(None),
+ values: RefCell::new(BTreeMap::new()),
+ };
+ let key_maps = app_key_maps_default();
+ let state = futures::executor::block_on(app_setup_finalize_with_key(
+ &datastore,
+ &key_maps,
+ "pub".to_string(),
+ "2025-01-01T00:00:00Z".to_string(),
+ ))
+ .expect("finalize");
+ assert_eq!(state.active_key, "pub");
+ let key_name = app_datastore_key_nostr_key(&key_maps).expect("key name");
+ let stored = futures::executor::block_on(datastore.get(key_name)).expect("stored");
+ assert_eq!(stored, "pub");
+ assert!(datastore.record.borrow().is_some());
+ }
}