feat: make the web version fully working #67

Merged
reya merged 9 commits from web into master 2026-10-25 13:10:52 +00:00
4 changed files with 62 additions and 8 deletions
Showing only changes of commit 4b4a99c464 - Show all commits
+5 -6
View File
@@ -18,7 +18,7 @@ use nostr_sdk::prelude::*;
use person::Directory;
use settings::AppSettings;
use smallvec::{SmallVec, smallvec};
use state::{Announcement, CLIENT_NAME, NostrRegistry, UniversalSigner};
use state::{Announcement, NostrRegistry, UniversalSigner, credentials};
use ui::Avatar;
const IDENTIFIER: &str = "coop:device";
@@ -840,10 +840,10 @@ fn authorization_code(public_key: &PublicKey) -> SharedString {
/// Get or create new app keys (async, returns a task)
fn get_or_init_app_keys(cx: &App) -> Task<Result<Keys, Error>> {
let read = cx.read_credentials(CLIENT_NAME);
let read = credentials::read_app_keys(cx);
cx.spawn(async move |cx| {
if let Ok(Some((_, secret))) = read.await
if let Ok(Some(secret)) = read.await
&& let Ok(keys) = SecretKey::from_slice(&secret).map(Keys::new)
{
return Ok(keys);
@@ -851,14 +851,13 @@ fn get_or_init_app_keys(cx: &App) -> Task<Result<Keys, Error>> {
// No stored keys found or invalid — generate new ones
let keys = Keys::generate();
let user = keys.public_key().to_hex();
let secret = keys.secret_key().to_secret_bytes();
cx.update(|cx| {
let write = cx.write_credentials(CLIENT_NAME, &user, &secret);
let write = credentials::write_app_keys(cx, &secret);
cx.background_spawn(async move {
if let Err(e) = write.await {
log::error!("Keyring not available or panic: {e}")
log::error!("Failed to persist app keys: {e}")
}
})
.detach();
+55 -1
View File
@@ -1,10 +1,13 @@
use anyhow::Result;
#[cfg(target_arch = "wasm32")]
use common::web_store;
use gpui::{App, Task};
use gpui::{App, AppContext as _, Task};
use crate::USER_KEYRING;
/// The storage key for the NIP-4e app keys
const APP_KEYS_KEY: &str = "coop/nip4e/app-keys";
/// Read the stored credential as `(username, secret)`
#[allow(clippy::type_complexity)]
pub(crate) fn read(cx: &App) -> Task<Result<Option<(String, Vec<u8>)>>> {
@@ -49,3 +52,54 @@ pub(crate) fn delete(cx: &App) -> Task<Result<()>> {
Task::ready(web_store::remove(USER_KEYRING))
}
}
/// Read named bytes from the platform keyring,
/// falling back to browser storage on web where no keyring exists
fn read_named(cx: &App, key: &str) -> Task<Result<Option<Vec<u8>>>> {
#[cfg(not(target_arch = "wasm32"))]
{
let key = key.to_owned();
let read = cx.read_credentials(&key);
cx.background_spawn(async move { Ok(read.await?.map(|(_, secret)| secret)) })
}
#[cfg(target_arch = "wasm32")]
{
let _ = cx;
let stored = web_store::get(key).map(|content| content.map(|s| s.into_bytes()));
Task::ready(stored)
}
}
/// Write named bytes to the platform keyring,
/// falling back to browser storage on web where no keyring exists
fn write_named(cx: &App, key: &str, secret: &[u8]) -> Task<Result<()>> {
#[cfg(not(target_arch = "wasm32"))]
{
let key = key.to_owned();
let secret = secret.to_vec();
let write = cx.write_credentials(&key, "", &secret);
cx.background_spawn(write)
}
#[cfg(target_arch = "wasm32")]
{
let _ = cx;
let content = match std::str::from_utf8(secret) {
Ok(content) => content,
Err(e) => return Task::ready(Err(anyhow::anyhow!("secret is not UTF-8: {e}"))),
};
Task::ready(web_store::set(key, content))
}
}
/// Load the NIP-4e app keys from storage, as raw secret bytes
pub fn read_app_keys(cx: &App) -> Task<Result<Option<Vec<u8>>>> {
read_named(cx, APP_KEYS_KEY)
}
/// Persist the NIP-4e app keys, as raw secret bytes
pub fn write_app_keys(cx: &App, secret: &[u8]) -> Task<Result<()>> {
write_named(cx, APP_KEYS_KEY, secret)
}
+1 -1
View File
@@ -22,7 +22,7 @@ use nostr_sdk::prelude::*;
mod blossom;
mod constants;
mod credentials;
pub mod credentials;
mod file;
mod nip05;
mod nip4e;
+1
View File
@@ -54,6 +54,7 @@ impl Render for DragPanelPreview {
.cursor_grab()
.p_1()
.whitespace_nowrap()
.font_family(cx.theme().font_family.clone())
.border_1()
.border_color(cx.theme().border)
.rounded(cx.theme().radius_lg)