chore: clean up codebase (#186)
* refactor app state * clean up * clean up * .
This commit is contained in:
194
Cargo.lock
generated
194
Cargo.lock
generated
@@ -82,21 +82,6 @@ version = "1.0.100"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
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]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
@@ -507,7 +492,6 @@ name = "auto_update"
|
|||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app_state",
|
|
||||||
"cargo-packager-updater",
|
"cargo-packager-updater",
|
||||||
"common",
|
"common",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -515,6 +499,7 @@ dependencies = [
|
|||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
|
"states",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1094,11 +1079,11 @@ name = "client_keys"
|
|||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app_state",
|
|
||||||
"gpui",
|
"gpui",
|
||||||
"log",
|
"log",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"states",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1128,14 +1113,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa"
|
name = "cocoa"
|
||||||
version = "0.26.1"
|
version = "0.26.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c"
|
checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"block",
|
"block",
|
||||||
"cocoa-foundation 0.2.1",
|
"cocoa-foundation 0.2.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics 0.24.0",
|
"core-graphics 0.24.0",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1158,14 +1143,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa-foundation"
|
name = "cocoa-foundation"
|
||||||
version = "0.2.1"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d"
|
checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"block",
|
"block",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics-types 0.2.0",
|
"core-graphics-types 0.2.0",
|
||||||
|
"libc",
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1183,11 +1169,10 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1221,7 +1206,6 @@ name = "common"
|
|||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app_state",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"futures",
|
"futures",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -1234,6 +1218,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
|
"states",
|
||||||
"webbrowser",
|
"webbrowser",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1295,7 +1280,6 @@ name = "coop"
|
|||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app_state",
|
|
||||||
"assets",
|
"assets",
|
||||||
"auto_update",
|
"auto_update",
|
||||||
"client_keys",
|
"client_keys",
|
||||||
@@ -1319,9 +1303,9 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"signer_proxy",
|
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
|
"states",
|
||||||
"theme",
|
"theme",
|
||||||
"title_bar",
|
"title_bar",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -1341,9 +1325,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1375,7 +1359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics-types 0.2.0",
|
"core-graphics-types 0.2.0",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1412,7 +1396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1425,7 +1409,7 @@ dependencies = [
|
|||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"block",
|
"block",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1435,7 +1419,7 @@ version = "21.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130"
|
checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics 0.24.0",
|
"core-graphics 0.24.0",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1448,7 +1432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d45e71d5be22206bed53c3c3cb99315fc4c3d31b8963808c6bc4538168c4f8ef"
|
checksum = "d45e71d5be22206bed53c3c3cb99315fc4c3d31b8963808c6bc4538168c4f8ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block",
|
"block",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics2",
|
"core-graphics2",
|
||||||
"io-surface",
|
"io-surface",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1625,12 +1609,11 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2003,9 +1986,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "file-locker"
|
name = "file-locker"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6c3e69656680c6c3d76750b46dfa64bf07626bd2130c540d6cf2d306ba595a8"
|
checksum = "75ae8b5984a4863d8a32109a848d038bd6d914f20f010cc141375f7a183c41cf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix 0.29.0",
|
"nix 0.29.0",
|
||||||
]
|
]
|
||||||
@@ -2523,7 +2506,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
@@ -2538,9 +2521,10 @@ dependencies = [
|
|||||||
"calloop",
|
"calloop",
|
||||||
"calloop-wayland-source",
|
"calloop-wayland-source",
|
||||||
"cbindgen",
|
"cbindgen",
|
||||||
"cocoa 0.26.1",
|
"cocoa 0.26.0",
|
||||||
|
"cocoa-foundation 0.2.0",
|
||||||
"collections",
|
"collections",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"core-graphics 0.24.0",
|
"core-graphics 0.24.0",
|
||||||
"core-text",
|
"core-text",
|
||||||
@@ -2572,6 +2556,7 @@ dependencies = [
|
|||||||
"parking",
|
"parking",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pathfinder_geometry",
|
"pathfinder_geometry",
|
||||||
|
"pin-project",
|
||||||
"postage",
|
"postage",
|
||||||
"profiling",
|
"profiling",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
@@ -2605,7 +2590,6 @@ dependencies = [
|
|||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-numerics",
|
"windows-numerics",
|
||||||
"windows-registry 0.5.3",
|
"windows-registry 0.5.3",
|
||||||
"workspace-hack",
|
|
||||||
"x11-clipboard",
|
"x11-clipboard",
|
||||||
"x11rb",
|
"x11rb",
|
||||||
"xkbcommon",
|
"xkbcommon",
|
||||||
@@ -2617,25 +2601,23 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_tokio"
|
name = "gpui_tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
"tokio",
|
"tokio",
|
||||||
"util",
|
"util",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2859,7 +2841,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -2878,18 +2860,16 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"url",
|
"url",
|
||||||
"util",
|
"util",
|
||||||
"workspace-hack",
|
|
||||||
"zed-reqwest",
|
"zed-reqwest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2898,12 +2878,6 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "httpdate"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@@ -2918,7 +2892,6 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
@@ -3188,9 +3161,9 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.11.4"
|
version = "2.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.0",
|
"hashbrown 0.16.0",
|
||||||
@@ -3256,7 +3229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e"
|
checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cgl",
|
"cgl",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"leaky-cow",
|
"leaky-cow",
|
||||||
]
|
]
|
||||||
@@ -3687,17 +3660,16 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.71.1",
|
"bindgen 0.71.1",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-video",
|
"core-video",
|
||||||
"ctor 0.4.3",
|
"ctor 0.4.3",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types 0.5.0",
|
||||||
"metal",
|
"metal",
|
||||||
"objc",
|
"objc",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3794,13 +3766,13 @@ checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.4"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4521,12 +4493,11 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5138,10 +5109,9 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5178,17 +5148,20 @@ name = "registry"
|
|||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app_state",
|
|
||||||
"common",
|
"common",
|
||||||
|
"flume",
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
"gpui",
|
"gpui",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"log",
|
"log",
|
||||||
"nostr",
|
"nostr",
|
||||||
|
"nostr-lmdb",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
|
"rustls",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"smol",
|
"smol",
|
||||||
|
"states",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5243,7 +5216,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5254,7 +5227,6 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"workspace-hack",
|
|
||||||
"zed-reqwest",
|
"zed-reqwest",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5298,7 +5270,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rope"
|
name = "rope"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -5307,7 +5279,6 @@ dependencies = [
|
|||||||
"sum_tree",
|
"sum_tree",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"util",
|
"util",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5464,9 +5435,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.32"
|
version = "0.23.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
|
checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"log",
|
"log",
|
||||||
@@ -5515,7 +5486,7 @@ version = "0.5.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1"
|
checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
@@ -5741,7 +5712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
@@ -5766,11 +5737,10 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "semantic_version"
|
name = "semantic_version"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5915,7 +5885,6 @@ name = "settings"
|
|||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app_state",
|
|
||||||
"gpui",
|
"gpui",
|
||||||
"log",
|
"log",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
@@ -5923,6 +5892,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"states",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5977,28 +5947,6 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@@ -6151,6 +6099,21 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"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]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -6218,12 +6181,11 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
"rayon",
|
"rayon",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7255,7 +7217,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -7285,18 +7247,16 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"which",
|
"which",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#908ae95cf86930893140bee37cf37c8918ac90e8"
|
source = "git+https://github.com/zed-industries/zed#3d6722be9abf94af5680a3323b69a452154c55b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
"workspace-hack",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7657,7 +7617,7 @@ version = "1.0.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97"
|
checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
"ndk-context",
|
"ndk-context",
|
||||||
@@ -8345,12 +8305,6 @@ version = "0.46.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "workspace-hack"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "beffa227304dbaea3ad6a06ac674f9bc83a3dec3b7f63eeb442de37e7cb6bb01"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -8583,7 +8537,7 @@ source = "git+https://github.com/zed-industries/font-kit?rev=110523127440aefb11c
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics 0.24.0",
|
"core-graphics 0.24.0",
|
||||||
"core-text",
|
"core-text",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ publish.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
app_state = { path = "../app_state" }
|
states = { path = "../states" }
|
||||||
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use app_state::constants::{APP_PUBKEY, APP_UPDATER_ENDPOINT};
|
|
||||||
use cargo_packager_updater::semver::Version;
|
use cargo_packager_updater::semver::Version;
|
||||||
use cargo_packager_updater::{check_update, Config, Update};
|
use cargo_packager_updater::{check_update, Config, Update};
|
||||||
use gpui::http_client::Url;
|
use gpui::http_client::Url;
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
|
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::constants::{APP_PUBKEY, APP_UPDATER_ENDPOINT};
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
AutoUpdater::set_global(cx.new(AutoUpdater::new), cx);
|
AutoUpdater::set_global(cx.new(AutoUpdater::new), cx);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ edition.workspace = true
|
|||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
app_state = { path = "../app_state" }
|
states = { path = "../states" }
|
||||||
|
|
||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
|||||||
@@ -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 gpui::{App, AppContext, Context, Entity, Global, Subscription, Window};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::constants::KEYRING_URL;
|
||||||
|
use states::paths::config_dir;
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
ClientKeys::set_global(cx.new(ClientKeys::new), cx);
|
ClientKeys::set_global(cx.new(ClientKeys::new), cx);
|
||||||
@@ -61,7 +59,6 @@ impl ClientKeys {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let app_state = app_state();
|
|
||||||
let read_client_keys = cx.read_credentials(KEYRING_URL);
|
let read_client_keys = cx.read_credentials(KEYRING_URL);
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
@@ -76,7 +73,7 @@ impl ClientKeys {
|
|||||||
this.set_keys(Some(keys), false, true, cx);
|
this.set_keys(Some(keys), false, true, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.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
|
// If this is the first run, generate new keys and use them for the client keys
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.new_keys(cx);
|
this.new_keys(cx);
|
||||||
@@ -139,4 +136,9 @@ impl ClientKeys {
|
|||||||
pub fn has_keys(&self) -> bool {
|
pub fn has_keys(&self) -> bool {
|
||||||
self.keys.is_some()
|
self.keys.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn first_run() -> bool {
|
||||||
|
let flag = config_dir().join(".first_run");
|
||||||
|
!flag.exists() && std::fs::write(&flag, "").is_ok()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ edition.workspace = true
|
|||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
app_state = { path = "../app_state" }
|
states = { path = "../states" }
|
||||||
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
nostr-connect.workspace = true
|
nostr-connect.workspace = true
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use app_state::constants::IMAGE_RESIZE_SERVICE;
|
|
||||||
use chrono::{Local, TimeZone};
|
use chrono::{Local, TimeZone};
|
||||||
use gpui::{Image, ImageFormat, SharedString, SharedUri};
|
use gpui::{Image, ImageFormat, SharedString, SharedUri};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use qrcode::render::svg;
|
use qrcode::render::svg;
|
||||||
use qrcode::QrCode;
|
use qrcode::QrCode;
|
||||||
|
use states::constants::IMAGE_RESIZE_SERVICE;
|
||||||
|
|
||||||
const NOW: &str = "now";
|
const NOW: &str = "now";
|
||||||
const SECONDS_IN_MINUTE: i64 = 60;
|
const SECONDS_IN_MINUTE: i64 = 60;
|
||||||
|
|||||||
@@ -32,12 +32,11 @@ ui = { path = "../ui" }
|
|||||||
title_bar = { path = "../title_bar" }
|
title_bar = { path = "../title_bar" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
app_state = { path = "../app_state" }
|
states = { path = "../states" }
|
||||||
registry = { path = "../registry" }
|
registry = { path = "../registry" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
client_keys = { path = "../client_keys" }
|
client_keys = { path = "../client_keys" }
|
||||||
auto_update = { path = "../auto_update" }
|
auto_update = { path = "../auto_update" }
|
||||||
signer_proxy = { path = "../signer_proxy" }
|
|
||||||
|
|
||||||
rust-i18n.workspace = true
|
rust-i18n.workspace = true
|
||||||
i18n.workspace = true
|
i18n.workspace = true
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
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 auto_update::AutoUpdater;
|
||||||
use client_keys::ClientKeys;
|
use client_keys::ClientKeys;
|
||||||
use common::display::RenderedProfile;
|
use common::display::RenderedProfile;
|
||||||
@@ -24,8 +19,10 @@ use nostr_connect::prelude::*;
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use registry::{Registry, RegistryEvent};
|
use registry::{Registry, RegistryEvent};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use signer_proxy::{BrowserSignerProxy, BrowserSignerProxyOptions};
|
|
||||||
use smallvec::{smallvec, SmallVec};
|
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 theme::{ActiveTheme, Theme, ThemeMode};
|
||||||
use title_bar::TitleBar;
|
use title_bar::TitleBar;
|
||||||
use ui::actions::{CopyPublicKey, OpenPublicKey};
|
use ui::actions::{CopyPublicKey, OpenPublicKey};
|
||||||
@@ -121,21 +118,15 @@ impl ChatSpace {
|
|||||||
let status = status.read(cx);
|
let status = status.read(cx);
|
||||||
let all_panels = this.get_all_panel_ids(cx);
|
let all_panels = this.get_all_panel_ids(cx);
|
||||||
|
|
||||||
match status {
|
if matches!(
|
||||||
UnwrappingStatus::Processing => {
|
status,
|
||||||
registry.update(cx, |this, cx| {
|
UnwrappingStatus::Processing | UnwrappingStatus::Complete
|
||||||
this.load_rooms(window, cx);
|
) {
|
||||||
this.refresh_rooms(all_panels, cx);
|
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -184,14 +175,14 @@ impl ChatSpace {
|
|||||||
// Wait for the signer to be set
|
// Wait for the signer to be set
|
||||||
// Also verify NIP-65 and NIP-17 relays after the signer is set
|
// Also verify NIP-65 and NIP-17 relays after the signer is set
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
Self::observe_signer().await;
|
app_state().observe_signer().await;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Observe gift wrap process in the background
|
// Observe gift wrap process in the background
|
||||||
cx.background_spawn(async move {
|
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<ChatSpace>, cx: &mut AsyncWindowContext) {
|
async fn handle_signals(view: WeakEntity<ChatSpace>, cx: &mut AsyncWindowContext) {
|
||||||
let app_state = app_state();
|
let states = app_state();
|
||||||
let mut is_open_proxy_modal = false;
|
|
||||||
|
|
||||||
while let Ok(signal) = app_state.signal.receiver().recv_async().await {
|
while let Ok(signal) = states.signal().receiver().recv_async().await {
|
||||||
cx.update(|window, cx| {
|
view.update_in(cx, |this, window, cx| {
|
||||||
let registry = Registry::global(cx);
|
let registry = Registry::global(cx);
|
||||||
let settings = AppSettings::global(cx);
|
let settings = AppSettings::global(cx);
|
||||||
|
|
||||||
match signal {
|
match signal {
|
||||||
SignalKind::SignerSet(public_key) => {
|
SignalKind::SignerSet(public_key) => {
|
||||||
|
// Close the latest modal if it exists
|
||||||
window.close_modal(cx);
|
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
|
// Load user's settings
|
||||||
settings.update(cx, |this, cx| {
|
settings.update(cx, |this, cx| {
|
||||||
this.load_settings(cx);
|
this.load_settings(cx);
|
||||||
@@ -307,45 +227,33 @@ impl ChatSpace {
|
|||||||
this.set_signer_pubkey(public_key, cx);
|
this.set_signer_pubkey(public_key, cx);
|
||||||
this.load_rooms(window, cx);
|
this.load_rooms(window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Setup the default layout for current workspace
|
||||||
|
this.set_default_layout(window, cx);
|
||||||
}
|
}
|
||||||
SignalKind::SignerUnset => {
|
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
|
// Clear all current chat rooms
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.reset(cx);
|
this.reset(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Setup the onboarding layout for current workspace
|
||||||
|
this.set_onboarding_layout(window, cx);
|
||||||
}
|
}
|
||||||
SignalKind::Auth(req) => {
|
SignalKind::Auth(req) => {
|
||||||
let url = &req.url;
|
let url = &req.url;
|
||||||
let auto_auth = AppSettings::get_auto_auth(cx);
|
let auto_auth = AppSettings::get_auto_auth(cx);
|
||||||
let is_authenticated = AppSettings::read_global(cx).is_authenticated(url);
|
let is_authenticated = AppSettings::read_global(cx).is_authenticated(url);
|
||||||
|
|
||||||
view.update(cx, |this, cx| {
|
// Store the auth request in the current view
|
||||||
this.push_auth_request(&req, cx);
|
this.push_auth_request(&req, cx);
|
||||||
|
|
||||||
if auto_auth && is_authenticated {
|
if auto_auth && is_authenticated {
|
||||||
// Automatically authenticate if the relay is authenticated before
|
// Automatically authenticate if the relay is authenticated before
|
||||||
this.auth(req, window, cx);
|
this.auth(req, window, cx);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise open the auth request popup
|
// Otherwise open the auth request popup
|
||||||
this.open_auth_request(req, window, cx);
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SignalKind::GiftWrapStatus(status) => {
|
SignalKind::GiftWrapStatus(status) => {
|
||||||
@@ -364,17 +272,11 @@ impl ChatSpace {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
SignalKind::GossipRelaysNotFound => {
|
SignalKind::GossipRelaysNotFound => {
|
||||||
view.update(cx, |this, cx| {
|
this.set_required_gossip_relays(cx);
|
||||||
this.set_required_gossip_relays(cx);
|
this.render_setup_gossip_relays_modal(window, cx);
|
||||||
this.render_setup_gossip_relays_modal(window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
SignalKind::MessagingRelaysNotFound => {
|
SignalKind::MessagingRelaysNotFound => {
|
||||||
view.update(cx, |this, cx| {
|
this.set_required_dm_relays(cx);
|
||||||
this.set_required_dm_relays(cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -395,8 +297,8 @@ impl ChatSpace {
|
|||||||
self.sending_auth_request(&challenge, cx);
|
self.sending_auth_request(&challenge, cx);
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let states = app_state();
|
||||||
let app_state = app_state();
|
let client = states.client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
|
|
||||||
// Construct event
|
// Construct event
|
||||||
@@ -427,9 +329,9 @@ impl ChatSpace {
|
|||||||
relay.resubscribe().await?;
|
relay.resubscribe().await?;
|
||||||
|
|
||||||
// Get all failed events that need to be resent
|
// 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<EventId> = event_tracker
|
let ids: Vec<EventId> = tracker
|
||||||
.resend_queue
|
.resend_queue
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, url)| relay_url == *url)
|
.filter(|(_, url)| relay_url == *url)
|
||||||
@@ -437,7 +339,7 @@ impl ChatSpace {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for id in ids.into_iter() {
|
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? {
|
if let Some(event) = client.database().event_by_id(&id).await? {
|
||||||
let event_id = relay.send_event(&event).await?;
|
let event_id = relay.send_event(&event).await?;
|
||||||
|
|
||||||
@@ -447,8 +349,8 @@ impl ChatSpace {
|
|||||||
success: HashSet::from([relay_url]),
|
success: HashSet::from([relay_url]),
|
||||||
};
|
};
|
||||||
|
|
||||||
event_tracker.sent_ids.insert(event_id);
|
tracker.sent_ids.insert(event_id);
|
||||||
event_tracker.resent_ids.push(output);
|
tracker.resent_ids.push(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -656,7 +558,8 @@ impl ChatSpace {
|
|||||||
|
|
||||||
fn load_local_account(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn load_local_account(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let task = cx.background_spawn(async move {
|
let task = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
.identifier(ACCOUNT_IDENTIFIER)
|
.identifier(ACCOUNT_IDENTIFIER)
|
||||||
@@ -717,9 +620,10 @@ impl ChatSpace {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let states = app_state();
|
||||||
let app_state = app_state();
|
let client = states.client();
|
||||||
|
|
||||||
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let filter = Filter::new().kind(Kind::PrivateDirectMessage);
|
let filter = Filter::new().kind(Kind::PrivateDirectMessage);
|
||||||
|
|
||||||
let pubkeys: Vec<PublicKey> = client
|
let pubkeys: Vec<PublicKey> = client
|
||||||
@@ -737,7 +641,7 @@ impl ChatSpace {
|
|||||||
.authors(pubkeys);
|
.authors(pubkeys);
|
||||||
|
|
||||||
client
|
client
|
||||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, app_state.auto_close_opts)
|
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -756,8 +660,8 @@ impl ChatSpace {
|
|||||||
|
|
||||||
fn on_sign_out(&mut self, _e: &Logout, _window: &mut Window, cx: &mut Context<Self>) {
|
fn on_sign_out(&mut self, _e: &Logout, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let states = app_state();
|
||||||
let app_state = app_state();
|
let client = states.client();
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
@@ -770,7 +674,7 @@ impl ChatSpace {
|
|||||||
client.reset().await;
|
client.reset().await;
|
||||||
|
|
||||||
// Notify the channel about the signer being unset
|
// Notify the channel about the signer being unset
|
||||||
app_state.signal.send(SignalKind::SignerUnset).await;
|
states.signal().send(SignalKind::SignerUnset).await;
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
@@ -799,6 +703,37 @@ impl ChatSpace {
|
|||||||
window.push_notification(t!("common.copied"), cx);
|
window.push_notification(t!("common.copied"), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_all_panel_ids(&self, cx: &App) -> Option<Vec<u64>> {
|
||||||
|
let ids: Vec<u64> = self
|
||||||
|
.dock
|
||||||
|
.read(cx)
|
||||||
|
.items
|
||||||
|
.panel_ids(cx)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|panel| panel.parse::<u64>().ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Some(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_center_panel<P>(panel: P, window: &mut Window, cx: &mut App)
|
||||||
|
where
|
||||||
|
P: PanelView,
|
||||||
|
{
|
||||||
|
if let Some(Some(root)) = window.root::<Root>() {
|
||||||
|
if let Ok(chatspace) = root.read(cx).view().clone().downcast::<ChatSpace>() {
|
||||||
|
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) {
|
fn render_setup_gossip_relays_modal(&mut self, window: &mut Window, cx: &mut App) {
|
||||||
let relays = default_nip65_relays();
|
let relays = default_nip65_relays();
|
||||||
|
|
||||||
@@ -875,9 +810,9 @@ impl ChatSpace {
|
|||||||
.on_ok(|_, window, cx| {
|
.on_ok(|_, window, cx| {
|
||||||
window
|
window
|
||||||
.spawn(cx, async move |cx| {
|
.spawn(cx, async move |cx| {
|
||||||
let app_state = app_state();
|
let states = app_state();
|
||||||
let relays = default_nip65_relays();
|
let relays = default_nip65_relays();
|
||||||
let result = app_state.set_nip65(relays).await;
|
let result = states.set_nip65(relays).await;
|
||||||
|
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
match result {
|
match result {
|
||||||
@@ -977,9 +912,9 @@ impl ChatSpace {
|
|||||||
.on_ok(|_, window, cx| {
|
.on_ok(|_, window, cx| {
|
||||||
window
|
window
|
||||||
.spawn(cx, async move |cx| {
|
.spawn(cx, async move |cx| {
|
||||||
let app_state = app_state();
|
let states = app_state();
|
||||||
let relays = default_nip17_relays();
|
let relays = default_nip17_relays();
|
||||||
let result = app_state.set_nip17(relays).await;
|
let result = states.set_nip17(relays).await;
|
||||||
|
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
match result {
|
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<Self>) {
|
fn render_client_keys_modal(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
window.open_modal(cx, move |this, _window, cx| {
|
window.open_modal(cx, move |this, _window, cx| {
|
||||||
this.overlay_closable(false)
|
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::<Root>() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(chatspace) = root.read(cx).view().clone().downcast::<ChatSpace>() 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<Vec<u64>> {
|
|
||||||
let ids: Vec<u64> = self
|
|
||||||
.dock
|
|
||||||
.read(cx)
|
|
||||||
.items
|
|
||||||
.panel_ids(cx)
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|panel| panel.parse::<u64>().ok())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Some(ids)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_center_panel<P>(panel: P, window: &mut Window, cx: &mut App)
|
|
||||||
where
|
|
||||||
P: PanelView,
|
|
||||||
{
|
|
||||||
if let Some(Some(root)) = window.root::<Root>() {
|
|
||||||
if let Ok(chatspace) = root.read(cx).view().clone().downcast::<ChatSpace>() {
|
|
||||||
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 {
|
impl Render for ChatSpace {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use app_state::constants::{APP_ID, APP_NAME};
|
|
||||||
use app_state::{app_state, nostr_client};
|
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
point, px, size, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, SharedString,
|
point, px, size, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, SharedString,
|
||||||
TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
|
TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
|
||||||
WindowOptions,
|
WindowOptions,
|
||||||
};
|
};
|
||||||
|
use states::app_state;
|
||||||
|
use states::constants::{APP_ID, APP_NAME};
|
||||||
use ui::Root;
|
use ui::Root;
|
||||||
|
|
||||||
use crate::actions::{load_embedded_fonts, quit, Quit};
|
use crate::actions::{load_embedded_fonts, quit, Quit};
|
||||||
@@ -22,9 +22,6 @@ fn main() {
|
|||||||
// Initialize logging
|
// Initialize logging
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
// Initialize the Nostr client
|
|
||||||
let _client = nostr_client();
|
|
||||||
|
|
||||||
// Initialize the coop simple storage
|
// Initialize the coop simple storage
|
||||||
let _app_state = app_state();
|
let _app_state = app_state();
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Error;
|
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 client_keys::ClientKeys;
|
||||||
use common::display::RenderedProfile;
|
use common::display::RenderedProfile;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
@@ -17,6 +14,9 @@ use i18n::{shared_t, t};
|
|||||||
use nostr_connect::prelude::*;
|
use nostr_connect::prelude::*;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
|
use states::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT};
|
||||||
|
use states::state::SignalKind;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -28,7 +28,6 @@ use ui::popup_menu::PopupMenu;
|
|||||||
use ui::{h_flex, v_flex, ContextModal, Sizable, StyledExt};
|
use ui::{h_flex, v_flex, ContextModal, Sizable, StyledExt};
|
||||||
|
|
||||||
use crate::actions::CoopAuthUrlHandler;
|
use crate::actions::CoopAuthUrlHandler;
|
||||||
use crate::chatspace::ChatSpace;
|
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
profile: Profile,
|
profile: Profile,
|
||||||
@@ -43,7 +42,6 @@ pub struct Account {
|
|||||||
profile: Profile,
|
profile: Profile,
|
||||||
stored_secret: String,
|
stored_secret: String,
|
||||||
is_bunker: bool,
|
is_bunker: bool,
|
||||||
is_extension: bool,
|
|
||||||
loading: bool,
|
loading: bool,
|
||||||
|
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
@@ -57,8 +55,6 @@ pub struct Account {
|
|||||||
impl Account {
|
impl Account {
|
||||||
fn new(secret: String, profile: Profile, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(secret: String, profile: Profile, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let is_bunker = secret.starts_with("bunker://");
|
let is_bunker = secret.starts_with("bunker://");
|
||||||
let is_extension = secret.starts_with("extension");
|
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
@@ -74,7 +70,6 @@ impl Account {
|
|||||||
Self {
|
Self {
|
||||||
profile,
|
profile,
|
||||||
is_bunker,
|
is_bunker,
|
||||||
is_extension,
|
|
||||||
stored_secret: secret,
|
stored_secret: secret,
|
||||||
loading: false,
|
loading: false,
|
||||||
name: "Account".into(),
|
name: "Account".into(),
|
||||||
@@ -92,8 +87,6 @@ impl Account {
|
|||||||
if let Ok(uri) = NostrConnectURI::parse(&self.stored_secret) {
|
if let Ok(uri) = NostrConnectURI::parse(&self.stored_secret) {
|
||||||
self.nostr_connect(uri, window, cx);
|
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) {
|
} else if let Ok(enc) = EncryptedSecretKey::from_bech32(&self.stored_secret) {
|
||||||
self.keys(enc, window, cx);
|
self.keys(enc, window, cx);
|
||||||
} else {
|
} else {
|
||||||
@@ -115,7 +108,7 @@ impl Account {
|
|||||||
self._tasks.push(
|
self._tasks.push(
|
||||||
// Handle connection in the background
|
// Handle connection in the background
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
|
|
||||||
match signer.bunker_uri().await {
|
match signer.bunker_uri().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -134,10 +127,6 @@ impl Account {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_proxy(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
ChatSpace::proxy_signer(window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keys(&mut self, enc: EncryptedSecretKey, window: &mut Window, cx: &mut Context<Self>) {
|
fn keys(&mut self, enc: EncryptedSecretKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let pwd_input: Entity<InputState> = cx.new(|cx| InputState::new(window, cx).masked(true));
|
let pwd_input: Entity<InputState> = cx.new(|cx| InputState::new(window, cx).masked(true));
|
||||||
let weak_input = pwd_input.downgrade();
|
let weak_input = pwd_input.downgrade();
|
||||||
@@ -245,7 +234,7 @@ impl Account {
|
|||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let keys = Keys::new(secret);
|
let keys = Keys::new(secret);
|
||||||
|
|
||||||
// Set the client's signer with the current keys
|
// Set the client's signer with the current keys
|
||||||
@@ -268,8 +257,8 @@ impl Account {
|
|||||||
self._tasks.push(
|
self._tasks.push(
|
||||||
// Reset the nostr client in the background
|
// Reset the nostr client in the background
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let states = app_state();
|
||||||
let app_state = app_state();
|
let client = states.client();
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
@@ -282,7 +271,7 @@ impl Account {
|
|||||||
client.unset_signer().await;
|
client.unset_signer().await;
|
||||||
|
|
||||||
// Notify the channel about the signer being unset
|
// 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(Avatar::new(avatar).size(rems(1.5)))
|
||||||
.child(div().pb_px().font_semibold().child(name)),
|
.child(div().pb_px().font_semibold().child(name)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(div().when(self.is_bunker, |this| {
|
||||||
div()
|
let label = SharedString::from("Nostr Connect");
|
||||||
.when(self.is_bunker, |this| {
|
|
||||||
let label = SharedString::from("Nostr Connect");
|
|
||||||
|
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.py_0p5()
|
.py_0p5()
|
||||||
.px_2()
|
.px_2()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.bg(cx.theme().secondary_active)
|
.bg(cx.theme().secondary_active)
|
||||||
.text_color(
|
.text_color(cx.theme().secondary_foreground)
|
||||||
cx.theme().secondary_foreground,
|
.rounded_full()
|
||||||
)
|
.child(label),
|
||||||
.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),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.active(|this| this.bg(cx.theme().element_active))
|
.active(|this| this.bg(cx.theme().element_active))
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use app_state::{app_state, nostr_client};
|
|
||||||
use common::display::{RenderedProfile, RenderedTimestamp};
|
use common::display::{RenderedProfile, RenderedTimestamp};
|
||||||
use common::nip96::nip96_upload;
|
use common::nip96::nip96_upload;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
@@ -24,6 +23,7 @@ use serde::Deserialize;
|
|||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
|
use states::app_state;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::actions::{CopyPublicKey, OpenPublicKey};
|
use ui::actions::{CopyPublicKey, OpenPublicKey};
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
@@ -169,8 +169,8 @@ impl Chat {
|
|||||||
let message = Message::user(event);
|
let message = Message::user(event);
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let app_state = app_state();
|
let states = app_state();
|
||||||
let event_tracker = app_state.event_tracker.read().await;
|
let event_tracker = states.tracker().read().await;
|
||||||
let sent_ids = event_tracker.sent_ids();
|
let sent_ids = event_tracker.sent_ids();
|
||||||
|
|
||||||
this.update_in(cx, |this, _window, cx| {
|
this.update_in(cx, |this, _window, cx| {
|
||||||
@@ -530,7 +530,7 @@ impl Chat {
|
|||||||
let path = paths.pop()?;
|
let path = paths.pop()?;
|
||||||
|
|
||||||
let upload = Tokio::spawn(cx, async move {
|
let upload = Tokio::spawn(cx, async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let file = fs::read(path).await.ok()?;
|
let file = fs::read(path).await.ok()?;
|
||||||
let url = nip96_upload(client, &nip96_server, file).await.ok()?;
|
let url = nip96_upload(client, &nip96_server, file).await.ok()?;
|
||||||
|
|
||||||
@@ -1239,9 +1239,9 @@ impl Chat {
|
|||||||
let id = ev.0;
|
let id = ev.0;
|
||||||
|
|
||||||
let task: Task<Result<Vec<RelayUrl>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Vec<RelayUrl>, Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let states = app_state();
|
||||||
let app_state = app_state();
|
let client = states.client();
|
||||||
let event_tracker = app_state.event_tracker.read().await;
|
let event_tracker = states.tracker().read().await;
|
||||||
let mut relays: Vec<RelayUrl> = vec![];
|
let mut relays: Vec<RelayUrl> = vec![];
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ use std::ops::Range;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use app_state::constants::BOOTSTRAP_RELAYS;
|
|
||||||
use app_state::{app_state, nostr_client};
|
|
||||||
use common::display::{RenderedProfile, TextUtils};
|
use common::display::{RenderedProfile, TextUtils};
|
||||||
use common::nip05::nip05_profile;
|
use common::nip05::nip05_profile;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
@@ -19,6 +17,8 @@ use registry::room::Room;
|
|||||||
use registry::Registry;
|
use registry::Registry;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
|
use states::constants::BOOTSTRAP_RELAYS;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -129,7 +129,7 @@ impl Compose {
|
|||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
let get_contacts: Task<Result<Vec<Contact>, Error>> = cx.background_spawn(async move {
|
let get_contacts: Task<Result<Vec<Contact>, Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
let profiles = client.database().contacts(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> {
|
async fn request_metadata(public_key: PublicKey) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
let states = app_state();
|
||||||
let app_state = app_state();
|
let client = states.client();
|
||||||
|
|
||||||
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
||||||
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
||||||
|
|
||||||
client
|
client
|
||||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, app_state.auto_close_opts)
|
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::str::FromStr;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use app_state::nostr_client;
|
|
||||||
use common::nip96::nip96_upload;
|
use common::nip96::nip96_upload;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -13,6 +12,7 @@ use i18n::{shared_t, t};
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
|
use states::app_state;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::input::{InputState, TextInput};
|
use ui::input::{InputState, TextInput};
|
||||||
@@ -58,7 +58,7 @@ impl EditProfile {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let task: Task<Result<Option<Metadata>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Option<Metadata>, Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
let metadata = client
|
let metadata = client
|
||||||
@@ -125,7 +125,9 @@ impl EditProfile {
|
|||||||
let (tx, rx) = oneshot::channel::<Url>();
|
let (tx, rx) = oneshot::channel::<Url>();
|
||||||
|
|
||||||
nostr_sdk::async_utility::task::spawn(async move {
|
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);
|
_ = tx.send(url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -188,7 +190,7 @@ impl EditProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
|
|
||||||
// Sign the new metadata event
|
// Sign the new metadata event
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use app_state::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT};
|
|
||||||
use app_state::nostr_client;
|
|
||||||
use client_keys::ClientKeys;
|
use client_keys::ClientKeys;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -12,6 +10,8 @@ use gpui::{
|
|||||||
use i18n::{shared_t, t};
|
use i18n::{shared_t, t};
|
||||||
use nostr_connect::prelude::*;
|
use nostr_connect::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
|
use states::constants::{ACCOUNT_IDENTIFIER, BUNKER_TIMEOUT};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||||
@@ -248,7 +248,7 @@ impl Login {
|
|||||||
|
|
||||||
// Set the client's signer with the current keys
|
// Set the client's signer with the current keys
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
client.set_signer(keys).await;
|
client.set_signer(keys).await;
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
@@ -331,7 +331,7 @@ impl Login {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
|
|
||||||
// Update the client's signer
|
// Update the client's signer
|
||||||
client.set_signer(signer).await;
|
client.set_signer(signer).await;
|
||||||
@@ -362,7 +362,7 @@ impl Login {
|
|||||||
if let Ok(enc_key) =
|
if let Ok(enc_key) =
|
||||||
EncryptedSecretKey::new(keys.secret_key(), &password, 8, KeySecurity::Unknown)
|
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 value = enc_key.to_bech32().unwrap();
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)];
|
let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)];
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use anyhow::{anyhow, Error};
|
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 common::nip96::nip96_upload;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, relative, rems, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity,
|
div, relative, rems, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity,
|
||||||
@@ -12,6 +10,8 @@ use i18n::{shared_t, t};
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
|
use states::constants::{ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS};
|
||||||
|
use states::{app_state, default_nip17_relays, default_nip65_relays};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -124,7 +124,7 @@ impl NewAccount {
|
|||||||
|
|
||||||
// Set the client's signer with the current keys
|
// Set the client's signer with the current keys
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
|
|
||||||
// Set the client's signer with the current keys
|
// Set the client's signer with the current keys
|
||||||
client.set_signer(keys).await;
|
client.set_signer(keys).await;
|
||||||
@@ -176,7 +176,7 @@ impl NewAccount {
|
|||||||
if let Ok(enc_key) =
|
if let Ok(enc_key) =
|
||||||
EncryptedSecretKey::new(keys.secret_key(), &password, 8, KeySecurity::Unknown)
|
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 value = enc_key.to_bech32().unwrap();
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)];
|
let tags = vec![Tag::identifier(ACCOUNT_IDENTIFIER)];
|
||||||
@@ -217,7 +217,7 @@ impl NewAccount {
|
|||||||
Ok(Some(mut paths)) => {
|
Ok(Some(mut paths)) => {
|
||||||
if let Some(path) = paths.pop() {
|
if let Some(path) = paths.pop() {
|
||||||
let file = fs::read(path).await?;
|
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)
|
Ok(url)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
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 client_keys::ClientKeys;
|
||||||
use common::display::TextUtils;
|
use common::display::TextUtils;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
@@ -16,6 +12,8 @@ use gpui::{
|
|||||||
use i18n::{shared_t, t};
|
use i18n::{shared_t, t};
|
||||||
use nostr_connect::prelude::*;
|
use nostr_connect::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
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 theme::ActiveTheme;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||||
@@ -23,7 +21,7 @@ use ui::notification::Notification;
|
|||||||
use ui::popup_menu::PopupMenu;
|
use ui::popup_menu::PopupMenu;
|
||||||
use ui::{divider, h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt};
|
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> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Onboarding> {
|
||||||
Onboarding::new(window, cx)
|
Onboarding::new(window, cx)
|
||||||
@@ -163,10 +161,6 @@ impl Onboarding {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_proxy(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
ChatSpace::proxy_signer(window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_uri_to_disk(
|
fn write_uri_to_disk(
|
||||||
&mut self,
|
&mut self,
|
||||||
signer: NostrConnect,
|
signer: NostrConnect,
|
||||||
@@ -181,7 +175,7 @@ impl Onboarding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
|
|
||||||
// Update the client's signer
|
// Update the client's signer
|
||||||
client.set_signer(signer).await;
|
client.set_signer(signer).await;
|
||||||
@@ -348,30 +342,11 @@ impl Render for Onboarding {
|
|||||||
.child(
|
.child(
|
||||||
Button::new("key")
|
Button::new("key")
|
||||||
.label(t!("onboarding.key_login"))
|
.label(t!("onboarding.key_login"))
|
||||||
|
.large()
|
||||||
.ghost_alt()
|
.ghost_alt()
|
||||||
.on_click(cx.listener(move |_, _, window, cx| {
|
.on_click(cx.listener(move |_, _, window, cx| {
|
||||||
chatspace::login(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")),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use app_state::constants::BOOTSTRAP_RELAYS;
|
|
||||||
use app_state::nostr_client;
|
|
||||||
use common::display::{shorten_pubkey, RenderedProfile, RenderedTimestamp};
|
use common::display::{shorten_pubkey, RenderedProfile, RenderedTimestamp};
|
||||||
use common::nip05::nip05_verify;
|
use common::nip05::nip05_verify;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
@@ -15,6 +13,8 @@ use nostr_sdk::prelude::*;
|
|||||||
use registry::Registry;
|
use registry::Registry;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
|
use states::constants::BOOTSTRAP_RELAYS;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -43,7 +43,7 @@ impl Screening {
|
|||||||
|
|
||||||
let contact_check: Task<Result<(bool, Vec<Profile>), Error>> =
|
let contact_check: Task<Result<(bool, Vec<Profile>), Error>> =
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let signer_pubkey = signer.get_public_key().await?;
|
let signer_pubkey = signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ impl Screening {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let activity_check = cx.background_spawn(async move {
|
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 filter = Filter::new().author(public_key).limit(1);
|
||||||
let mut activity: Option<Timestamp> = None;
|
let mut activity: Option<Timestamp> = None;
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ impl Screening {
|
|||||||
let public_key = self.profile.public_key();
|
let public_key = self.profile.public_key();
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
|
|
||||||
let tag = Tag::public_key_report(public_key, Report::Impersonation);
|
let tag = Tag::public_key_report(public_key, Report::Impersonation);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::collections::HashSet;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use app_state::{app_state, nostr_client};
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, uniform_list, App, AppContext, AsyncWindowContext, Context, Entity,
|
div, px, uniform_list, App, AppContext, AsyncWindowContext, Context, Entity,
|
||||||
@@ -10,8 +9,10 @@ use gpui::{
|
|||||||
Task, TextAlign, UniformList, Window,
|
Task, TextAlign, UniformList, Window,
|
||||||
};
|
};
|
||||||
use i18n::{shared_t, t};
|
use i18n::{shared_t, t};
|
||||||
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::input::{InputEvent, InputState, TextInput};
|
use ui::input::{InputEvent, InputState, TextInput};
|
||||||
@@ -80,7 +81,7 @@ impl SetupRelay {
|
|||||||
|
|
||||||
fn load(cx: &AsyncWindowContext) -> Task<Result<Vec<RelayUrl>, Error>> {
|
fn load(cx: &AsyncWindowContext) -> Task<Result<Vec<RelayUrl>, Error>> {
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -152,8 +153,8 @@ impl SetupRelay {
|
|||||||
let relays = self.relays.clone();
|
let relays = self.relays.clone();
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let app_state = app_state();
|
let states = app_state();
|
||||||
let client = nostr_client();
|
let client = states.client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -177,16 +178,10 @@ impl SetupRelay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch gift wrap events
|
// Fetch gift wrap events
|
||||||
let sub_id = app_state.gift_wrap_sub_id.clone();
|
states
|
||||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
.get_messages(public_key, &relays.into_iter().collect_vec())
|
||||||
|
|
||||||
if client
|
|
||||||
.subscribe_with_id_to(relays.clone(), sub_id, filter, None)
|
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.ok();
|
||||||
{
|
|
||||||
log::info!("Subscribed to messages in: {relays:?}");
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ use std::ops::Range;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
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::debounced_delay::DebouncedDelay;
|
||||||
use common::display::{RenderedTimestamp, TextUtils};
|
use common::display::{RenderedTimestamp, TextUtils};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
@@ -23,6 +20,9 @@ use registry::room::{Room, RoomKind};
|
|||||||
use registry::{Registry, RegistryEvent};
|
use registry::{Registry, RegistryEvent};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
|
use states::constants::{BOOTSTRAP_RELAYS, SEARCH_RELAYS};
|
||||||
|
use states::state::UnwrappingStatus;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||||
@@ -140,7 +140,7 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn request_metadata(public_key: PublicKey) -> Result<(), Error> {
|
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 opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
||||||
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
||||||
@@ -165,7 +165,7 @@ impl Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn nip50(query: &str) -> Result<BTreeSet<Room>, Error> {
|
async fn nip50(query: &str) -> Result<BTreeSet<Room>, Error> {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().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<Self>) {
|
fn on_manage(&mut self, _ev: &RelayStatus, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let task: Task<Result<Vec<Relay>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Vec<Relay>, Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let app_state = app_state();
|
let subscription = client.subscription(&SubscriptionId::new("inbox")).await;
|
||||||
let subscription = client.subscription(&app_state.gift_wrap_sub_id).await;
|
|
||||||
let mut relays: Vec<Relay> = vec![];
|
let mut relays: Vec<Relay> = vec![];
|
||||||
|
|
||||||
for (url, _filter) in subscription.into_iter() {
|
for (url, _filter) in subscription.into_iter() {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use app_state::nostr_client;
|
|
||||||
use common::display::RenderedProfile;
|
use common::display::RenderedProfile;
|
||||||
use common::nip05::nip05_verify;
|
use common::nip05::nip05_verify;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
@@ -14,6 +13,7 @@ use nostr_sdk::prelude::*;
|
|||||||
use registry::Registry;
|
use registry::Registry;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
@@ -39,7 +39,7 @@ impl UserProfile {
|
|||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
let check_follow: Task<Result<bool, Error>> = cx.background_spawn(async move {
|
let check_follow: Task<Result<bool, Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
let contact_list = client.database().contacts_public_keys(public_key).await?;
|
let contact_list = client.database().contacts_public_keys(public_key).await?;
|
||||||
|
|||||||
@@ -6,16 +6,19 @@ publish.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
app_state = { path = "../app_state" }
|
states = { path = "../states" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
nostr.workspace = true
|
nostr.workspace = true
|
||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
|
nostr-lmdb.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
flume.workspace = true
|
||||||
|
|
||||||
fuzzy-matcher = "0.3.7"
|
fuzzy-matcher = "0.3.7"
|
||||||
|
rustls = "0.23.23"
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ use std::cmp::Reverse;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use app_state::nostr_client;
|
|
||||||
use app_state::state::UnwrappingStatus;
|
|
||||||
use common::event::EventUtils;
|
use common::event::EventUtils;
|
||||||
use fuzzy_matcher::skim::SkimMatcherV2;
|
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||||
use fuzzy_matcher::FuzzyMatcher;
|
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 itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use room::RoomKind;
|
use room::RoomKind;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
|
use states::state::UnwrappingStatus;
|
||||||
|
|
||||||
use crate::room::Room;
|
use crate::room::Room;
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ pub enum RegistryEvent {
|
|||||||
NewRequest(RoomKind),
|
NewRequest(RoomKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main registry for managing chat rooms and user profiles
|
#[derive(Debug)]
|
||||||
pub struct Registry {
|
pub struct Registry {
|
||||||
/// Collection of all chat rooms
|
/// Collection of all chat rooms
|
||||||
pub rooms: Vec<Entity<Room>>,
|
pub rooms: Vec<Entity<Room>>,
|
||||||
@@ -45,7 +47,7 @@ pub struct Registry {
|
|||||||
/// Status of the unwrapping process
|
/// Status of the unwrapping process
|
||||||
pub unwrapping_status: Entity<UnwrappingStatus>,
|
pub unwrapping_status: Entity<UnwrappingStatus>,
|
||||||
|
|
||||||
/// Public key of the currently activated signer
|
/// Public Key of the currently activated signer
|
||||||
signer_pubkey: Option<PublicKey>,
|
signer_pubkey: Option<PublicKey>,
|
||||||
|
|
||||||
/// Tasks for asynchronous operations
|
/// Tasks for asynchronous operations
|
||||||
@@ -55,51 +57,40 @@ pub struct Registry {
|
|||||||
impl EventEmitter<RegistryEvent> for Registry {}
|
impl EventEmitter<RegistryEvent> for Registry {}
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
/// Retrieve the Global Registry state
|
/// Retrieve the global registry state
|
||||||
pub fn global(cx: &App) -> Entity<Self> {
|
pub fn global(cx: &App) -> Entity<Self> {
|
||||||
cx.global::<GlobalRegistry>().0.clone()
|
cx.global::<GlobalRegistry>().0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the Registry instance
|
/// Retrieve the registry instance
|
||||||
pub fn read_global(cx: &App) -> &Self {
|
pub fn read_global(cx: &App) -> &Self {
|
||||||
cx.global::<GlobalRegistry>().0.read(cx)
|
cx.global::<GlobalRegistry>().0.read(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the global Registry instance
|
/// Set the global registry instance
|
||||||
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
|
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
|
||||||
cx.set_global(GlobalRegistry(state));
|
cx.set_global(GlobalRegistry(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Registry instance
|
/// Create a new registry instance
|
||||||
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
|
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
|
||||||
let unwrapping_status = cx.new(|_| UnwrappingStatus::default());
|
let unwrapping_status = cx.new(|_| UnwrappingStatus::default());
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
let load_local_persons: Task<Result<Vec<Profile>, 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(
|
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| {
|
cx.spawn(async move |this, cx| {
|
||||||
if let Ok(profiles) = load_local_persons.await {
|
match Self::load_persons(cx).await {
|
||||||
this.update(cx, |this, cx| {
|
Ok(profiles) => {
|
||||||
this.set_persons(profiles, cx);
|
this.update(cx, |this, cx| {
|
||||||
})
|
this.set_persons(profiles, cx);
|
||||||
.ok();
|
})
|
||||||
}
|
.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<Result<Vec<Profile>, 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.
|
/// Returns the public key of the currently activated signer.
|
||||||
pub fn signer_pubkey(&self) -> Option<PublicKey> {
|
pub fn signer_pubkey(&self) -> Option<PublicKey> {
|
||||||
self.signer_pubkey
|
self.signer_pubkey
|
||||||
@@ -269,7 +279,7 @@ impl Registry {
|
|||||||
let bypass_setting = AppSettings::get_contact_bypass(cx);
|
let bypass_setting = AppSettings::get_contact_bypass(cx);
|
||||||
|
|
||||||
let task: Task<Result<HashSet<Room>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<HashSet<Room>, Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
let contacts = client.database().contacts_public_keys(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| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
match task.await {
|
match task.await {
|
||||||
Ok(rooms) => {
|
Ok(rooms) => {
|
||||||
this.update_in(cx, move |_, window, cx| {
|
this.update_in(cx, move |this, _window, cx| {
|
||||||
cx.defer_in(window, move |this, _window, cx| {
|
this.extend_rooms(rooms, cx);
|
||||||
this.extend_rooms(rooms, cx);
|
this.sort(cx);
|
||||||
this.sort(cx);
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ use std::hash::{Hash, Hasher};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use app_state::constants::SEND_RETRY;
|
|
||||||
use app_state::{app_state, nostr_client};
|
|
||||||
use common::display::RenderedProfile;
|
use common::display::RenderedProfile;
|
||||||
use common::event::EventUtils;
|
use common::event::EventUtils;
|
||||||
use gpui::{App, AppContext, Context, EventEmitter, SharedString, SharedUri, Task};
|
use gpui::{App, AppContext, Context, EventEmitter, SharedString, SharedUri, Task};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use states::app_state;
|
||||||
|
use states::constants::SEND_RETRY;
|
||||||
|
|
||||||
use crate::Registry;
|
use crate::Registry;
|
||||||
|
|
||||||
@@ -171,9 +171,9 @@ impl From<&UnsignedEvent> for Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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<String>, receivers: Vec<PublicKey>) -> Result<Self, Error> {
|
pub async fn new(subject: Option<String>, receivers: Vec<PublicKey>) -> Result<Self, Error> {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ impl Room {
|
|||||||
let members = self.members.clone();
|
let members = self.members.clone();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -363,11 +363,11 @@ impl Room {
|
|||||||
let members = self.members.clone();
|
let members = self.members.clone();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
let sent_ids: Vec<EventId> = app_state()
|
let sent_ids: Vec<EventId> = app_state()
|
||||||
.event_tracker
|
.tracker()
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.sent_ids()
|
.sent_ids()
|
||||||
@@ -482,8 +482,8 @@ impl Room {
|
|||||||
let mut members = self.members.clone();
|
let mut members = self.members.clone();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let app_state = app_state();
|
let states = app_state();
|
||||||
let client = nostr_client();
|
let client = states.client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -514,7 +514,7 @@ impl Room {
|
|||||||
if auth_required {
|
if auth_required {
|
||||||
// Wait for authenticated and resent event successfully
|
// Wait for authenticated and resent event successfully
|
||||||
for attempt in 0..=SEND_RETRY {
|
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();
|
let ids = retry_manager.resent_ids();
|
||||||
|
|
||||||
// Check if event was successfully resent
|
// Check if event was successfully resent
|
||||||
@@ -579,7 +579,7 @@ impl Room {
|
|||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Task<Result<Vec<SendReport>, Error>> {
|
) -> Task<Result<Vec<SendReport>, Error>> {
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let mut resend_reports = vec![];
|
let mut resend_reports = vec![];
|
||||||
|
|
||||||
for report in reports.into_iter() {
|
for report in reports.into_iter() {
|
||||||
@@ -633,7 +633,7 @@ impl Room {
|
|||||||
|
|
||||||
/// Gets messaging relays for public key
|
/// Gets messaging relays for public key
|
||||||
async fn messaging_relays(public_key: PublicKey) -> Vec<RelayUrl> {
|
async fn messaging_relays(public_key: PublicKey) -> Vec<RelayUrl> {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let mut relay_urls = vec![];
|
let mut relay_urls = vec![];
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ edition.workspace = true
|
|||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
app_state = { path = "../app_state" }
|
states = { path = "../states" }
|
||||||
|
|
||||||
nostr-sdk.workspace = true
|
nostr-sdk.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use app_state::constants::SETTINGS_IDENTIFIER;
|
|
||||||
use app_state::nostr_client;
|
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use states::app_state;
|
||||||
|
use states::constants::SETTINGS_IDENTIFIER;
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
let state = cx.new(AppSettings::new);
|
let state = cx.new(AppSettings::new);
|
||||||
@@ -121,7 +121,7 @@ impl AppSettings {
|
|||||||
|
|
||||||
pub fn load_settings(&self, cx: &mut Context<Self>) {
|
pub fn load_settings(&self, cx: &mut Context<Self>) {
|
||||||
let task: Task<Result<Settings, anyhow::Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Settings, anyhow::Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ impl AppSettings {
|
|||||||
pub fn set_settings(&self, cx: &mut Context<Self>) {
|
pub fn set_settings(&self, cx: &mut Context<Self>) {
|
||||||
if let Ok(content) = serde_json::to_string(&self.setting_values) {
|
if let Ok(content) = serde_json::to_string(&self.setting_values) {
|
||||||
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), anyhow::Error>> = cx.background_spawn(async move {
|
||||||
let client = nostr_client();
|
let client = app_state().client();
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>NIP-07 Proxy</title>
|
|
||||||
<link rel="stylesheet" href="style.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>NIP-07 Proxy</h1>
|
|
||||||
<p>
|
|
||||||
This page acts as a proxy between your native application and
|
|
||||||
the NIP-07 browser extension.
|
|
||||||
</p>
|
|
||||||
<div class="status-box">
|
|
||||||
<strong>Status:</strong> <span id="status">Checking...</span>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<small
|
|
||||||
>Keep this tab open while using your application. The page
|
|
||||||
will automatically poll for requests from your native
|
|
||||||
app.</small
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Debug Info</h3>
|
|
||||||
<p>
|
|
||||||
<small
|
|
||||||
>Check the browser console (F12) for detailed logs.</small
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="proxy.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
@@ -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<io::Error> for Error {
|
|
||||||
fn from(e: io::Error) -> Self {
|
|
||||||
Self::Io(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<http::Error> for Error {
|
|
||||||
fn from(e: http::Error) -> Self {
|
|
||||||
Self::Http(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_json::Error> for Error {
|
|
||||||
fn from(e: serde_json::Error) -> Self {
|
|
||||||
Self::Json(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<event::Error> for Error {
|
|
||||||
fn from(e: event::Error) -> Self {
|
|
||||||
Self::Event(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RecvError> for Error {
|
|
||||||
fn from(e: RecvError) -> Self {
|
|
||||||
Self::OneShotRecv(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<TcpStream> compatible with hyper
|
|
||||||
struct HyperIo<T> {
|
|
||||||
inner: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> HyperIo<T> {
|
|
||||||
fn new(inner: T) -> Self {
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsyncRead + Unpin> hyper::rt::Read for HyperIo<T> {
|
|
||||||
fn poll_read(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
mut buf: hyper::rt::ReadBufCursor<'_>,
|
|
||||||
) -> Poll<Result<(), std::io::Error>> {
|
|
||||||
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<T: AsyncWrite + Unpin> hyper::rt::Write for HyperIo<T> {
|
|
||||||
fn poll_write(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
buf: &[u8],
|
|
||||||
) -> Poll<Result<usize, std::io::Error>> {
|
|
||||||
Pin::new(&mut self.inner).poll_write(cx, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_flush(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Result<(), std::io::Error>> {
|
|
||||||
Pin::new(&mut self.inner).poll_flush(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_shutdown(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Result<(), std::io::Error>> {
|
|
||||||
Pin::new(&mut self.inner).poll_close(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PendingResponseMap = HashMap<Uuid, oneshot::Sender<Result<Value, String>>>;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Message {
|
|
||||||
id: Uuid,
|
|
||||||
error: Option<String>,
|
|
||||||
result: Option<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message {
|
|
||||||
fn into_result(self) -> Result<Value, String> {
|
|
||||||
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
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<Vec<RequestData>>,
|
|
||||||
/// Map of request ID to response sender
|
|
||||||
pub pending_responses: Mutex<PendingResponseMap>,
|
|
||||||
/// Last time the client ask for the pending requests
|
|
||||||
pub last_pending_request: Arc<AtomicU64>,
|
|
||||||
/// 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<ProxyState>,
|
|
||||||
/// Flag to indicate if the server is shutdown
|
|
||||||
is_shutdown: Arc<AtomicBool>,
|
|
||||||
/// Flat indicating if the server is started
|
|
||||||
is_started: Arc<AtomicBool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<InnerBrowserSignerProxy>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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::<TcpListener>::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<ProxyState> = 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<ProxyState> = 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<Result<Value, String>>) {
|
|
||||||
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<T>(&self, method: RequestMethod, params: Value) -> Result<T, Error>
|
|
||||||
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<PublicKey, Error> {
|
|
||||||
self.request(RequestMethod::GetPublicKey, json!({})).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
async fn _sign_event(&self, event: UnsignedEvent) -> Result<Event, Error> {
|
|
||||||
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<String, Error> {
|
|
||||||
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<String, Error> {
|
|
||||||
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<String, Error> {
|
|
||||||
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<String, Error> {
|
|
||||||
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<Result<PublicKey, SignerError>> {
|
|
||||||
Box::pin(async move { self._get_public_key().await.map_err(SignerError::backend) })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn sign_event(&self, unsigned: UnsignedEvent) -> BoxedFuture<Result<Event, SignerError>> {
|
|
||||||
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<String, SignerError>> {
|
|
||||||
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<String, SignerError>> {
|
|
||||||
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<String, SignerError>> {
|
|
||||||
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<String, SignerError>> {
|
|
||||||
Box::pin(async move {
|
|
||||||
self._nip44_decrypt(public_key, payload)
|
|
||||||
.await
|
|
||||||
.map_err(SignerError::backend)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_request(
|
|
||||||
req: Request<Incoming>,
|
|
||||||
state: Arc<ProxyState>,
|
|
||||||
) -> Result<Response<BoxBody<Bytes, Error>>, 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<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, Error> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "app_state"
|
name = "states"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use nostr_lmdb::NostrLMDB;
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use paths::nostr_file;
|
|
||||||
|
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
@@ -12,7 +9,6 @@ pub mod paths;
|
|||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
static APP_STATE: OnceLock<AppState> = OnceLock::new();
|
static APP_STATE: OnceLock<AppState> = OnceLock::new();
|
||||||
static NOSTR_CLIENT: OnceLock<Client> = OnceLock::new();
|
|
||||||
static NIP65_RELAYS: OnceLock<Vec<(RelayUrl, Option<RelayMetadata>)>> = OnceLock::new();
|
static NIP65_RELAYS: OnceLock<Vec<(RelayUrl, Option<RelayMetadata>)>> = OnceLock::new();
|
||||||
static NIP17_RELAYS: OnceLock<Vec<RelayUrl>> = OnceLock::new();
|
static NIP17_RELAYS: OnceLock<Vec<RelayUrl>> = OnceLock::new();
|
||||||
|
|
||||||
@@ -21,31 +17,7 @@ pub fn app_state() -> &'static AppState {
|
|||||||
APP_STATE.get_or_init(AppState::new)
|
APP_STATE.get_or_init(AppState::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the nostr client.
|
/// Default NIP-65 Relays. Used for new account
|
||||||
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
|
|
||||||
pub fn default_nip65_relays() -> &'static Vec<(RelayUrl, Option<RelayMetadata>)> {
|
pub fn default_nip65_relays() -> &'static Vec<(RelayUrl, Option<RelayMetadata>)> {
|
||||||
NIP65_RELAYS.get_or_init(|| {
|
NIP65_RELAYS.get_or_init(|| {
|
||||||
vec![
|
vec![
|
||||||
@@ -71,7 +43,7 @@ pub fn default_nip65_relays() -> &'static Vec<(RelayUrl, Option<RelayMetadata>)>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default NIP17 Relays. Used for new account
|
/// Default NIP-17 Relays. Used for new account
|
||||||
pub fn default_nip17_relays() -> &'static Vec<RelayUrl> {
|
pub fn default_nip17_relays() -> &'static Vec<RelayUrl> {
|
||||||
NIP17_RELAYS.get_or_init(|| {
|
NIP17_RELAYS.get_or_init(|| {
|
||||||
vec![
|
vec![
|
||||||
@@ -5,14 +5,16 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use flume::{Receiver, Sender};
|
use flume::{Receiver, Sender};
|
||||||
|
use nostr_lmdb::NostrLMDB;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smol::lock::RwLock;
|
use smol::lock::RwLock;
|
||||||
|
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, SEARCH_RELAYS,
|
BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, SEARCH_RELAYS,
|
||||||
};
|
};
|
||||||
use crate::nostr_client;
|
use crate::paths::config_dir;
|
||||||
use crate::paths::support_dir;
|
|
||||||
|
const TIMEOUT: u64 = 5;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct AuthRequest {
|
pub struct AuthRequest {
|
||||||
@@ -51,9 +53,6 @@ pub enum SignalKind {
|
|||||||
/// A signal to notify UI that the relay requires authentication
|
/// A signal to notify UI that the relay requires authentication
|
||||||
Auth(AuthRequest),
|
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
|
/// A signal to notify UI that a new profile has been received
|
||||||
NewProfile(Profile),
|
NewProfile(Profile),
|
||||||
|
|
||||||
@@ -70,7 +69,7 @@ pub enum SignalKind {
|
|||||||
GiftWrapStatus(UnwrappingStatus),
|
GiftWrapStatus(UnwrappingStatus),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Signal {
|
pub struct Signal {
|
||||||
rx: Receiver<SignalKind>,
|
rx: Receiver<SignalKind>,
|
||||||
tx: Sender<SignalKind>,
|
tx: Sender<SignalKind>,
|
||||||
@@ -103,7 +102,7 @@ impl Signal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Ingester {
|
pub struct Ingester {
|
||||||
rx: Receiver<PublicKey>,
|
rx: Receiver<PublicKey>,
|
||||||
tx: Sender<PublicKey>,
|
tx: Sender<PublicKey>,
|
||||||
@@ -165,32 +164,25 @@ impl EventTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple storage to store all states that using across the application.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
/// A client to interact with Nostr
|
||||||
|
client: Client,
|
||||||
|
|
||||||
|
/// Tracks activity related to Nostr events
|
||||||
|
event_tracker: RwLock<EventTracker>,
|
||||||
|
|
||||||
|
/// 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.
|
/// The timestamp when the application was initialized.
|
||||||
pub initialized_at: Timestamp,
|
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.
|
/// Whether gift wrap processing is in progress.
|
||||||
pub gift_wrap_processing: AtomicBool,
|
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<SubscribeAutoCloseOptions>,
|
|
||||||
|
|
||||||
/// Tracks activity related to Nostr events
|
|
||||||
pub event_tracker: RwLock<EventTracker>,
|
|
||||||
|
|
||||||
/// Signal channel for communication between Nostr and GPUI
|
|
||||||
pub signal: Signal,
|
|
||||||
|
|
||||||
/// Ingester channel for processing public keys
|
|
||||||
pub ingester: Ingester,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppState {
|
impl Default for AppState {
|
||||||
@@ -201,28 +193,127 @@ impl Default for AppState {
|
|||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let first_run = Self::first_run();
|
// rustls uses the `aws_lc_rs` provider by default
|
||||||
let initialized_at = Timestamp::now();
|
// This only errors if the default provider has already
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
// 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 signal = Signal::default();
|
||||||
let ingester = Ingester::default();
|
let ingester = Ingester::default();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
initialized_at,
|
client,
|
||||||
|
event_tracker,
|
||||||
signal,
|
signal,
|
||||||
ingester,
|
ingester,
|
||||||
is_first_run: AtomicBool::new(first_run),
|
initialized_at: Timestamp::now(),
|
||||||
gift_wrap_sub_id: SubscriptionId::new("inbox"),
|
|
||||||
gift_wrap_processing: AtomicBool::new(false),
|
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> {
|
/// Returns a reference to the nostr client
|
||||||
let client = 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<EventTracker> {
|
||||||
|
&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
|
// Get all bootstrapping relays
|
||||||
let mut urls = vec![];
|
let mut urls = vec![];
|
||||||
urls.extend(BOOTSTRAP_RELAYS);
|
urls.extend(BOOTSTRAP_RELAYS);
|
||||||
@@ -230,15 +321,15 @@ impl AppState {
|
|||||||
|
|
||||||
// Add relay to the relay pool
|
// Add relay to the relay pool
|
||||||
for url in urls.into_iter() {
|
for url in urls.into_iter() {
|
||||||
client.add_relay(url).await?;
|
self.client.add_relay(url).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establish connection to relays
|
// Establish connection to relays
|
||||||
client.connect().await;
|
self.client.connect().await;
|
||||||
|
|
||||||
let mut processed_events: HashSet<EventId> = HashSet::new();
|
let mut processed_events: HashSet<EventId> = HashSet::new();
|
||||||
let mut challenges: HashSet<Cow<'_, str>> = HashSet::new();
|
let mut challenges: HashSet<Cow<'_, str>> = HashSet::new();
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = self.client.notifications();
|
||||||
|
|
||||||
while let Ok(notification) = notifications.recv().await {
|
while let Ok(notification) = notifications.recv().await {
|
||||||
let RelayPoolNotification::Message { message, relay_url } = notification else {
|
let RelayPoolNotification::Message { message, relay_url } = notification else {
|
||||||
@@ -265,7 +356,7 @@ impl AppState {
|
|||||||
match event.kind {
|
match event.kind {
|
||||||
Kind::RelayList => {
|
Kind::RelayList => {
|
||||||
// Get events if relay list belongs to current user
|
// 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;
|
let author = event.pubkey;
|
||||||
|
|
||||||
// Fetch user's metadata event
|
// Fetch user's metadata event
|
||||||
@@ -286,7 +377,7 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
Kind::InboxRelays => {
|
Kind::InboxRelays => {
|
||||||
// Subscribe to gift wrap events if messaging relays belong to the current user
|
// 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<RelayUrl> =
|
let urls: Vec<RelayUrl> =
|
||||||
nip17::extract_relay_list(event.as_ref()).cloned().collect();
|
nip17::extract_relay_list(event.as_ref()).cloned().collect();
|
||||||
|
|
||||||
@@ -296,7 +387,7 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kind::ContactList => {
|
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<PublicKey> =
|
let public_keys: HashSet<PublicKey> =
|
||||||
event.tags.public_keys().copied().collect();
|
event.tags.public_keys().copied().collect();
|
||||||
|
|
||||||
@@ -318,7 +409,7 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
RelayMessage::EndOfStoredEvents(subscription_id) => {
|
RelayMessage::EndOfStoredEvents(subscription_id) => {
|
||||||
if *subscription_id == self.gift_wrap_sub_id {
|
if subscription_id.as_ref() == &SubscriptionId::new("inbox") {
|
||||||
self.signal
|
self.signal
|
||||||
.send(SignalKind::GiftWrapStatus(UnwrappingStatus::Processing))
|
.send(SignalKind::GiftWrapStatus(UnwrappingStatus::Processing))
|
||||||
.await;
|
.await;
|
||||||
@@ -353,6 +444,7 @@ impl AppState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Batch metadata requests into a single subscription
|
||||||
pub async fn handle_metadata_batching(&self) {
|
pub async fn handle_metadata_batching(&self) {
|
||||||
let timeout = Duration::from_millis(METADATA_BATCH_TIMEOUT);
|
let timeout = Duration::from_millis(METADATA_BATCH_TIMEOUT);
|
||||||
let mut processed_pubkeys: HashSet<PublicKey> = HashSet::new();
|
let mut processed_pubkeys: HashSet<PublicKey> = HashSet::new();
|
||||||
@@ -411,41 +503,46 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_self_authored(event: &Event) -> Result<bool, Error> {
|
/// Check if event is published by current user
|
||||||
let client = nostr_client();
|
async fn is_self_authored(&self, event: &Event) -> Result<bool, Error> {
|
||||||
let signer = client.signer().await?;
|
let signer = self.client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
Ok(public_key == event.pubkey)
|
Ok(public_key == event.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subscribe for events that match the given kind for a given author
|
/// Subscribe for events that match the given kind for a given author
|
||||||
async fn subscribe(&self, author: PublicKey, kind: Kind) -> Result<(), Error> {
|
pub async fn subscribe(&self, author: PublicKey, kind: Kind) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let filter = Filter::new().author(author).kind(kind).limit(1);
|
let filter = Filter::new().author(author).kind(kind).limit(1);
|
||||||
|
|
||||||
// Subscribe to filters from the user's write relays
|
// Subscribe to filters from the user's write relays
|
||||||
client.subscribe(filter, Some(opts)).await?;
|
self.client.subscribe(filter, Some(opts)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get metadata for a list of public keys
|
/// Get metadata for a list of public keys
|
||||||
async fn get_metadata_for_list(&self, public_keys: HashSet<PublicKey>) -> Result<(), Error> {
|
pub async fn get_metadata_for_list<I>(&self, public_keys: I) -> Result<(), Error>
|
||||||
if public_keys.is_empty() {
|
where
|
||||||
return Err(anyhow!("You need at least one public key"));
|
I: IntoIterator<Item = PublicKey>,
|
||||||
|
{
|
||||||
|
let authors: Vec<PublicKey> = 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 filter = Filter::new()
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
.limit(authors.len() * kinds.len() + 20)
|
||||||
|
.authors(authors)
|
||||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
.kinds(kinds);
|
||||||
let limit = public_keys.len() * kinds.len() + 20;
|
|
||||||
let filter = Filter::new().authors(public_keys).kinds(kinds).limit(limit);
|
|
||||||
|
|
||||||
// Subscribe to filters to the bootstrap relays
|
// Subscribe to filters to the bootstrap relays
|
||||||
client
|
self.client
|
||||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -454,9 +551,7 @@ impl AppState {
|
|||||||
|
|
||||||
/// Get and verify NIP-65 relays for a given public key
|
/// Get and verify NIP-65 relays for a given public key
|
||||||
pub async fn get_nip65(&self, public_key: PublicKey) -> Result<(), Error> {
|
pub async fn get_nip65(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
let timeout = Duration::from_secs(TIMEOUT);
|
||||||
let tx = self.signal.sender().clone();
|
|
||||||
let timeout = Duration::from_secs(5);
|
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
@@ -465,15 +560,18 @@ impl AppState {
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
// Subscribe to events from the bootstrapping relays
|
// Subscribe to events from the bootstrapping relays
|
||||||
client
|
self.client
|
||||||
.subscribe_to(BOOTSTRAP_RELAYS, filter.clone(), Some(opts))
|
.subscribe_to(BOOTSTRAP_RELAYS, filter.clone(), Some(opts))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let tx = self.signal.sender().clone();
|
||||||
|
let database = self.client.database().clone();
|
||||||
|
|
||||||
// Verify the received data after a timeout
|
// Verify the received data after a timeout
|
||||||
smol::spawn(async move {
|
smol::spawn(async move {
|
||||||
smol::Timer::after(timeout).await;
|
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();
|
tx.send_async(SignalKind::GossipRelaysNotFound).await.ok();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -487,12 +585,12 @@ impl AppState {
|
|||||||
&self,
|
&self,
|
||||||
relays: &[(RelayUrl, Option<RelayMetadata>)],
|
relays: &[(RelayUrl, Option<RelayMetadata>)],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
let signer = self.client.signer().await?;
|
||||||
let signer = client.signer().await?;
|
|
||||||
|
|
||||||
let tags: Vec<Tag> = relays
|
let tags: Vec<Tag> = relays
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(url, metadata)| Tag::relay_metadata(url.to_owned(), metadata.to_owned()))
|
.cloned()
|
||||||
|
.map(|(url, metadata)| Tag::relay_metadata(url, metadata))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let event = EventBuilder::new(Kind::RelayList, "")
|
let event = EventBuilder::new(Kind::RelayList, "")
|
||||||
@@ -501,7 +599,7 @@ impl AppState {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Send event to the public relays
|
// 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
|
// Get NIP-17 relays
|
||||||
self.get_nip17(event.pubkey).await?;
|
self.get_nip17(event.pubkey).await?;
|
||||||
@@ -511,9 +609,7 @@ impl AppState {
|
|||||||
|
|
||||||
/// Get and verify NIP-17 relays for a given public key
|
/// Get and verify NIP-17 relays for a given public key
|
||||||
pub async fn get_nip17(&self, public_key: PublicKey) -> Result<(), Error> {
|
pub async fn get_nip17(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
let timeout = Duration::from_secs(TIMEOUT);
|
||||||
let tx = self.signal.sender().clone();
|
|
||||||
let timeout = Duration::from_secs(5);
|
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
@@ -522,13 +618,16 @@ impl AppState {
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
// Subscribe to events from the bootstrapping relays
|
// 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
|
// Verify the received data after a timeout
|
||||||
smol::spawn(async move {
|
smol::spawn(async move {
|
||||||
smol::Timer::after(timeout).await;
|
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)
|
tx.send_async(SignalKind::MessagingRelaysNotFound)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
@@ -541,26 +640,28 @@ impl AppState {
|
|||||||
|
|
||||||
/// Set NIP-17 relays for a current user
|
/// Set NIP-17 relays for a current user
|
||||||
pub async fn set_nip17(&self, relays: &[RelayUrl]) -> Result<(), Error> {
|
pub async fn set_nip17(&self, relays: &[RelayUrl]) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
let signer = self.client.signer().await?;
|
||||||
let signer = client.signer().await?;
|
|
||||||
|
|
||||||
let event = EventBuilder::new(Kind::InboxRelays, "")
|
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)
|
.sign(&signer)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Send event to the public relays
|
// 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?;
|
self.get_messages(event.pubkey, relays).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all gift wrap events in the messaging relays for a given public key
|
/// 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> {
|
pub async fn get_messages(
|
||||||
let client = nostr_client();
|
&self,
|
||||||
|
public_key: PublicKey,
|
||||||
|
urls: &[RelayUrl],
|
||||||
|
) -> Result<(), Error> {
|
||||||
let id = SubscriptionId::new("inbox");
|
let id = SubscriptionId::new("inbox");
|
||||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||||
|
|
||||||
@@ -571,22 +672,22 @@ impl AppState {
|
|||||||
|
|
||||||
// Ensure connection to relays
|
// Ensure connection to relays
|
||||||
for url in urls.iter() {
|
for url in urls.iter() {
|
||||||
client.add_relay(url).await?;
|
self.client.add_relay(url).await?;
|
||||||
client.connect_relay(url).await?;
|
self.client.connect_relay(url).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to filters to user's messaging relays
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores an unwrapped event in local database with reference to original
|
/// Stores an unwrapped event in local database with reference to original
|
||||||
async fn set_rumor(&self, id: EventId, rumor: &Event) -> Result<(), Error> {
|
async fn set_rumor(&self, id: EventId, rumor: &Event) -> Result<(), Error> {
|
||||||
let client = nostr_client();
|
|
||||||
|
|
||||||
// Save unwrapped event
|
// 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
|
// Create a reference event pointing to the unwrapped event
|
||||||
let event = EventBuilder::new(Kind::ApplicationSpecificData, "")
|
let event = EventBuilder::new(Kind::ApplicationSpecificData, "")
|
||||||
@@ -595,23 +696,22 @@ impl AppState {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Save reference event
|
// Save reference event
|
||||||
client.database().save_event(&event).await?;
|
self.client.database().save_event(&event).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a previously unwrapped event from local database
|
/// Retrieves a previously unwrapped event from local database
|
||||||
async fn get_rumor(&self, id: EventId) -> Result<Event, Error> {
|
async fn get_rumor(&self, id: EventId) -> Result<Event, Error> {
|
||||||
let client = nostr_client();
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
.identifier(id)
|
.identifier(id)
|
||||||
.limit(1);
|
.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::<Vec<_>>()[0];
|
let target_id = event.tags.event_ids().collect::<Vec<_>>()[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)
|
Ok(event)
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Event not found."))
|
Err(anyhow!("Event not found."))
|
||||||
@@ -623,13 +723,11 @@ impl AppState {
|
|||||||
|
|
||||||
// Unwraps a gift-wrapped event and processes its contents.
|
// Unwraps a gift-wrapped event and processes its contents.
|
||||||
async fn extract_rumor(&self, gift_wrap: &Event) {
|
async fn extract_rumor(&self, gift_wrap: &Event) {
|
||||||
let client = nostr_client();
|
|
||||||
|
|
||||||
let mut rumor: Option<Event> = None;
|
let mut rumor: Option<Event> = None;
|
||||||
|
|
||||||
if let Ok(event) = self.get_rumor(gift_wrap.id).await {
|
if let Ok(event) = self.get_rumor(gift_wrap.id).await {
|
||||||
rumor = Some(event);
|
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
|
// Sign the unwrapped event with a RANDOM KEYS
|
||||||
if let Ok(event) = unwrapped.rumor.sign_with_keys(&Keys::generate()) {
|
if let Ok(event) = unwrapped.rumor.sign_with_keys(&Keys::generate()) {
|
||||||
// Save this event to the database for future use.
|
// 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user