wip: refactor

This commit is contained in:
2024-11-05 08:47:24 +07:00
parent 578ed5550b
commit ebf87812e9
25 changed files with 1868 additions and 1284 deletions

472
src-tauri/Cargo.lock generated
View File

@@ -2,6 +2,39 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "COOP"
version = "0.2.0"
dependencies = [
"border",
"futures",
"itertools 0.13.0",
"keyring",
"keyring-search",
"nostr-connect",
"nostr-sdk",
"serde",
"serde_json",
"specta",
"specta-typescript",
"tauri",
"tauri-build",
"tauri-plugin-clipboard-manager",
"tauri-plugin-decorum",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-notification",
"tauri-plugin-os",
"tauri-plugin-prevent-default",
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-store",
"tauri-plugin-updater",
"tauri-specta",
"tokio",
"tracing-subscriber",
]
[[package]]
name = "Inflector"
version = "0.11.4"
@@ -109,9 +142,9 @@ checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13"
[[package]]
name = "arbitrary"
version = "1.3.2"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
checksum = "775a8770d29db3dadcb858482cc240af7b2ffde4ac4de67d1d4955728103f0e2"
dependencies = [
"derive_arbitrary",
]
@@ -619,7 +652,7 @@ dependencies = [
[[package]]
name = "border"
version = "0.1.0"
source = "git+https://github.com/ahkohd/tauri-toolkit?branch=v2#39952d8694268694836b5d9e5fa8473474fe4c45"
source = "git+https://github.com/ahkohd/tauri-toolkit?branch=v2#7f5223abbb672664c5ce8b45f55e71f472d53c17"
dependencies = [
"cocoa 0.25.0",
"color",
@@ -935,7 +968,7 @@ dependencies = [
[[package]]
name = "color"
version = "0.1.0"
source = "git+https://github.com/ahkohd/tauri-toolkit?branch=v2#39952d8694268694836b5d9e5fa8473474fe4c45"
source = "git+https://github.com/ahkohd/tauri-toolkit?branch=v2#7f5223abbb672664c5ce8b45f55e71f472d53c17"
dependencies = [
"cocoa 0.25.0",
"objc",
@@ -973,36 +1006,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "coop"
version = "0.1.0"
dependencies = [
"border",
"futures",
"itertools 0.13.0",
"keyring",
"keyring-search",
"nostr-connect",
"nostr-sdk",
"serde",
"serde_json",
"specta",
"specta-typescript",
"tauri",
"tauri-build",
"tauri-plugin-clipboard-manager",
"tauri-plugin-decorum",
"tauri-plugin-dialog",
"tauri-plugin-fs",
"tauri-plugin-notification",
"tauri-plugin-os",
"tauri-plugin-prevent-default",
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-updater",
"tauri-specta",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -1256,9 +1259,9 @@ dependencies = [
[[package]]
name = "derive_arbitrary"
version = "1.3.2"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
checksum = "d475dfebcb4854d596b17b09f477616f80f17a550517f2b3615d8c205d5c802b"
dependencies = [
"proc-macro2",
"quote",
@@ -2193,9 +2196,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.15.0"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
dependencies = [
"allocator-api2",
"equivalent",
@@ -2454,6 +2457,124 @@ dependencies = [
"png",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -2462,12 +2583,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
@@ -2527,7 +2659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown 0.15.0",
"hashbrown 0.15.1",
"serde",
]
@@ -2760,7 +2892,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f8fe839464d4e4b37d756d7e910063696af79a7e877282cb1825e4ec5f10833"
dependencies = [
"byteorder",
"linux-keyutils",
"log",
"security-framework 2.11.1",
"security-framework 3.0.0",
@@ -2770,8 +2901,7 @@ dependencies = [
[[package]]
name = "keyring-search"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fba83ff0a0efb658afeaaa6de89c7abd3ccd34333f5a36d5dae417334fcd488"
source = "git+https://github.com/reyamir/keyring-search#59d54e6a28229f09f87b9b043690ee8a1d63221e"
dependencies = [
"byteorder",
"lazy_static",
@@ -2865,7 +2995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -2918,6 +3048,12 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "litemap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]]
name = "lmdb-master-sys"
version = "0.2.4"
@@ -2972,7 +3108,7 @@ version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
dependencies = [
"hashbrown 0.15.0",
"hashbrown 0.15.1",
]
[[package]]
@@ -3098,9 +3234,9 @@ dependencies = [
[[package]]
name = "muda"
version = "0.15.2"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b18047edf23933de40835403d4b9211ffd1dcc65c0eec569df38a1fb8aebd719"
checksum = "fdae9c00e61cc0579bcac625e8ad22104c60548a025bfc972dc83868a28e1484"
dependencies = [
"crossbeam-channel",
"dpi",
@@ -3213,7 +3349,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "nostr"
version = "0.35.0"
source = "git+https://github.com/rust-nostr/nostr#4da48df74e494f8705e4887ce31a63adeba7b47b"
source = "git+https://github.com/rust-nostr/nostr#b233dcf1a4769eca97a01a34e2b0f83e3eb96d3c"
dependencies = [
"aes",
"async-trait",
@@ -3244,7 +3380,7 @@ dependencies = [
[[package]]
name = "nostr-connect"
version = "0.35.0"
source = "git+https://github.com/rust-nostr/nostr#4da48df74e494f8705e4887ce31a63adeba7b47b"
source = "git+https://github.com/rust-nostr/nostr#b233dcf1a4769eca97a01a34e2b0f83e3eb96d3c"
dependencies = [
"async-trait",
"async-utility",
@@ -3258,7 +3394,7 @@ dependencies = [
[[package]]
name = "nostr-database"
version = "0.35.0"
source = "git+https://github.com/rust-nostr/nostr#4da48df74e494f8705e4887ce31a63adeba7b47b"
source = "git+https://github.com/rust-nostr/nostr#b233dcf1a4769eca97a01a34e2b0f83e3eb96d3c"
dependencies = [
"async-trait",
"flatbuffers",
@@ -3272,7 +3408,7 @@ dependencies = [
[[package]]
name = "nostr-lmdb"
version = "0.35.0"
source = "git+https://github.com/rust-nostr/nostr#4da48df74e494f8705e4887ce31a63adeba7b47b"
source = "git+https://github.com/rust-nostr/nostr#b233dcf1a4769eca97a01a34e2b0f83e3eb96d3c"
dependencies = [
"heed",
"nostr",
@@ -3285,7 +3421,7 @@ dependencies = [
[[package]]
name = "nostr-relay-pool"
version = "0.35.0"
source = "git+https://github.com/rust-nostr/nostr#4da48df74e494f8705e4887ce31a63adeba7b47b"
source = "git+https://github.com/rust-nostr/nostr#b233dcf1a4769eca97a01a34e2b0f83e3eb96d3c"
dependencies = [
"async-utility",
"async-wsocket",
@@ -3303,7 +3439,7 @@ dependencies = [
[[package]]
name = "nostr-sdk"
version = "0.35.0"
source = "git+https://github.com/rust-nostr/nostr#4da48df74e494f8705e4887ce31a63adeba7b47b"
source = "git+https://github.com/rust-nostr/nostr#b233dcf1a4769eca97a01a34e2b0f83e3eb96d3c"
dependencies = [
"async-utility",
"atomic-destructor",
@@ -3322,7 +3458,7 @@ dependencies = [
[[package]]
name = "nostr-zapper"
version = "0.35.0"
source = "git+https://github.com/rust-nostr/nostr#4da48df74e494f8705e4887ce31a63adeba7b47b"
source = "git+https://github.com/rust-nostr/nostr#b233dcf1a4769eca97a01a34e2b0f83e3eb96d3c"
dependencies = [
"async-trait",
"nostr",
@@ -3342,6 +3478,16 @@ dependencies = [
"zbus",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num"
version = "0.4.3"
@@ -3456,7 +3602,7 @@ dependencies = [
[[package]]
name = "nwc"
version = "0.35.0"
source = "git+https://github.com/rust-nostr/nostr#4da48df74e494f8705e4887ce31a63adeba7b47b"
source = "git+https://github.com/rust-nostr/nostr#b233dcf1a4769eca97a01a34e2b0f83e3eb96d3c"
dependencies = [
"async-trait",
"async-utility",
@@ -3794,6 +3940,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "page_size"
version = "0.6.0"
@@ -4638,9 +4790,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.38"
version = "0.38.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee"
dependencies = [
"bitflags 2.6.0",
"errno",
@@ -5046,6 +5198,15 @@ dependencies = [
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "shared_child"
version = "1.0.1"
@@ -5338,6 +5499,17 @@ dependencies = [
"crossbeam-queue",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "sys-locale"
version = "0.3.2"
@@ -5714,6 +5886,22 @@ dependencies = [
"tokio",
]
[[package]]
name = "tauri-plugin-store"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9a580be53f04bb62422d239aa798e88522877f58a0d4a0e745f030055a51bb4"
dependencies = [
"dunce",
"log",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror",
"tokio",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.0.2"
@@ -5906,24 +6094,34 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]]
name = "thiserror"
version = "1.0.67"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3c6efbfc763e64eb85c11c25320f0737cb7364c4b6336db90aa9ebe27a0bbd"
checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.67"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6"
checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tiff"
version = "0.9.1"
@@ -5966,6 +6164,16 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
@@ -5991,6 +6199,7 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@@ -6167,6 +6376,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
@@ -6280,12 +6515,6 @@ dependencies = [
"unic-common",
]
[[package]]
name = "unicode-bidi"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
[[package]]
name = "unicode-ident"
version = "1.0.13"
@@ -6325,9 +6554,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.2"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
dependencies = [
"form_urlencoded",
"idna",
@@ -6353,6 +6582,18 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.11.0"
@@ -6374,6 +6615,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version-compare"
version = "0.2.0"
@@ -6704,7 +6951,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -7125,6 +7372,18 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wry"
version = "0.46.3"
@@ -7225,6 +7484,30 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"synstructure",
]
[[package]]
name = "zbus"
version = "4.0.1"
@@ -7311,12 +7594,55 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "zerofrom"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "zip"
version = "2.2.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "coop"
version = "0.1.0"
name = "COOP"
version = "0.2.0"
description = "direct message client for desktop"
authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"]
repository = "https://github.com/lumehq/coop"
@@ -24,6 +24,7 @@ tauri-plugin-updater = "2.0.0"
tauri-plugin-process = "2.0.0"
tauri-plugin-fs = "2.0.0"
tauri-plugin-notification = "2.0.0"
tauri-plugin-store = "2.1.0"
tauri-plugin-decorum = "1.1.0"
tauri-plugin-prevent-default = "^0.4"
tauri-specta = { version = "2.0.0-rc", features = ["derive", "typescript"] }
@@ -33,16 +34,14 @@ nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
specta = "^2.0.0-rc.20"
specta-typescript = "0.0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
itertools = "0.13.0"
futures = "0.3.30"
keyring-search = "1.2.0"
keyring = { version = "3", features = [
"apple-native",
"windows-native",
"linux-native",
] }
keyring = { version = "3", features = ["apple-native", "windows-native"] }
keyring-search = { git = "https://github.com/reyamir/keyring-search" }
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
[target.'cfg(target_os = "macos")'.dependencies]
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }

View File

@@ -14,11 +14,6 @@
"core:resources:default",
"core:menu:default",
"core:tray:default",
"shell:allow-open",
"dialog:default",
"dialog:allow-open",
"dialog:allow-ask",
"dialog:allow-message",
"core:window:allow-close",
"core:window:allow-center",
"core:window:allow-minimize",
@@ -27,14 +22,18 @@
"core:window:allow-set-focus",
"core:window:allow-start-dragging",
"core:window:allow-toggle-maximize",
"shell:allow-open",
"dialog:default",
"dialog:allow-open",
"dialog:allow-ask",
"dialog:allow-message",
"decorum:allow-show-snap-overlay",
"prevent-default:default",
"updater:default",
"updater:allow-check",
"updater:allow-download-and-install",
"clipboard-manager:allow-write-text",
"clipboard-manager:allow-read-text",
"fs:allow-read-file",
"notification:default"
"notification:default",
"store:default"
]
}

View File

@@ -1,4 +1,5 @@
wss://purplepag.es/,
wss://directory.yabu.me/,
wss://user.kindpag.es/,
wss://relay.damus.io/,
wss://relay.damus.io,
wss://relay.primal.net,
wss://nostr.fmt.wiz.biz,
wss://directory.yabu.me,
wss://purplepag.es,

View File

@@ -1,11 +0,0 @@
edition = "2021"
format_strings = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
comment_width = 100
format_code_in_doc_comments = true
wrap_comments = true
use_field_init_shorthand = true
use_small_heuristics = "Max"
hard_tabs = true
ignore = ["target/", "gen/"]

View File

@@ -5,429 +5,371 @@ use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize};
use specta::Type;
use std::{collections::HashSet, str::FromStr, time::Duration};
use tauri::{Emitter, Manager, State};
use tauri_plugin_notification::NotificationExt;
use tauri::{Manager, State};
use crate::Nostr;
use crate::{Nostr, SUBSCRIPTION_ID};
#[derive(Clone, Serialize)]
pub struct EventPayload {
event: String, // JSON String
sender: String,
event: String, // JSON String
sender: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
struct Account {
password: String,
nostr_connect: Option<String>,
password: String,
nostr_connect: Option<String>,
}
#[tauri::command]
#[specta::specta]
pub async fn get_metadata(id: String, state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
let client = &state.client;
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
let filter = Filter::new().author(public_key).kind(Kind::Metadata).limit(1);
let filter = Filter::new()
.author(public_key)
.kind(Kind::Metadata)
.limit(1);
let events = client.database().query(vec![filter]).await.map_err(|e| e.to_string())?;
let events = client
.database()
.query(vec![filter])
.await
.map_err(|e| e.to_string())?;
match events.first() {
Some(event) => match Metadata::from_json(&event.content) {
Ok(metadata) => Ok(metadata.as_json()),
Err(e) => Err(e.to_string()),
},
None => Err("Metadata not found".into()),
}
match events.first() {
Some(event) => match Metadata::from_json(&event.content) {
Ok(metadata) => Ok(metadata.as_json()),
Err(e) => Err(e.to_string()),
},
None => Err("Metadata not found".into()),
}
}
#[tauri::command]
#[specta::specta]
pub fn get_accounts() -> Vec<String> {
let search = Search::new().expect("Unexpected.");
let results = search.by_service("Coop Secret Storage");
let list = List::list_credentials(&results, Limit::All);
let accounts: HashSet<String> =
list.split_whitespace().filter(|v| v.starts_with("npub1")).map(String::from).collect();
let search = Search::new().expect("Unexpected.");
let results = search.by_service("Coop Secret Storage");
let list = List::list_credentials(&results, Limit::All);
let accounts: HashSet<String> = list
.split_whitespace()
.filter(|v| v.starts_with("npub1"))
.map(String::from)
.collect();
accounts.into_iter().collect()
accounts.into_iter().collect()
}
#[tauri::command]
#[specta::specta]
pub async fn get_current_account(state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.get_public_key().await.map_err(|e| e.to_string())?;
let bech32 = public_key.to_bech32().map_err(|e| e.to_string())?;
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.get_public_key().await.map_err(|e| e.to_string())?;
let bech32 = public_key.to_bech32().map_err(|e| e.to_string())?;
Ok(bech32)
Ok(bech32)
}
#[tauri::command]
#[specta::specta]
pub async fn create_account(
name: String,
about: String,
picture: String,
password: String,
state: State<'_, Nostr>,
name: String,
about: String,
picture: String,
password: String,
state: State<'_, Nostr>,
) -> Result<String, String> {
let client = &state.client;
let keys = Keys::generate();
let npub = keys.public_key().to_bech32().map_err(|e| e.to_string())?;
let secret_key = keys.secret_key();
let enc = EncryptedSecretKey::new(secret_key, password, 16, KeySecurity::Medium)
.map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
let client = &state.client;
let keys = Keys::generate();
let npub = keys.public_key().to_bech32().map_err(|e| e.to_string())?;
let secret_key = keys.secret_key();
let enc = EncryptedSecretKey::new(secret_key, password, 16, KeySecurity::Medium)
.map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
// Save account
let keyring = Entry::new("Coop Secret Storage", &npub).map_err(|e| e.to_string())?;
let account = Account { password: enc_bech32, nostr_connect: None };
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
let _ = keyring.set_password(&j);
// Save account
let keyring = Entry::new("Coop Secret Storage", &npub).map_err(|e| e.to_string())?;
let account = Account {
password: enc_bech32,
nostr_connect: None,
};
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
let _ = keyring.set_password(&j);
// Update signer
client.set_signer(keys).await;
// Update signer
client.set_signer(keys).await;
let mut metadata =
Metadata::new().display_name(name.clone()).name(name.to_lowercase()).about(about);
let mut metadata = Metadata::new()
.display_name(name.clone())
.name(name.to_lowercase())
.about(about);
if let Ok(url) = Url::parse(&picture) {
metadata = metadata.picture(url)
}
if let Ok(url) = Url::parse(&picture) {
metadata = metadata.picture(url)
}
match client.set_metadata(&metadata).await {
Ok(_) => Ok(npub),
Err(e) => Err(e.to_string()),
}
match client.set_metadata(&metadata).await {
Ok(_) => Ok(npub),
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn import_account(key: String, password: String) -> Result<String, String> {
let (npub, enc_bech32) = match key.starts_with("ncryptsec") {
true => {
let enc = EncryptedSecretKey::from_bech32(key).map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
let secret_key = enc.to_secret_key(password).map_err(|err| err.to_string())?;
let keys = Keys::new(secret_key);
let npub = keys.public_key().to_bech32().unwrap();
let (npub, enc_bech32) = match key.starts_with("ncryptsec") {
true => {
let enc = EncryptedSecretKey::from_bech32(key).map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
let secret_key = enc.to_secret_key(password).map_err(|err| err.to_string())?;
let keys = Keys::new(secret_key);
let npub = keys.public_key().to_bech32().unwrap();
(npub, enc_bech32)
}
false => {
let secret_key = SecretKey::from_bech32(key).map_err(|err| err.to_string())?;
let keys = Keys::new(secret_key.clone());
let npub = keys.public_key().to_bech32().unwrap();
(npub, enc_bech32)
}
false => {
let secret_key = SecretKey::from_bech32(key).map_err(|err| err.to_string())?;
let keys = Keys::new(secret_key.clone());
let npub = keys.public_key().to_bech32().unwrap();
let enc = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium)
.map_err(|err| err.to_string())?;
let enc = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium)
.map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
(npub, enc_bech32)
}
};
(npub, enc_bech32)
}
};
let keyring = Entry::new("Coop Secret Storage", &npub).map_err(|e| e.to_string())?;
let keyring = Entry::new("Coop Secret Storage", &npub).map_err(|e| e.to_string())?;
let account = Account { password: enc_bech32, nostr_connect: None };
let account = Account {
password: enc_bech32,
nostr_connect: None,
};
let pwd = serde_json::to_string(&account).map_err(|e| e.to_string())?;
keyring.set_password(&pwd).map_err(|e| e.to_string())?;
let pwd = serde_json::to_string(&account).map_err(|e| e.to_string())?;
keyring.set_password(&pwd).map_err(|e| e.to_string())?;
Ok(npub)
Ok(npub)
}
#[tauri::command]
#[specta::specta]
pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result<String, String> {
let client = &state.client;
let client = &state.client;
match NostrConnectURI::parse(uri.clone()) {
Ok(bunker_uri) => {
// Local user
let app_keys = Keys::generate();
let app_secret = app_keys.secret_key().to_secret_hex();
match NostrConnectURI::parse(uri.clone()) {
Ok(bunker_uri) => {
// Local user
let app_keys = Keys::generate();
let app_secret = app_keys.secret_key().to_secret_hex();
// Get remote user
let remote_user = bunker_uri.remote_signer_public_key().unwrap();
let remote_npub = remote_user.to_bech32().unwrap();
// Get remote user
let remote_user = bunker_uri.remote_signer_public_key().unwrap();
let remote_npub = remote_user.to_bech32().unwrap();
match NostrConnect::new(bunker_uri, app_keys, Duration::from_secs(120), None) {
Ok(signer) => {
let mut url = Url::parse(&uri).unwrap();
let query: Vec<(String, String)> = url
.query_pairs()
.filter(|(name, _)| name != "secret")
.map(|(name, value)| (name.into_owned(), value.into_owned()))
.collect();
url.query_pairs_mut().clear().extend_pairs(&query);
match NostrConnect::new(bunker_uri, app_keys, Duration::from_secs(120), None) {
Ok(signer) => {
let mut url = Url::parse(&uri).unwrap();
let query: Vec<(String, String)> = url
.query_pairs()
.filter(|(name, _)| name != "secret")
.map(|(name, value)| (name.into_owned(), value.into_owned()))
.collect();
url.query_pairs_mut().clear().extend_pairs(&query);
let key = format!("{}_nostrconnect", remote_npub);
let keyring = Entry::new("Coop Secret Storage", &key).unwrap();
let account =
Account { password: app_secret, nostr_connect: Some(url.to_string()) };
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
let _ = keyring.set_password(&j);
let key = format!("{}_nostrconnect", remote_npub);
let keyring = Entry::new("Coop Secret Storage", &key).unwrap();
let account = Account {
password: app_secret,
nostr_connect: Some(url.to_string()),
};
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
let _ = keyring.set_password(&j);
// Update signer
let _ = client.set_signer(signer).await;
// Update signer
let _ = client.set_signer(signer).await;
Ok(remote_npub)
}
Err(err) => Err(err.to_string()),
}
}
Err(err) => Err(err.to_string()),
}
Ok(remote_npub)
}
Err(err) => Err(err.to_string()),
}
}
Err(err) => Err(err.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn reset_password(key: String, password: String) -> Result<(), String> {
let secret_key = SecretKey::from_bech32(key).map_err(|err| err.to_string())?;
let keys = Keys::new(secret_key.clone());
let npub = keys.public_key().to_bech32().unwrap();
let secret_key = SecretKey::from_bech32(key).map_err(|err| err.to_string())?;
let keys = Keys::new(secret_key.clone());
let npub = keys.public_key().to_bech32().unwrap();
let enc = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium)
.map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
let enc = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium)
.map_err(|err| err.to_string())?;
let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?;
let keyring = Entry::new("Coop Secret Storage", &npub).map_err(|e| e.to_string())?;
let account = Account { password: enc_bech32, nostr_connect: None };
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
let _ = keyring.set_password(&j);
let keyring = Entry::new("Coop Secret Storage", &npub).map_err(|e| e.to_string())?;
let account = Account {
password: enc_bech32,
nostr_connect: None,
};
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
let _ = keyring.set_password(&j);
Ok(())
Ok(())
}
#[tauri::command]
#[specta::specta]
pub fn delete_account(id: String) -> Result<(), String> {
let keyring = Entry::new("Coop Secret Storage", &id).map_err(|e| e.to_string())?;
let _ = keyring.delete_credential();
let keyring = Entry::new("Coop Secret Storage", &id).map_err(|e| e.to_string())?;
let _ = keyring.delete_credential();
Ok(())
Ok(())
}
#[tauri::command]
#[specta::specta]
pub async fn get_contact_list(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
let client = &state.client;
let client = &state.client;
match client.get_contact_list(Some(Duration::from_secs(10))).await {
Ok(contacts) => {
let list = contacts.into_iter().map(|c| c.public_key.to_hex()).collect::<Vec<_>>();
Ok(list)
}
Err(e) => Err(e.to_string()),
}
match client.get_contact_list(Some(Duration::from_secs(10))).await {
Ok(contacts) => {
let list = contacts
.into_iter()
.map(|c| c.public_key.to_hex())
.collect::<Vec<_>>();
Ok(list)
}
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn login(
account: String,
password: String,
state: State<'_, Nostr>,
handle: tauri::AppHandle,
account: String,
password: String,
state: State<'_, Nostr>,
handle: tauri::AppHandle,
) -> Result<String, String> {
let client = &state.client;
let keyring = Entry::new("Coop Secret Storage", &account).map_err(|e| e.to_string())?;
let client = &state.client;
let keyring = Entry::new("Coop Secret Storage", &account).map_err(|e| e.to_string())?;
let account = match keyring.get_password() {
Ok(pw) => {
let account: Account = serde_json::from_str(&pw).map_err(|e| e.to_string())?;
account
}
Err(e) => return Err(e.to_string()),
};
let account = match keyring.get_password() {
Ok(pw) => {
let account: Account = serde_json::from_str(&pw).map_err(|e| e.to_string())?;
account
}
Err(e) => return Err(e.to_string()),
};
let public_key = match account.nostr_connect {
None => {
let ncryptsec =
EncryptedSecretKey::from_bech32(account.password).map_err(|e| e.to_string())?;
let secret_key = ncryptsec.to_secret_key(password).map_err(|_| "Wrong password.")?;
let keys = Keys::new(secret_key);
let public_key = keys.public_key();
let public_key = match account.nostr_connect {
None => {
let ncryptsec =
EncryptedSecretKey::from_bech32(account.password).map_err(|e| e.to_string())?;
let secret_key = ncryptsec
.to_secret_key(password)
.map_err(|_| "Wrong password.")?;
let keys = Keys::new(secret_key);
let public_key = keys.public_key();
// Update signer
client.set_signer(keys).await;
// Update signer
client.set_signer(keys).await;
public_key
}
Some(bunker) => {
let uri = NostrConnectURI::parse(bunker).map_err(|e| e.to_string())?;
let public_key = uri.remote_signer_public_key().unwrap().clone();
let app_keys = Keys::from_str(&account.password).map_err(|e| e.to_string())?;
public_key
}
Some(bunker) => {
let uri = NostrConnectURI::parse(bunker).map_err(|e| e.to_string())?;
let public_key = uri.remote_signer_public_key().unwrap().to_owned();
let app_keys = Keys::from_str(&account.password).map_err(|e| e.to_string())?;
match NostrConnect::new(uri, app_keys, Duration::from_secs(120), None) {
Ok(signer) => {
// Update signer
client.set_signer(signer).await;
// Return public key
public_key
}
Err(e) => return Err(e.to_string()),
}
}
};
match NostrConnect::new(uri, app_keys, Duration::from_secs(120), None) {
Ok(signer) => {
// Update signer
client.set_signer(signer).await;
// Return public key
public_key
}
Err(e) => return Err(e.to_string()),
}
}
};
let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1);
let filter = Filter::new()
.kind(Kind::Custom(10050))
.author(public_key)
.limit(1);
if let Ok(events) =
client.get_events_of(vec![inbox], EventSource::relays(Some(Duration::from_secs(3)))).await
{
if let Some(event) = events.into_iter().next() {
let urls = event
.tags
.iter()
.filter_map(|tag| {
if let Some(TagStandard::Relay(relay)) = tag.as_standardized() {
Some(relay.to_string())
} else {
None
}
})
.collect::<Vec<_>>();
let mut rx = client
.stream_events(vec![filter], Some(Duration::from_secs(3)))
.await
.map_err(|e| e.to_string())?;
for url in urls.iter() {
if let Err(e) = client.add_relay(url).await {
println!("Connect relay failed: {}", e)
}
}
while let Some(event) = rx.next().await {
let urls = event
.tags
.iter()
.filter_map(|tag| {
if let Some(TagStandard::Relay(relay)) = tag.as_standardized() {
Some(relay.to_string())
} else {
None
}
})
.collect::<Vec<_>>();
// Workaround for https://github.com/rust-nostr/nostr/issues/509
// TODO: remove this
let _ = client
.get_events_from(
urls.clone(),
vec![Filter::new().kind(Kind::TextNote).limit(0)],
Some(Duration::from_secs(5)),
)
.await;
for url in urls.iter() {
let _ = client.add_relay(url).await;
let _ = client.connect_relay(url).await;
}
let mut inbox_relays = state.inbox_relays.lock().await;
inbox_relays.insert(public_key, urls);
} else {
return Err("404".into());
}
}
let mut inbox_relays = state.inbox_relays.write().await;
inbox_relays.insert(public_key, urls);
}
let sub_id = SubscriptionId::new("inbox");
let new_message = Filter::new().kind(Kind::GiftWrap).pubkey(public_key).limit(0);
tauri::async_runtime::spawn(async move {
let state = handle.state::<Nostr>();
let client = &state.client;
if client.subscription(&sub_id).await.is_some() {
// Remove old subscriotion
client.unsubscribe(sub_id.clone()).await;
// Resubscribe new message for current user
let _ = client.subscribe_with_id(sub_id.clone(), vec![new_message], None).await;
} else {
let _ = client.subscribe_with_id(sub_id.clone(), vec![new_message], None).await;
}
let inbox_relays = state.inbox_relays.read().await;
let relays = inbox_relays.get(&public_key).unwrap().to_owned();
tauri::async_runtime::spawn(async move {
let state = handle.state::<Nostr>();
let client = &state.client;
let sub_id = SubscriptionId::new(SUBSCRIPTION_ID);
let new_message = Filter::new()
.kind(Kind::GiftWrap)
.pubkey(public_key)
.limit(0);
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
if let Err(e) = client
.subscribe_with_id_to(&relays, sub_id, vec![new_message], None)
.await
{
println!("Subscribe error: {}", e)
};
// Generate a fake sig for rumor event.
// TODO: Find better way to save unsigned event to database.
let fake_sig = Signature::from_str("f9e79d141c004977192d05a86f81ec7c585179c371f7350a5412d33575a2a356433f58e405c2296ed273e2fe0aafa25b641e39cc4e1f3f261ebf55bce0cbac83").unwrap();
let filter = Filter::new()
.kind(Kind::GiftWrap)
.pubkey(public_key)
.limit(200);
if let Ok(events) = client
.pool()
.get_events_of(
vec![filter],
Duration::from_secs(12),
FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(20)),
)
.await
{
for event in events.iter() {
if let Ok(UnwrappedGift { rumor, .. }) = client.unwrap_gift_wrap(event).await {
let rumor_clone = rumor.clone();
let ev = Event::new(
rumor_clone.id.unwrap(),
rumor_clone.pubkey,
rumor_clone.created_at,
rumor_clone.kind,
rumor_clone.tags,
rumor_clone.content,
fake_sig,
);
let mut rx = client
.stream_events_from(&relays, vec![filter], Some(Duration::from_secs(40)))
.await
.unwrap();
if let Err(e) = client.database().save_event(&ev).await {
println!("Error: {}", e)
}
}
}
handle.emit("synchronized", ()).unwrap();
}
while let Some(event) = rx.next().await {
println!("Event: {}", event.as_json());
}
client
.handle_notifications(|notification| async {
if let RelayPoolNotification::Message { message, .. } = notification {
if let RelayMessage::Event { event, subscription_id, .. } = message {
if subscription_id == sub_id && event.kind == Kind::GiftWrap {
if let Ok(UnwrappedGift { rumor, sender }) =
client.unwrap_gift_wrap(&event).await
{
let rumor_clone = rumor.clone();
let ev = Event::new(
rumor_clone.id.unwrap(),
rumor_clone.pubkey,
rumor_clone.created_at,
rumor_clone.kind,
rumor_clone.tags,
rumor_clone.content,
fake_sig,
);
// handle.emit("synchronized", ()).unwrap();
});
// Save rumor to database to further query
if let Err(e) = client.database().save_event(&ev).await {
println!("[save event] error: {}", e)
}
// Emit new event to frontend
if let Err(e) = handle.emit(
"event",
EventPayload {
event: rumor.as_json(),
sender: sender.to_hex(),
},
) {
println!("[emit] error: {}", e)
}
if sender != public_key {
if let Some(window) = handle.get_webview_window("main") {
if !window.is_focused().unwrap() {
if let Err(e) = handle
.notification()
.builder()
.body("You have a new message")
.title("Coop")
.show()
{
println!("[notification] error: {}", e);
}
}
}
}
}
}
} else {
println!("relay message: {}", message.as_json())
}
}
Ok(false)
})
.await
});
Ok(public_key.to_hex())
Ok(public_key.to_hex())
}

View File

@@ -8,89 +8,110 @@ use crate::Nostr;
#[tauri::command]
#[specta::specta]
pub async fn get_chats(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.get_public_key().await.map_err(|e| e.to_string())?;
let client = &state.client;
let filter = Filter::new().kind(Kind::PrivateDirectMessage).pubkey(public_key);
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.get_public_key().await.map_err(|e| e.to_string())?;
match client.database().query(vec![filter]).await {
Ok(events) => {
let ev = events
.into_iter()
.sorted_by_key(|ev| Reverse(ev.created_at))
.filter(|ev| ev.pubkey != public_key)
.unique_by(|ev| ev.pubkey)
.map(|ev| ev.as_json())
.collect::<Vec<_>>();
let filter = Filter::new()
.kind(Kind::PrivateDirectMessage)
.pubkey(public_key);
let events = client
.database()
.query(vec![filter])
.await
.map_err(|e| e.to_string())?;
Ok(ev)
}
Err(e) => Err(e.to_string()),
}
let data = events
.into_iter()
.sorted_by_key(|ev| Reverse(ev.created_at))
.filter(|ev| ev.pubkey != public_key)
.unique_by(|ev| ev.pubkey)
.map(|ev| ev.as_json())
.collect::<Vec<_>>();
Ok(data)
}
#[tauri::command]
#[specta::specta]
pub async fn get_chat_messages(id: String, state: State<'_, Nostr>) -> Result<Vec<String>, String> {
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let client = &state.client;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let receiver = signer.get_public_key().await.map_err(|e| e.to_string())?;
let sender = PublicKey::parse(id).map_err(|e| e.to_string())?;
let receiver = signer.get_public_key().await.map_err(|e| e.to_string())?;
let sender = PublicKey::parse(id).map_err(|e| e.to_string())?;
let recv_filter =
Filter::new().kind(Kind::PrivateDirectMessage).author(sender).pubkey(receiver);
let sender_filter =
Filter::new().kind(Kind::PrivateDirectMessage).author(receiver).pubkey(sender);
let recv_filter = Filter::new()
.kind(Kind::PrivateDirectMessage)
.author(sender)
.pubkey(receiver);
let sender_filter = Filter::new()
.kind(Kind::PrivateDirectMessage)
.author(receiver)
.pubkey(sender);
match client.database().query(vec![recv_filter, sender_filter]).await {
Ok(events) => {
let ev = events.into_iter().map(|ev| ev.as_json()).collect::<Vec<_>>();
Ok(ev)
}
Err(e) => Err(e.to_string()),
}
match client
.database()
.query(vec![recv_filter, sender_filter])
.await
{
Ok(events) => {
let ev = events
.into_iter()
.map(|ev| ev.as_json())
.collect::<Vec<_>>();
Ok(ev)
}
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn send_message(
to: String,
message: String,
state: State<'_, Nostr>,
to: String,
message: String,
state: State<'_, Nostr>,
) -> Result<(), String> {
let client = &state.client;
let relays = state.inbox_relays.lock().await;
let client = &state.client;
let relays = state.inbox_relays.read().await;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.get_public_key().await.map_err(|e| e.to_string())?;
let receiver = PublicKey::parse(&to).map_err(|e| e.to_string())?;
let signer = client.signer().await.map_err(|e| e.to_string())?;
let public_key = signer.get_public_key().await.map_err(|e| e.to_string())?;
let receiver = PublicKey::parse(&to).map_err(|e| e.to_string())?;
// TODO: Add support reply_to
let rumor = EventBuilder::private_msg_rumor(receiver, message, None);
// TODO: Add support reply_to
let rumor = EventBuilder::private_msg_rumor(receiver, message, None);
// Get inbox relays per member
let outbox_urls = match relays.get(&receiver) {
Some(relays) => relays,
None => return Err("Receiver didn't have inbox relays to receive message.".into()),
};
// Get inbox relays for receiver
let outbox_urls = match relays.get(&receiver) {
Some(relays) => relays,
None => return Err("Receiver didn't have inbox relays to receive message.".into()),
};
let inbox_urls = match relays.get(&public_key) {
Some(relays) => relays,
None => return Err("Please config inbox relays to backup your message.".into()),
};
// Get inbox relays for current user
let inbox_urls = match relays.get(&public_key) {
Some(relays) => relays,
None => return Err("Please config inbox relays to backup your message.".into()),
};
// Send message to [receiver]
match client.gift_wrap_to(outbox_urls, &receiver, rumor.clone(), None).await {
Ok(_) => {
// Send message to [yourself]
if let Err(e) = client.gift_wrap_to(inbox_urls, &public_key, rumor, None).await {
return Err(e.to_string());
}
// Send message to [receiver]
match client
.gift_wrap_to(outbox_urls, &receiver, rumor.clone(), None)
.await
{
Ok(_) => {
// Send message to [yourself]
if let Err(e) = client
.gift_wrap_to(inbox_urls, &public_key, rumor, None)
.await
{
return Err(e.to_string());
}
Ok(())
}
Err(e) => Err(e.to_string()),
}
Ok(())
}
Err(e) => Err(e.to_string()),
}
}

View File

@@ -1,8 +1,8 @@
use nostr_sdk::prelude::*;
use std::{
fs::OpenOptions,
io::{self, BufRead, Write},
time::Duration,
fs::OpenOptions,
io::{self, BufRead, Write},
time::Duration,
};
use tauri::{Manager, State};
@@ -11,177 +11,198 @@ use crate::Nostr;
#[tauri::command]
#[specta::specta]
pub fn get_bootstrap_relays(app: tauri::AppHandle) -> Result<Vec<String>, String> {
let relays_path = app
.path()
.resolve("resources/relays.txt", tauri::path::BaseDirectory::Resource)
.map_err(|e| e.to_string())?;
let relays_path = app
.path()
.resolve("resources/relays.txt", tauri::path::BaseDirectory::Resource)
.map_err(|e| e.to_string())?;
let file = std::fs::File::open(relays_path).map_err(|e| e.to_string())?;
let reader = io::BufReader::new(file);
let file = std::fs::File::open(relays_path).map_err(|e| e.to_string())?;
let reader = io::BufReader::new(file);
reader.lines().collect::<Result<Vec<String>, io::Error>>().map_err(|e| e.to_string())
reader
.lines()
.collect::<Result<Vec<String>, io::Error>>()
.map_err(|e| e.to_string())
}
#[tauri::command]
#[specta::specta]
pub fn set_bootstrap_relays(relays: String, app: tauri::AppHandle) -> Result<(), String> {
let relays_path = app
.path()
.resolve("resources/relays.txt", tauri::path::BaseDirectory::Resource)
.map_err(|e| e.to_string())?;
let mut file = OpenOptions::new().write(true).open(relays_path).map_err(|e| e.to_string())?;
let relays_path = app
.path()
.resolve("resources/relays.txt", tauri::path::BaseDirectory::Resource)
.map_err(|e| e.to_string())?;
let mut file = OpenOptions::new()
.write(true)
.open(relays_path)
.map_err(|e| e.to_string())?;
file.write_all(relays.as_bytes()).map_err(|e| e.to_string())
file.write_all(relays.as_bytes()).map_err(|e| e.to_string())
}
#[tauri::command]
#[specta::specta]
pub async fn get_inbox_relays(
user_id: String,
state: State<'_, Nostr>,
user_id: String,
state: State<'_, Nostr>,
) -> Result<Vec<String>, String> {
let client = &state.client;
let public_key = PublicKey::parse(user_id).map_err(|e| e.to_string())?;
let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1);
let client = &state.client;
let public_key = PublicKey::parse(user_id).map_err(|e| e.to_string())?;
let filter = Filter::new()
.kind(Kind::Custom(10050))
.author(public_key)
.limit(1);
match client.get_events_of(vec![inbox], EventSource::relays(Some(Duration::from_secs(3)))).await
{
Ok(events) => {
if let Some(event) = events.into_iter().next() {
let urls = event
.tags
.iter()
.filter_map(|tag| {
if let Some(TagStandard::Relay(relay)) = tag.as_standardized() {
Some(relay.to_string())
} else {
None
}
})
.collect::<Vec<_>>();
let mut events = Events::new(&[filter.clone()]);
Ok(urls)
} else {
Ok(Vec::new())
}
}
Err(e) => Err(e.to_string()),
}
let mut rx = client
.stream_events(vec![filter], Some(Duration::from_secs(3)))
.await
.map_err(|e| e.to_string())?;
while let Some(event) = rx.next().await {
events.insert(event);
}
if let Some(event) = events.first() {
let urls = event
.tags
.iter()
.filter_map(|tag| {
if let Some(TagStandard::Relay(relay)) = tag.as_standardized() {
Some(relay.to_string())
} else {
None
}
})
.collect::<Vec<_>>();
Ok(urls)
} else {
Ok(Vec::new())
}
}
#[tauri::command]
#[specta::specta]
pub async fn ensure_inbox_relays(
user_id: String,
state: State<'_, Nostr>,
user_id: String,
state: State<'_, Nostr>,
) -> Result<Vec<String>, String> {
let public_key = PublicKey::parse(user_id).map_err(|e| e.to_string())?;
let relays = state.inbox_relays.lock().await;
let public_key = PublicKey::parse(user_id).map_err(|e| e.to_string())?;
let relays = state.inbox_relays.read().await;
match relays.get(&public_key) {
Some(relays) => {
if relays.is_empty() {
Err("404".into())
} else {
Ok(relays.to_owned())
}
}
None => Err("404".into()),
}
match relays.get(&public_key) {
Some(relays) => {
if relays.is_empty() {
Err("404".into())
} else {
Ok(relays.to_owned())
}
}
None => Err("404".into()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn set_inbox_relays(relays: Vec<String>, state: State<'_, Nostr>) -> Result<(), String> {
let client = &state.client;
let client = &state.client;
let tags = relays.into_iter().map(|t| Tag::custom(TagKind::Relay, vec![t])).collect::<Vec<_>>();
let event = EventBuilder::new(Kind::Custom(10050), "", tags);
let tags = relays
.into_iter()
.map(|t| Tag::custom(TagKind::Relay, vec![t]))
.collect::<Vec<_>>();
let event = EventBuilder::new(Kind::Custom(10050), "", tags);
match client.send_event_builder(event).await {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
match client.send_event_builder(event).await {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
#[specta::specta]
pub async fn connect_inbox_relays(
user_id: String,
ignore_cache: bool,
state: State<'_, Nostr>,
user_id: String,
ignore_cache: bool,
state: State<'_, Nostr>,
) -> Result<Vec<String>, String> {
let client = &state.client;
let public_key = PublicKey::parse(&user_id).map_err(|e| e.to_string())?;
let client = &state.client;
let public_key = PublicKey::parse(&user_id).map_err(|e| e.to_string())?;
// let nip65_relays = connect_nip65_relays(public_key, client).await;
let mut inbox_relays = state.inbox_relays.lock().await;
let mut inbox_relays = state.inbox_relays.write().await;
if !ignore_cache {
if let Some(relays) = inbox_relays.get(&public_key) {
for url in relays {
if let Ok(relay) = client.relay(url).await {
if !relay.is_connected() {
if let Err(e) = client.connect_relay(url).await {
println!("Connect relay failed: {}", e)
}
}
} else if let Err(e) = client.add_relay(url).await {
println!("Connect relay failed: {}", e)
}
}
return Ok(relays.to_owned());
};
};
if !ignore_cache {
if let Some(relays) = inbox_relays.get(&public_key) {
for url in relays {
if let Ok(relay) = client.relay(url).await {
if !relay.is_connected() {
if let Err(e) = client.connect_relay(url).await {
println!("Connect relay failed: {}", e)
}
}
} else if let Err(e) = client.add_relay(url).await {
println!("Connect relay failed: {}", e)
}
}
return Ok(relays.to_owned());
};
};
let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1);
let filter = Filter::new()
.kind(Kind::Custom(10050))
.author(public_key)
.limit(1);
match client.get_events_of(vec![inbox], EventSource::relays(Some(Duration::from_secs(3)))).await
{
Ok(events) => {
let mut relays = Vec::new();
let mut events = Events::new(&[filter.clone()]);
if let Some(event) = events.into_iter().next() {
for tag in event.tags.iter() {
if let Some(TagStandard::Relay(relay)) = tag.as_standardized() {
let url = relay.to_string();
let mut rx = client
.stream_events(vec![filter], Some(Duration::from_secs(3)))
.await
.map_err(|e| e.to_string())?;
if let Err(e) = client.add_relay(&url).await {
println!("Connect relay failed: {}", e)
};
while let Some(event) = rx.next().await {
events.insert(event);
}
relays.push(url)
}
}
if let Some(event) = events.first() {
let mut relays = Vec::new();
// Update state
inbox_relays.insert(public_key, relays.clone());
for tag in event.tags.iter() {
if let Some(TagStandard::Relay(relay)) = tag.as_standardized() {
let url = relay.to_string();
let _ = client.add_relay(&url).await;
let _ = client.connect_relay(&url).await;
// Disconnect user's nip65 relays to save bandwidth
// disconnect_nip65_relays(nip65_relays, client).await;
}
relays.push(url)
}
}
Ok(relays)
}
Err(e) => Err(e.to_string()),
}
// Update state
inbox_relays.insert(public_key, relays.clone());
Ok(relays)
} else {
Err("User's inbox relays not found.".to_string())
}
}
#[tauri::command]
#[specta::specta]
pub async fn disconnect_inbox_relays(
user_id: String,
state: State<'_, Nostr>,
user_id: String,
state: State<'_, Nostr>,
) -> Result<(), String> {
let client = &state.client;
let public_key = PublicKey::parse(&user_id).map_err(|e| e.to_string())?;
let inbox_relays = state.inbox_relays.lock().await;
let client = &state.client;
let public_key = PublicKey::parse(&user_id).map_err(|e| e.to_string())?;
let inbox_relays = state.inbox_relays.read().await;
if let Some(relays) = inbox_relays.get(&public_key) {
for relay in relays {
let _ = client.disconnect_relay(relay).await;
}
}
if let Some(relays) = inbox_relays.get(&public_key) {
for relay in relays {
let _ = client.disconnect_relay(relay).await;
}
}
Ok(())
Ok(())
}

View File

@@ -5,60 +5,52 @@ use tauri::{Manager, WebviewWindowBuilder};
use tauri_plugin_decorum::WebviewWindowExt;
pub fn setup_tray_icon(app: &tauri::AppHandle) -> tauri::Result<()> {
let tray = app.tray_by_id("main").expect("Error: tray icon not found.");
let tray = app.tray_by_id("main").expect("Error: tray icon not found.");
let menu = tauri::menu::MenuBuilder::new(app)
.item(
&tauri::menu::MenuItem::with_id(app, "open", "Open Coop", true, None::<&str>).unwrap(),
)
.item(&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap())
.build()
.expect("Error: cannot create menu.");
let menu = tauri::menu::MenuBuilder::new(app)
.item(
&tauri::menu::MenuItem::with_id(app, "open", "Open Coop", true, None::<&str>).unwrap(),
)
.item(&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap())
.build()
.expect("Error: cannot create menu.");
if tray.set_menu(Some(menu)).is_err() {
panic!("Error: cannot set menu for tray icon.")
}
if tray.set_menu(Some(menu)).is_err() {
panic!("Error: cannot set menu for tray icon.")
}
tray.on_menu_event(move |app, event| match event.id.0.as_str() {
"open" => {
if let Some(window) = app.get_webview_window("main") {
if window.is_visible().unwrap_or_default() {
let _ = window.set_focus();
} else {
let _ = window.show();
let _ = window.set_focus();
};
} else {
let config = app.config().app.windows.first().unwrap();
let window =
WebviewWindowBuilder::from_config(app, config).unwrap().build().unwrap();
tray.on_menu_event(move |app, event| match event.id.0.as_str() {
"open" => {
if let Some(window) = app.get_webview_window("main") {
if window.is_visible().unwrap_or_default() {
let _ = window.set_focus();
} else {
let _ = window.show();
let _ = window.set_focus();
};
} else {
let config = app.config().app.windows.first().unwrap();
let window = WebviewWindowBuilder::from_config(app, config)
.unwrap()
.build()
.unwrap();
// Set custom decoration
#[cfg(target_os = "windows")]
window.create_overlay_titlebar().unwrap();
// Set custom decoration
#[cfg(target_os = "windows")]
window.create_overlay_titlebar().unwrap();
// Set traffic light inset
#[cfg(target_os = "macos")]
window.set_traffic_lights_inset(12.0, 18.0).unwrap();
// Set traffic light inset
#[cfg(target_os = "macos")]
window.set_traffic_lights_inset(12.0, 18.0).unwrap();
// Workaround for reset traffic light when theme changed
#[cfg(target_os = "macos")]
let win_ = window.clone();
#[cfg(target_os = "macos")]
window.on_window_event(move |event| {
if let tauri::WindowEvent::ThemeChanged(_) = event {
win_.set_traffic_lights_inset(12.0, 18.0).unwrap();
}
});
// Restore native border
#[cfg(target_os = "macos")]
window.add_border(None);
}
}
"quit" => std::process::exit(0),
_ => {}
});
// Restore native border
#[cfg(target_os = "macos")]
window.add_border(None);
}
}
"quit" => std::process::exit(0),
_ => {}
});
Ok(())
Ok(())
}

View File

@@ -3,65 +3,91 @@
#[cfg(target_os = "macos")]
use border::WebviewWindowExt as WebviewWindowExtAlt;
use commands::tray::setup_tray_icon;
use nostr_sdk::prelude::*;
use serde::{Deserialize, Serialize};
use specta_typescript::Typescript;
use std::{
collections::HashMap,
env, fs,
io::{self, BufRead},
str::FromStr,
collections::{HashMap, HashSet},
env, fs,
io::{self, BufRead},
str::FromStr,
};
use tauri::{async_runtime::Mutex, Manager};
use tauri::{Emitter, Listener, Manager};
#[cfg(not(target_os = "linux"))]
use tauri_plugin_decorum::WebviewWindowExt;
use tauri_specta::{collect_commands, Builder};
use tokio::{sync::RwLock, time::sleep, time::Duration};
use commands::{account::*, chat::*, relay::*};
use commands::{account::*, chat::*, relay::*, tray::*};
mod commands;
pub struct Nostr {
client: Client,
inbox_relays: Mutex<HashMap<PublicKey, Vec<String>>>,
client: Client,
queue: RwLock<HashSet<PublicKey>>,
inbox_relays: RwLock<HashMap<PublicKey, Vec<String>>>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Payload {
id: String,
}
#[derive(Clone, Serialize)]
pub struct EventPayload {
event: String, // JSON String
sender: String,
}
pub const QUEUE_DELAY: u64 = 300;
pub const SUBSCRIPTION_ID: &str = "inbox";
fn main() {
#[cfg(target_os = "linux")]
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
#[cfg(target_os = "linux")]
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
let builder = Builder::<tauri::Wry>::new().commands(collect_commands![
get_bootstrap_relays,
set_bootstrap_relays,
get_inbox_relays,
set_inbox_relays,
ensure_inbox_relays,
connect_inbox_relays,
disconnect_inbox_relays,
login,
create_account,
import_account,
connect_account,
delete_account,
reset_password,
get_accounts,
get_current_account,
get_metadata,
get_contact_list,
get_chats,
get_chat_messages,
send_message,
]);
let builder = Builder::<tauri::Wry>::new().commands(collect_commands![
get_bootstrap_relays,
set_bootstrap_relays,
get_inbox_relays,
set_inbox_relays,
ensure_inbox_relays,
connect_inbox_relays,
disconnect_inbox_relays,
login,
create_account,
import_account,
connect_account,
delete_account,
reset_password,
get_accounts,
get_current_account,
get_metadata,
get_contact_list,
get_chats,
get_chat_messages,
send_message,
]);
#[cfg(debug_assertions)]
builder
.export(Typescript::default(), "../src/commands.ts")
.expect("Failed to export typescript bindings");
#[cfg(debug_assertions)]
builder
.export(Typescript::default(), "../src/commands.ts")
.expect("Failed to export typescript bindings");
tauri::Builder::default()
#[cfg(debug_assertions)]
let tauri_builder = tauri::Builder::default().plugin(tauri_plugin_devtools::init());
#[cfg(not(debug_assertions))]
let tauri_builder = tauri::Builder::default();
tauri_builder
.invoke_handler(builder.invoke_handler())
.setup(move |app| {
let handle = app.handle();
let handle_clone = handle.clone();
let handle_clone_child = handle_clone.clone();
// Setup tray icon
let _ = setup_tray_icon(handle);
#[cfg(not(target_os = "linux"))]
@@ -88,11 +114,10 @@ fn main() {
let _ = fs::create_dir_all(&dir);
// Setup database
let database = NostrLMDB::open(dir.join("nostr-lmdb"))
.expect("Error: cannot create database.");
let database = NostrLMDB::open(dir.join("nostr-lmdb")).expect("Error: cannot create database.");
// Setup nostr client
let opts = Options::new().gossip(true).autoconnect(true);
let opts = Options::new().gossip(true).automatic_authentication(false).max_avg_latency(Duration::from_millis(500));
let client = ClientBuilder::default().opts(opts).database(database).build();
// Add bootstrap relays
@@ -123,15 +148,128 @@ fn main() {
}
}
let _ = client.add_discovery_relay("wss://user.kindpag.es/").await;
// Connect
client.connect().await;
client
});
// Create global state
app.manage(Nostr { client, inbox_relays: Mutex::new(HashMap::new()) });
app.manage(Nostr {
client,
queue: RwLock::new(HashSet::new()),
inbox_relays: RwLock::new(HashMap::new()),
});
// Listen for request metadata
app.listen_any("request_metadata", move |event| {
let payload = event.payload();
let parsed_payload: Payload = serde_json::from_str(payload).expect("Parse failed");
let handle = handle_clone.to_owned();
tauri::async_runtime::spawn(async move {
let state = handle.state::<Nostr>();
let client = &state.client;
if let Ok(public_key) = PublicKey::parse(parsed_payload.id) {
let mut write_queue = state.queue.write().await;
write_queue.insert(public_key);
};
// Wait for [QUEUE_DELAY]
sleep(Duration::from_millis(QUEUE_DELAY)).await;
let read_queue = state.queue.read().await;
if !read_queue.is_empty() {
let authors: HashSet<PublicKey> = read_queue.iter().copied().collect();
let filter = Filter::new().authors(authors).kind(Kind::Metadata).limit(200);
let opts = SubscribeAutoCloseOptions::default()
.filter(FilterOptions::WaitDurationAfterEOSE(Duration::from_secs(2)));
// Drop queue, you don't need it at this time anymore
drop(read_queue);
// Clear queue
let mut write_queue = state.queue.write().await;
write_queue.clear();
if let Err(e) = client.subscribe(vec![filter], Some(opts)).await {
println!("Subscribe error: {}", e);
}
}
});
});
// Run a thread for handle notification
tauri::async_runtime::spawn(async move {
let handle = handle_clone_child.to_owned();
let state = handle.state::<Nostr>();
let client = &state.client;
// Generate a fake sig for rumor event.
// TODO: Find better way to save unsigned event to database.
let fake_sig = Signature::from_str("f9e79d141c004977192d05a86f81ec7c585179c371f7350a5412d33575a2a356433f58e405c2296ed273e2fe0aafa25b641e39cc4e1f3f261ebf55bce0cbac83").unwrap();
let _ = client
.handle_notifications(|notification| async {
#[allow(clippy::collapsible_match)]
if let RelayPoolNotification::Message { message, .. } = notification {
if let RelayMessage::Event { event, .. } = message {
if event.kind == Kind::GiftWrap {
if let Ok(UnwrappedGift { rumor, sender }) =
client.unwrap_gift_wrap(&event).await
{
let mut rumor_clone = rumor.clone();
// Compute event id if not exist
rumor_clone.ensure_id();
let ev = Event::new(
rumor_clone.id.unwrap(), // unwrap() must be fine
rumor_clone.pubkey,
rumor_clone.created_at,
rumor_clone.kind,
rumor_clone.tags,
rumor_clone.content,
fake_sig,
);
// Save rumor to database to further query
if let Err(e) = client.database().save_event(&ev).await {
println!("[save event] error: {}", e)
}
// Emit new event to frontend
if let Err(e) = handle.emit(
"event",
EventPayload {
event: rumor.as_json(),
sender: sender.to_hex(),
},
) {
println!("[emit] error: {}", e)
}
}
} else if event.kind == Kind::Metadata {
if let Err(e) = handle.emit("metadata", event.as_json()) {
println!("Emit error: {}", e)
}
}
}
}
Ok(false)
})
.await;
});
Ok(())
})
.plugin(prevent_default())
.plugin(tauri_plugin_store::Builder::default().build())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
@@ -152,14 +290,14 @@ fn main() {
#[cfg(debug_assertions)]
fn prevent_default() -> tauri::plugin::TauriPlugin<tauri::Wry> {
use tauri_plugin_prevent_default::Flags;
use tauri_plugin_prevent_default::Flags;
tauri_plugin_prevent_default::Builder::new()
.with_flags(Flags::all().difference(Flags::CONTEXT_MENU))
.build()
tauri_plugin_prevent_default::Builder::new()
.with_flags(Flags::all().difference(Flags::CONTEXT_MENU))
.build()
}
#[cfg(not(debug_assertions))]
fn prevent_default() -> tauri::plugin::TauriPlugin<tauri::Wry> {
tauri_plugin_prevent_default::Builder::new().build()
tauri_plugin_prevent_default::Builder::new().build()
}

View File

@@ -1,5 +1,5 @@
{
"productName": "Coop",
"productName": "COOP",
"version": "0.2.0",
"identifier": "su.reya.coop",
"build": {