From a1e0934fc3e0be2a99673870119d4881643b5816 Mon Sep 17 00:00:00 2001 From: reya <123083837+reyamir@users.noreply.github.com> Date: Sat, 18 Oct 2025 09:46:45 +0700 Subject: [PATCH] chore: clean up codebase (#186) * refactor app state * clean up * clean up * . --- Cargo.lock | 194 ++--- crates/auto_update/Cargo.toml | 2 +- crates/auto_update/src/lib.rs | 2 +- crates/client_keys/Cargo.toml | 2 +- crates/client_keys/src/lib.rs | 14 +- crates/common/Cargo.toml | 2 +- crates/common/src/display.rs | 2 +- crates/coop/Cargo.toml | 3 +- crates/coop/src/chatspace.rs | 349 +++------ crates/coop/src/main.rs | 7 +- crates/coop/src/views/account.rs | 74 +- crates/coop/src/views/chat/mod.rs | 14 +- crates/coop/src/views/compose.rs | 14 +- crates/coop/src/views/edit_profile.rs | 10 +- crates/coop/src/views/login.rs | 10 +- crates/coop/src/views/new_account.rs | 10 +- crates/coop/src/views/onboarding.rs | 35 +- crates/coop/src/views/screening.rs | 10 +- crates/coop/src/views/setup_relay.rs | 21 +- crates/coop/src/views/sidebar/mod.rs | 15 +- crates/coop/src/views/user_profile.rs | 4 +- crates/registry/Cargo.toml | 5 +- crates/registry/src/lib.rs | 84 ++- crates/registry/src/room.rs | 24 +- crates/settings/Cargo.toml | 2 +- crates/settings/src/lib.rs | 8 +- crates/signer_proxy/Cargo.toml | 25 - crates/signer_proxy/index.html | 35 - crates/signer_proxy/proxy.js | 151 ---- crates/signer_proxy/src/error.rs | 73 -- crates/signer_proxy/src/lib.rs | 678 ------------------ crates/signer_proxy/style.css | 30 - crates/{app_state => states}/Cargo.toml | 2 +- crates/{app_state => states}/src/constants.rs | 0 crates/{app_state => states}/src/lib.rs | 32 +- crates/{app_state => states}/src/paths.rs | 0 .../src/state/mod.rs => states/src/state.rs} | 289 +++++--- 37 files changed, 516 insertions(+), 1716 deletions(-) delete mode 100644 crates/signer_proxy/Cargo.toml delete mode 100644 crates/signer_proxy/index.html delete mode 100644 crates/signer_proxy/proxy.js delete mode 100644 crates/signer_proxy/src/error.rs delete mode 100644 crates/signer_proxy/src/lib.rs delete mode 100644 crates/signer_proxy/style.css rename crates/{app_state => states}/Cargo.toml (94%) rename crates/{app_state => states}/src/constants.rs (100%) rename crates/{app_state => states}/src/lib.rs (57%) rename crates/{app_state => states}/src/paths.rs (100%) rename crates/{app_state/src/state/mod.rs => states/src/state.rs} (72%) diff --git a/Cargo.lock b/Cargo.lock index fb090e8..04c95e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,21 +82,6 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" -[[package]] -name = "app_state" -version = "0.2.11" -dependencies = [ - "anyhow", - "dirs 5.0.1", - "flume", - "log", - "nostr-lmdb", - "nostr-sdk", - "rustls", - "smol", - "whoami", -] - [[package]] name = "arbitrary" version = "1.4.2" @@ -507,7 +492,6 @@ name = "auto_update" version = "0.2.11" dependencies = [ "anyhow", - "app_state", "cargo-packager-updater", "common", "gpui", @@ -515,6 +499,7 @@ dependencies = [ "nostr-sdk", "smallvec", "smol", + "states", ] [[package]] @@ -1094,11 +1079,11 @@ name = "client_keys" version = "0.2.11" dependencies = [ "anyhow", - "app_state", "gpui", "log", "nostr-sdk", "smallvec", + "states", ] [[package]] @@ -1128,14 +1113,14 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.26.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" dependencies = [ "bitflags 2.9.4", "block", - "cocoa-foundation 0.2.1", - "core-foundation 0.10.1", + "cocoa-foundation 0.2.0", + "core-foundation 0.10.0", "core-graphics 0.24.0", "foreign-types 0.5.0", "libc", @@ -1158,14 +1143,15 @@ dependencies = [ [[package]] name = "cocoa-foundation" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" dependencies = [ "bitflags 2.9.4", "block", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-graphics-types 0.2.0", + "libc", "objc", ] @@ -1183,11 +1169,10 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "indexmap", "rustc-hash 2.1.1", - "workspace-hack", ] [[package]] @@ -1221,7 +1206,6 @@ name = "common" version = "0.2.11" dependencies = [ "anyhow", - "app_state", "chrono", "futures", "gpui", @@ -1234,6 +1218,7 @@ dependencies = [ "reqwest", "smallvec", "smol", + "states", "webbrowser", ] @@ -1295,7 +1280,6 @@ name = "coop" version = "0.2.11" dependencies = [ "anyhow", - "app_state", "assets", "auto_update", "client_keys", @@ -1319,9 +1303,9 @@ dependencies = [ "serde", "serde_json", "settings", - "signer_proxy", "smallvec", "smol", + "states", "theme", "title_bar", "tracing-subscriber", @@ -1341,9 +1325,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", @@ -1375,7 +1359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.9.4", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", @@ -1412,7 +1396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.9.4", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "libc", ] @@ -1425,7 +1409,7 @@ dependencies = [ "bitflags 2.9.4", "block", "cfg-if", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "libc", ] @@ -1435,7 +1419,7 @@ version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" dependencies = [ - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-graphics 0.24.0", "foreign-types 0.5.0", "libc", @@ -1448,7 +1432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d45e71d5be22206bed53c3c3cb99315fc4c3d31b8963808c6bc4538168c4f8ef" dependencies = [ "block", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-graphics2", "io-surface", "libc", @@ -1625,12 +1609,11 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", - "workspace-hack", ] [[package]] @@ -2003,9 +1986,9 @@ dependencies = [ [[package]] name = "file-locker" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c3e69656680c6c3d76750b46dfa64bf07626bd2130c540d6cf2d306ba595a8" +checksum = "75ae8b5984a4863d8a32109a848d038bd6d914f20f010cc141375f7a183c41cf" dependencies = [ "nix 0.29.0", ] @@ -2523,7 +2506,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.1" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2538,9 +2521,10 @@ dependencies = [ "calloop", "calloop-wayland-source", "cbindgen", - "cocoa 0.26.1", + "cocoa 0.26.0", + "cocoa-foundation 0.2.0", "collections", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-foundation-sys", "core-graphics 0.24.0", "core-text", @@ -2572,6 +2556,7 @@ dependencies = [ "parking", "parking_lot", "pathfinder_geometry", + "pin-project", "postage", "profiling", "rand 0.9.2", @@ -2605,7 +2590,6 @@ dependencies = [ "windows-core 0.61.2", "windows-numerics", "windows-registry 0.5.3", - "workspace-hack", "x11-clipboard", "x11rb", "xkbcommon", @@ -2617,25 +2601,23 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", - "workspace-hack", ] [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "anyhow", "gpui", "tokio", "util", - "workspace-hack", ] [[package]] @@ -2859,7 +2841,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "anyhow", "async-compression", @@ -2878,18 +2860,16 @@ dependencies = [ "tempfile", "url", "util", - "workspace-hack", "zed-reqwest", ] [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "rustls", "rustls-platform-verifier", - "workspace-hack", ] [[package]] @@ -2898,12 +2878,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.7.0" @@ -2918,7 +2892,6 @@ dependencies = [ "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -3188,9 +3161,9 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", @@ -3256,7 +3229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" dependencies = [ "cgl", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-foundation-sys", "leaky-cow", ] @@ -3687,17 +3660,16 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "anyhow", "bindgen 0.71.1", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-video", "ctor 0.4.3", "foreign-types 0.5.0", "metal", "objc", - "workspace-hack", ] [[package]] @@ -3794,13 +3766,13 @@ checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "wasi", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4521,12 +4493,11 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "collections", "serde", "serde_json", - "workspace-hack", ] [[package]] @@ -5138,10 +5109,9 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "derive_refineable", - "workspace-hack", ] [[package]] @@ -5178,17 +5148,20 @@ name = "registry" version = "0.2.11" dependencies = [ "anyhow", - "app_state", "common", + "flume", "fuzzy-matcher", "gpui", "itertools 0.13.0", "log", "nostr", + "nostr-lmdb", "nostr-sdk", + "rustls", "settings", "smallvec", "smol", + "states", ] [[package]] @@ -5243,7 +5216,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "anyhow", "bytes", @@ -5254,7 +5227,6 @@ dependencies = [ "regex", "serde", "tokio", - "workspace-hack", "zed-reqwest", ] @@ -5298,7 +5270,7 @@ dependencies = [ [[package]] name = "rope" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "arrayvec", "log", @@ -5307,7 +5279,6 @@ dependencies = [ "sum_tree", "unicode-segmentation", "util", - "workspace-hack", ] [[package]] @@ -5464,9 +5435,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" dependencies = [ "aws-lc-rs", "log", @@ -5515,7 +5486,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-foundation-sys", "jni", "log", @@ -5741,7 +5712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags 2.9.4", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -5766,11 +5737,10 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "anyhow", "serde", - "workspace-hack", ] [[package]] @@ -5915,7 +5885,6 @@ name = "settings" version = "0.2.11" dependencies = [ "anyhow", - "app_state", "gpui", "log", "nostr-sdk", @@ -5923,6 +5892,7 @@ dependencies = [ "serde", "serde_json", "smallvec", + "states", ] [[package]] @@ -5977,28 +5947,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signer_proxy" -version = "0.2.11" -dependencies = [ - "anyhow", - "app_state", - "atomic-destructor", - "bytes", - "futures", - "http-body-util", - "hyper", - "hyper-util", - "log", - "nostr", - "oneshot", - "serde", - "serde_json", - "smallvec", - "smol", - "uuid", -] - [[package]] name = "simd-adler32" version = "0.3.7" @@ -6151,6 +6099,21 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "states" +version = "0.2.11" +dependencies = [ + "anyhow", + "dirs 5.0.1", + "flume", + "log", + "nostr-lmdb", + "nostr-sdk", + "rustls", + "smol", + "whoami", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -6218,12 +6181,11 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "arrayvec", "log", "rayon", - "workspace-hack", ] [[package]] @@ -7255,7 +7217,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "anyhow", "async-fs", @@ -7285,18 +7247,16 @@ dependencies = [ "unicase", "walkdir", "which", - "workspace-hack", ] [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8" +source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2" dependencies = [ "perf", "quote", "syn 2.0.106", - "workspace-hack", ] [[package]] @@ -7657,7 +7617,7 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" dependencies = [ - "core-foundation 0.10.1", + "core-foundation 0.10.0", "jni", "log", "ndk-context", @@ -8345,12 +8305,6 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -[[package]] -name = "workspace-hack" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beffa227304dbaea3ad6a06ac674f9bc83a3dec3b7f63eeb442de37e7cb6bb01" - [[package]] name = "writeable" version = "0.6.1" @@ -8583,7 +8537,7 @@ source = "git+https://github.com/zed-industries/font-kit?rev=110523127440aefb11c dependencies = [ "bitflags 2.9.4", "byteorder", - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-graphics 0.24.0", "core-text", "dirs 5.0.1", diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index dee274f..1c3fad9 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -6,7 +6,7 @@ publish.workspace = true [dependencies] common = { path = "../common" } -app_state = { path = "../app_state" } +states = { path = "../states" } gpui.workspace = true nostr-sdk.workspace = true diff --git a/crates/auto_update/src/lib.rs b/crates/auto_update/src/lib.rs index 2151c41..0dbe551 100644 --- a/crates/auto_update/src/lib.rs +++ b/crates/auto_update/src/lib.rs @@ -1,10 +1,10 @@ use anyhow::Error; -use app_state::constants::{APP_PUBKEY, APP_UPDATER_ENDPOINT}; use cargo_packager_updater::semver::Version; use cargo_packager_updater::{check_update, Config, Update}; use gpui::http_client::Url; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window}; use smallvec::{smallvec, SmallVec}; +use states::constants::{APP_PUBKEY, APP_UPDATER_ENDPOINT}; pub fn init(cx: &mut App) { AutoUpdater::set_global(cx.new(AutoUpdater::new), cx); diff --git a/crates/client_keys/Cargo.toml b/crates/client_keys/Cargo.toml index 8dfb3d6..dafecba 100644 --- a/crates/client_keys/Cargo.toml +++ b/crates/client_keys/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true publish.workspace = true [dependencies] -app_state = { path = "../app_state" } +states = { path = "../states" } nostr-sdk.workspace = true gpui.workspace = true diff --git a/crates/client_keys/src/lib.rs b/crates/client_keys/src/lib.rs index 3f12f88..5428fe3 100644 --- a/crates/client_keys/src/lib.rs +++ b/crates/client_keys/src/lib.rs @@ -1,10 +1,8 @@ -use std::sync::atomic::Ordering; - -use app_state::app_state; -use app_state::constants::KEYRING_URL; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Window}; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; +use states::constants::KEYRING_URL; +use states::paths::config_dir; pub fn init(cx: &mut App) { ClientKeys::set_global(cx.new(ClientKeys::new), cx); @@ -61,7 +59,6 @@ impl ClientKeys { return; } - let app_state = app_state(); let read_client_keys = cx.read_credentials(KEYRING_URL); cx.spawn_in(window, async move |this, cx| { @@ -76,7 +73,7 @@ impl ClientKeys { this.set_keys(Some(keys), false, true, cx); }) .ok(); - } else if app_state.is_first_run.load(Ordering::Acquire) { + } else if Self::first_run() { // If this is the first run, generate new keys and use them for the client keys this.update(cx, |this, cx| { this.new_keys(cx); @@ -139,4 +136,9 @@ impl ClientKeys { pub fn has_keys(&self) -> bool { self.keys.is_some() } + + fn first_run() -> bool { + let flag = config_dir().join(".first_run"); + !flag.exists() && std::fs::write(&flag, "").is_ok() + } } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 422f860..4023c61 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true publish.workspace = true [dependencies] -app_state = { path = "../app_state" } +states = { path = "../states" } gpui.workspace = true nostr-connect.workspace = true diff --git a/crates/common/src/display.rs b/crates/common/src/display.rs index a129c56..534ba7c 100644 --- a/crates/common/src/display.rs +++ b/crates/common/src/display.rs @@ -1,12 +1,12 @@ use std::sync::Arc; use anyhow::{anyhow, Error}; -use app_state::constants::IMAGE_RESIZE_SERVICE; use chrono::{Local, TimeZone}; use gpui::{Image, ImageFormat, SharedString, SharedUri}; use nostr_sdk::prelude::*; use qrcode::render::svg; use qrcode::QrCode; +use states::constants::IMAGE_RESIZE_SERVICE; const NOW: &str = "now"; const SECONDS_IN_MINUTE: i64 = 60; diff --git a/crates/coop/Cargo.toml b/crates/coop/Cargo.toml index f7d2d89..be90563 100644 --- a/crates/coop/Cargo.toml +++ b/crates/coop/Cargo.toml @@ -32,12 +32,11 @@ ui = { path = "../ui" } title_bar = { path = "../title_bar" } theme = { path = "../theme" } common = { path = "../common" } -app_state = { path = "../app_state" } +states = { path = "../states" } registry = { path = "../registry" } settings = { path = "../settings" } client_keys = { path = "../client_keys" } auto_update = { path = "../auto_update" } -signer_proxy = { path = "../signer_proxy" } rust-i18n.workspace = true i18n.workspace = true diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index d142db0..cb68e08 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -1,13 +1,8 @@ use std::borrow::Cow; use std::collections::{HashMap, HashSet}; -use std::sync::atomic::Ordering; use std::sync::Arc; -use std::time::Duration; use anyhow::{anyhow, Error}; -use app_state::constants::{ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH}; -use app_state::state::{AuthRequest, SignalKind, UnwrappingStatus}; -use app_state::{app_state, default_nip17_relays, default_nip65_relays, nostr_client}; use auto_update::AutoUpdater; use client_keys::ClientKeys; use common::display::RenderedProfile; @@ -24,8 +19,10 @@ use nostr_connect::prelude::*; use nostr_sdk::prelude::*; use registry::{Registry, RegistryEvent}; use settings::AppSettings; -use signer_proxy::{BrowserSignerProxy, BrowserSignerProxyOptions}; use smallvec::{smallvec, SmallVec}; +use states::constants::{ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH}; +use states::state::{AuthRequest, SignalKind, UnwrappingStatus}; +use states::{app_state, default_nip17_relays, default_nip65_relays}; use theme::{ActiveTheme, Theme, ThemeMode}; use title_bar::TitleBar; use ui::actions::{CopyPublicKey, OpenPublicKey}; @@ -121,21 +118,15 @@ impl ChatSpace { let status = status.read(cx); let all_panels = this.get_all_panel_ids(cx); - match status { - UnwrappingStatus::Processing => { - registry.update(cx, |this, cx| { - this.load_rooms(window, cx); - this.refresh_rooms(all_panels, cx); - }); - } - UnwrappingStatus::Complete => { - registry.update(cx, |this, cx| { - this.load_rooms(window, cx); - this.refresh_rooms(all_panels, cx); - }); - } - _ => {} - }; + if matches!( + status, + UnwrappingStatus::Processing | UnwrappingStatus::Complete + ) { + registry.update(cx, |this, cx| { + this.load_rooms(window, cx); + this.refresh_rooms(all_panels, cx); + }); + } }), ); @@ -184,14 +175,14 @@ impl ChatSpace { // Wait for the signer to be set // Also verify NIP-65 and NIP-17 relays after the signer is set cx.background_spawn(async move { - Self::observe_signer().await; + app_state().observe_signer().await; }), ); tasks.push( // Observe gift wrap process in the background cx.background_spawn(async move { - Self::observe_giftwrap().await; + app_state().observe_giftwrap().await; }), ); @@ -213,90 +204,19 @@ impl ChatSpace { } } - async fn observe_signer() { - let client = nostr_client(); - let app_state = app_state(); - let loop_duration = Duration::from_millis(800); - - loop { - if let Ok(signer) = client.signer().await { - if let Ok(pk) = signer.get_public_key().await { - // Notify the app that the signer has been set - app_state.signal.send(SignalKind::SignerSet(pk)).await; - - // Get user's gossip relays - app_state.get_nip65(pk).await.ok(); - - // Exit the current loop - break; - } - } - - smol::Timer::after(loop_duration).await; - } - } - - async fn observe_giftwrap() { - let client = nostr_client(); - let app_state = app_state(); - let loop_duration = Duration::from_secs(20); - let mut is_start_processing = false; - let mut total_loops = 0; - - loop { - if client.has_signer().await { - total_loops += 1; - - if app_state.gift_wrap_processing.load(Ordering::Acquire) { - is_start_processing = true; - - // Reset gift wrap processing flag - let _ = app_state.gift_wrap_processing.compare_exchange( - true, - false, - Ordering::Release, - Ordering::Relaxed, - ); - - let signal = SignalKind::GiftWrapStatus(UnwrappingStatus::Processing); - app_state.signal.send(signal).await; - } else { - // Only run further if we are already processing - // Wait until after 2 loops to prevent exiting early while events are still being processed - if is_start_processing && total_loops >= 2 { - let signal = SignalKind::GiftWrapStatus(UnwrappingStatus::Complete); - app_state.signal.send(signal).await; - - // Reset the counter - is_start_processing = false; - total_loops = 0; - } - } - } - - smol::Timer::after(loop_duration).await; - } - } - async fn handle_signals(view: WeakEntity, cx: &mut AsyncWindowContext) { - let app_state = app_state(); - let mut is_open_proxy_modal = false; + let states = app_state(); - while let Ok(signal) = app_state.signal.receiver().recv_async().await { - cx.update(|window, cx| { + while let Ok(signal) = states.signal().receiver().recv_async().await { + view.update_in(cx, |this, window, cx| { let registry = Registry::global(cx); let settings = AppSettings::global(cx); match signal { SignalKind::SignerSet(public_key) => { + // Close the latest modal if it exists window.close_modal(cx); - // Setup the default layout for current workspace - view.update(cx, |this, cx| { - this.set_default_layout(window, cx); - }) - .ok(); - // Load user's settings settings.update(cx, |this, cx| { this.load_settings(cx); @@ -307,45 +227,33 @@ impl ChatSpace { this.set_signer_pubkey(public_key, cx); this.load_rooms(window, cx); }); + + // Setup the default layout for current workspace + this.set_default_layout(window, cx); } SignalKind::SignerUnset => { - // Setup the onboarding layout for current workspace - view.update(cx, |this, cx| { - this.set_onboarding_layout(window, cx); - }) - .ok(); - // Clear all current chat rooms registry.update(cx, |this, cx| { this.reset(cx); }); + + // Setup the onboarding layout for current workspace + this.set_onboarding_layout(window, cx); } SignalKind::Auth(req) => { let url = &req.url; let auto_auth = AppSettings::get_auto_auth(cx); let is_authenticated = AppSettings::read_global(cx).is_authenticated(url); - view.update(cx, |this, cx| { - this.push_auth_request(&req, cx); + // Store the auth request in the current view + this.push_auth_request(&req, cx); - if auto_auth && is_authenticated { - // Automatically authenticate if the relay is authenticated before - this.auth(req, window, cx); - } else { - // Otherwise open the auth request popup - this.open_auth_request(req, window, cx); - } - }) - .ok(); - } - SignalKind::ProxyDown => { - if !is_open_proxy_modal { - is_open_proxy_modal = true; - - view.update(cx, |this, cx| { - this.render_proxy_modal(window, cx); - }) - .ok(); + if auto_auth && is_authenticated { + // Automatically authenticate if the relay is authenticated before + this.auth(req, window, cx); + } else { + // Otherwise open the auth request popup + this.open_auth_request(req, window, cx); } } SignalKind::GiftWrapStatus(status) => { @@ -364,17 +272,11 @@ impl ChatSpace { }); } SignalKind::GossipRelaysNotFound => { - view.update(cx, |this, cx| { - this.set_required_gossip_relays(cx); - this.render_setup_gossip_relays_modal(window, cx); - }) - .ok(); + this.set_required_gossip_relays(cx); + this.render_setup_gossip_relays_modal(window, cx); } SignalKind::MessagingRelaysNotFound => { - view.update(cx, |this, cx| { - this.set_required_dm_relays(cx); - }) - .ok(); + this.set_required_dm_relays(cx); } }; }) @@ -395,8 +297,8 @@ impl ChatSpace { self.sending_auth_request(&challenge, cx); let task: Task> = cx.background_spawn(async move { - let client = nostr_client(); - let app_state = app_state(); + let states = app_state(); + let client = states.client(); let signer = client.signer().await?; // Construct event @@ -427,9 +329,9 @@ impl ChatSpace { relay.resubscribe().await?; // Get all failed events that need to be resent - let mut event_tracker = app_state.event_tracker.write().await; + let mut tracker = states.tracker().write().await; - let ids: Vec = event_tracker + let ids: Vec = tracker .resend_queue .iter() .filter(|(_, url)| relay_url == *url) @@ -437,7 +339,7 @@ impl ChatSpace { .collect(); for id in ids.into_iter() { - if let Some(relay_url) = event_tracker.resend_queue.remove(&id) { + if let Some(relay_url) = tracker.resend_queue.remove(&id) { if let Some(event) = client.database().event_by_id(&id).await? { let event_id = relay.send_event(&event).await?; @@ -447,8 +349,8 @@ impl ChatSpace { success: HashSet::from([relay_url]), }; - event_tracker.sent_ids.insert(event_id); - event_tracker.resent_ids.push(output); + tracker.sent_ids.insert(event_id); + tracker.resent_ids.push(output); } } } @@ -656,7 +558,8 @@ impl ChatSpace { fn load_local_account(&mut self, window: &mut Window, cx: &mut Context) { let task = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); + let filter = Filter::new() .kind(Kind::ApplicationSpecificData) .identifier(ACCOUNT_IDENTIFIER) @@ -717,9 +620,10 @@ impl ChatSpace { cx: &mut Context, ) { let task: Task> = cx.background_spawn(async move { - let client = nostr_client(); - let app_state = app_state(); + let states = app_state(); + let client = states.client(); + let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let filter = Filter::new().kind(Kind::PrivateDirectMessage); let pubkeys: Vec = client @@ -737,7 +641,7 @@ impl ChatSpace { .authors(pubkeys); client - .subscribe_to(BOOTSTRAP_RELAYS, filter, app_state.auto_close_opts) + .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) .await?; Ok(()) @@ -756,8 +660,8 @@ impl ChatSpace { fn on_sign_out(&mut self, _e: &Logout, _window: &mut Window, cx: &mut Context) { cx.background_spawn(async move { - let client = nostr_client(); - let app_state = app_state(); + let states = app_state(); + let client = states.client(); let filter = Filter::new() .kind(Kind::ApplicationSpecificData) @@ -770,7 +674,7 @@ impl ChatSpace { client.reset().await; // Notify the channel about the signer being unset - app_state.signal.send(SignalKind::SignerUnset).await; + states.signal().send(SignalKind::SignerUnset).await; }) .detach(); } @@ -799,6 +703,37 @@ impl ChatSpace { window.push_notification(t!("common.copied"), cx); } + fn get_all_panel_ids(&self, cx: &App) -> Option> { + let ids: Vec = self + .dock + .read(cx) + .items + .panel_ids(cx) + .into_iter() + .filter_map(|panel| panel.parse::().ok()) + .collect(); + + Some(ids) + } + + fn set_center_panel

(panel: P, window: &mut Window, cx: &mut App) + where + P: PanelView, + { + if let Some(Some(root)) = window.root::() { + if let Ok(chatspace) = root.read(cx).view().clone().downcast::() { + let panel = Arc::new(panel); + let center = DockItem::panel(panel); + + chatspace.update(cx, |this, cx| { + this.dock.update(cx, |this, cx| { + this.set_center(center, window, cx); + }); + }); + } + } + } + fn render_setup_gossip_relays_modal(&mut self, window: &mut Window, cx: &mut App) { let relays = default_nip65_relays(); @@ -875,9 +810,9 @@ impl ChatSpace { .on_ok(|_, window, cx| { window .spawn(cx, async move |cx| { - let app_state = app_state(); + let states = app_state(); let relays = default_nip65_relays(); - let result = app_state.set_nip65(relays).await; + let result = states.set_nip65(relays).await; cx.update(|window, cx| { match result { @@ -977,9 +912,9 @@ impl ChatSpace { .on_ok(|_, window, cx| { window .spawn(cx, async move |cx| { - let app_state = app_state(); + let states = app_state(); let relays = default_nip17_relays(); - let result = app_state.set_nip17(relays).await; + let result = states.set_nip17(relays).await; cx.update(|window, cx| { match result { @@ -1001,32 +936,6 @@ impl ChatSpace { }) } - fn render_proxy_modal(&mut self, window: &mut Window, cx: &mut App) { - window.open_modal(cx, |this, _window, _cx| { - this.overlay_closable(false) - .show_close(false) - .keyboard(false) - .alert() - .button_props(ModalButtonProps::default().ok_text(t!("common.open_browser"))) - .title(shared_t!("proxy.label")) - .child( - v_flex() - .p_3() - .gap_1() - .w_full() - .items_center() - .justify_center() - .text_center() - .text_sm() - .child(shared_t!("proxy.description")), - ) - .on_ok(move |_e, _window, cx| { - cx.open_url("http://localhost:7400"); - false - }) - }); - } - fn render_client_keys_modal(&mut self, window: &mut Window, cx: &mut Context) { window.open_modal(cx, move |this, _window, cx| { this.overlay_closable(false) @@ -1196,92 +1105,6 @@ impl ChatSpace { }), ) } - - pub(crate) fn proxy_signer(window: &mut Window, cx: &mut App) { - let Some(Some(root)) = window.root::() else { - return; - }; - - let Ok(chatspace) = root.read(cx).view().clone().downcast::() else { - return; - }; - - chatspace.update(cx, |this, cx| { - let proxy = BrowserSignerProxy::new(BrowserSignerProxyOptions::default()); - let url = proxy.url(); - - this._tasks.push(cx.background_spawn(async move { - let client = nostr_client(); - let app_state = app_state(); - - if proxy.start().await.is_ok() { - webbrowser::open(&url).ok(); - - loop { - if proxy.is_session_active() { - // Save the signer to disk for further logins - if let Ok(public_key) = proxy.get_public_key().await { - let keys = Keys::generate(); - let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)]; - let kind = Kind::ApplicationSpecificData; - - let builder = EventBuilder::new(kind, "extension") - .tags(tags) - .build(public_key) - .sign(&keys) - .await; - - if let Ok(event) = builder { - if let Err(e) = client.database().save_event(&event).await { - log::error!("Failed to save event: {e}"); - }; - } - } - - // Set the client's signer with current proxy signer - client.set_signer(proxy.clone()).await; - - break; - } else { - app_state.signal.send(SignalKind::ProxyDown).await; - } - smol::Timer::after(Duration::from_secs(1)).await; - } - } - })); - }); - } - - fn get_all_panel_ids(&self, cx: &App) -> Option> { - let ids: Vec = self - .dock - .read(cx) - .items - .panel_ids(cx) - .into_iter() - .filter_map(|panel| panel.parse::().ok()) - .collect(); - - Some(ids) - } - - pub(crate) fn set_center_panel

(panel: P, window: &mut Window, cx: &mut App) - where - P: PanelView, - { - if let Some(Some(root)) = window.root::() { - if let Ok(chatspace) = root.read(cx).view().clone().downcast::() { - let panel = Arc::new(panel); - let center = DockItem::panel(panel); - - chatspace.update(cx, |this, cx| { - this.dock.update(cx, |this, cx| { - this.set_center(center, window, cx); - }); - }); - } - } - } } impl Render for ChatSpace { diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index 045c539..a19229a 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -1,13 +1,13 @@ use std::sync::Arc; -use app_state::constants::{APP_ID, APP_NAME}; -use app_state::{app_state, nostr_client}; use assets::Assets; use gpui::{ point, px, size, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, }; +use states::app_state; +use states::constants::{APP_ID, APP_NAME}; use ui::Root; use crate::actions::{load_embedded_fonts, quit, Quit}; @@ -22,9 +22,6 @@ fn main() { // Initialize logging tracing_subscriber::fmt::init(); - // Initialize the Nostr client - let _client = nostr_client(); - // Initialize the coop simple storage let _app_state = app_state(); diff --git a/crates/coop/src/views/account.rs b/crates/coop/src/views/account.rs index 1970bfb..7b0ec63 100644 --- a/crates/coop/src/views/account.rs +++ b/crates/coop/src/views/account.rs @@ -1,9 +1,6 @@ use std::time::Duration; use anyhow::Error; -use app_state::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT}; -use app_state::state::SignalKind; -use app_state::{app_state, nostr_client}; use client_keys::ClientKeys; use common::display::RenderedProfile; use gpui::prelude::FluentBuilder; @@ -17,6 +14,9 @@ use i18n::{shared_t, t}; use nostr_connect::prelude::*; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; +use states::app_state; +use states::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT}; +use states::state::SignalKind; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -28,7 +28,6 @@ use ui::popup_menu::PopupMenu; use ui::{h_flex, v_flex, ContextModal, Sizable, StyledExt}; use crate::actions::CoopAuthUrlHandler; -use crate::chatspace::ChatSpace; pub fn init( profile: Profile, @@ -43,7 +42,6 @@ pub struct Account { profile: Profile, stored_secret: String, is_bunker: bool, - is_extension: bool, loading: bool, name: SharedString, @@ -57,8 +55,6 @@ pub struct Account { impl Account { fn new(secret: String, profile: Profile, window: &mut Window, cx: &mut Context) -> Self { let is_bunker = secret.starts_with("bunker://"); - let is_extension = secret.starts_with("extension"); - let mut subscriptions = smallvec![]; subscriptions.push( @@ -74,7 +70,6 @@ impl Account { Self { profile, is_bunker, - is_extension, stored_secret: secret, loading: false, name: "Account".into(), @@ -92,8 +87,6 @@ impl Account { if let Ok(uri) = NostrConnectURI::parse(&self.stored_secret) { self.nostr_connect(uri, window, cx); } - } else if self.is_extension { - self.set_proxy(window, cx); } else if let Ok(enc) = EncryptedSecretKey::from_bech32(&self.stored_secret) { self.keys(enc, window, cx); } else { @@ -115,7 +108,7 @@ impl Account { self._tasks.push( // Handle connection in the background cx.spawn_in(window, async move |this, cx| { - let client = nostr_client(); + let client = app_state().client(); match signer.bunker_uri().await { Ok(_) => { @@ -134,10 +127,6 @@ impl Account { ); } - fn set_proxy(&mut self, window: &mut Window, cx: &mut Context) { - ChatSpace::proxy_signer(window, cx); - } - fn keys(&mut self, enc: EncryptedSecretKey, window: &mut Window, cx: &mut Context) { let pwd_input: Entity = cx.new(|cx| InputState::new(window, cx).masked(true)); let weak_input = pwd_input.downgrade(); @@ -245,7 +234,7 @@ impl Account { }) .ok(); - let client = nostr_client(); + let client = app_state().client(); let keys = Keys::new(secret); // Set the client's signer with the current keys @@ -268,8 +257,8 @@ impl Account { self._tasks.push( // Reset the nostr client in the background cx.background_spawn(async move { - let client = nostr_client(); - let app_state = app_state(); + let states = app_state(); + let client = states.client(); let filter = Filter::new() .kind(Kind::ApplicationSpecificData) @@ -282,7 +271,7 @@ impl Account { client.unset_signer().await; // Notify the channel about the signer being unset - app_state.signal.send(SignalKind::SignerUnset).await; + states.signal().send(SignalKind::SignerUnset).await; }), ); } @@ -392,41 +381,20 @@ impl Render for Account { .child(Avatar::new(avatar).size(rems(1.5))) .child(div().pb_px().font_semibold().child(name)), ) - .child( - div() - .when(self.is_bunker, |this| { - let label = SharedString::from("Nostr Connect"); + .child(div().when(self.is_bunker, |this| { + let label = SharedString::from("Nostr Connect"); - this.child( - div() - .py_0p5() - .px_2() - .text_xs() - .bg(cx.theme().secondary_active) - .text_color( - cx.theme().secondary_foreground, - ) - .rounded_full() - .child(label), - ) - }) - .when(self.is_extension, |this| { - let label = SharedString::from("Extension"); - - this.child( - div() - .py_0p5() - .px_2() - .text_xs() - .bg(cx.theme().secondary_active) - .text_color( - cx.theme().secondary_foreground, - ) - .rounded_full() - .child(label), - ) - }), - ), + this.child( + div() + .py_0p5() + .px_2() + .text_xs() + .bg(cx.theme().secondary_active) + .text_color(cx.theme().secondary_foreground) + .rounded_full() + .child(label), + ) + })), ) }) .active(|this| this.bg(cx.theme().element_active)) diff --git a/crates/coop/src/views/chat/mod.rs b/crates/coop/src/views/chat/mod.rs index 81e2f24..350c163 100644 --- a/crates/coop/src/views/chat/mod.rs +++ b/crates/coop/src/views/chat/mod.rs @@ -1,7 +1,6 @@ use std::collections::{HashMap, HashSet}; use std::time::Duration; -use app_state::{app_state, nostr_client}; use common::display::{RenderedProfile, RenderedTimestamp}; use common::nip96::nip96_upload; use gpui::prelude::FluentBuilder; @@ -24,6 +23,7 @@ use serde::Deserialize; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use smol::fs; +use states::app_state; use theme::ActiveTheme; use ui::actions::{CopyPublicKey, OpenPublicKey}; use ui::avatar::Avatar; @@ -169,8 +169,8 @@ impl Chat { let message = Message::user(event); cx.spawn_in(window, async move |this, cx| { - let app_state = app_state(); - let event_tracker = app_state.event_tracker.read().await; + let states = app_state(); + let event_tracker = states.tracker().read().await; let sent_ids = event_tracker.sent_ids(); this.update_in(cx, |this, _window, cx| { @@ -530,7 +530,7 @@ impl Chat { let path = paths.pop()?; let upload = Tokio::spawn(cx, async move { - let client = nostr_client(); + let client = app_state().client(); let file = fs::read(path).await.ok()?; let url = nip96_upload(client, &nip96_server, file).await.ok()?; @@ -1239,9 +1239,9 @@ impl Chat { let id = ev.0; let task: Task, Error>> = cx.background_spawn(async move { - let client = nostr_client(); - let app_state = app_state(); - let event_tracker = app_state.event_tracker.read().await; + let states = app_state(); + let client = states.client(); + let event_tracker = states.tracker().read().await; let mut relays: Vec = vec![]; let filter = Filter::new() diff --git a/crates/coop/src/views/compose.rs b/crates/coop/src/views/compose.rs index d0a55e6..511fd81 100644 --- a/crates/coop/src/views/compose.rs +++ b/crates/coop/src/views/compose.rs @@ -2,8 +2,6 @@ use std::ops::Range; use std::time::Duration; use anyhow::{anyhow, Error}; -use app_state::constants::BOOTSTRAP_RELAYS; -use app_state::{app_state, nostr_client}; use common::display::{RenderedProfile, TextUtils}; use common::nip05::nip05_profile; use gpui::prelude::FluentBuilder; @@ -19,6 +17,8 @@ use registry::room::Room; use registry::Registry; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; +use states::app_state; +use states::constants::BOOTSTRAP_RELAYS; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -129,7 +129,7 @@ impl Compose { let mut tasks = smallvec![]; let get_contacts: Task, Error>> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; let profiles = client.database().contacts(public_key).await?; @@ -195,13 +195,15 @@ impl Compose { } async fn request_metadata(public_key: PublicKey) -> Result<(), Error> { - let client = nostr_client(); - let app_state = app_state(); + let states = app_state(); + let client = states.client(); + + let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList]; let filter = Filter::new().author(public_key).kinds(kinds).limit(10); client - .subscribe_to(BOOTSTRAP_RELAYS, filter, app_state.auto_close_opts) + .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) .await?; Ok(()) diff --git a/crates/coop/src/views/edit_profile.rs b/crates/coop/src/views/edit_profile.rs index 9e5f764..5bef198 100644 --- a/crates/coop/src/views/edit_profile.rs +++ b/crates/coop/src/views/edit_profile.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use std::time::Duration; use anyhow::Error; -use app_state::nostr_client; use common::nip96::nip96_upload; use gpui::prelude::FluentBuilder; use gpui::{ @@ -13,6 +12,7 @@ use i18n::{shared_t, t}; use nostr_sdk::prelude::*; use settings::AppSettings; use smol::fs; +use states::app_state; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::input::{InputState, TextInput}; @@ -58,7 +58,7 @@ impl EditProfile { }; let task: Task, Error>> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; let metadata = client @@ -125,7 +125,9 @@ impl EditProfile { let (tx, rx) = oneshot::channel::(); nostr_sdk::async_utility::task::spawn(async move { - if let Ok(url) = nip96_upload(nostr_client(), &nip96, file_data).await { + if let Ok(url) = + nip96_upload(app_state().client(), &nip96, file_data).await + { _ = tx.send(url); } }); @@ -188,7 +190,7 @@ impl EditProfile { } cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; // Sign the new metadata event diff --git a/crates/coop/src/views/login.rs b/crates/coop/src/views/login.rs index 306a02a..c863e66 100644 --- a/crates/coop/src/views/login.rs +++ b/crates/coop/src/views/login.rs @@ -1,7 +1,5 @@ use std::time::Duration; -use app_state::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT}; -use app_state::nostr_client; use client_keys::ClientKeys; use gpui::prelude::FluentBuilder; use gpui::{ @@ -12,6 +10,8 @@ use gpui::{ use i18n::{shared_t, t}; use nostr_connect::prelude::*; use smallvec::{smallvec, SmallVec}; +use states::app_state; +use states::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT}; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; @@ -248,7 +248,7 @@ impl Login { // Set the client's signer with the current keys cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); client.set_signer(keys).await; }) .detach(); @@ -331,7 +331,7 @@ impl Login { } let task: Task> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); // Update the client's signer client.set_signer(signer).await; @@ -362,7 +362,7 @@ impl Login { if let Ok(enc_key) = EncryptedSecretKey::new(keys.secret_key(), &password, 8, KeySecurity::Unknown) { - let client = nostr_client(); + let client = app_state().client(); let value = enc_key.to_bech32().unwrap(); let keys = Keys::generate(); let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)]; diff --git a/crates/coop/src/views/new_account.rs b/crates/coop/src/views/new_account.rs index a7b1ec9..d845c22 100644 --- a/crates/coop/src/views/new_account.rs +++ b/crates/coop/src/views/new_account.rs @@ -1,6 +1,4 @@ use anyhow::{anyhow, Error}; -use app_state::constants::{ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS}; -use app_state::{default_nip17_relays, default_nip65_relays, nostr_client}; use common::nip96::nip96_upload; use gpui::{ div, relative, rems, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity, @@ -12,6 +10,8 @@ use i18n::{shared_t, t}; use nostr_sdk::prelude::*; use settings::AppSettings; use smol::fs; +use states::constants::{ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS}; +use states::{app_state, default_nip17_relays, default_nip65_relays}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -124,7 +124,7 @@ impl NewAccount { // Set the client's signer with the current keys let task: Task> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); // Set the client's signer with the current keys client.set_signer(keys).await; @@ -176,7 +176,7 @@ impl NewAccount { if let Ok(enc_key) = EncryptedSecretKey::new(keys.secret_key(), &password, 8, KeySecurity::Unknown) { - let client = nostr_client(); + let client = app_state().client(); let value = enc_key.to_bech32().unwrap(); let keys = Keys::generate(); let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)]; @@ -217,7 +217,7 @@ impl NewAccount { Ok(Some(mut paths)) => { if let Some(path) = paths.pop() { let file = fs::read(path).await?; - let url = nip96_upload(nostr_client(), &nip96_server, file).await?; + let url = nip96_upload(app_state().client(), &nip96_server, file).await?; Ok(url) } else { diff --git a/crates/coop/src/views/onboarding.rs b/crates/coop/src/views/onboarding.rs index 3966a73..658b832 100644 --- a/crates/coop/src/views/onboarding.rs +++ b/crates/coop/src/views/onboarding.rs @@ -1,10 +1,6 @@ use std::sync::Arc; use std::time::Duration; -use app_state::constants::{ - ACCOUNT_IDENTIFIER, APP_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT, -}; -use app_state::nostr_client; use client_keys::ClientKeys; use common::display::TextUtils; use gpui::prelude::FluentBuilder; @@ -16,6 +12,8 @@ use gpui::{ use i18n::{shared_t, t}; use nostr_connect::prelude::*; use smallvec::{smallvec, SmallVec}; +use states::app_state; +use states::constants::{ACCOUNT_IDENTIFIER, APP_NAME, NOSTR_CONNECT_RELAY, NOSTR_CONNECT_TIMEOUT}; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; @@ -23,7 +21,7 @@ use ui::notification::Notification; use ui::popup_menu::PopupMenu; use ui::{divider, h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt}; -use crate::chatspace::{self, ChatSpace}; +use crate::chatspace; pub fn init(window: &mut Window, cx: &mut App) -> Entity { Onboarding::new(window, cx) @@ -163,10 +161,6 @@ impl Onboarding { ) } - fn set_proxy(&mut self, window: &mut Window, cx: &mut Context) { - ChatSpace::proxy_signer(window, cx); - } - fn write_uri_to_disk( &mut self, signer: NostrConnect, @@ -181,7 +175,7 @@ impl Onboarding { } let task: Task> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); // Update the client's signer client.set_signer(signer).await; @@ -348,30 +342,11 @@ impl Render for Onboarding { .child( Button::new("key") .label(t!("onboarding.key_login")) + .large() .ghost_alt() .on_click(cx.listener(move |_, _, window, cx| { chatspace::login(window, cx); })), - ) - .child( - v_flex() - .gap_1() - .child( - Button::new("ext") - .label(t!("onboarding.ext_login")) - .ghost_alt() - .on_click(cx.listener(move |this, _, window, cx| { - this.set_proxy(window, cx); - })), - ) - .child( - div() - .italic() - .text_xs() - .text_center() - .text_color(cx.theme().text_muted) - .child(shared_t!("onboarding.ext_login_note")), - ), ), ), ) diff --git a/crates/coop/src/views/screening.rs b/crates/coop/src/views/screening.rs index 1da9d45..e834d82 100644 --- a/crates/coop/src/views/screening.rs +++ b/crates/coop/src/views/screening.rs @@ -1,7 +1,5 @@ use std::time::Duration; -use app_state::constants::BOOTSTRAP_RELAYS; -use app_state::nostr_client; use common::display::{shorten_pubkey, RenderedProfile, RenderedTimestamp}; use common::nip05::nip05_verify; use gpui::prelude::FluentBuilder; @@ -15,6 +13,8 @@ use nostr_sdk::prelude::*; use registry::Registry; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; +use states::app_state; +use states::constants::BOOTSTRAP_RELAYS; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -43,7 +43,7 @@ impl Screening { let contact_check: Task), Error>> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let signer_pubkey = signer.get_public_key().await?; @@ -68,7 +68,7 @@ impl Screening { }); let activity_check = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let filter = Filter::new().author(public_key).limit(1); let mut activity: Option = None; @@ -157,7 +157,7 @@ impl Screening { let public_key = self.profile.public_key(); let task: Task> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let tag = Tag::public_key_report(public_key, Report::Impersonation); diff --git a/crates/coop/src/views/setup_relay.rs b/crates/coop/src/views/setup_relay.rs index 3b5a196..115ef57 100644 --- a/crates/coop/src/views/setup_relay.rs +++ b/crates/coop/src/views/setup_relay.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; use std::time::Duration; use anyhow::{anyhow, Error}; -use app_state::{app_state, nostr_client}; use gpui::prelude::FluentBuilder; use gpui::{ div, px, uniform_list, App, AppContext, AsyncWindowContext, Context, Entity, @@ -10,8 +9,10 @@ use gpui::{ Task, TextAlign, UniformList, Window, }; use i18n::{shared_t, t}; +use itertools::Itertools; use nostr_sdk::prelude::*; use smallvec::{smallvec, SmallVec}; +use states::app_state; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::input::{InputEvent, InputState, TextInput}; @@ -80,7 +81,7 @@ impl SetupRelay { fn load(cx: &AsyncWindowContext) -> Task, Error>> { cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -152,8 +153,8 @@ impl SetupRelay { let relays = self.relays.clone(); let task: Task> = cx.background_spawn(async move { - let app_state = app_state(); - let client = nostr_client(); + let states = app_state(); + let client = states.client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -177,16 +178,10 @@ impl SetupRelay { } // Fetch gift wrap events - let sub_id = app_state.gift_wrap_sub_id.clone(); - let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); - - if client - .subscribe_with_id_to(relays.clone(), sub_id, filter, None) + states + .get_messages(public_key, &relays.into_iter().collect_vec()) .await - .is_ok() - { - log::info!("Subscribed to messages in: {relays:?}"); - }; + .ok(); Ok(()) }); diff --git a/crates/coop/src/views/sidebar/mod.rs b/crates/coop/src/views/sidebar/mod.rs index 45c224c..a1bcf1c 100644 --- a/crates/coop/src/views/sidebar/mod.rs +++ b/crates/coop/src/views/sidebar/mod.rs @@ -3,9 +3,6 @@ use std::ops::Range; use std::time::Duration; use anyhow::{anyhow, Error}; -use app_state::constants::{BOOTSTRAP_RELAYS, SEARCH_RELAYS}; -use app_state::state::UnwrappingStatus; -use app_state::{app_state, nostr_client}; use common::debounced_delay::DebouncedDelay; use common::display::{RenderedTimestamp, TextUtils}; use gpui::prelude::FluentBuilder; @@ -23,6 +20,9 @@ use registry::room::{Room, RoomKind}; use registry::{Registry, RegistryEvent}; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; +use states::app_state; +use states::constants::{BOOTSTRAP_RELAYS, SEARCH_RELAYS}; +use states::state::UnwrappingStatus; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock_area::panel::{Panel, PanelEvent}; @@ -140,7 +140,7 @@ impl Sidebar { } async fn request_metadata(public_key: PublicKey) -> Result<(), Error> { - let client = nostr_client(); + let client = app_state().client(); let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList]; let filter = Filter::new().author(public_key).kinds(kinds).limit(10); @@ -165,7 +165,7 @@ impl Sidebar { } async fn nip50(query: &str) -> Result, Error> { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -530,9 +530,8 @@ impl Sidebar { fn on_manage(&mut self, _ev: &RelayStatus, window: &mut Window, cx: &mut Context) { let task: Task, Error>> = cx.background_spawn(async move { - let client = nostr_client(); - let app_state = app_state(); - let subscription = client.subscription(&app_state.gift_wrap_sub_id).await; + let client = app_state().client(); + let subscription = client.subscription(&SubscriptionId::new("inbox")).await; let mut relays: Vec = vec![]; for (url, _filter) in subscription.into_iter() { diff --git a/crates/coop/src/views/user_profile.rs b/crates/coop/src/views/user_profile.rs index 903625b..a7d3698 100644 --- a/crates/coop/src/views/user_profile.rs +++ b/crates/coop/src/views/user_profile.rs @@ -1,6 +1,5 @@ use std::time::Duration; -use app_state::nostr_client; use common::display::RenderedProfile; use common::nip05::nip05_verify; use gpui::prelude::FluentBuilder; @@ -14,6 +13,7 @@ use nostr_sdk::prelude::*; use registry::Registry; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; +use states::app_state; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -39,7 +39,7 @@ impl UserProfile { let mut tasks = smallvec![]; let check_follow: Task> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; let contact_list = client.database().contacts_public_keys(public_key).await?; diff --git a/crates/registry/Cargo.toml b/crates/registry/Cargo.toml index fe596c9..eeebbd6 100644 --- a/crates/registry/Cargo.toml +++ b/crates/registry/Cargo.toml @@ -6,16 +6,19 @@ publish.workspace = true [dependencies] common = { path = "../common" } -app_state = { path = "../app_state" } +states = { path = "../states" } settings = { path = "../settings" } gpui.workspace = true nostr.workspace = true nostr-sdk.workspace = true +nostr-lmdb.workspace = true anyhow.workspace = true itertools.workspace = true smallvec.workspace = true smol.workspace = true log.workspace = true +flume.workspace = true fuzzy-matcher = "0.3.7" +rustls = "0.23.23" diff --git a/crates/registry/src/lib.rs b/crates/registry/src/lib.rs index 85c2720..cb53f77 100644 --- a/crates/registry/src/lib.rs +++ b/crates/registry/src/lib.rs @@ -2,17 +2,19 @@ use std::cmp::Reverse; use std::collections::{HashMap, HashSet}; use anyhow::Error; -use app_state::nostr_client; -use app_state::state::UnwrappingStatus; use common::event::EventUtils; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; -use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task, WeakEntity, Window}; +use gpui::{ + App, AppContext, AsyncApp, Context, Entity, EventEmitter, Global, Task, WeakEntity, Window, +}; use itertools::Itertools; use nostr_sdk::prelude::*; use room::RoomKind; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; +use states::app_state; +use states::state::UnwrappingStatus; use crate::room::Room; @@ -34,7 +36,7 @@ pub enum RegistryEvent { NewRequest(RoomKind), } -/// Main registry for managing chat rooms and user profiles +#[derive(Debug)] pub struct Registry { /// Collection of all chat rooms pub rooms: Vec>, @@ -45,7 +47,7 @@ pub struct Registry { /// Status of the unwrapping process pub unwrapping_status: Entity, - /// Public key of the currently activated signer + /// Public Key of the currently activated signer signer_pubkey: Option, /// Tasks for asynchronous operations @@ -55,51 +57,40 @@ pub struct Registry { impl EventEmitter for Registry {} impl Registry { - /// Retrieve the Global Registry state + /// Retrieve the global registry state pub fn global(cx: &App) -> Entity { cx.global::().0.clone() } - /// Retrieve the Registry instance + /// Retrieve the registry instance pub fn read_global(cx: &App) -> &Self { cx.global::().0.read(cx) } - /// Set the global Registry instance + /// Set the global registry instance pub(crate) fn set_global(state: Entity, cx: &mut App) { cx.set_global(GlobalRegistry(state)); } - /// Create a new Registry instance + /// Create a new registry instance pub(crate) fn new(cx: &mut Context) -> Self { let unwrapping_status = cx.new(|_| UnwrappingStatus::default()); let mut tasks = smallvec![]; - let load_local_persons: Task, Error>> = - cx.background_spawn(async move { - let client = nostr_client(); - let filter = Filter::new().kind(Kind::Metadata).limit(200); - let events = client.database().query(filter).await?; - let mut profiles = vec![]; - - for event in events.into_iter() { - let metadata = Metadata::from_json(event.content).unwrap_or_default(); - let profile = Profile::new(event.pubkey, metadata); - profiles.push(profile); - } - - Ok(profiles) - }); - tasks.push( - // Load all user profiles from the database when the Registry is created + // Load all user profiles from the database cx.spawn(async move |this, cx| { - if let Ok(profiles) = load_local_persons.await { - this.update(cx, |this, cx| { - this.set_persons(profiles, cx); - }) - .ok(); - } + match Self::load_persons(cx).await { + Ok(profiles) => { + this.update(cx, |this, cx| { + this.set_persons(profiles, cx); + }) + .ok(); + } + Err(e) => { + log::error!("Failed to load persons: {e}"); + } + }; }), ); @@ -112,6 +103,25 @@ impl Registry { } } + /// Create a task to load all user profiles from the database + fn load_persons(cx: &AsyncApp) -> Task, Error>> { + cx.background_spawn(async move { + let client = app_state().client(); + let filter = Filter::new().kind(Kind::Metadata).limit(200); + let events = client.database().query(filter).await?; + + let mut profiles = vec![]; + + for event in events.into_iter() { + let metadata = Metadata::from_json(event.content).unwrap_or_default(); + let profile = Profile::new(event.pubkey, metadata); + profiles.push(profile); + } + + Ok(profiles) + }) + } + /// Returns the public key of the currently activated signer. pub fn signer_pubkey(&self) -> Option { self.signer_pubkey @@ -269,7 +279,7 @@ impl Registry { let bypass_setting = AppSettings::get_contact_bypass(cx); let task: Task, Error>> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; let contacts = client.database().contacts_public_keys(public_key).await?; @@ -339,11 +349,9 @@ impl Registry { cx.spawn_in(window, async move |this, cx| { match task.await { Ok(rooms) => { - this.update_in(cx, move |_, window, cx| { - cx.defer_in(window, move |this, _window, cx| { - this.extend_rooms(rooms, cx); - this.sort(cx); - }); + this.update_in(cx, move |this, _window, cx| { + this.extend_rooms(rooms, cx); + this.sort(cx); }) .ok(); } diff --git a/crates/registry/src/room.rs b/crates/registry/src/room.rs index d099275..4c5c33d 100644 --- a/crates/registry/src/room.rs +++ b/crates/registry/src/room.rs @@ -4,13 +4,13 @@ use std::hash::{Hash, Hasher}; use std::time::Duration; use anyhow::{anyhow, Error}; -use app_state::constants::SEND_RETRY; -use app_state::{app_state, nostr_client}; use common::display::RenderedProfile; use common::event::EventUtils; use gpui::{App, AppContext, Context, EventEmitter, SharedString, SharedUri, Task}; use itertools::Itertools; use nostr_sdk::prelude::*; +use states::app_state; +use states::constants::SEND_RETRY; use crate::Registry; @@ -171,9 +171,9 @@ impl From<&UnsignedEvent> for Room { } impl Room { - /// Constructs a new room instance for a private message with the given receiver and tags. + /// Constructs a new room with the given receiver and tags. pub async fn new(subject: Option, receivers: Vec) -> Result { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -310,7 +310,7 @@ impl Room { let members = self.members.clone(); cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -363,11 +363,11 @@ impl Room { let members = self.members.clone(); cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; let sent_ids: Vec = app_state() - .event_tracker + .tracker() .read() .await .sent_ids() @@ -482,8 +482,8 @@ impl Room { let mut members = self.members.clone(); cx.background_spawn(async move { - let app_state = app_state(); - let client = nostr_client(); + let states = app_state(); + let client = states.client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -514,7 +514,7 @@ impl Room { if auth_required { // Wait for authenticated and resent event successfully for attempt in 0..=SEND_RETRY { - let retry_manager = app_state.event_tracker.read().await; + let retry_manager = states.tracker().read().await; let ids = retry_manager.resent_ids(); // Check if event was successfully resent @@ -579,7 +579,7 @@ impl Room { cx: &App, ) -> Task, Error>> { cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let mut resend_reports = vec![]; for report in reports.into_iter() { @@ -633,7 +633,7 @@ impl Room { /// Gets messaging relays for public key async fn messaging_relays(public_key: PublicKey) -> Vec { - let client = nostr_client(); + let client = app_state().client(); let mut relay_urls = vec![]; let filter = Filter::new() diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index b031cce..47ff570 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true publish.workspace = true [dependencies] -app_state = { path = "../app_state" } +states = { path = "../states" } nostr-sdk.workspace = true gpui.workspace = true diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 7b63b2a..9f8524d 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -1,10 +1,10 @@ use anyhow::anyhow; -use app_state::constants::SETTINGS_IDENTIFIER; -use app_state::nostr_client; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task}; use nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use smallvec::{smallvec, SmallVec}; +use states::app_state; +use states::constants::SETTINGS_IDENTIFIER; pub fn init(cx: &mut App) { let state = cx.new(AppSettings::new); @@ -121,7 +121,7 @@ impl AppSettings { pub fn load_settings(&self, cx: &mut Context) { let task: Task> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -153,7 +153,7 @@ impl AppSettings { pub fn set_settings(&self, cx: &mut Context) { if let Ok(content) = serde_json::to_string(&self.setting_values) { let task: Task> = cx.background_spawn(async move { - let client = nostr_client(); + let client = app_state().client(); let signer = client.signer().await?; let public_key = signer.get_public_key().await?; diff --git a/crates/signer_proxy/Cargo.toml b/crates/signer_proxy/Cargo.toml deleted file mode 100644 index 527e9a2..0000000 --- a/crates/signer_proxy/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "signer_proxy" -version.workspace = true -edition.workspace = true -publish.workspace = true - -[dependencies] -app_state = { path = "../app_state" } - -nostr.workspace = true -smol.workspace = true -oneshot.workspace = true -anyhow.workspace = true -log.workspace = true -futures.workspace = true -smallvec.workspace = true -serde.workspace = true -serde_json.workspace = true - -atomic-destructor = "0.3.0" -uuid = { version = "1.17", features = ["serde", "v4"] } -hyper = { version = "1.6", features = ["server", "http1"] } -hyper-util = { version = "0.1", features = ["server"] } -bytes = "1.10" -http-body-util = "0.1" diff --git a/crates/signer_proxy/index.html b/crates/signer_proxy/index.html deleted file mode 100644 index a5e05d9..0000000 --- a/crates/signer_proxy/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - NIP-07 Proxy - - - -

-

NIP-07 Proxy

-

- This page acts as a proxy between your native application and - the NIP-07 browser extension. -

-
- Status: Checking... -
-

- Keep this tab open while using your application. The page - will automatically poll for requests from your native - app. -

- -

Debug Info

-

- Check the browser console (F12) for detailed logs. -

-
- - - - diff --git a/crates/signer_proxy/proxy.js b/crates/signer_proxy/proxy.js deleted file mode 100644 index 545ed3e..0000000 --- a/crates/signer_proxy/proxy.js +++ /dev/null @@ -1,151 +0,0 @@ -let isPolling = false; - -async function pollForRequests() { - if (isPolling) return; - isPolling = true; - - try { - const response = await fetch("/api/pending"); - const data = await response.json(); - - console.log("Polled for requests, got:", data); - - // Process any new requests - if (data.requests && data.requests.length > 0) { - console.log(`Processing ${data.requests.length} requests`); - for (const request of data.requests) { - await handleNip07Request(request); - } - } - } catch (error) { - console.error("Polling error:", error); - updateStatus("Error: " + error.message, "error"); - } - - isPolling = false; -} - -async function handleNip07Request(request) { - console.log("Handling request:", request); - - try { - let result; - - if (!window.nostr) { - throw new Error("NIP-07 extension not available"); - } - - switch (request.method) { - case "get_public_key": - console.log("Calling nostr.getPublicKey()"); - result = await window.nostr.getPublicKey(); - console.log("Got public key:", result); - break; - - case "sign_event": - console.log("Calling nostr.signEvent() with:", request.params); - result = await window.nostr.signEvent(request.params); - console.log("Got signed event:", result); - break; - - case "nip04_encrypt": - console.log("Calling nostr.nip04.encrypt()"); - result = await window.nostr.nip04.encrypt( - request.params.public_key, - request.params.content, - ); - break; - - case "nip04_decrypt": - console.log("Calling nostr.nip04.decrypt()"); - result = await window.nostr.nip04.decrypt( - request.params.public_key, - request.params.content, - ); - break; - - case "nip44_encrypt": - console.log("Calling nostr.nip44.encrypt()"); - result = await window.nostr.nip44.encrypt( - request.params.public_key, - request.params.content, - ); - break; - - case "nip44_decrypt": - console.log("Calling nostr.nip44.decrypt()"); - result = await window.nostr.nip44.decrypt( - request.params.public_key, - request.params.content, - ); - break; - - default: - throw new Error(`Unknown method: ${request.method}`); - } - - // Send response back to server - const responsePayload = { - id: request.id, - result: result, - error: null, - }; - - console.log("Sending response:", responsePayload); - - await fetch("/api/response", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(responsePayload), - }); - - console.log("Response sent successfully"); - updateStatus("Request processed successfully", "connected"); - } catch (error) { - console.error("Error handling request:", error); - - // Send error response back to server - const errorPayload = { - id: request.id, - result: null, - error: error.message, - }; - - console.log("Sending error response:", errorPayload); - - await fetch("/api/response", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(errorPayload), - }); - - updateStatus("Error: " + error.message, "error"); - } -} - -function updateStatus(message, className) { - const statusEl = document.getElementById("status"); - statusEl.textContent = message; - statusEl.className = className; -} - -// Start polling when page loads -window.addEventListener("load", () => { - console.log("NIP-07 Proxy loaded"); - - // Check if NIP-07 extension is available - if (window.nostr) { - console.log("NIP-07 extension detected"); - updateStatus("Connected to NIP-07 extension - Ready", "connected"); - } else { - console.log("NIP-07 extension not found"); - updateStatus("NIP-07 extension not found", "error"); - } - - // Start polling every 500 ms - setInterval(pollForRequests, 500); -}); diff --git a/crates/signer_proxy/src/error.rs b/crates/signer_proxy/src/error.rs deleted file mode 100644 index bb31119..0000000 --- a/crates/signer_proxy/src/error.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::{fmt, io}; - -use hyper::http; -use nostr::event; -use oneshot::RecvError; - -/// Error -#[derive(Debug)] -pub enum Error { - /// I/O error - Io(io::Error), - /// HTTP error - Http(http::Error), - /// Json error - Json(serde_json::Error), - /// Event error - Event(event::Error), - /// Oneshot channel receive error - OneShotRecv(RecvError), - /// Generic error - Generic(String), - /// Timeout - Timeout, - /// The server is shutdown - Shutdown, -} - -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(e) => write!(f, "{e}"), - Self::Http(e) => write!(f, "{e}"), - Self::Json(e) => write!(f, "{e}"), - Self::Event(e) => write!(f, "{e}"), - Self::OneShotRecv(e) => write!(f, "{e}"), - Self::Generic(e) => write!(f, "{e}"), - Self::Timeout => write!(f, "timeout"), - Self::Shutdown => write!(f, "server is shutdown"), - } - } -} - -impl From for Error { - fn from(e: io::Error) -> Self { - Self::Io(e) - } -} - -impl From for Error { - fn from(e: http::Error) -> Self { - Self::Http(e) - } -} - -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Self::Json(e) - } -} - -impl From for Error { - fn from(e: event::Error) -> Self { - Self::Event(e) - } -} - -impl From for Error { - fn from(e: RecvError) -> Self { - Self::OneShotRecv(e) - } -} diff --git a/crates/signer_proxy/src/lib.rs b/crates/signer_proxy/src/lib.rs deleted file mode 100644 index ebe3fa8..0000000 --- a/crates/signer_proxy/src/lib.rs +++ /dev/null @@ -1,678 +0,0 @@ -use std::collections::HashMap; -use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener}; -use std::pin::Pin; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use atomic_destructor::{AtomicDestroyer, AtomicDestructor}; -use bytes::Bytes; -use futures::FutureExt; -use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Full}; -use hyper::body::Incoming; -use hyper::server::conn::http1; -use hyper::service::service_fn; -use hyper::{Method, Request, Response, StatusCode}; -use nostr::prelude::{BoxedFuture, SignerBackend}; -use nostr::{Event, NostrSigner, PublicKey, SignerError, UnsignedEvent}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize, Serializer}; -use serde_json::{json, Value}; -use smol::io::{AsyncRead, AsyncWrite}; -use smol::lock::Mutex; -use uuid::Uuid; - -use crate::error::Error; - -mod error; - -const HTML: &str = include_str!("../index.html"); -const JS: &str = include_str!("../proxy.js"); -const CSS: &str = include_str!("../style.css"); - -/// Wrapper to make smol::Async compatible with hyper -struct HyperIo { - inner: T, -} - -impl HyperIo { - fn new(inner: T) -> Self { - Self { inner } - } -} - -impl hyper::rt::Read for HyperIo { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - mut buf: hyper::rt::ReadBufCursor<'_>, - ) -> Poll> { - let mut tbuf = vec![0; buf.remaining()]; - match Pin::new(&mut self.inner).poll_read(cx, &mut tbuf) { - Poll::Ready(Ok(n)) => { - buf.put_slice(&tbuf[..n]); - Poll::Ready(Ok(())) - } - Poll::Ready(Err(e)) => Poll::Ready(Err(e)), - Poll::Pending => Poll::Pending, - } - } -} - -impl hyper::rt::Write for HyperIo { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.inner).poll_write(cx, buf) - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(&mut self.inner).poll_flush(cx) - } - - fn poll_shutdown( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(&mut self.inner).poll_close(cx) - } -} - -type PendingResponseMap = HashMap>>; - -#[derive(Debug, Deserialize)] -struct Message { - id: Uuid, - error: Option, - result: Option, -} - -impl Message { - fn into_result(self) -> Result { - if let Some(error) = self.error { - Err(error) - } else { - Ok(self.result.unwrap_or(Value::Null)) - } - } -} - -#[derive(Debug, Clone, Copy)] -enum RequestMethod { - GetPublicKey, - SignEvent, - Nip04Encrypt, - Nip04Decrypt, - Nip44Encrypt, - Nip44Decrypt, -} - -impl RequestMethod { - fn as_str(&self) -> &str { - match self { - Self::GetPublicKey => "get_public_key", - Self::SignEvent => "sign_event", - Self::Nip04Encrypt => "nip04_encrypt", - Self::Nip04Decrypt => "nip04_decrypt", - Self::Nip44Encrypt => "nip44_encrypt", - Self::Nip44Decrypt => "nip44_decrypt", - } - } -} - -impl Serialize for RequestMethod { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.as_str()) - } -} - -#[derive(Debug, Clone, Serialize)] -struct RequestData { - id: Uuid, - method: RequestMethod, - params: Value, -} - -impl RequestData { - #[inline] - fn new(method: RequestMethod, params: Value) -> Self { - Self { - id: Uuid::new_v4(), - method, - params, - } - } -} - -#[derive(Serialize)] -struct Requests<'a> { - requests: &'a [RequestData], -} - -impl<'a> Requests<'a> { - #[inline] - fn new(requests: &'a [RequestData]) -> Self { - Self { requests } - } - - #[inline] - fn len(&self) -> usize { - self.requests.len() - } -} - -/// Params for NIP-04 and NIP-44 encryption/decryption -#[derive(Serialize)] -struct CryptoParams<'a> { - public_key: &'a PublicKey, - content: &'a str, -} - -impl<'a> CryptoParams<'a> { - #[inline] - fn new(public_key: &'a PublicKey, content: &'a str) -> Self { - Self { - public_key, - content, - } - } -} - -#[derive(Debug)] -struct ProxyState { - /// Requests waiting to be picked up by browser - pub outgoing_requests: Mutex>, - /// Map of request ID to response sender - pub pending_responses: Mutex, - /// Last time the client ask for the pending requests - pub last_pending_request: Arc, - /// Notification for shutdown - pub shutdown_notify: smol::channel::Receiver<()>, - pub shutdown_sender: smol::channel::Sender<()>, -} - -/// Configuration options for [`BrowserSignerProxy`]. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BrowserSignerProxyOptions { - /// Request timeout for the signer extension. Default is 30 seconds. - pub timeout: Duration, - /// Proxy server IP address and port. Default is `127.0.0.1:7400`. - pub addr: SocketAddr, -} - -#[derive(Debug, Clone)] -struct InnerBrowserSignerProxy { - /// Configuration options for the proxy - options: BrowserSignerProxyOptions, - /// Internal state of the proxy including request queues - state: Arc, - /// Flag to indicate if the server is shutdown - is_shutdown: Arc, - /// Flat indicating if the server is started - is_started: Arc, -} - -impl AtomicDestroyer for InnerBrowserSignerProxy { - fn on_destroy(&self) { - self.shutdown(); - } -} - -impl InnerBrowserSignerProxy { - #[inline] - fn is_shutdown(&self) -> bool { - self.is_shutdown.load(Ordering::SeqCst) - } - - fn shutdown(&self) { - // Mark the server as shutdown - self.is_shutdown.store(true, Ordering::SeqCst); - - // Notify all waiters that the proxy is shutting down - let _ = self.state.shutdown_sender.try_send(()); - } -} - -/// Nostr Browser Signer Proxy -/// -/// Proxy to use Nostr Browser signer (NIP-07) in native applications. -#[derive(Debug, Clone)] -pub struct BrowserSignerProxy { - inner: AtomicDestructor, -} - -impl Default for BrowserSignerProxyOptions { - fn default() -> Self { - Self { - timeout: Duration::from_secs(30), - // 7 for NIP-07 and 400 because the NIP title is 40 bytes :) - addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 7400)), - } - } -} - -impl BrowserSignerProxyOptions { - /// Sets the timeout duration. - pub const fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self - } - - /// Sets the IP address. - pub const fn ip_addr(mut self, new_ip: IpAddr) -> Self { - self.addr = SocketAddr::new(new_ip, self.addr.port()); - self - } - - /// Sets the port number. - pub const fn port(mut self, new_port: u16) -> Self { - self.addr = SocketAddr::new(self.addr.ip(), new_port); - self - } -} - -impl BrowserSignerProxy { - /// Construct a new browser signer proxy - pub fn new(options: BrowserSignerProxyOptions) -> Self { - let (shutdown_sender, shutdown_notify) = smol::channel::unbounded(); - let state = ProxyState { - outgoing_requests: Mutex::new(Vec::new()), - pending_responses: Mutex::new(HashMap::new()), - last_pending_request: Arc::new(AtomicU64::new(0)), - shutdown_notify, - shutdown_sender, - }; - - Self { - inner: AtomicDestructor::new(InnerBrowserSignerProxy { - options, - state: Arc::new(state), - is_shutdown: Arc::new(AtomicBool::new(false)), - is_started: Arc::new(AtomicBool::new(false)), - }), - } - } - - /// Indicates whether the server is currently running. - #[inline] - pub fn is_started(&self) -> bool { - self.inner.is_started.load(Ordering::SeqCst) - } - - /// Checks if there is an open browser tap ready to respond to requests by - /// verifying the time since the last pending request. - #[inline] - pub fn is_session_active(&self) -> bool { - current_time() - self.inner.state.last_pending_request.load(Ordering::SeqCst) < 2 - } - - /// Get the signer proxy webpage URL - #[inline] - pub fn url(&self) -> String { - format!("http://{}", self.inner.options.addr) - } - - /// Start the proxy - /// - /// If this is not called, will be automatically started on the first interaction with the signer. - pub async fn start(&self) -> Result<(), Error> { - // Ensure is not shutdown - if self.inner.is_shutdown() { - return Err(Error::Shutdown); - } - - // Mark the proxy as started and check if was already started - let is_started: bool = self.inner.is_started.swap(true, Ordering::SeqCst); - - // Immediately return if already started - if is_started { - return Ok(()); - } - - let listener = match smol::Async::::bind(self.inner.options.addr) { - Ok(listener) => listener, - Err(e) => { - // Undo the started flag if binding fails - self.inner.is_started.store(false, Ordering::SeqCst); - - // Propagate error - return Err(Error::from(e)); - } - }; - - let addr: SocketAddr = self.inner.options.addr; - let state: Arc = self.inner.state.clone(); - - smol::spawn(async move { - log::info!("Starting proxy server on {addr}"); - - loop { - futures::select! { - accept_result = listener.accept().fuse() => { - let (stream, _) = match accept_result { - Ok(conn) => conn, - Err(e) => { - log::error!("Failed to accept connection: {e}"); - continue; - } - }; - - let io = HyperIo::new(stream); - let state: Arc = state.clone(); - let shutdown_notify = state.shutdown_notify.clone(); - - smol::spawn(async move { - let service = service_fn(move |req| { - handle_request(req, state.clone()) - }); - - futures::select! { - res = http1::Builder::new().serve_connection(io, service).fuse() => { - if let Err(e) = res { - log::error!("Error serving connection: {e}"); - } - } - _ = shutdown_notify.recv().fuse() => { - log::debug!("Closing connection, proxy server is shutting down."); - } - } - }).detach(); - }, - _ = state.shutdown_notify.recv().fuse() => { - break; - } - } - } - - log::info!("Shutting down proxy server."); - }).detach(); - - Ok(()) - } - - #[inline] - async fn store_pending_response(&self, id: Uuid, tx: oneshot::Sender>) { - let mut pending_responses = self.inner.state.pending_responses.lock().await; - pending_responses.insert(id, tx); - } - - #[inline] - async fn store_outgoing_request(&self, request: RequestData) { - let mut outgoing_requests = self.inner.state.outgoing_requests.lock().await; - outgoing_requests.push(request); - } - - async fn request(&self, method: RequestMethod, params: Value) -> Result - where - T: DeserializeOwned, - { - // Start the proxy if not already started - self.start().await?; - - // Construct the request - let request: RequestData = RequestData::new(method, params); - - // Create a oneshot channel - let (tx, rx) = oneshot::channel(); - - // Store the response sender - self.store_pending_response(request.id, tx).await; - - // Add to outgoing requests queue - self.store_outgoing_request(request).await; - - // Wait for response - let timeout_fut = smol::Timer::after(self.inner.options.timeout); - let recv_fut = rx; - - match futures::future::select(timeout_fut, recv_fut).await { - futures::future::Either::Left(_) => Err(Error::Timeout), - futures::future::Either::Right((recv_result, _)) => { - match recv_result.map_err(|_| Error::Generic("Channel closed".to_string()))? { - Ok(res) => Ok(serde_json::from_value(res)?), - Err(error) => Err(Error::Generic(error)), - } - } - } - } - - #[inline] - async fn _get_public_key(&self) -> Result { - self.request(RequestMethod::GetPublicKey, json!({})).await - } - - #[inline] - async fn _sign_event(&self, event: UnsignedEvent) -> Result { - let event: Event = self - .request(RequestMethod::SignEvent, serde_json::to_value(event)?) - .await?; - event.verify()?; - Ok(event) - } - - #[inline] - async fn _nip04_encrypt(&self, public_key: &PublicKey, content: &str) -> Result { - let params = CryptoParams::new(public_key, content); - self.request(RequestMethod::Nip04Encrypt, serde_json::to_value(params)?) - .await - } - - #[inline] - async fn _nip04_decrypt(&self, public_key: &PublicKey, content: &str) -> Result { - let params = CryptoParams::new(public_key, content); - self.request(RequestMethod::Nip04Decrypt, serde_json::to_value(params)?) - .await - } - - #[inline] - async fn _nip44_encrypt(&self, public_key: &PublicKey, content: &str) -> Result { - let params = CryptoParams::new(public_key, content); - self.request(RequestMethod::Nip44Encrypt, serde_json::to_value(params)?) - .await - } - - #[inline] - async fn _nip44_decrypt(&self, public_key: &PublicKey, content: &str) -> Result { - let params = CryptoParams::new(public_key, content); - self.request(RequestMethod::Nip44Decrypt, serde_json::to_value(params)?) - .await - } -} - -impl NostrSigner for BrowserSignerProxy { - fn backend(&self) -> SignerBackend { - SignerBackend::BrowserExtension - } - - #[inline] - fn get_public_key(&self) -> BoxedFuture> { - Box::pin(async move { self._get_public_key().await.map_err(SignerError::backend) }) - } - - #[inline] - fn sign_event(&self, unsigned: UnsignedEvent) -> BoxedFuture> { - Box::pin(async move { - self._sign_event(unsigned) - .await - .map_err(SignerError::backend) - }) - } - - #[inline] - fn nip04_encrypt<'a>( - &'a self, - public_key: &'a PublicKey, - content: &'a str, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - self._nip04_encrypt(public_key, content) - .await - .map_err(SignerError::backend) - }) - } - - #[inline] - fn nip04_decrypt<'a>( - &'a self, - public_key: &'a PublicKey, - encrypted_content: &'a str, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - self._nip04_decrypt(public_key, encrypted_content) - .await - .map_err(SignerError::backend) - }) - } - - #[inline] - fn nip44_encrypt<'a>( - &'a self, - public_key: &'a PublicKey, - content: &'a str, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - self._nip44_encrypt(public_key, content) - .await - .map_err(SignerError::backend) - }) - } - - #[inline] - fn nip44_decrypt<'a>( - &'a self, - public_key: &'a PublicKey, - payload: &'a str, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - self._nip44_decrypt(public_key, payload) - .await - .map_err(SignerError::backend) - }) - } -} - -async fn handle_request( - req: Request, - state: Arc, -) -> Result>, Error> { - match (req.method(), req.uri().path()) { - // Serve the HTML proxy page - (&Method::GET, "/") => Ok(Response::builder() - .header("Content-Type", "text/html") - .body(full(HTML))?), - // Serve the CSS page style - (&Method::GET, "/style.css") => Ok(Response::builder() - .header("Content-Type", "text/css") - .body(full(CSS))?), - // Serve the JS proxy script - (&Method::GET, "/proxy.js") => Ok(Response::builder() - .header("Content-Type", "application/javascript") - .body(full(JS))?), - // Browser polls this endpoint to get pending requests - (&Method::GET, "/api/pending") => { - state - .last_pending_request - .store(current_time(), Ordering::SeqCst); - - let mut outgoing = state.outgoing_requests.lock().await; - - let requests: Requests<'_> = Requests::new(&outgoing); - let json: String = serde_json::to_string(&requests)?; - - log::debug!("Sending {} pending requests to browser", requests.len()); - - // Clear the outgoing requests after sending them - outgoing.clear(); - - Ok(Response::builder() - .header("Content-Type", "application/json") - .header("Access-Control-Allow-Origin", "*") - .body(full(json))?) - } - // Get response - (&Method::POST, "/api/response") => { - // Correctly collect the body bytes from the stream - let body_bytes: Bytes = match req.into_body().collect().await { - Ok(collected) => collected.to_bytes(), - Err(e) => { - log::error!("Failed to read body: {e}"); - let response = Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(full("Failed to read body"))?; - return Ok(response); - } - }; - - // Handle responses from the browser extension - let message: Message = match serde_json::from_slice(&body_bytes) { - Ok(json) => json, - Err(_) => { - let response = Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(full("Invalid JSON"))?; - return Ok(response); - } - }; - - log::debug!("Received response from browser: {message:?}"); - - let id: Uuid = message.id; - let mut pending = state.pending_responses.lock().await; - - match pending.remove(&id) { - Some(sender) => { - let _ = sender.send(message.into_result()); - } - None => log::warn!("No pending request found for {id}"), - } - - let response = Response::builder() - .header("Access-Control-Allow-Origin", "*") - .body(full("OK"))?; - Ok(response) - } - (&Method::OPTIONS, _) => { - // Handle CORS preflight requests - let response = Response::builder() - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .body(full(""))?; - Ok(response) - } - // 404 - not found - _ => { - let response = Response::builder() - .status(StatusCode::NOT_FOUND) - .body(full("Not Found"))?; - Ok(response) - } - } -} - -#[inline] -fn full>(chunk: T) -> BoxBody { - Full::new(chunk.into()) - .map_err(|never| match never {}) - .boxed() -} - -/// Gets the current time in seconds since the Unix epoch (1970-01-01). If the -/// time is before the epoch, returns 0. -#[inline] -fn current_time() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_secs()) - .unwrap_or_default() -} diff --git a/crates/signer_proxy/style.css b/crates/signer_proxy/style.css deleted file mode 100644 index 32d0609..0000000 --- a/crates/signer_proxy/style.css +++ /dev/null @@ -1,30 +0,0 @@ -body { - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, - Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - padding: 20px; - background-color: #f0f0f0; -} -.container { - max-width: 600px; - margin: 0 auto; - background: white; - padding: 20px; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); -} -.connected { - color: green; - font-weight: bold; -} -.error { - color: red; - font-weight: bold; -} -.status-box { - background: #f9f9f9; - padding: 10px; - border-radius: 4px; - margin: 10px 0; - border-left: 4px solid #ccc; -} diff --git a/crates/app_state/Cargo.toml b/crates/states/Cargo.toml similarity index 94% rename from crates/app_state/Cargo.toml rename to crates/states/Cargo.toml index 77a88b7..74df1db 100644 --- a/crates/app_state/Cargo.toml +++ b/crates/states/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "app_state" +name = "states" version.workspace = true edition.workspace = true publish.workspace = true diff --git a/crates/app_state/src/constants.rs b/crates/states/src/constants.rs similarity index 100% rename from crates/app_state/src/constants.rs rename to crates/states/src/constants.rs diff --git a/crates/app_state/src/lib.rs b/crates/states/src/lib.rs similarity index 57% rename from crates/app_state/src/lib.rs rename to crates/states/src/lib.rs index ea5090d..d234f39 100644 --- a/crates/app_state/src/lib.rs +++ b/crates/states/src/lib.rs @@ -1,9 +1,6 @@ use std::sync::OnceLock; -use std::time::Duration; -use nostr_lmdb::NostrLMDB; use nostr_sdk::prelude::*; -use paths::nostr_file; use crate::state::AppState; @@ -12,7 +9,6 @@ pub mod paths; pub mod state; static APP_STATE: OnceLock = OnceLock::new(); -static NOSTR_CLIENT: OnceLock = OnceLock::new(); static NIP65_RELAYS: OnceLock)>> = OnceLock::new(); static NIP17_RELAYS: OnceLock> = OnceLock::new(); @@ -21,31 +17,7 @@ pub fn app_state() -> &'static AppState { APP_STATE.get_or_init(AppState::new) } -/// Initialize the nostr client. -pub fn nostr_client() -> &'static Client { - NOSTR_CLIENT.get_or_init(|| { - // rustls uses the `aws_lc_rs` provider by default - // This only errors if the default provider has already - // been installed. We can ignore this `Result`. - rustls::crypto::aws_lc_rs::default_provider() - .install_default() - .ok(); - - let lmdb = NostrLMDB::open(nostr_file()).expect("Database is NOT initialized"); - - let opts = ClientOptions::new() - .gossip(true) - .automatic_authentication(false) - .verify_subscriptions(false) - .sleep_when_idle(SleepWhenIdle::Enabled { - timeout: Duration::from_secs(600), - }); - - ClientBuilder::default().database(lmdb).opts(opts).build() - }) -} - -/// Default NIP65 Relays. Used for new account +/// Default NIP-65 Relays. Used for new account pub fn default_nip65_relays() -> &'static Vec<(RelayUrl, Option)> { NIP65_RELAYS.get_or_init(|| { vec![ @@ -71,7 +43,7 @@ pub fn default_nip65_relays() -> &'static Vec<(RelayUrl, Option)> }) } -/// Default NIP17 Relays. Used for new account +/// Default NIP-17 Relays. Used for new account pub fn default_nip17_relays() -> &'static Vec { NIP17_RELAYS.get_or_init(|| { vec![ diff --git a/crates/app_state/src/paths.rs b/crates/states/src/paths.rs similarity index 100% rename from crates/app_state/src/paths.rs rename to crates/states/src/paths.rs diff --git a/crates/app_state/src/state/mod.rs b/crates/states/src/state.rs similarity index 72% rename from crates/app_state/src/state/mod.rs rename to crates/states/src/state.rs index 3ff5dfa..dfcc272 100644 --- a/crates/app_state/src/state/mod.rs +++ b/crates/states/src/state.rs @@ -5,14 +5,16 @@ use std::time::Duration; use anyhow::{anyhow, Error}; use flume::{Receiver, Sender}; +use nostr_lmdb::NostrLMDB; use nostr_sdk::prelude::*; use smol::lock::RwLock; use crate::constants::{ BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, SEARCH_RELAYS, }; -use crate::nostr_client; -use crate::paths::support_dir; +use crate::paths::config_dir; + +const TIMEOUT: u64 = 5; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct AuthRequest { @@ -51,9 +53,6 @@ pub enum SignalKind { /// A signal to notify UI that the relay requires authentication Auth(AuthRequest), - /// A signal to notify UI that the browser proxy service is down - ProxyDown, - /// A signal to notify UI that a new profile has been received NewProfile(Profile), @@ -70,7 +69,7 @@ pub enum SignalKind { GiftWrapStatus(UnwrappingStatus), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Signal { rx: Receiver, tx: Sender, @@ -103,7 +102,7 @@ impl Signal { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Ingester { rx: Receiver, tx: Sender, @@ -165,32 +164,25 @@ impl EventTracker { } } -/// A simple storage to store all states that using across the application. #[derive(Debug)] pub struct AppState { + /// A client to interact with Nostr + client: Client, + + /// Tracks activity related to Nostr events + event_tracker: RwLock, + + /// Signal channel for communication between Nostr and GPUI + signal: Signal, + + /// Ingester channel for processing public keys + ingester: Ingester, + /// The timestamp when the application was initialized. pub initialized_at: Timestamp, - /// Whether this is the first run of the application. - pub is_first_run: AtomicBool, - /// Whether gift wrap processing is in progress. pub gift_wrap_processing: AtomicBool, - - /// Subscription ID for listening to gift wrap events from relays. - pub gift_wrap_sub_id: SubscriptionId, - - /// Auto-close options for relay subscriptions - pub auto_close_opts: Option, - - /// Tracks activity related to Nostr events - pub event_tracker: RwLock, - - /// Signal channel for communication between Nostr and GPUI - pub signal: Signal, - - /// Ingester channel for processing public keys - pub ingester: Ingester, } impl Default for AppState { @@ -201,28 +193,127 @@ impl Default for AppState { impl AppState { pub fn new() -> Self { - let first_run = Self::first_run(); - let initialized_at = Timestamp::now(); - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); + // rustls uses the `aws_lc_rs` provider by default + // This only errors if the default provider has already + // been installed. We can ignore this `Result`. + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .ok(); + + let lmdb = + NostrLMDB::open(config_dir().join("nostr")).expect("Database is NOT initialized"); + + let opts = ClientOptions::new() + .gossip(true) + .automatic_authentication(false) + .verify_subscriptions(false) + .sleep_when_idle(SleepWhenIdle::Enabled { + timeout: Duration::from_secs(600), + }); + + let client = ClientBuilder::default().database(lmdb).opts(opts).build(); + let event_tracker = RwLock::new(EventTracker::default()); let signal = Signal::default(); let ingester = Ingester::default(); Self { - initialized_at, + client, + event_tracker, signal, ingester, - is_first_run: AtomicBool::new(first_run), - gift_wrap_sub_id: SubscriptionId::new("inbox"), + initialized_at: Timestamp::now(), gift_wrap_processing: AtomicBool::new(false), - auto_close_opts: Some(opts), - event_tracker: RwLock::new(EventTracker::default()), } } - pub async fn handle_notifications(&self) -> Result<(), Error> { - let client = nostr_client(); + /// Returns a reference to the nostr client + pub fn client(&'static self) -> &'static Client { + &self.client + } + /// Returns a reference to the event tracker + pub fn tracker(&'static self) -> &'static RwLock { + &self.event_tracker + } + + /// Returns a reference to the signal channel + pub fn signal(&'static self) -> &'static Signal { + &self.signal + } + + /// Returns a reference to the ingester channel + pub fn ingester(&'static self) -> &'static Ingester { + &self.ingester + } + + /// Observes the signer and notifies the app when it's set + pub async fn observe_signer(&'static self) { + let client = self.client(); + let loop_duration = Duration::from_millis(800); + + loop { + if let Ok(signer) = client.signer().await { + if let Ok(pk) = signer.get_public_key().await { + // Notify the app that the signer has been set + self.signal().send(SignalKind::SignerSet(pk)).await; + + // Get user's gossip relays + self.get_nip65(pk).await.ok(); + + // Exit the current loop + break; + } + } + + smol::Timer::after(loop_duration).await; + } + } + + /// Observes the gift wrap status and notifies the app when it's set + pub async fn observe_giftwrap(&'static self) { + let client = self.client(); + let loop_duration = Duration::from_secs(20); + let mut is_start_processing = false; + let mut total_loops = 0; + + loop { + if client.has_signer().await { + total_loops += 1; + + if self.gift_wrap_processing.load(Ordering::Acquire) { + is_start_processing = true; + + // Reset gift wrap processing flag + let _ = self.gift_wrap_processing.compare_exchange( + true, + false, + Ordering::Release, + Ordering::Relaxed, + ); + + let signal = SignalKind::GiftWrapStatus(UnwrappingStatus::Processing); + self.signal().send(signal).await; + } else { + // Only run further if we are already processing + // Wait until after 2 loops to prevent exiting early while events are still being processed + if is_start_processing && total_loops >= 2 { + let signal = SignalKind::GiftWrapStatus(UnwrappingStatus::Complete); + self.signal().send(signal).await; + + // Reset the counter + is_start_processing = false; + total_loops = 0; + } + } + } + + smol::Timer::after(loop_duration).await; + } + } + + /// Handles events from the nostr client + pub async fn handle_notifications(&self) -> Result<(), Error> { // Get all bootstrapping relays let mut urls = vec![]; urls.extend(BOOTSTRAP_RELAYS); @@ -230,15 +321,15 @@ impl AppState { // Add relay to the relay pool for url in urls.into_iter() { - client.add_relay(url).await?; + self.client.add_relay(url).await?; } // Establish connection to relays - client.connect().await; + self.client.connect().await; let mut processed_events: HashSet = HashSet::new(); let mut challenges: HashSet> = HashSet::new(); - let mut notifications = client.notifications(); + let mut notifications = self.client.notifications(); while let Ok(notification) = notifications.recv().await { let RelayPoolNotification::Message { message, relay_url } = notification else { @@ -265,7 +356,7 @@ impl AppState { match event.kind { Kind::RelayList => { // Get events if relay list belongs to current user - if let Ok(true) = Self::is_self_authored(&event).await { + if let Ok(true) = self.is_self_authored(&event).await { let author = event.pubkey; // Fetch user's metadata event @@ -286,7 +377,7 @@ impl AppState { } Kind::InboxRelays => { // Subscribe to gift wrap events if messaging relays belong to the current user - if let Ok(true) = Self::is_self_authored(&event).await { + if let Ok(true) = self.is_self_authored(&event).await { let urls: Vec = nip17::extract_relay_list(event.as_ref()).cloned().collect(); @@ -296,7 +387,7 @@ impl AppState { } } Kind::ContactList => { - if let Ok(true) = Self::is_self_authored(&event).await { + if let Ok(true) = self.is_self_authored(&event).await { let public_keys: HashSet = event.tags.public_keys().copied().collect(); @@ -318,7 +409,7 @@ impl AppState { } } RelayMessage::EndOfStoredEvents(subscription_id) => { - if *subscription_id == self.gift_wrap_sub_id { + if subscription_id.as_ref() == &SubscriptionId::new("inbox") { self.signal .send(SignalKind::GiftWrapStatus(UnwrappingStatus::Processing)) .await; @@ -353,6 +444,7 @@ impl AppState { Ok(()) } + /// Batch metadata requests into a single subscription pub async fn handle_metadata_batching(&self) { let timeout = Duration::from_millis(METADATA_BATCH_TIMEOUT); let mut processed_pubkeys: HashSet = HashSet::new(); @@ -411,41 +503,46 @@ impl AppState { } } - async fn is_self_authored(event: &Event) -> Result { - let client = nostr_client(); - let signer = client.signer().await?; + /// Check if event is published by current user + async fn is_self_authored(&self, event: &Event) -> Result { + let signer = self.client.signer().await?; let public_key = signer.get_public_key().await?; Ok(public_key == event.pubkey) } /// Subscribe for events that match the given kind for a given author - async fn subscribe(&self, author: PublicKey, kind: Kind) -> Result<(), Error> { - let client = nostr_client(); + pub async fn subscribe(&self, author: PublicKey, kind: Kind) -> Result<(), Error> { let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let filter = Filter::new().author(author).kind(kind).limit(1); // Subscribe to filters from the user's write relays - client.subscribe(filter, Some(opts)).await?; + self.client.subscribe(filter, Some(opts)).await?; Ok(()) } /// Get metadata for a list of public keys - async fn get_metadata_for_list(&self, public_keys: HashSet) -> Result<(), Error> { - if public_keys.is_empty() { - return Err(anyhow!("You need at least one public key")); + pub async fn get_metadata_for_list(&self, public_keys: I) -> Result<(), Error> + where + I: IntoIterator, + { + let authors: Vec = public_keys.into_iter().collect(); + let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); + let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList]; + + // Return if the list is empty + if authors.is_empty() { + return Err(anyhow!("You need at least one public key".to_string(),)); } - let client = nostr_client(); - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); - - let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList]; - let limit = public_keys.len() * kinds.len() + 20; - let filter = Filter::new().authors(public_keys).kinds(kinds).limit(limit); + let filter = Filter::new() + .limit(authors.len() * kinds.len() + 20) + .authors(authors) + .kinds(kinds); // Subscribe to filters to the bootstrap relays - client + self.client .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) .await?; @@ -454,9 +551,7 @@ impl AppState { /// Get and verify NIP-65 relays for a given public key pub async fn get_nip65(&self, public_key: PublicKey) -> Result<(), Error> { - let client = nostr_client(); - let tx = self.signal.sender().clone(); - let timeout = Duration::from_secs(5); + let timeout = Duration::from_secs(TIMEOUT); let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let filter = Filter::new() @@ -465,15 +560,18 @@ impl AppState { .limit(1); // Subscribe to events from the bootstrapping relays - client + self.client .subscribe_to(BOOTSTRAP_RELAYS, filter.clone(), Some(opts)) .await?; + let tx = self.signal.sender().clone(); + let database = self.client.database().clone(); + // Verify the received data after a timeout smol::spawn(async move { smol::Timer::after(timeout).await; - if client.database().count(filter).await.unwrap_or(0) < 1 { + if database.count(filter).await.unwrap_or(0) < 1 { tx.send_async(SignalKind::GossipRelaysNotFound).await.ok(); } }) @@ -487,12 +585,12 @@ impl AppState { &self, relays: &[(RelayUrl, Option)], ) -> Result<(), Error> { - let client = nostr_client(); - let signer = client.signer().await?; + let signer = self.client.signer().await?; let tags: Vec = relays .iter() - .map(|(url, metadata)| Tag::relay_metadata(url.to_owned(), metadata.to_owned())) + .cloned() + .map(|(url, metadata)| Tag::relay_metadata(url, metadata)) .collect(); let event = EventBuilder::new(Kind::RelayList, "") @@ -501,7 +599,7 @@ impl AppState { .await?; // Send event to the public relays - client.send_event_to(BOOTSTRAP_RELAYS, &event).await?; + self.client.send_event_to(BOOTSTRAP_RELAYS, &event).await?; // Get NIP-17 relays self.get_nip17(event.pubkey).await?; @@ -511,9 +609,7 @@ impl AppState { /// Get and verify NIP-17 relays for a given public key pub async fn get_nip17(&self, public_key: PublicKey) -> Result<(), Error> { - let client = nostr_client(); - let tx = self.signal.sender().clone(); - let timeout = Duration::from_secs(5); + let timeout = Duration::from_secs(TIMEOUT); let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let filter = Filter::new() @@ -522,13 +618,16 @@ impl AppState { .limit(1); // Subscribe to events from the bootstrapping relays - client.subscribe(filter.clone(), Some(opts)).await?; + self.client.subscribe(filter.clone(), Some(opts)).await?; + + let tx = self.signal.sender().clone(); + let database = self.client.database().clone(); // Verify the received data after a timeout smol::spawn(async move { smol::Timer::after(timeout).await; - if client.database().count(filter).await.unwrap_or(0) < 1 { + if database.count(filter).await.unwrap_or(0) < 1 { tx.send_async(SignalKind::MessagingRelaysNotFound) .await .ok(); @@ -541,26 +640,28 @@ impl AppState { /// Set NIP-17 relays for a current user pub async fn set_nip17(&self, relays: &[RelayUrl]) -> Result<(), Error> { - let client = nostr_client(); - let signer = client.signer().await?; + let signer = self.client.signer().await?; let event = EventBuilder::new(Kind::InboxRelays, "") - .tags(relays.iter().map(|relay| Tag::relay(relay.to_owned()))) + .tags(relays.iter().cloned().map(Tag::relay)) .sign(&signer) .await?; // Send event to the public relays - client.send_event(&event).await?; + self.client.send_event(&event).await?; - // Run inbox monitor + // Get all gift wrap events after published event self.get_messages(event.pubkey, relays).await?; Ok(()) } /// Get all gift wrap events in the messaging relays for a given public key - async fn get_messages(&self, public_key: PublicKey, urls: &[RelayUrl]) -> Result<(), Error> { - let client = nostr_client(); + pub async fn get_messages( + &self, + public_key: PublicKey, + urls: &[RelayUrl], + ) -> Result<(), Error> { let id = SubscriptionId::new("inbox"); let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); @@ -571,22 +672,22 @@ impl AppState { // Ensure connection to relays for url in urls.iter() { - client.add_relay(url).await?; - client.connect_relay(url).await?; + self.client.add_relay(url).await?; + self.client.connect_relay(url).await?; } // Subscribe to filters to user's messaging relays - client.subscribe_with_id_to(urls, id, filter, None).await?; + self.client + .subscribe_with_id_to(urls, id, filter, None) + .await?; Ok(()) } /// Stores an unwrapped event in local database with reference to original async fn set_rumor(&self, id: EventId, rumor: &Event) -> Result<(), Error> { - let client = nostr_client(); - // Save unwrapped event - client.database().save_event(rumor).await?; + self.client.database().save_event(rumor).await?; // Create a reference event pointing to the unwrapped event let event = EventBuilder::new(Kind::ApplicationSpecificData, "") @@ -595,23 +696,22 @@ impl AppState { .await?; // Save reference event - client.database().save_event(&event).await?; + self.client.database().save_event(&event).await?; Ok(()) } /// Retrieves a previously unwrapped event from local database async fn get_rumor(&self, id: EventId) -> Result { - let client = nostr_client(); let filter = Filter::new() .kind(Kind::ApplicationSpecificData) .identifier(id) .limit(1); - if let Some(event) = client.database().query(filter).await?.first_owned() { + if let Some(event) = self.client.database().query(filter).await?.first_owned() { let target_id = event.tags.event_ids().collect::>()[0]; - if let Some(event) = client.database().event_by_id(target_id).await? { + if let Some(event) = self.client.database().event_by_id(target_id).await? { Ok(event) } else { Err(anyhow!("Event not found.")) @@ -623,13 +723,11 @@ impl AppState { // Unwraps a gift-wrapped event and processes its contents. async fn extract_rumor(&self, gift_wrap: &Event) { - let client = nostr_client(); - let mut rumor: Option = None; if let Ok(event) = self.get_rumor(gift_wrap.id).await { rumor = Some(event); - } else if let Ok(unwrapped) = client.unwrap_gift_wrap(gift_wrap).await { + } else if let Ok(unwrapped) = self.client.unwrap_gift_wrap(gift_wrap).await { // Sign the unwrapped event with a RANDOM KEYS if let Ok(event) = unwrapped.rumor.sign_with_keys(&Keys::generate()) { // Save this event to the database for future use. @@ -661,9 +759,4 @@ impl AppState { } } } - - fn first_run() -> bool { - let flag = support_dir().join(".first_run"); - !flag.exists() && std::fs::write(&flag, "").is_ok() - } }