enostr

rust nostr lib that works on the web and natively
git clone git://jb55.com/enostr
Log | Files | Refs

commit 3fcd4dd504a054015f5e8f5aa63dc881fda46c50
Author: William Casarin <jb55@jb55.com>
Date:   Sun, 26 Mar 2023 11:18:25 -0600

init

Diffstat:
ACargo.lock | 900+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACargo.toml | 13+++++++++++++
Asrc/client/message.rs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/client/mod.rs | 3+++
Asrc/error.rs | 38++++++++++++++++++++++++++++++++++++++
Asrc/event.rs | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/filter.rs | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib.rs | 20++++++++++++++++++++
Asrc/profile.rs | 38++++++++++++++++++++++++++++++++++++++
Asrc/pubkey.rs | 22++++++++++++++++++++++
Asrc/relay/message.rs | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/relay/mod.rs | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/relay/pool.rs | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13 files changed, 1710 insertions(+), 0 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -0,0 +1,900 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "enostr" +version = "0.1.0" +dependencies = [ + "ewebsock", + "serde", + "serde_derive", + "serde_json", + "tracing", +] + +[[package]] +name = "ewebsock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "689197e24a57aee379b3bbef527e70607fc6d4b58fae4f1d98a2c6d91503e230" +dependencies = [ + "async-stream", + "futures", + "futures-util", + "js-sys", + "tokio", + "tokio-tungstenite", + "tracing", + "tungstenite", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "pin-project-lite", + "socket2", + "windows-sys", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "rustls", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki", + "webpki-roots", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "enostr" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ewebsock = { version = "0.2.0", features = ["tls"] } +serde_derive = "1" +serde = { version = "1", features = ["derive"] } # You only need this if you want app persistence +serde_json = "1.0.89" +tracing = "0.1.37" diff --git a/src/client/message.rs b/src/client/message.rs @@ -0,0 +1,50 @@ +use crate::{Event, Filter}; +use serde_json::json; + +/// Messages sent by clients, received by relays +#[derive(Debug, Eq, PartialEq)] +pub enum ClientMessage { + Event { + event: Event, + }, + Req { + sub_id: String, + filters: Vec<Filter>, + }, + Close { + sub_id: String, + }, +} + +impl ClientMessage { + pub fn event(event: Event) -> Self { + ClientMessage::Event { event } + } + + pub fn req(sub_id: String, filters: Vec<Filter>) -> Self { + ClientMessage::Req { sub_id, filters } + } + + pub fn close(sub_id: String) -> Self { + ClientMessage::Close { sub_id } + } + + pub fn to_json(&self) -> String { + match self { + Self::Event { event } => json!(["EVENT", event]).to_string(), + Self::Req { sub_id, filters } => { + let mut json = json!(["REQ", sub_id]); + let mut filters = json!(filters); + + if let Some(json) = json.as_array_mut() { + if let Some(filters) = filters.as_array_mut() { + json.append(filters); + } + } + + json.to_string() + } + Self::Close { sub_id } => json!(["CLOSE", sub_id]).to_string(), + } + } +} diff --git a/src/client/mod.rs b/src/client/mod.rs @@ -0,0 +1,3 @@ +mod message; + +pub use message::ClientMessage; diff --git a/src/error.rs b/src/error.rs @@ -0,0 +1,38 @@ +use serde_json; + +#[derive(Debug)] +pub enum Error { + MessageEmpty, + MessageDecodeFailed, + InvalidSignature, + Json(serde_json::Error), + Generic(String), +} + +impl std::cmp::PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Error::MessageEmpty, Error::MessageEmpty) => true, + (Error::MessageDecodeFailed, Error::MessageDecodeFailed) => true, + (Error::InvalidSignature, Error::InvalidSignature) => true, + // This is slightly wrong but whatevs + (Error::Json(..), Error::Json(..)) => true, + (Error::Generic(left), Error::Generic(right)) => left == right, + _ => false, + } + } +} + +impl std::cmp::Eq for Error {} + +impl From<String> for Error { + fn from(s: String) -> Self { + Error::Generic(s) + } +} + +impl From<serde_json::Error> for Error { + fn from(e: serde_json::Error) -> Self { + Error::Json(e) + } +} diff --git a/src/event.rs b/src/event.rs @@ -0,0 +1,96 @@ +use crate::{Error, Pubkey, Result}; +use serde_derive::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; + +/// Event is the struct used to represent a Nostr event +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Event { + /// 32-bytes sha256 of the the serialized event data + pub id: EventId, + /// 32-bytes hex-encoded public key of the event creator + #[serde(rename = "pubkey")] + pub pubkey: Pubkey, + /// unix timestamp in seconds + pub created_at: u64, + /// integer + /// 0: NostrEvent + pub kind: u64, + /// Tags + pub tags: Vec<Vec<String>>, + /// arbitrary string + pub content: String, + /// 64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field + pub sig: String, +} + +// Implement Hash trait +impl Hash for Event { + fn hash<H: Hasher>(&self, state: &mut H) { + self.id.0.hash(state); + } +} + +impl PartialEq for Event { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for Event {} + +impl Event { + pub fn from_json(s: &str) -> Result<Self> { + serde_json::from_str(s).map_err(Into::into) + } + + pub fn verify(&self) -> Result<Self> { + return Err(Error::InvalidSignature); + } + + /// This is just for serde sanity checking + #[allow(dead_code)] + pub(crate) fn new_dummy( + id: &str, + pubkey: &str, + created_at: u64, + kind: u64, + tags: Vec<Vec<String>>, + content: &str, + sig: &str, + ) -> Result<Self> { + let event = Event { + id: id.to_string().into(), + pubkey: pubkey.to_string().into(), + created_at, + kind, + tags, + content: content.to_string(), + sig: sig.to_string(), + }; + + event.verify() + } +} + +impl std::str::FromStr for Event { + type Err = Error; + + fn from_str(s: &str) -> Result<Self> { + Event::from_json(s) + } +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Hash)] +pub struct EventId(String); + +impl From<String> for EventId { + fn from(s: String) -> Self { + EventId(s) + } +} + +impl From<EventId> for String { + fn from(evid: EventId) -> Self { + evid.0 + } +} diff --git a/src/filter.rs b/src/filter.rs @@ -0,0 +1,79 @@ +use crate::{EventId, Pubkey}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Filter { + #[serde(skip_serializing_if = "Option::is_none")] + ids: Option<Vec<EventId>>, + #[serde(skip_serializing_if = "Option::is_none")] + authors: Option<Vec<Pubkey>>, + #[serde(skip_serializing_if = "Option::is_none")] + kinds: Option<Vec<u64>>, + #[serde(rename = "#e")] + #[serde(skip_serializing_if = "Option::is_none")] + events: Option<Vec<EventId>>, + #[serde(rename = "#p")] + #[serde(skip_serializing_if = "Option::is_none")] + pubkeys: Option<Vec<Pubkey>>, + #[serde(skip_serializing_if = "Option::is_none")] + since: Option<u64>, // unix timestamp seconds + #[serde(skip_serializing_if = "Option::is_none")] + until: Option<u64>, // unix timestamp seconds + #[serde(skip_serializing_if = "Option::is_none")] + limit: Option<u16>, +} + +impl Filter { + pub fn new() -> Filter { + Filter { + ids: None, + authors: None, + kinds: None, + events: None, + pubkeys: None, + since: None, + until: None, + limit: None, + } + } + + pub fn ids(mut self, ids: Vec<EventId>) -> Self { + self.ids = Some(ids); + self + } + + pub fn authors(mut self, authors: Vec<Pubkey>) -> Self { + self.authors = Some(authors); + self + } + + pub fn kinds(mut self, kinds: Vec<u64>) -> Self { + self.kinds = Some(kinds); + self + } + + pub fn events(mut self, events: Vec<EventId>) -> Self { + self.events = Some(events); + self + } + + pub fn pubkeys(mut self, pubkeys: Vec<Pubkey>) -> Self { + self.pubkeys = Some(pubkeys); + self + } + + pub fn since(mut self, since: u64) -> Self { + self.since = Some(since); + self + } + + pub fn until(mut self, until: u64) -> Self { + self.until = Some(until); + self + } + + pub fn limit(mut self, limit: u16) -> Self { + self.limit = Some(limit); + self + } +} diff --git a/src/lib.rs b/src/lib.rs @@ -0,0 +1,20 @@ +mod client; +mod error; +mod event; +mod filter; +mod profile; +mod pubkey; +mod relay; + +pub use client::ClientMessage; +pub use error::Error; +pub use event::{Event, EventId}; +pub use ewebsock; +pub use filter::Filter; +pub use profile::Profile; +pub use pubkey::Pubkey; +pub use relay::message::{RelayEvent, RelayMessage}; +pub use relay::pool::{PoolEvent, RelayPool}; +pub use relay::Relay; + +pub type Result<T> = std::result::Result<T, error::Error>; diff --git a/src/profile.rs b/src/profile.rs @@ -0,0 +1,38 @@ +use serde_json::Value; + +#[derive(Debug, Clone)] +pub struct Profile(Value); + +impl Profile { + pub fn new(value: Value) -> Profile { + Profile(value) + } + + pub fn name(&self) -> Option<&str> { + return self.0["name"].as_str(); + } + + pub fn display_name(&self) -> Option<&str> { + return self.0["display_name"].as_str(); + } + + pub fn lud06(&self) -> Option<&str> { + return self.0["lud06"].as_str(); + } + + pub fn lud16(&self) -> Option<&str> { + return self.0["lud16"].as_str(); + } + + pub fn about(&self) -> Option<&str> { + return self.0["about"].as_str(); + } + + pub fn picture(&self) -> Option<&str> { + return self.0["picture"].as_str(); + } + + pub fn website(&self) -> Option<&str> { + return self.0["website"].as_str(); + } +} diff --git a/src/pubkey.rs b/src/pubkey.rs @@ -0,0 +1,22 @@ +use serde_derive::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Hash)] +pub struct Pubkey(String); + +impl AsRef<str> for Pubkey { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl From<String> for Pubkey { + fn from(s: String) -> Self { + Pubkey(s) + } +} + +impl From<Pubkey> for String { + fn from(pk: Pubkey) -> Self { + pk.0 + } +} diff --git a/src/relay/message.rs b/src/relay/message.rs @@ -0,0 +1,283 @@ +use crate::Error; +use crate::Event; +use crate::Result; +use serde_json::Value; + +use ewebsock::{WsEvent, WsMessage}; + +#[derive(Debug, Eq, PartialEq)] +pub struct CommandResult { + event_id: String, + status: bool, + message: String, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum RelayMessage { + OK(CommandResult), + Eose(String), + Event(String, Event), + Notice(String), +} + +#[derive(Debug)] +pub enum RelayEvent { + Opened, + Closed, + Other(WsMessage), + Message(RelayMessage), +} + +impl TryFrom<WsEvent> for RelayEvent { + type Error = Error; + + fn try_from(message: WsEvent) -> Result<Self> { + match message { + WsEvent::Opened => Ok(RelayEvent::Opened), + WsEvent::Closed => Ok(RelayEvent::Closed), + WsEvent::Message(ws_msg) => ws_msg.try_into(), + WsEvent::Error(s) => Err(s.into()), + } + } +} + +impl TryFrom<WsMessage> for RelayEvent { + type Error = Error; + + fn try_from(wsmsg: WsMessage) -> Result<Self> { + match wsmsg { + WsMessage::Text(s) => RelayMessage::from_json(&s).map(RelayEvent::Message), + wsmsg => Ok(RelayEvent::Other(wsmsg)), + } + } +} + +impl RelayMessage { + pub fn eose(subid: String) -> Self { + RelayMessage::Eose(subid) + } + + pub fn notice(msg: String) -> Self { + RelayMessage::Notice(msg) + } + + pub fn ok(event_id: String, status: bool, message: String) -> Self { + RelayMessage::OK(CommandResult { + event_id: event_id, + status, + message: message, + }) + } + + pub fn event(ev: Event, sub_id: String) -> Self { + RelayMessage::Event(sub_id, ev) + } + + // I was lazy and took this from the nostr crate. thx yuki! + pub fn from_json(msg: &str) -> Result<Self> { + if msg.is_empty() { + return Err(Error::MessageEmpty); + } + + let v: Vec<Value> = serde_json::from_str(msg).map_err(|_| Error::MessageDecodeFailed)?; + + // Notice + // Relay response format: ["NOTICE", <message>] + if v[0] == "NOTICE" { + if v.len() != 2 { + return Err(Error::MessageDecodeFailed); + } + let v_notice: String = + serde_json::from_value(v[1].clone()).map_err(|_| Error::MessageDecodeFailed)?; + return Ok(Self::notice(v_notice)); + } + + // Event + // Relay response format: ["EVENT", <subscription id>, <event JSON>] + if v[0] == "EVENT" { + if v.len() != 3 { + return Err(Error::MessageDecodeFailed); + } + + let event = + Event::from_json(&v[2].to_string()).map_err(|_| Error::MessageDecodeFailed)?; + + let subscription_id: String = + serde_json::from_value(v[1].clone()).map_err(|_| Error::MessageDecodeFailed)?; + + return Ok(Self::event(event, subscription_id)); + } + + // EOSE (NIP-15) + // Relay response format: ["EOSE", <subscription_id>] + if v[0] == "EOSE" { + if v.len() != 2 { + return Err(Error::MessageDecodeFailed); + } + + let subscription_id: String = + serde_json::from_value(v[1].clone()).map_err(|_| Error::MessageDecodeFailed)?; + + return Ok(Self::eose(subscription_id)); + } + + // OK (NIP-20) + // Relay response format: ["OK", <event_id>, <true|false>, <message>] + if v[0] == "OK" { + if v.len() != 4 { + return Err(Error::MessageDecodeFailed); + } + + let event_id: String = + serde_json::from_value(v[1].clone()).map_err(|_| Error::MessageDecodeFailed)?; + + let status: bool = + serde_json::from_value(v[2].clone()).map_err(|_| Error::MessageDecodeFailed)?; + + let message: String = + serde_json::from_value(v[3].clone()).map_err(|_| Error::MessageDecodeFailed)?; + + return Ok(Self::ok(event_id, status, message)); + } + + Err(Error::MessageDecodeFailed) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_handle_valid_notice() -> Result<()> { + let valid_notice_msg = r#"["NOTICE","Invalid event format!"]"#; + let handled_valid_notice_msg = RelayMessage::notice("Invalid event format!".to_string()); + + assert_eq!( + RelayMessage::from_json(valid_notice_msg)?, + handled_valid_notice_msg + ); + + Ok(()) + } + #[test] + fn test_handle_invalid_notice() { + //Missing content + let invalid_notice_msg = r#"["NOTICE"]"#; + //The content is not string + let invalid_notice_msg_content = r#"["NOTICE": 404]"#; + + assert_eq!( + RelayMessage::from_json(invalid_notice_msg).unwrap_err(), + Error::MessageDecodeFailed + ); + assert_eq!( + RelayMessage::from_json(invalid_notice_msg_content).unwrap_err(), + Error::MessageDecodeFailed + ); + } + + #[test] + fn test_handle_valid_event() -> Result<()> { + let valid_event_msg = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe","created_at":1612809991,"kind":1,"tags":[],"content":"test","sig":"273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"}]"#; + + let id = "70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5"; + let pubkey = "379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"; + let created_at = 1612809991; + let kind = 1; + let tags = vec![]; + let content = "test"; + let sig = "273a9cd5d11455590f4359500bccb7a89428262b96b3ea87a756b770964472f8c3e87f5d5e64d8d2e859a71462a3f477b554565c4f2f326cb01dd7620db71502"; + + let handled_event = Event::new_dummy(id, pubkey, created_at, kind, tags, content, sig); + + assert_eq!( + RelayMessage::from_json(valid_event_msg)?, + RelayMessage::event(handled_event?, "random_string".to_string()) + ); + + Ok(()) + } + + #[test] + fn test_handle_invalid_event() { + //Mising Event field + let invalid_event_msg = r#"["EVENT", "random_string"]"#; + //Event JSON with incomplete content + let invalid_event_msg_content = r#"["EVENT", "random_string", {"id":"70b10f70c1318967eddf12527799411b1a9780ad9c43858f5e5fcd45486a13a5","pubkey":"379e863e8357163b5bce5d2688dc4f1dcc2d505222fb8d74db600f30535dfdfe"}]"#; + + assert_eq!( + RelayMessage::from_json(invalid_event_msg).unwrap_err(), + Error::MessageDecodeFailed + ); + + assert_eq!( + RelayMessage::from_json(invalid_event_msg_content).unwrap_err(), + Error::MessageDecodeFailed + ); + } + + #[test] + fn test_handle_valid_eose() -> Result<()> { + let valid_eose_msg = r#"["EOSE","random-subscription-id"]"#; + let handled_valid_eose_msg = RelayMessage::eose("random-subscription-id".to_string()); + + assert_eq!( + RelayMessage::from_json(valid_eose_msg)?, + handled_valid_eose_msg + ); + + Ok(()) + } + #[test] + fn test_handle_invalid_eose() { + // Missing subscription ID + assert_eq!( + RelayMessage::from_json(r#"["EOSE"]"#).unwrap_err(), + Error::MessageDecodeFailed + ); + + // The subscription ID is not string + assert_eq!( + RelayMessage::from_json(r#"["EOSE", 404]"#).unwrap_err(), + Error::MessageDecodeFailed + ); + } + + #[test] + fn test_handle_valid_ok() -> Result<()> { + let valid_ok_msg = r#"["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, "pow: difficulty 25>=24"]"#; + let handled_valid_ok_msg = RelayMessage::ok( + "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30".to_string(), + true, + "pow: difficulty 25>=24".into(), + ); + + assert_eq!(RelayMessage::from_json(valid_ok_msg)?, handled_valid_ok_msg); + + Ok(()) + } + #[test] + fn test_handle_invalid_ok() { + // Missing params + assert_eq!( + RelayMessage::from_json( + r#"["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"# + ) + .unwrap_err(), + Error::MessageDecodeFailed + ); + + // Invalid status + assert_eq!( + RelayMessage::from_json(r#"["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", hello, ""]"#).unwrap_err(), + Error::MessageDecodeFailed + ); + + // Invalid message + assert_eq!( + RelayMessage::from_json(r#"["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", hello, 404]"#).unwrap_err(), + Error::MessageDecodeFailed + ); + } +} diff --git a/src/relay/mod.rs b/src/relay/mod.rs @@ -0,0 +1,69 @@ +use ewebsock::{WsMessage, WsReceiver, WsSender}; + +use crate::{ClientMessage, Filter, Result}; +use std::fmt; +use std::hash::{Hash, Hasher}; + +pub mod message; +pub mod pool; + +#[derive(Debug)] +pub enum RelayStatus { + Connected, + Connecting, + Disconnected, +} + +pub struct Relay { + pub url: String, + pub status: RelayStatus, + pub sender: WsSender, + pub receiver: WsReceiver, +} + +impl fmt::Debug for Relay { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Relay") + .field("url", &self.url) + .field("status", &self.status) + .finish() + } +} + +impl Hash for Relay { + fn hash<H: Hasher>(&self, state: &mut H) { + // Hashes the Relay by hashing the URL + self.url.hash(state); + } +} + +impl PartialEq for Relay { + fn eq(&self, other: &Self) -> bool { + self.url == other.url + } +} + +impl Eq for Relay {} + +impl Relay { + pub fn new(url: String, wakeup: impl Fn() + Send + Sync + 'static) -> Result<Self> { + let status = RelayStatus::Connecting; + let (sender, receiver) = ewebsock::connect_with_wakeup(&url, wakeup)?; + + Ok(Self { + url, + sender, + receiver, + status, + }) + } + + pub fn send(&mut self, msg: &ClientMessage) { + let txt = WsMessage::Text(msg.to_json()); + self.sender.send(txt); + } + + pub fn subscribe(&mut self, subid: String, filters: Vec<Filter>) { + self.send(&ClientMessage::req(subid, filters)); + } +} diff --git a/src/relay/pool.rs b/src/relay/pool.rs @@ -0,0 +1,99 @@ +use crate::relay::message::RelayEvent; +use crate::relay::Relay; +use crate::{ClientMessage, Result}; +use ewebsock::WsMessage; +use tracing::{debug, error}; + +#[derive(Debug)] +pub struct PoolEvent<'a> { + pub relay: &'a str, + pub event: RelayEvent, +} + +pub struct RelayPool { + pub relays: Vec<Relay>, +} + +impl Default for RelayPool { + fn default() -> RelayPool { + RelayPool { relays: Vec::new() } + } +} + +impl RelayPool { + // Constructs a new, empty RelayPool. + pub fn new() -> RelayPool { + RelayPool { relays: vec![] } + } + + pub fn has(&self, url: &str) -> bool { + for relay in &self.relays { + if &relay.url == url { + return true; + } + } + return false; + } + + pub fn send(&mut self, cmd: &ClientMessage) { + for relay in &mut self.relays { + relay.send(cmd); + } + } + + pub fn send_to(&mut self, cmd: &ClientMessage, relay_url: &str) { + for relay in &mut self.relays { + if relay.url == relay_url { + relay.send(cmd); + return; + } + } + } + + // Adds a websocket url to the RelayPool. + pub fn add_url( + &mut self, + url: String, + wakeup: impl Fn() + Send + Sync + 'static, + ) -> Result<()> { + let relay = Relay::new(url, wakeup)?; + + self.relays.push(relay); + + Ok(()) + } + + /// Attempts to receive a pool event from a list of relays. The function searches each relay in the list in order, attempting to receive a message from each. If a message is received, return it. If no message is received from any relays, None is returned. + pub fn try_recv(&mut self) -> Option<PoolEvent<'_>> { + for relay in &mut self.relays { + if let Some(msg) = relay.receiver.try_recv() { + match msg.try_into() { + Ok(event) => { + // let's just handle pongs here. + // We only need to do this natively. + #[cfg(not(target_arch = "wasm32"))] + match event { + RelayEvent::Other(WsMessage::Ping(ref bs)) => { + debug!("pong {}", &relay.url); + relay.sender.send(WsMessage::Pong(bs.to_owned())); + } + _ => {} + } + + return Some(PoolEvent { + event, + relay: &relay.url, + }); + } + + Err(e) => { + error!("{:?}", e); + continue; + } + } + } + } + + None + } +}