commit 3fcd4dd504a054015f5e8f5aa63dc881fda46c50
Author: William Casarin <jb55@jb55.com>
Date: Sun, 26 Mar 2023 11:18:25 -0600
init
Diffstat:
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
+ }
+}