feat: use latest nostr sdk

This commit is contained in:
2024-10-23 10:16:56 +07:00
parent cc7de41bfd
commit 172566028b
21 changed files with 596 additions and 240 deletions

597
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -29,12 +29,12 @@ tauri-plugin-shell = "2.0.0"
tauri-plugin-updater = "2.0.0"
tauri-plugin-upload = "2.0.0"
tauri-plugin-store = "2.0.0"
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum.git" }
tauri-plugin-prevent-default = "0.6"
tauri-plugin-theme = "2.1.2"
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] }
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb"] }
nostr-relay-builder = { git = "https://github.com/rust-nostr/nostr" }
specta = "^2.0.0-rc.20"
specta-typescript = "0.0.7"
@@ -49,14 +49,11 @@ regex = "1.10.4"
keyring = { version = "3", features = ["apple-native", "windows-native"] }
keyring-search = "1.2.0"
tracing-subscriber = "0.3.18"
log = "^0.4"
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.25.0"
objc = "0.2.7"
rand = "0.8.5"
monitor = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" }
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
share-picker = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
[profile.release]
codegen-units = 1

View File

@@ -28,6 +28,7 @@
"store:allow-get",
"store:allow-set",
"store:allow-delete",
"theme:default",
{
"identifier": "http:default",
"allow": [

View File

@@ -63,6 +63,7 @@
"shell:allow-open",
"store:default",
"prevent-default:default",
"theme:default",
{
"identifier": "http:default",
"allow": [

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"column":{"identifier":"column","description":"Capability for the column","local":true,"windows":["column-*"],"permissions":["core:resources:default","core:tray:default","os:allow-locale","os:allow-os-type","clipboard-manager:allow-write-text","dialog:allow-open","dialog:allow-ask","dialog:allow-message","fs:allow-read-file","core:menu:default","core:menu:allow-new","core:menu:allow-popup","http:default","shell:allow-open","store:allow-get","store:allow-set","store:allow-delete",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]},"window":{"identifier":"window","description":"Capability for the desktop","local":true,"windows":["main","panel","settings","search-*","zap-*","event-*","user-*","editor-*","popup-*"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","core:window:allow-create","core:window:allow-close","core:window:allow-destroy","core:window:allow-set-focus","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-start-dragging","core:window:allow-toggle-maximize","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-set-webview-size","core:webview:allow-set-webview-position","core:webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","process:allow-exit","fs:allow-read-file","core:menu:allow-new","core:menu:allow-popup","shell:allow-open","store:default","prevent-default:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["macOS","windows"]}}
{"column":{"identifier":"column","description":"Capability for the column","local":true,"windows":["column-*"],"permissions":["core:resources:default","core:tray:default","os:allow-locale","os:allow-os-type","clipboard-manager:allow-write-text","dialog:allow-open","dialog:allow-ask","dialog:allow-message","fs:allow-read-file","core:menu:default","core:menu:allow-new","core:menu:allow-popup","http:default","shell:allow-open","store:allow-get","store:allow-set","store:allow-delete","theme:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]},"window":{"identifier":"window","description":"Capability for the desktop","local":true,"windows":["main","panel","settings","search-*","zap-*","event-*","user-*","editor-*","popup-*"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","os:allow-os-type","updater:default","updater:allow-check","updater:allow-download-and-install","core:window:allow-create","core:window:allow-close","core:window:allow-destroy","core:window:allow-set-focus","core:window:allow-center","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-set-size","core:window:allow-start-dragging","core:window:allow-toggle-maximize","decorum:allow-show-snap-overlay","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-set-webview-size","core:webview:allow-set-webview-position","core:webview:allow-webview-close","dialog:allow-open","dialog:allow-ask","dialog:allow-message","process:allow-restart","process:allow-exit","fs:allow-read-file","core:menu:allow-new","core:menu:allow-popup","shell:allow-open","store:default","prevent-default:default","theme:default",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["macOS","windows"]}}

View File

@@ -5569,6 +5569,31 @@
"type": "string",
"const": "store:deny-values"
},
{
"description": "Allow all",
"type": "string",
"const": "theme:default"
},
{
"description": "Enables the get_theme command without any pre-configured scope.",
"type": "string",
"const": "theme:allow-get-theme"
},
{
"description": "Enables the set_theme command without any pre-configured scope.",
"type": "string",
"const": "theme:allow-set-theme"
},
{
"description": "Denies the get_theme command without any pre-configured scope.",
"type": "string",
"const": "theme:deny-get-theme"
},
{
"description": "Denies the set_theme command without any pre-configured scope.",
"type": "string",
"const": "theme:deny-set-theme"
},
{
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n",
"type": "string",

View File

@@ -5569,6 +5569,31 @@
"type": "string",
"const": "store:deny-values"
},
{
"description": "Allow all",
"type": "string",
"const": "theme:default"
},
{
"description": "Enables the get_theme command without any pre-configured scope.",
"type": "string",
"const": "theme:allow-get-theme"
},
{
"description": "Enables the set_theme command without any pre-configured scope.",
"type": "string",
"const": "theme:allow-set-theme"
},
{
"description": "Denies the get_theme command without any pre-configured scope.",
"type": "string",
"const": "theme:deny-get-theme"
},
{
"description": "Denies the set_theme command without any pre-configured scope.",
"type": "string",
"const": "theme:deny-set-theme"
},
{
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n",
"type": "string",

View File

@@ -28,6 +28,9 @@ pub async fn watch_account(key: String, state: State<'_, Nostr>) -> Result<Strin
keyring.set_password("").map_err(|e| e.to_string())?;
// Run sync for this account
// run_sync_for(public_key, app_handle);
// Update state
state.accounts.lock().unwrap().push(bech32.clone());
@@ -41,27 +44,32 @@ pub async fn import_account(
password: String,
state: State<'_, Nostr>,
) -> Result<String, String> {
let (npub, enc_bech32) = match key.starts_with("ncryptsec") {
let client = &state.client;
let (npub, enc_bech32, signer) = match key.starts_with("ncryptsec") {
true => {
let enc = EncryptedSecretKey::from_bech32(key).map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
let secret_key = enc.to_secret_key(password).map_err(|err| err.to_string())?;
let keys = Keys::new(secret_key);
let npub = keys.public_key().to_bech32().unwrap();
let npub = keys
.public_key()
.to_bech32()
.map_err(|err| err.to_string())?;
let signer = NostrSigner::Keys(keys);
(npub, enc_bech32)
(npub, enc_bech32, signer)
}
false => {
let secret_key = SecretKey::from_bech32(key).map_err(|err| err.to_string())?;
let keys = Keys::new(secret_key.clone());
let npub = keys.public_key().to_bech32().unwrap();
let signer = NostrSigner::Keys(keys);
let enc = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium)
.map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
(npub, enc_bech32)
(npub, enc_bech32, signer)
}
};
@@ -75,6 +83,12 @@ pub async fn import_account(
let pwd = serde_json::to_string(&account).map_err(|e| e.to_string())?;
keyring.set_password(&pwd).map_err(|e| e.to_string())?;
// Run sync for this account
// run_sync_for(public_key, app_handle);
// Update signer
client.set_signer(Some(signer)).await;
// Update state
state.accounts.lock().unwrap().push(npub.clone());
@@ -115,6 +129,9 @@ pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result<Str
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
let _ = keyring.set_password(&j);
// Run sync for this account
// run_sync_for(public_key, app_handle);
// Update signer
let _ = client.set_signer(Some(signer.into())).await;

View File

@@ -313,8 +313,14 @@ pub async fn publish(
let builder =
EventBuilder::text_note(content, tags).pow(difficulty.unwrap_or(DEFAULT_DIFFICULTY));
// Sign event
let event = client
.sign_event_builder(builder)
.await
.map_err(|err| err.to_string())?;
// Publish
match client.send_event_builder(builder).await {
match client.send_event(event).await {
Ok(event_id) => Ok(event_id.to_hex()),
Err(err) => Err(err.to_string()),
}

View File

@@ -6,7 +6,7 @@ use std::{str::FromStr, time::Duration};
use tauri::{Emitter, Manager, State};
use crate::{
common::{get_all_accounts, get_latest_event, get_tags_content, process_event},
common::{get_all_accounts, get_latest_event, process_event},
Nostr, RichEvent, Settings,
};
@@ -182,10 +182,8 @@ pub async fn is_contact(id: String, state: State<'_, Nostr>) -> Result<bool, Str
match client.database().query(vec![filter]).await {
Ok(events) => {
if let Some(event) = events.into_iter().next() {
let hex = public_key.to_hex();
let pubkeys = get_tags_content(&event, TagKind::p());
Ok(pubkeys.iter().any(|i| i == &hex))
let pubkeys = event.tags.public_keys().collect::<Vec<_>>();
Ok(pubkeys.iter().any(|&i| i == &public_key))
} else {
Ok(false)
}
@@ -270,7 +268,7 @@ pub async fn set_group(
.authors(public_keys)
.limit(500);
if let Ok(report) = client.reconcile(filter, NegentropyOptions::default()).await {
if let Ok(report) = client.sync(filter, NegentropyOptions::default()).await {
println!("Received: {}", report.received.len());
handle.emit("synchronized", ()).unwrap();
};
@@ -357,7 +355,7 @@ pub async fn set_interest(
.hashtags(hashtags)
.limit(500);
if let Ok(report) = client.reconcile(filter, NegentropyOptions::default()).await {
if let Ok(report) = client.sync(filter, NegentropyOptions::default()).await {
println!("Received: {}", report.received.len());
handle.emit("synchronized", ()).unwrap();
};

View File

@@ -5,7 +5,7 @@ use std::str::FromStr;
use tauri::{AppHandle, Manager};
use tauri_specta::Event as TauriEvent;
use crate::{common::get_tags_content, Nostr};
use crate::Nostr;
#[derive(Clone, Serialize, Type, TauriEvent)]
pub struct NegentropyEvent {
@@ -53,7 +53,7 @@ pub fn run_fast_sync(accounts: Vec<String>, app_handle: AppHandle) {
.limit(4);
if let Ok(report) = client
.reconcile_with(&bootstrap_relays, profile, NegentropyOptions::default())
.sync_with(&bootstrap_relays, profile, NegentropyOptions::default())
.await
{
NegentropyEvent {
@@ -72,7 +72,7 @@ pub fn run_fast_sync(accounts: Vec<String>, app_handle: AppHandle) {
.limit(4);
if let Ok(report) = client
.reconcile_with(
.sync_with(
&bootstrap_relays,
contact_list.clone(),
NegentropyOptions::default(),
@@ -92,16 +92,7 @@ pub fn run_fast_sync(accounts: Vec<String>, app_handle: AppHandle) {
if let Ok(events) = client.database().query(vec![contact_list]).await {
let pubkeys: Vec<PublicKey> = events
.iter()
.flat_map(|ev| {
let tags = get_tags_content(ev, TagKind::p());
tags.into_iter().filter_map(|p| {
if let Ok(pk) = PublicKey::from_hex(p) {
Some(pk)
} else {
None
}
})
})
.flat_map(|ev| ev.tags.public_keys().copied())
.collect();
for chunk in pubkeys.chunks(500) {
@@ -119,7 +110,7 @@ pub fn run_fast_sync(accounts: Vec<String>, app_handle: AppHandle) {
.limit(1000);
if let Ok(report) = client
.reconcile_with(&bootstrap_relays, events, NegentropyOptions::default())
.sync_with(&bootstrap_relays, events, NegentropyOptions::default())
.await
{
NegentropyEvent {
@@ -138,7 +129,7 @@ pub fn run_fast_sync(accounts: Vec<String>, app_handle: AppHandle) {
.limit(1000);
if let Ok(report) = client
.reconcile_with(&bootstrap_relays, metadata, NegentropyOptions::default())
.sync_with(&bootstrap_relays, metadata, NegentropyOptions::default())
.await
{
NegentropyEvent {
@@ -162,7 +153,7 @@ pub fn run_fast_sync(accounts: Vec<String>, app_handle: AppHandle) {
]);
if let Ok(report) = client
.reconcile_with(&bootstrap_relays, others, NegentropyOptions::default())
.sync_with(&bootstrap_relays, others, NegentropyOptions::default())
.await
{
NegentropyEvent {
@@ -186,7 +177,7 @@ pub fn run_fast_sync(accounts: Vec<String>, app_handle: AppHandle) {
.limit(10000);
if let Ok(report) = client
.reconcile_with(
.sync_with(
&bootstrap_relays,
notification,
NegentropyOptions::default(),

View File

@@ -100,9 +100,12 @@ pub fn update_column(
#[tauri::command(async)]
#[specta::specta]
pub fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result<bool, String> {
pub fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result<(), String> {
match app_handle.get_webview(&label) {
Some(webview) => Ok(webview.eval("window.location.reload()").is_ok()),
Some(webview) => {
webview.eval("location.reload(true)").unwrap();
Ok(())
}
None => Err("Cannot reload, column not found.".into()),
}
}

View File

@@ -51,15 +51,6 @@ pub fn get_latest_event(events: &Events) -> Option<&Event> {
events.iter().next()
}
pub fn get_tags_content(event: &Event, kind: TagKind) -> Vec<String> {
event
.tags
.iter()
.filter(|t| t.kind() == kind)
.filter_map(|t| t.content().map(|content| content.to_string()))
.collect()
}
pub fn create_tags(content: &str) -> Vec<Tag> {
let mut tags: Vec<Tag> = vec![];
let mut tag_set: HashSet<String> = HashSet::new();

View File

@@ -13,7 +13,8 @@ use commands::{
sync::{run_fast_sync, NegentropyEvent},
window::*,
};
use common::{get_all_accounts, get_tags_content, parse_event};
use common::{get_all_accounts, parse_event};
use log::info;
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
use serde::{Deserialize, Serialize};
use specta::Type;
@@ -94,9 +95,6 @@ pub const FETCH_LIMIT: usize = 50;
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
fn main() {
#[cfg(debug_assertions)]
tracing_subscriber::fmt::init();
let builder = Builder::<tauri::Wry>::new()
// Then register them (separated by a comma)
.commands(collect_commands![
@@ -171,10 +169,7 @@ fn main() {
.export(Typescript::default(), "../src/commands.gen.ts")
.expect("Failed to export typescript bindings");
#[cfg(target_os = "macos")]
let tauri_builder = tauri::Builder::default().plugin(tauri_nspanel::init());
#[cfg(not(target_os = "macos"))]
let mut ctx = tauri::generate_context!();
let tauri_builder = tauri::Builder::default();
tauri_builder
@@ -193,13 +188,7 @@ fn main() {
.app_config_dir()
.expect("Error: app config directory not found.");
let data_dir = handle
.path()
.app_data_dir()
.expect("Error: app data directory not found.");
let _ = fs::create_dir_all(&config_dir);
let _ = fs::create_dir_all(&data_dir);
// Set custom decoration for Windows
#[cfg(target_os = "windows")]
@@ -225,7 +214,7 @@ fn main() {
let (client, bootstrap_relays) = tauri::async_runtime::block_on(async move {
// Setup database
let database = NostrLMDB::open(config_dir.join("nostr-lmdb"))
let database = NostrLMDB::open(config_dir.join("nostr"))
.expect("Error: cannot create database.");
// Config
@@ -234,7 +223,8 @@ fn main() {
.max_avg_latency(Duration::from_millis(800))
.automatic_authentication(false)
.connection_timeout(Some(Duration::from_secs(20)))
.send_timeout(Some(Duration::from_secs(20)))
.send_timeout(Some(Duration::from_secs(10)))
.wait_for_send(false)
.timeout(Duration::from_secs(20));
// Setup nostr client
@@ -403,21 +393,6 @@ fn main() {
});
});
// Run local relay thread
//tauri::async_runtime::spawn(async move {
// let database = NostrLMDB::open(data_dir.join("local-relay"))
// .expect("Error: cannot create database.");
// let builder = RelayBuilder::default().database(database).port(1984);
//
// if let Ok(relay) = LocalRelay::run(builder).await {
// println!("Running local relay: {}", relay.url())
// }
//
// loop {
// tokio::time::sleep(Duration::from_secs(60)).await;
// }
//});
// Run notification thread
tauri::async_runtime::spawn(async move {
let state = handle_clone.state::<Nostr>();
@@ -465,6 +440,7 @@ fn main() {
while let Ok(notification) = notifications.recv().await {
match notification {
RelayPoolNotification::Message { relay_url, message } => {
info!(target: "relay_events", "message: {}", message.as_pretty_json());
if let RelayMessage::Auth { challenge } = message {
match client.auth(challenge, relay_url.clone()).await {
Ok(..) => {
@@ -521,7 +497,17 @@ fn main() {
event,
} = message
{
let tags = get_tags_content(&event, TagKind::p());
let tags: Vec<String> = event
.tags
.public_keys()
.filter_map(|pk| {
if let Ok(bech32) = pk.to_bech32() {
Some(bech32)
} else {
None
}
})
.collect();
// Handle events from notification subscription
if subscription_id == notification_id
@@ -587,6 +573,7 @@ fn main() {
Ok(())
})
.plugin(prevent_default())
.plugin(tauri_plugin_theme::init(ctx.config_mut()))
.plugin(tauri_plugin_decorum::init())
.plugin(tauri_plugin_store::Builder::default().build())
.plugin(tauri_plugin_clipboard_manager::init())
@@ -600,7 +587,7 @@ fn main() {
.plugin(tauri_plugin_upload::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_window_state::Builder::default().build())
.run(tauri::generate_context!())
.run(ctx)
.expect("error while running tauri application");
}

View File

@@ -464,7 +464,7 @@ async updateColumn(label: string, width: number, height: number, x: number, y: n
else return { status: "error", error: e as any };
}
},
async reloadColumn(label: string) : Promise<Result<boolean, string>> {
async reloadColumn(label: string) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("reload_column", { label }) };
} catch (e) {

View File

@@ -74,6 +74,7 @@ export function Column({ column }: { column: LumeColumn }) {
<div className="flex flex-col gap-px size-full">
<Header
label={column.label}
webviewLabel={webviewLabel}
name={column.name}
account={column.account}
/>
@@ -85,9 +86,10 @@ export function Column({ column }: { column: LumeColumn }) {
function Header({
label,
webviewLabel,
name,
account,
}: { label: string; name: string; account?: string }) {
}: { label: string; webviewLabel: string; name: string; account?: string }) {
const [title, setTitle] = useState("");
const [isChanged, setIsChanged] = useState(false);
@@ -100,7 +102,7 @@ function Header({
MenuItem.new({
text: "Reload",
action: async () => {
await commands.reloadColumn(label);
await commands.reloadColumn(webviewLabel);
},
}),
PredefinedMenuItem.new({ item: "Separator" }),

View File

@@ -137,6 +137,13 @@ function Account({ pubkey }: { pubkey: string }) {
e.preventDefault();
const items = await Promise.all([
MenuItem.new({
text: "Unlock",
enabled: !isActive || true,
action: () =>
LumeWindow.openPopup(`/set-signer/${pubkey}`, undefined, false),
}),
PredefinedMenuItem.new({ item: "Separator" }),
MenuItem.new({
text: "View Profile",
action: () => LumeWindow.openProfile(pubkey),

View File

@@ -26,6 +26,7 @@ export const Route = createLazyFileRoute("/_layout/")({
});
function Screen() {
const { accounts } = Route.useRouteContext();
const columns = useStore(appColumns, (state) => state);
const [emblaRef, emblaApi] = useEmblaCarousel({
@@ -161,7 +162,10 @@ function Screen() {
getSystemColumns();
} else {
const parsed: LumeColumn[] = JSON.parse(prevColumns);
appColumns.setState(() => parsed);
const fil = parsed.filter((item) =>
item.account ? accounts.includes(item.account) : item,
);
appColumns.setState(() => fil);
}
} else {
window.localStorage.setItem("columns", JSON.stringify(columns));

View File

@@ -197,6 +197,10 @@ function Screen() {
};
const submit = async () => {
if (!text.length) {
return;
}
if (currentUser) {
const signer = await commands.hasSigner(currentUser);

View File

@@ -174,8 +174,8 @@ export const LumeWindow = {
label: `popup-${nanoid()}`,
url,
title: title ?? "",
width: 400,
height: 500,
width: 360,
height: 460,
maximizable: false,
minimizable: false,
hidden_title: !!title,